real trade data on the page!
This commit is contained in:
parent
6681f9642a
commit
54abd1ac80
6 changed files with 374 additions and 66 deletions
|
@ -1,6 +1,12 @@
|
|||
import React from "react";
|
||||
import { css } from "emotion";
|
||||
import { Box, Tooltip, useColorModeValue, useToken } from "@chakra-ui/core";
|
||||
import {
|
||||
Box,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
useColorModeValue,
|
||||
useToken,
|
||||
} from "@chakra-ui/core";
|
||||
import gql from "graphql-tag";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
@ -14,6 +20,25 @@ export function ItemTradesOfferingPage() {
|
|||
title="Trades: Offering"
|
||||
userHeading="Owner"
|
||||
compareListHeading="They're seeking"
|
||||
tradesQuery={gql`
|
||||
query ItemTradesTableOffering($itemId: ID!) {
|
||||
item(id: $itemId) {
|
||||
id
|
||||
trades: tradesOffering {
|
||||
id
|
||||
user {
|
||||
id
|
||||
username
|
||||
# lastUpdatedAnyTrade
|
||||
}
|
||||
closetList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -24,14 +49,38 @@ export function ItemTradesSeekingPage() {
|
|||
title="Trades: Seeking"
|
||||
userHeading="Seeker"
|
||||
compareListHeading="They're offering"
|
||||
tradesQuery={gql`
|
||||
query ItemTradesTableSeeking($itemId: ID!) {
|
||||
item(id: $itemId) {
|
||||
id
|
||||
trades: tradesSeeking {
|
||||
id
|
||||
user {
|
||||
id
|
||||
username
|
||||
# lastUpdatedAnyTrade
|
||||
}
|
||||
closetList {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemTradesPage({ title, userHeading, compareListHeading }) {
|
||||
function ItemTradesPage({
|
||||
title,
|
||||
userHeading,
|
||||
compareListHeading,
|
||||
tradesQuery,
|
||||
}) {
|
||||
const { itemId } = useParams();
|
||||
|
||||
const { loading, error, data } = useQuery(
|
||||
const { error, data } = useQuery(
|
||||
gql`
|
||||
query ItemTradesPage($itemId: ID!) {
|
||||
item(id: $itemId) {
|
||||
|
@ -48,7 +97,7 @@ function ItemTradesPage({ title, userHeading, compareListHeading }) {
|
|||
{ variables: { itemId }, returnPartialData: true }
|
||||
);
|
||||
|
||||
usePageTitle(`${data?.item?.name} | ${title}`, { skip: loading });
|
||||
usePageTitle(`${data?.item?.name} | ${title}`, { skip: !data?.item?.name });
|
||||
|
||||
if (error) {
|
||||
return <Box color="red.400">{error.message}</Box>;
|
||||
|
@ -63,12 +112,26 @@ function ItemTradesPage({ title, userHeading, compareListHeading }) {
|
|||
itemId={itemId}
|
||||
userHeading={userHeading}
|
||||
compareListHeading={compareListHeading}
|
||||
tradesQuery={tradesQuery}
|
||||
/>
|
||||
</ItemPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemTradesTable({ itemId, userHeading, compareListHeading }) {
|
||||
function ItemTradesTable({
|
||||
itemId,
|
||||
userHeading,
|
||||
compareListHeading,
|
||||
tradesQuery,
|
||||
}) {
|
||||
const { loading, error, data } = useQuery(tradesQuery, {
|
||||
variables: { itemId },
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <Box color="red.400">{error.message}</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="table"
|
||||
|
@ -78,34 +141,65 @@ function ItemTradesTable({ itemId, userHeading, compareListHeading }) {
|
|||
/* Chakra doesn't have props for these! */
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
`}
|
||||
>
|
||||
<Box as="thead">
|
||||
<Box as="thead" fontSize={{ base: "xs", sm: "sm" }}>
|
||||
<Box as="tr">
|
||||
<ItemTradesTableCell as="th">{userHeading}</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th">List</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th">
|
||||
<ItemTradesTableCell as="th" width={{ base: "30%", md: "auto" }}>
|
||||
List
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th" width={{ base: "23%", md: "18ex" }}>
|
||||
{userHeading}
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th" width={{ base: "23%", md: "18ex" }}>
|
||||
{/* A small wording tweak to fit better on the xsmall screens! */}
|
||||
<Box display={{ base: "none", sm: "block" }}>Last updated</Box>
|
||||
<Box display={{ base: "none", sm: "block" }}>Last active</Box>
|
||||
<Box display={{ base: "block", sm: "none" }}>Updated</Box>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th">Compare</ItemTradesTableCell>
|
||||
<ItemTradesTableCell as="th" width={{ base: "23%", md: "18ex" }}>
|
||||
Compare
|
||||
</ItemTradesTableCell>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box as="tbody">
|
||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
||||
{loading && (
|
||||
<>
|
||||
<ItemTradesTableRowSkeleton />
|
||||
<ItemTradesTableRowSkeleton />
|
||||
<ItemTradesTableRowSkeleton />
|
||||
<ItemTradesTableRowSkeleton />
|
||||
<ItemTradesTableRowSkeleton />
|
||||
</>
|
||||
)}
|
||||
{!loading &&
|
||||
data.item.trades.length > 0 &&
|
||||
data.item.trades.map((trade) => (
|
||||
<ItemTradesTableRow
|
||||
key={trade.id}
|
||||
compareListHeading={compareListHeading}
|
||||
href={`/user/${trade.user.id}/items#list-${trade.closetList.id}`}
|
||||
username={trade.user.username}
|
||||
listName={trade.closetList.name}
|
||||
/>
|
||||
))}
|
||||
{!loading && data.item.trades.length === 0 && (
|
||||
<Box as="tr">
|
||||
<ItemTradesTableCell
|
||||
colSpan="4"
|
||||
textAlign="center"
|
||||
fontStyle="italic"
|
||||
>
|
||||
No trades yet!
|
||||
</ItemTradesTableCell>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemTradesTableRow({ compareListHeading }) {
|
||||
const href = "/user/6/items#list-1";
|
||||
|
||||
function ItemTradesTableRow({ compareListHeading, href, username, listName }) {
|
||||
const history = useHistory();
|
||||
const onClick = React.useCallback(() => history.push(href), [history, href]);
|
||||
const focusBackground = useColorModeValue("gray.100", "gray.600");
|
||||
|
@ -113,16 +207,15 @@ function ItemTradesTableRow({ compareListHeading }) {
|
|||
return (
|
||||
<Box
|
||||
as="tr"
|
||||
cursor={"pointer"}
|
||||
cursor="pointer"
|
||||
_hover={{ background: focusBackground }}
|
||||
_focusWithin={{ background: focusBackground }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ItemTradesTableCell>Matchu</ItemTradesTableCell>
|
||||
<ItemTradesTableCell>
|
||||
<ItemTradesTableCell overflowWrap="break-word" fontSize="sm">
|
||||
<Box
|
||||
as="a"
|
||||
href="/user/6/items#list-1"
|
||||
href={href}
|
||||
className={css`
|
||||
&:hover,
|
||||
&:focus,
|
||||
|
@ -132,14 +225,17 @@ function ItemTradesTableRow({ compareListHeading }) {
|
|||
}
|
||||
`}
|
||||
>
|
||||
Top priorities and such so yeah
|
||||
{listName}
|
||||
</Box>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell>
|
||||
<ItemTradesTableCell overflowWrap="break-word" fontSize="xs">
|
||||
{username}
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell fontSize="xs">
|
||||
<Box display={{ base: "block", sm: "none" }}><1 week</Box>
|
||||
<Box display={{ base: "none", sm: "block" }}>This week</Box>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell height="100%">
|
||||
<ItemTradesTableCell fontSize="xs">
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
label={
|
||||
|
@ -177,6 +273,25 @@ function ItemTradesTableRow({ compareListHeading }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ItemTradesTableRowSkeleton() {
|
||||
return (
|
||||
<Box as="tr">
|
||||
<ItemTradesTableCell>
|
||||
<Skeleton width="100%">Placeholder</Skeleton>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell>
|
||||
<Skeleton width="100%">Placeholder</Skeleton>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell>
|
||||
<Skeleton width="100%">Placeholder</Skeleton>
|
||||
</ItemTradesTableCell>
|
||||
<ItemTradesTableCell>
|
||||
<Skeleton width="100%">Placeholder</Skeleton>
|
||||
</ItemTradesTableCell>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemTradesTableCell({ children, as = "td", ...props }) {
|
||||
const borderColor = useColorModeValue("gray.300", "gray.400");
|
||||
const borderColorCss = useToken("colors", borderColor);
|
||||
|
@ -188,7 +303,6 @@ function ItemTradesTableCell({ children, as = "td", ...props }) {
|
|||
paddingX="4"
|
||||
paddingY="2"
|
||||
textAlign="left"
|
||||
fontSize={{ base: "xs", sm: "sm" }}
|
||||
className={css`
|
||||
/* Lol sigh, getting this right is way more involved than I wish it
|
||||
* were. What I really want is border-collapse and a simple 1px border,
|
||||
|
|
|
@ -33,6 +33,7 @@ const schema = makeExecutableSchema(
|
|||
mergeTypeDefsAndResolvers([
|
||||
{ typeDefs: rootTypeDefs, resolvers: {} },
|
||||
require("./types/AppearanceLayer"),
|
||||
require("./types/ClosetList"),
|
||||
require("./types/Item"),
|
||||
require("./types/MutationsForSupport"),
|
||||
require("./types/Outfit"),
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
const DataLoader = require("dataloader");
|
||||
const { normalizeRow } = require("./util");
|
||||
|
||||
const buildClosetListLoader = (db) =>
|
||||
new DataLoader(async (ids) => {
|
||||
const qs = ids.map((_) => "?").join(",");
|
||||
const [rows, _] = await db.execute(
|
||||
`SELECT * FROM closet_lists WHERE id IN (${qs})`,
|
||||
ids
|
||||
);
|
||||
|
||||
const entities = rows.map(normalizeRow);
|
||||
|
||||
return ids.map((id) => entities.find((e) => e.id === id));
|
||||
});
|
||||
|
||||
const buildColorLoader = (db) => {
|
||||
const colorLoader = new DataLoader(async (colorIds) => {
|
||||
const qs = colorIds.map((_) => "?").join(",");
|
||||
|
@ -442,6 +455,71 @@ const buildItemTradeCountsLoader = (db) =>
|
|||
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
||||
);
|
||||
|
||||
const buildItemTradesLoader = (db, loaders) =>
|
||||
new DataLoader(
|
||||
async (itemIdOwnedPairs) => {
|
||||
const qs = itemIdOwnedPairs
|
||||
.map((_) => "(closet_hangers.item_id = ? AND closet_hangers.owned = ?)")
|
||||
.join(" OR ");
|
||||
const values = itemIdOwnedPairs
|
||||
.map(({ itemId, isOwned }) => [itemId, isOwned])
|
||||
.flat();
|
||||
const [rows, _] = await db.execute(
|
||||
{
|
||||
sql: `
|
||||
SELECT
|
||||
closet_hangers.*, closet_lists.*, users.*
|
||||
FROM closet_hangers
|
||||
INNER JOIN users ON users.id = closet_hangers.user_id
|
||||
LEFT JOIN closet_lists ON closet_lists.id = closet_hangers.list_id
|
||||
WHERE (
|
||||
(${qs})
|
||||
AND (
|
||||
(closet_hangers.list_id IS NOT NULL AND closet_lists.visibility >= 2)
|
||||
OR (
|
||||
closet_hangers.list_id IS NULL AND closet_hangers.owned = 1
|
||||
AND users.owned_closet_hangers_visibility >= 2
|
||||
)
|
||||
OR (
|
||||
closet_hangers.list_id IS NULL AND closet_hangers.owned = 0
|
||||
AND users.wanted_closet_hangers_visibility >= 2
|
||||
)
|
||||
)
|
||||
);
|
||||
`,
|
||||
nestTables: true,
|
||||
},
|
||||
values
|
||||
);
|
||||
|
||||
const entities = rows.map((row) => ({
|
||||
closetHanger: normalizeRow(row.closet_hangers),
|
||||
closetList: normalizeRow(row.closet_lists),
|
||||
user: normalizeRow(row.users),
|
||||
}));
|
||||
|
||||
for (const entity of entities) {
|
||||
loaders.userLoader.prime(entity.user.id, entity.user);
|
||||
loaders.closetListLoader.prime(entity.closetList.id, entity.closetList);
|
||||
}
|
||||
|
||||
return itemIdOwnedPairs.map(({ itemId, isOwned }) =>
|
||||
entities
|
||||
.filter(
|
||||
(e) =>
|
||||
e.closetHanger.itemId === itemId &&
|
||||
Boolean(e.closetHanger.owned) === isOwned
|
||||
)
|
||||
.map((e) => ({
|
||||
id: e.closetHanger.id,
|
||||
closetList: e.closetList.id ? e.closetList : null,
|
||||
user: e.user,
|
||||
}))
|
||||
);
|
||||
},
|
||||
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
||||
);
|
||||
|
||||
const buildPetTypeLoader = (db, loaders) =>
|
||||
new DataLoader(async (petTypeIds) => {
|
||||
const qs = petTypeIds.map((_) => "?").join(",");
|
||||
|
@ -821,7 +899,7 @@ const buildUserClosetHangersLoader = (db) =>
|
|||
);
|
||||
});
|
||||
|
||||
const buildUserClosetListsLoader = (db) =>
|
||||
const buildUserClosetListsLoader = (db, loaders) =>
|
||||
new DataLoader(async (userIds) => {
|
||||
const qs = userIds.map((_) => "?").join(",");
|
||||
const [rows, _] = await db.execute(
|
||||
|
@ -830,7 +908,11 @@ const buildUserClosetListsLoader = (db) =>
|
|||
ORDER BY name`,
|
||||
userIds
|
||||
);
|
||||
|
||||
const entities = rows.map(normalizeRow);
|
||||
for (const entity of entities) {
|
||||
loaders.closetListLoader.prime(entity.id, entity);
|
||||
}
|
||||
|
||||
return userIds.map((userId) =>
|
||||
entities.filter((e) => e.userId === String(userId))
|
||||
|
@ -891,6 +973,7 @@ function buildLoaders(db) {
|
|||
const loaders = {};
|
||||
loaders.loadAllPetTypes = loadAllPetTypes(db);
|
||||
|
||||
loaders.closetListLoader = buildClosetListLoader(db);
|
||||
loaders.colorLoader = buildColorLoader(db);
|
||||
loaders.colorTranslationLoader = buildColorTranslationLoader(db);
|
||||
loaders.itemLoader = buildItemLoader(db);
|
||||
|
@ -904,6 +987,7 @@ function buildLoaders(db) {
|
|||
);
|
||||
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
||||
loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db);
|
||||
loaders.itemTradesLoader = buildItemTradesLoader(db, loaders);
|
||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||
db,
|
||||
|
@ -937,7 +1021,7 @@ function buildLoaders(db) {
|
|||
loaders.userByNameLoader = buildUserByNameLoader(db);
|
||||
loaders.userByEmailLoader = buildUserByEmailLoader(db);
|
||||
loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db);
|
||||
loaders.userClosetListsLoader = buildUserClosetListsLoader(db);
|
||||
loaders.userClosetListsLoader = buildUserClosetListsLoader(db, loaders);
|
||||
loaders.zoneLoader = buildZoneLoader(db);
|
||||
loaders.zoneTranslationLoader = buildZoneTranslationLoader(db);
|
||||
|
||||
|
|
96
src/server/types/ClosetList.js
Normal file
96
src/server/types/ClosetList.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
const { gql } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
enum OwnsOrWants {
|
||||
OWNS
|
||||
WANTS
|
||||
}
|
||||
|
||||
type ClosetList {
|
||||
id: ID!
|
||||
name: String
|
||||
|
||||
# A user-customized description. May contain Markdown and limited HTML.
|
||||
description: String
|
||||
|
||||
# Whether this is a list of items they own, or items they want.
|
||||
ownsOrWantsItems: OwnsOrWants!
|
||||
|
||||
# Each user has a "default list" of items they own/want. When users click
|
||||
# the Own/Want button on the item page, items go here automatically. (On
|
||||
# the backend, this is managed as the hangers having a null list ID.)
|
||||
#
|
||||
# This field is true if the list is the default list, so we can style it
|
||||
# differently and change certain behaviors (e.g. can't be deleted).
|
||||
isDefaultList: Boolean!
|
||||
|
||||
items: [Item!]!
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
ClosetList: {
|
||||
id: ({ id, isDefaultList, userId, ownsOrWantsItems }) => {
|
||||
if (isDefaultList) {
|
||||
return `user-${userId}-default-list-${ownsOrWantsItems}`;
|
||||
}
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
name: async ({ id, isDefaultList }, _, { closetListLoader }) => {
|
||||
if (isDefaultList) {
|
||||
return "Not in a list";
|
||||
}
|
||||
|
||||
const list = await closetListLoader.load(id);
|
||||
return list.name;
|
||||
},
|
||||
|
||||
description: async ({ id, isDefaultList }, _, { closetListLoader }) => {
|
||||
if (isDefaultList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const list = await closetListLoader.load(id);
|
||||
return list.description;
|
||||
},
|
||||
|
||||
ownsOrWantsItems: async (
|
||||
{ id, isDefaultList, ownsOrWantsItems },
|
||||
_,
|
||||
{ closetListLoader }
|
||||
) => {
|
||||
if (isDefaultList) {
|
||||
return ownsOrWantsItems;
|
||||
}
|
||||
|
||||
const list = await closetListLoader.load(id);
|
||||
return list.hangersOwned ? "OWNS" : "WANTS";
|
||||
},
|
||||
|
||||
isDefaultList: ({ isDefaultList }) => {
|
||||
return Boolean(isDefaultList);
|
||||
},
|
||||
|
||||
items: ({ items }) => {
|
||||
// HACK: When called from User.js, for fetching all of a user's lists at
|
||||
// once, this is provided in the returned object. This was before
|
||||
// we separated out the ClosetList resolvers at all! But I'm not
|
||||
// bothering to port it, because it would mean writing a new
|
||||
// loader, and we don't yet have any endpoints that actually need
|
||||
// this.
|
||||
if (items) {
|
||||
return items;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`TODO: Not implemented, we still duplicate / bulk-implement some of ` +
|
||||
`the list resolver stuff in User.js. Break that out into real ` +
|
||||
`ClosetList loaders and resolvers!`
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { typeDefs, resolvers };
|
|
@ -26,6 +26,10 @@ const typeDefs = gql`
|
|||
numUsersOfferingThis: Int!
|
||||
numUsersSeekingThis: Int!
|
||||
|
||||
# The trades available for this item, grouped by offering vs seeking.
|
||||
tradesOffering: [ItemTrade!]!
|
||||
tradesSeeking: [ItemTrade!]!
|
||||
|
||||
# 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!
|
||||
|
@ -79,12 +83,21 @@ const typeDefs = gql`
|
|||
PB
|
||||
}
|
||||
|
||||
# TODO: I guess I didn't add the NC/NP/PB filter to this. Does that cause
|
||||
# bugs in comparing results on the client? (Also, should we just throw
|
||||
# this out for a better merge function?)
|
||||
type ItemSearchResult {
|
||||
query: String!
|
||||
zones: [Zone!]!
|
||||
items: [Item!]!
|
||||
}
|
||||
|
||||
type ItemTrade {
|
||||
id: ID!
|
||||
user: User!
|
||||
closetList: ClosetList!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
item(id: ID!): Item
|
||||
items(ids: [ID!]!): [Item!]!
|
||||
|
@ -204,6 +217,39 @@ const resolvers = {
|
|||
return count;
|
||||
},
|
||||
|
||||
tradesOffering: async ({ id }, _, { itemTradesLoader }) => {
|
||||
const trades = await itemTradesLoader.load({ itemId: id, isOwned: true });
|
||||
return trades.map((trade) => ({
|
||||
id: trade.id,
|
||||
closetList: trade.closetList
|
||||
? { id: trade.closetList.id }
|
||||
: {
|
||||
isDefaultList: true,
|
||||
userId: trade.user.id,
|
||||
ownsOrWantsItems: "OWNS",
|
||||
},
|
||||
user: { id: trade.user.id },
|
||||
}));
|
||||
},
|
||||
|
||||
tradesSeeking: async ({ id }, _, { itemTradesLoader }) => {
|
||||
const trades = await itemTradesLoader.load({
|
||||
itemId: id,
|
||||
isOwned: false,
|
||||
});
|
||||
return trades.map((trade) => ({
|
||||
id: trade.id,
|
||||
closetList: trade.closetList
|
||||
? { id: trade.closetList.id }
|
||||
: {
|
||||
isDefaultList: true,
|
||||
userId: trade.user.id,
|
||||
ownsOrWantsItems: "WANTS",
|
||||
},
|
||||
user: { id: trade.user.id },
|
||||
}));
|
||||
},
|
||||
|
||||
appearanceOn: async (
|
||||
{ id },
|
||||
{ speciesId, colorId },
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
const { gql } = require("apollo-server");
|
||||
|
||||
const typeDefs = gql`
|
||||
enum OwnsOrWants {
|
||||
OWNS
|
||||
WANTS
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
username: String!
|
||||
|
@ -17,27 +12,6 @@ const typeDefs = gql`
|
|||
itemsTheyWant: [Item!]!
|
||||
}
|
||||
|
||||
type ClosetList {
|
||||
id: ID!
|
||||
name: String
|
||||
|
||||
# A user-customized description. May contain Markdown and limited HTML.
|
||||
description: String
|
||||
|
||||
# Whether this is a list of items they own, or items they want.
|
||||
ownsOrWantsItems: OwnsOrWants!
|
||||
|
||||
# Each user has a "default list" of items they own/want. When users click
|
||||
# the Own/Want button on the item page, items go here automatically. (On
|
||||
# the backend, this is managed as the hangers having a null list ID.)
|
||||
#
|
||||
# This field is true if the list is the default list, so we can style it
|
||||
# differently and change certain behaviors (e.g. can't be deleted).
|
||||
isDefaultList: Boolean!
|
||||
|
||||
items: [Item!]!
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
user(id: ID!): User
|
||||
userByName(name: String!): User
|
||||
|
@ -162,10 +136,6 @@ const resolvers = {
|
|||
.filter((closetList) => isCurrentUser || closetList.visibility >= 1)
|
||||
.map((closetList) => ({
|
||||
id: closetList.id,
|
||||
name: closetList.name,
|
||||
description: closetList.description,
|
||||
ownsOrWantsItems: closetList.hangersOwned ? "OWNS" : "WANTS",
|
||||
isDefaultList: false,
|
||||
items: allClosetHangers
|
||||
.filter((h) => h.listId === closetList.id)
|
||||
.map((h) => ({ id: h.itemId })),
|
||||
|
@ -173,11 +143,9 @@ const resolvers = {
|
|||
|
||||
if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) {
|
||||
closetListNodes.push({
|
||||
id: `user-${id}-default-list-OWNS`,
|
||||
name: "Not in a list",
|
||||
description: null,
|
||||
ownsOrWantsItems: "OWNS",
|
||||
isDefaultList: true,
|
||||
userId: id,
|
||||
ownsOrWantsItems: "OWNS",
|
||||
items: allClosetHangers
|
||||
.filter((h) => h.listId == null && h.owned)
|
||||
.map((h) => ({ id: h.itemId })),
|
||||
|
@ -186,9 +154,8 @@ const resolvers = {
|
|||
|
||||
if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) {
|
||||
closetListNodes.push({
|
||||
id: `user-${id}-default-list-WANTS`,
|
||||
name: "Not in a list",
|
||||
description: null,
|
||||
isDefaultList: true,
|
||||
userId: id,
|
||||
ownsOrWantsItems: "WANTS",
|
||||
isDefaultList: true,
|
||||
items: allClosetHangers
|
||||
|
|
Loading…
Reference in a new issue