add individual lists to user items page
This commit is contained in:
parent
21039ec148
commit
4e00962edc
3 changed files with 149 additions and 83 deletions
|
@ -1,12 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Badge, Box, Center, Wrap } from "@chakra-ui/core";
|
import { Badge, Box, Center, Wrap, VStack } from "@chakra-ui/core";
|
||||||
import { CheckIcon, EmailIcon, StarIcon } from "@chakra-ui/icons";
|
import { CheckIcon, EmailIcon, StarIcon } from "@chakra-ui/icons";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
|
|
||||||
import HangerSpinner from "./components/HangerSpinner";
|
import HangerSpinner from "./components/HangerSpinner";
|
||||||
import { Heading1, Heading2 } from "./util";
|
import { Heading1, Heading2, Heading3 } from "./util";
|
||||||
import ItemCard, {
|
import ItemCard, {
|
||||||
ItemBadgeList,
|
ItemBadgeList,
|
||||||
ItemCardList,
|
ItemCardList,
|
||||||
|
@ -32,24 +32,18 @@ function UserItemsPage() {
|
||||||
username
|
username
|
||||||
contactNeopetsUsername
|
contactNeopetsUsername
|
||||||
|
|
||||||
itemsTheyOwn {
|
closetLists {
|
||||||
id
|
id
|
||||||
isNc
|
|
||||||
name
|
name
|
||||||
thumbnailUrl
|
ownsOrWantsItems
|
||||||
currentUserWantsThis
|
isDefaultList
|
||||||
allOccupiedZones {
|
items {
|
||||||
id
|
|
||||||
label @client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsTheyWant {
|
|
||||||
id
|
id
|
||||||
isNc
|
isNc
|
||||||
name
|
name
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
currentUserOwnsThis
|
currentUserOwnsThis
|
||||||
|
currentUserWantsThis
|
||||||
allOccupiedZones {
|
allOccupiedZones {
|
||||||
id
|
id
|
||||||
label @client
|
label @client
|
||||||
|
@ -57,6 +51,7 @@ function UserItemsPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
{ variables: { userId } }
|
{ variables: { userId } }
|
||||||
);
|
);
|
||||||
|
@ -73,36 +68,48 @@ function UserItemsPage() {
|
||||||
return <Box color="red.400">{error.message}</Box>;
|
return <Box color="red.400">{error.message}</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This helps you compare your owns/wants to other users! If they own
|
if (data.user == null) {
|
||||||
// something, and you want it, we say "You want this!". And if they want
|
return <Box color="red.400">User not found</Box>;
|
||||||
// something, and you own it, we say "You own this!".
|
}
|
||||||
const showYouOwnThisBadge = (item) =>
|
|
||||||
!isCurrentUser && item.currentUserOwnsThis;
|
|
||||||
const showYouWantThisBadge = (item) =>
|
|
||||||
!isCurrentUser && item.currentUserWantsThis;
|
|
||||||
|
|
||||||
const numYouOwnThisBadges = data.user.itemsTheyWant.filter(
|
const listsOfOwnedItems = data.user.closetLists.filter(
|
||||||
showYouOwnThisBadge
|
(l) => l.ownsOrWantsItems === "OWNS"
|
||||||
).length;
|
);
|
||||||
const numYouWantThisBadges = data.user.itemsTheyOwn.filter(
|
const listsOfWantedItems = data.user.closetLists.filter(
|
||||||
showYouWantThisBadge
|
(l) => l.ownsOrWantsItems === "WANTS"
|
||||||
).length;
|
);
|
||||||
|
|
||||||
const sortedItemsTheyOwn = [...data.user.itemsTheyOwn].sort((a, b) => {
|
// Sort default list to the end, then sort alphabetically. We use a similar
|
||||||
// This is a cute sort hack. We sort first by, bringing "You want this!" to
|
// sort hack that we use for sorting items in ClosetList!
|
||||||
// the top, and then sorting by name _within_ those two groups.
|
listsOfOwnedItems.sort((a, b) => {
|
||||||
const aName = `${showYouWantThisBadge(a) ? "000" : "999"} ${a.name}`;
|
const aName = `${a.isDefaultList ? "ZZZ" : "AAA"} ${a.name}`;
|
||||||
const bName = `${showYouWantThisBadge(b) ? "000" : "999"} ${b.name}`;
|
const bName = `${b.isDefaultList ? "ZZZ" : "AAA"} ${b.name}`;
|
||||||
|
return aName.localeCompare(bName);
|
||||||
|
});
|
||||||
|
listsOfWantedItems.sort((a, b) => {
|
||||||
|
const aName = `${a.isDefaultList ? "ZZZ" : "AAA"} ${a.name}`;
|
||||||
|
const bName = `${b.isDefaultList ? "ZZZ" : "AAA"} ${b.name}`;
|
||||||
return aName.localeCompare(bName);
|
return aName.localeCompare(bName);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedItemsTheyWant = [...data.user.itemsTheyWant].sort((a, b) => {
|
const allItemsTheyOwn = listsOfOwnedItems.map((l) => l.items).flat();
|
||||||
// This is a cute sort hack. We sort first by, bringing "You own this!" to
|
const allItemsTheyWant = listsOfWantedItems.map((l) => l.items).flat();
|
||||||
// the top, and then sorting by name _within_ those two groups.
|
|
||||||
const aName = `${showYouOwnThisBadge(a) ? "000" : "999"} ${a.name}`;
|
const itemsTheyOwnThatYouWant = allItemsTheyOwn.filter(
|
||||||
const bName = `${showYouOwnThisBadge(b) ? "000" : "999"} ${b.name}`;
|
(i) => i.currentUserWantsThis
|
||||||
return aName.localeCompare(bName);
|
);
|
||||||
});
|
const itemsTheyWantThatYouOwn = allItemsTheyWant.filter(
|
||||||
|
(i) => i.currentUserOwnsThis
|
||||||
|
);
|
||||||
|
|
||||||
|
// It's important to de-duplicate these! Otherwise, if the same item appears
|
||||||
|
// in multiple lists, we'll double-count it.
|
||||||
|
const numItemsTheyOwnThatYouWant = new Set(
|
||||||
|
itemsTheyOwnThatYouWant.map((i) => i.id)
|
||||||
|
).size;
|
||||||
|
const numItemsTheyWantThatYouOwn = new Set(
|
||||||
|
itemsTheyWantThatYouOwn.map((i) => i.id)
|
||||||
|
).size;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -140,7 +147,7 @@ function UserItemsPage() {
|
||||||
* _this user_ owns, so they come first. I think it's also probably a
|
* _this user_ owns, so they come first. I think it's also probably a
|
||||||
* more natural train of thought: you come to someone's list _wanting_
|
* more natural train of thought: you come to someone's list _wanting_
|
||||||
* something, and _then_ thinking about what you can offer. */}
|
* something, and _then_ thinking about what you can offer. */}
|
||||||
{numYouWantThisBadges > 0 && (
|
{!isCurrentUser && numItemsTheyOwnThatYouWant > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
as="a"
|
as="a"
|
||||||
href="#owned-items"
|
href="#owned-items"
|
||||||
|
@ -149,12 +156,12 @@ function UserItemsPage() {
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<StarIcon marginRight="1" />
|
<StarIcon marginRight="1" />
|
||||||
{numYouWantThisBadges > 1
|
{numItemsTheyOwnThatYouWant > 1
|
||||||
? `${numYouWantThisBadges} items you want`
|
? `${numItemsTheyOwnThatYouWant} items you want`
|
||||||
: "1 item you want"}
|
: "1 item you want"}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{numYouOwnThisBadges > 0 && (
|
{!isCurrentUser && numItemsTheyWantThatYouOwn > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
as="a"
|
as="a"
|
||||||
href="#wanted-items"
|
href="#wanted-items"
|
||||||
|
@ -163,48 +170,84 @@ function UserItemsPage() {
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<CheckIcon marginRight="1" />
|
<CheckIcon marginRight="1" />
|
||||||
{numYouOwnThisBadges > 1
|
{numItemsTheyWantThatYouOwn > 1
|
||||||
? `${numYouOwnThisBadges} items you own`
|
? `${numItemsTheyWantThatYouOwn} items you own`
|
||||||
: "1 item you own"}
|
: "1 item you own"}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</Wrap>
|
</Wrap>
|
||||||
<Heading2 id="owned-items" marginTop="4" marginBottom="6">
|
<Heading2 id="owned-items" marginTop="4" marginBottom="2">
|
||||||
{isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`}
|
{isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`}
|
||||||
</Heading2>
|
</Heading2>
|
||||||
<ItemCardList>
|
<VStack spacing="8" alignItems="stretch">
|
||||||
{sortedItemsTheyOwn.map((item) => {
|
{listsOfOwnedItems.map((closetList) => (
|
||||||
return (
|
<ClosetList
|
||||||
<ItemCard
|
key={closetList.id}
|
||||||
key={item.id}
|
closetList={closetList}
|
||||||
item={item}
|
isCurrentUser={isCurrentUser}
|
||||||
badges={
|
showHeading={listsOfOwnedItems.length > 1}
|
||||||
<ItemBadgeList>
|
|
||||||
{item.isNc ? <NcBadge /> : <NpBadge />}
|
|
||||||
{showYouWantThisBadge(item) && <YouWantThisBadge />}
|
|
||||||
<ZoneBadgeList
|
|
||||||
zones={item.allOccupiedZones}
|
|
||||||
variant="occupies"
|
|
||||||
/>
|
/>
|
||||||
</ItemBadgeList>
|
))}
|
||||||
}
|
</VStack>
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ItemCardList>
|
|
||||||
|
|
||||||
<Heading2 id="wanted-items" marginBottom="6" marginTop="8">
|
<Heading2 id="wanted-items" marginTop="10" marginBottom="2">
|
||||||
{isCurrentUser ? "Items you want" : `Items ${data.user.username} wants`}
|
{isCurrentUser ? "Items you want" : `Items ${data.user.username} wants`}
|
||||||
</Heading2>
|
</Heading2>
|
||||||
|
<VStack spacing="4" alignItems="stretch">
|
||||||
|
{listsOfWantedItems.map((closetList) => (
|
||||||
|
<ClosetList
|
||||||
|
key={closetList.id}
|
||||||
|
closetList={closetList}
|
||||||
|
isCurrentUser={isCurrentUser}
|
||||||
|
showHeading={listsOfWantedItems.length > 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClosetList({ closetList, isCurrentUser, showHeading }) {
|
||||||
|
const hasYouWantThisBadge = (item) =>
|
||||||
|
!isCurrentUser &&
|
||||||
|
closetList.ownsOrWantsItems === "OWNS" &&
|
||||||
|
item.currentUserWantsThis;
|
||||||
|
const hasYouOwnThisBadge = (item) =>
|
||||||
|
!isCurrentUser &&
|
||||||
|
closetList.ownsOrWantsItems === "WANTS" &&
|
||||||
|
item.currentUserOwnsThis;
|
||||||
|
const hasAnyTradeBadge = (item) =>
|
||||||
|
hasYouOwnThisBadge(item) || hasYouWantThisBadge(item);
|
||||||
|
|
||||||
|
const sortedItems = [...closetList.items].sort((a, b) => {
|
||||||
|
// This is a cute sort hack. We sort first by, bringing "You own/want
|
||||||
|
// this!" to the top, and then sorting by name _within_ those two groups.
|
||||||
|
const aName = `${hasAnyTradeBadge(a) ? "000" : "999"} ${a.name}`;
|
||||||
|
const bName = `${hasAnyTradeBadge(b) ? "000" : "999"} ${b.name}`;
|
||||||
|
return aName.localeCompare(bName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{showHeading && (
|
||||||
|
<Heading3
|
||||||
|
marginBottom="2"
|
||||||
|
fontStyle={closetList.isDefaultList ? "italic" : "normal"}
|
||||||
|
>
|
||||||
|
{closetList.name}
|
||||||
|
</Heading3>
|
||||||
|
)}
|
||||||
|
{sortedItems.length > 0 ? (
|
||||||
<ItemCardList>
|
<ItemCardList>
|
||||||
{sortedItemsTheyWant.map((item) => (
|
{sortedItems.map((item) => (
|
||||||
<ItemCard
|
<ItemCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
badges={
|
badges={
|
||||||
<ItemBadgeList>
|
<ItemBadgeList>
|
||||||
{item.isNc ? <NcBadge /> : <NpBadge />}
|
{item.isNc ? <NcBadge /> : <NpBadge />}
|
||||||
{showYouOwnThisBadge(item) && <YouOwnThisBadge />}
|
{hasYouOwnThisBadge(item) && <YouOwnThisBadge />}
|
||||||
|
{hasYouWantThisBadge(item) && <YouWantThisBadge />}
|
||||||
<ZoneBadgeList
|
<ZoneBadgeList
|
||||||
zones={item.allOccupiedZones}
|
zones={item.allOccupiedZones}
|
||||||
variant="occupies"
|
variant="occupies"
|
||||||
|
@ -214,6 +257,9 @@ function UserItemsPage() {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ItemCardList>
|
</ItemCardList>
|
||||||
|
) : (
|
||||||
|
<Box fontStyle="italic">This list is empty!</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ export function Delay({ children, ms = 300 }) {
|
||||||
export function Heading1({ children, ...props }) {
|
export function Heading1({ children, ...props }) {
|
||||||
return (
|
return (
|
||||||
<Heading
|
<Heading
|
||||||
|
as="h1"
|
||||||
size="2xl"
|
size="2xl"
|
||||||
fontFamily="Delicious, sans-serif"
|
fontFamily="Delicious, sans-serif"
|
||||||
fontWeight="800"
|
fontWeight="800"
|
||||||
|
@ -51,6 +52,7 @@ export function Heading1({ children, ...props }) {
|
||||||
export function Heading2({ children, ...props }) {
|
export function Heading2({ children, ...props }) {
|
||||||
return (
|
return (
|
||||||
<Heading
|
<Heading
|
||||||
|
as="h2"
|
||||||
size="xl"
|
size="xl"
|
||||||
fontFamily="Delicious, sans-serif"
|
fontFamily="Delicious, sans-serif"
|
||||||
fontWeight="700"
|
fontWeight="700"
|
||||||
|
@ -61,6 +63,24 @@ export function Heading2({ children, ...props }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heading2 is a minor subheading, with our DTI-brand-y Delicious font and some
|
||||||
|
* special typographical styles!!
|
||||||
|
*/
|
||||||
|
export function Heading3({ children, ...props }) {
|
||||||
|
return (
|
||||||
|
<Heading
|
||||||
|
as="h3"
|
||||||
|
size="lg"
|
||||||
|
fontFamily="Delicious, sans-serif"
|
||||||
|
fontWeight="700"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Heading>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* safeImageUrl returns an HTTPS-safe image URL for Neopets assets!
|
* safeImageUrl returns an HTTPS-safe image URL for Neopets assets!
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -168,7 +168,7 @@ const resolvers = {
|
||||||
if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) {
|
if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-OWNS`,
|
id: `user-${id}-default-list-OWNS`,
|
||||||
name: "(Not in a list)",
|
name: "Not in a list",
|
||||||
ownsOrWantsItems: "OWNS",
|
ownsOrWantsItems: "OWNS",
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
@ -180,7 +180,7 @@ const resolvers = {
|
||||||
if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) {
|
if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-WANTS`,
|
id: `user-${id}-default-list-WANTS`,
|
||||||
name: "(Not in a list)",
|
name: "Not in a list",
|
||||||
ownsOrWantsItems: "WANTS",
|
ownsOrWantsItems: "WANTS",
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
|
Loading…
Reference in a new issue