From 4e00962edc527a396386b2b9ef4919ff4c817bdd Mon Sep 17 00:00:00 2001 From: Matchu Date: Tue, 27 Oct 2020 23:09:42 -0700 Subject: [PATCH] add individual lists to user items page --- src/app/UserItemsPage.js | 208 ++++++++++++++++++++++++--------------- src/app/util.js | 20 ++++ src/server/types/User.js | 4 +- 3 files changed, 149 insertions(+), 83 deletions(-) diff --git a/src/app/UserItemsPage.js b/src/app/UserItemsPage.js index e67a288..0e02447 100644 --- a/src/app/UserItemsPage.js +++ b/src/app/UserItemsPage.js @@ -1,12 +1,12 @@ 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 gql from "graphql-tag"; import { useParams } from "react-router-dom"; import { useQuery } from "@apollo/client"; import HangerSpinner from "./components/HangerSpinner"; -import { Heading1, Heading2 } from "./util"; +import { Heading1, Heading2, Heading3 } from "./util"; import ItemCard, { ItemBadgeList, ItemCardList, @@ -32,27 +32,22 @@ function UserItemsPage() { username contactNeopetsUsername - itemsTheyOwn { + closetLists { id - isNc name - thumbnailUrl - currentUserWantsThis - allOccupiedZones { + ownsOrWantsItems + isDefaultList + items { id - label @client - } - } - - itemsTheyWant { - id - isNc - name - thumbnailUrl - currentUserOwnsThis - allOccupiedZones { - id - label @client + isNc + name + thumbnailUrl + currentUserOwnsThis + currentUserWantsThis + allOccupiedZones { + id + label @client + } } } } @@ -73,36 +68,48 @@ function UserItemsPage() { return {error.message}; } - // This helps you compare your owns/wants to other users! If they own - // something, and you want it, we say "You want this!". And if they want - // something, and you own it, we say "You own this!". - const showYouOwnThisBadge = (item) => - !isCurrentUser && item.currentUserOwnsThis; - const showYouWantThisBadge = (item) => - !isCurrentUser && item.currentUserWantsThis; + if (data.user == null) { + return User not found; + } - const numYouOwnThisBadges = data.user.itemsTheyWant.filter( - showYouOwnThisBadge - ).length; - const numYouWantThisBadges = data.user.itemsTheyOwn.filter( - showYouWantThisBadge - ).length; + const listsOfOwnedItems = data.user.closetLists.filter( + (l) => l.ownsOrWantsItems === "OWNS" + ); + const listsOfWantedItems = data.user.closetLists.filter( + (l) => l.ownsOrWantsItems === "WANTS" + ); - const sortedItemsTheyOwn = [...data.user.itemsTheyOwn].sort((a, b) => { - // This is a cute sort hack. We sort first by, bringing "You want this!" to - // the top, and then sorting by name _within_ those two groups. - const aName = `${showYouWantThisBadge(a) ? "000" : "999"} ${a.name}`; - const bName = `${showYouWantThisBadge(b) ? "000" : "999"} ${b.name}`; + // Sort default list to the end, then sort alphabetically. We use a similar + // sort hack that we use for sorting items in ClosetList! + listsOfOwnedItems.sort((a, b) => { + const aName = `${a.isDefaultList ? "ZZZ" : "AAA"} ${a.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); }); - const sortedItemsTheyWant = [...data.user.itemsTheyWant].sort((a, b) => { - // This is a cute sort hack. We sort first by, bringing "You own this!" to - // the top, and then sorting by name _within_ those two groups. - const aName = `${showYouOwnThisBadge(a) ? "000" : "999"} ${a.name}`; - const bName = `${showYouOwnThisBadge(b) ? "000" : "999"} ${b.name}`; - return aName.localeCompare(bName); - }); + const allItemsTheyOwn = listsOfOwnedItems.map((l) => l.items).flat(); + const allItemsTheyWant = listsOfWantedItems.map((l) => l.items).flat(); + + const itemsTheyOwnThatYouWant = allItemsTheyOwn.filter( + (i) => i.currentUserWantsThis + ); + 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 ( @@ -140,7 +147,7 @@ function UserItemsPage() { * _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_ * something, and _then_ thinking about what you can offer. */} - {numYouWantThisBadges > 0 && ( + {!isCurrentUser && numItemsTheyOwnThatYouWant > 0 && ( - {numYouWantThisBadges > 1 - ? `${numYouWantThisBadges} items you want` + {numItemsTheyOwnThatYouWant > 1 + ? `${numItemsTheyOwnThatYouWant} items you want` : "1 item you want"} )} - {numYouOwnThisBadges > 0 && ( + {!isCurrentUser && numItemsTheyWantThatYouOwn > 0 && ( - {numYouOwnThisBadges > 1 - ? `${numYouOwnThisBadges} items you own` + {numItemsTheyWantThatYouOwn > 1 + ? `${numItemsTheyWantThatYouOwn} items you own` : "1 item you own"} )} - + {isCurrentUser ? "Items you own" : `Items ${data.user.username} owns`} - - {sortedItemsTheyOwn.map((item) => { - return ( + + {listsOfOwnedItems.map((closetList) => ( + 1} + /> + ))} + + + + {isCurrentUser ? "Items you want" : `Items ${data.user.username} wants`} + + + {listsOfWantedItems.map((closetList) => ( + 1} + /> + ))} + + + ); +} + +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 ( + + {showHeading && ( + + {closetList.name} + + )} + {sortedItems.length > 0 ? ( + + {sortedItems.map((item) => ( {item.isNc ? : } - {showYouWantThisBadge(item) && } + {hasYouOwnThisBadge(item) && } + {hasYouWantThisBadge(item) && } } /> - ); - })} - - - - {isCurrentUser ? "Items you want" : `Items ${data.user.username} wants`} - - - {sortedItemsTheyWant.map((item) => ( - - {item.isNc ? : } - {showYouOwnThisBadge(item) && } - - - } - /> - ))} - + ))} + + ) : ( + This list is empty! + )} ); } diff --git a/src/app/util.js b/src/app/util.js index 8bd4122..8376aac 100644 --- a/src/app/util.js +++ b/src/app/util.js @@ -34,6 +34,7 @@ export function Delay({ children, ms = 300 }) { export function Heading1({ children, ...props }) { return ( + {children} + + ); +} + /** * safeImageUrl returns an HTTPS-safe image URL for Neopets assets! */ diff --git a/src/server/types/User.js b/src/server/types/User.js index bf5822e..bdcfbd6 100644 --- a/src/server/types/User.js +++ b/src/server/types/User.js @@ -168,7 +168,7 @@ const resolvers = { if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) { closetListNodes.push({ id: `user-${id}-default-list-OWNS`, - name: "(Not in a list)", + name: "Not in a list", ownsOrWantsItems: "OWNS", isDefaultList: true, items: allClosetHangers @@ -180,7 +180,7 @@ const resolvers = { if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) { closetListNodes.push({ id: `user-${id}-default-list-WANTS`, - name: "(Not in a list)", + name: "Not in a list", ownsOrWantsItems: "WANTS", isDefaultList: true, items: allClosetHangers