diff --git a/src/app/WardrobePage/Item.js b/src/app/WardrobePage/Item.js index ad235ac..08b579e 100644 --- a/src/app/WardrobePage/Item.js +++ b/src/app/WardrobePage/Item.js @@ -25,6 +25,8 @@ import { ItemBadgeTooltip, NcBadge, NpBadge, + YouOwnThisBadge, + YouWantThisBadge, } from "../components/ItemCard"; import SupportOnly from "./support/SupportOnly"; @@ -222,6 +224,8 @@ function ItemBadges({ item }) { // than try to line things up like a table. )} + {item.currentUserOwnsThis && } + {item.currentUserWantsThis && } {occupiedZoneLabels.map((zoneLabel) => ( ))} diff --git a/src/app/WardrobePage/SearchPanel.js b/src/app/WardrobePage/SearchPanel.js index 5df3685..2c357da 100644 --- a/src/app/WardrobePage/SearchPanel.js +++ b/src/app/WardrobePage/SearchPanel.js @@ -274,6 +274,8 @@ function useSearchResults(query, outfitState) { name thumbnailUrl isNc + currentUserOwnsThis + currentUserWantsThis appearanceOn(speciesId: $speciesId, colorId: $colorId) { # This enables us to quickly show the item when the user clicks it! diff --git a/src/app/WardrobePage/useOutfitState.js b/src/app/WardrobePage/useOutfitState.js index 1882202..ab56616 100644 --- a/src/app/WardrobePage/useOutfitState.js +++ b/src/app/WardrobePage/useOutfitState.js @@ -38,6 +38,8 @@ function useOutfitState() { name thumbnailUrl isNc + currentUserOwnsThis + currentUserWantsThis appearanceOn(speciesId: $speciesId, colorId: $colorId) { # This enables us to quickly show the item when the user clicks it! diff --git a/src/app/components/ItemCard.js b/src/app/components/ItemCard.js index b2e156a..4846115 100644 --- a/src/app/components/ItemCard.js +++ b/src/app/components/ItemCard.js @@ -9,7 +9,7 @@ import { useColorModeValue, useTheme, } from "@chakra-ui/core"; -import { StarIcon } from "@chakra-ui/icons"; +import { CheckIcon, StarIcon } from "@chakra-ui/icons"; import { Link } from "react-router-dom"; import { safeImageUrl } from "../util"; @@ -238,20 +238,22 @@ export function NpBadge() { ); } -export function YouOwnThisBadge() { +export function YouOwnThisBadge({ variant = "long" }) { return ( - - - You own this! + + + {variant === "long" && <>You own this!} + {variant === "short" && <>Own} ); } -export function YouWantThisBadge() { +export function YouWantThisBadge({ variant = "long" }) { return ( - You want this! + {variant === "long" && <>You want this!} + {variant === "short" && <>Want} ); } diff --git a/src/server/query-tests/Item.test.js b/src/server/query-tests/Item.test.js index e06f888..e730989 100644 --- a/src/server/query-tests/Item.test.js +++ b/src/server/query-tests/Item.test.js @@ -1,5 +1,5 @@ const gql = require("graphql-tag"); -const { query, getDbCalls } = require("./setup.js"); +const { query, getDbCalls, logInAsTestUser } = require("./setup.js"); describe("Item", () => { it("loads metadata", async () => { @@ -288,6 +288,98 @@ describe("Item", () => { `); }); + it("loads whether we own/want items", async () => { + await logInAsTestUser(); + + const res = await query({ + query: gql` + query { + items(ids: ["38913", "39945", "39948"]) { + id + currentUserOwnsThis + currentUserWantsThis + } + } + `, + }); + + expect(res).toHaveNoErrors(); + expect(res.data).toMatchInlineSnapshot(` + Object { + "items": Array [ + Object { + "currentUserOwnsThis": false, + "currentUserWantsThis": false, + "id": "38913", + }, + Object { + "currentUserOwnsThis": false, + "currentUserWantsThis": true, + "id": "39945", + }, + Object { + "currentUserOwnsThis": true, + "currentUserWantsThis": false, + "id": "39948", + }, + ], + } + `); + expect(getDbCalls()).toMatchInlineSnapshot(` + Array [ + Array [ + "SELECT closet_hangers.*, item_translations.name as item_name FROM closet_hangers + INNER JOIN items ON items.id = closet_hangers.item_id + INNER JOIN item_translations ON + item_translations.item_id = items.id AND locale = \\"en\\" + WHERE user_id IN (?) + ORDER BY item_name", + Array [ + "44743", + ], + ], + ] + `); + }); + + it("does not own/want items if not logged in", async () => { + const res = await query({ + query: gql` + query { + items(ids: ["38913", "39945", "39948"]) { + id + currentUserOwnsThis + currentUserWantsThis + } + } + `, + }); + + expect(res).toHaveNoErrors(); + expect(res.data).toMatchInlineSnapshot(` + Object { + "items": Array [ + Object { + "currentUserOwnsThis": false, + "currentUserWantsThis": false, + "id": "38913", + }, + Object { + "currentUserOwnsThis": false, + "currentUserWantsThis": false, + "id": "39945", + }, + Object { + "currentUserOwnsThis": false, + "currentUserWantsThis": false, + "id": "39948", + }, + ], + } + `); + expect(getDbCalls()).toMatchInlineSnapshot(`Array []`); + }); + it("loads items that need models", async () => { jest.setTimeout(20000); diff --git a/src/server/types/Item.js b/src/server/types/Item.js index ca31e63..4cf1b5e 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -10,6 +10,9 @@ const typeDefs = gql` rarityIndex: Int! isNc: Boolean! + currentUserOwnsThis: Boolean! + currentUserWantsThis: Boolean! + # How this item appears on the given species/color combo. If it does not # fit the pet, we'll return an empty ItemAppearance with no layers. appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance! @@ -91,6 +94,26 @@ const resolvers = { const item = await itemLoader.load(id); return item.rarityIndex === 500 || item.rarityIndex === 0; }, + + currentUserOwnsThis: async ( + { id }, + _, + { currentUserId, userClosetHangersLoader } + ) => { + if (currentUserId == null) return false; + const closetHangers = await userClosetHangersLoader.load(currentUserId); + return closetHangers.some((h) => h.itemId === id && h.owned); + }, + currentUserWantsThis: async ( + { id }, + _, + { currentUserId, userClosetHangersLoader } + ) => { + if (currentUserId == null) return false; + const closetHangers = await userClosetHangersLoader.load(currentUserId); + return closetHangers.some((h) => h.itemId === id && !h.owned); + }, + appearanceOn: async ( { id }, { speciesId, colorId },