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 React from "react";
|
||||||
import { css } from "emotion";
|
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 gql from "graphql-tag";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
|
@ -14,6 +20,25 @@ export function ItemTradesOfferingPage() {
|
||||||
title="Trades: Offering"
|
title="Trades: Offering"
|
||||||
userHeading="Owner"
|
userHeading="Owner"
|
||||||
compareListHeading="They're seeking"
|
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"
|
title="Trades: Seeking"
|
||||||
userHeading="Seeker"
|
userHeading="Seeker"
|
||||||
compareListHeading="They're offering"
|
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 { itemId } = useParams();
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(
|
const { error, data } = useQuery(
|
||||||
gql`
|
gql`
|
||||||
query ItemTradesPage($itemId: ID!) {
|
query ItemTradesPage($itemId: ID!) {
|
||||||
item(id: $itemId) {
|
item(id: $itemId) {
|
||||||
|
@ -48,7 +97,7 @@ function ItemTradesPage({ title, userHeading, compareListHeading }) {
|
||||||
{ variables: { itemId }, returnPartialData: true }
|
{ variables: { itemId }, returnPartialData: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
usePageTitle(`${data?.item?.name} | ${title}`, { skip: loading });
|
usePageTitle(`${data?.item?.name} | ${title}`, { skip: !data?.item?.name });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Box color="red.400">{error.message}</Box>;
|
return <Box color="red.400">{error.message}</Box>;
|
||||||
|
@ -63,12 +112,26 @@ function ItemTradesPage({ title, userHeading, compareListHeading }) {
|
||||||
itemId={itemId}
|
itemId={itemId}
|
||||||
userHeading={userHeading}
|
userHeading={userHeading}
|
||||||
compareListHeading={compareListHeading}
|
compareListHeading={compareListHeading}
|
||||||
|
tradesQuery={tradesQuery}
|
||||||
/>
|
/>
|
||||||
</ItemPageLayout>
|
</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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="table"
|
as="table"
|
||||||
|
@ -78,34 +141,65 @@ function ItemTradesTable({ itemId, userHeading, compareListHeading }) {
|
||||||
/* Chakra doesn't have props for these! */
|
/* Chakra doesn't have props for these! */
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
table-layout: fixed;
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Box as="thead">
|
<Box as="thead" fontSize={{ base: "xs", sm: "sm" }}>
|
||||||
<Box as="tr">
|
<Box as="tr">
|
||||||
<ItemTradesTableCell as="th">{userHeading}</ItemTradesTableCell>
|
<ItemTradesTableCell as="th" width={{ base: "30%", md: "auto" }}>
|
||||||
<ItemTradesTableCell as="th">List</ItemTradesTableCell>
|
List
|
||||||
<ItemTradesTableCell as="th">
|
</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! */}
|
{/* 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>
|
<Box display={{ base: "block", sm: "none" }}>Updated</Box>
|
||||||
</ItemTradesTableCell>
|
</ItemTradesTableCell>
|
||||||
<ItemTradesTableCell as="th">Compare</ItemTradesTableCell>
|
<ItemTradesTableCell as="th" width={{ base: "23%", md: "18ex" }}>
|
||||||
|
Compare
|
||||||
|
</ItemTradesTableCell>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box as="tbody">
|
<Box as="tbody">
|
||||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
{loading && (
|
||||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
<>
|
||||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
<ItemTradesTableRowSkeleton />
|
||||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
<ItemTradesTableRowSkeleton />
|
||||||
<ItemTradesTableRow compareListHeading={compareListHeading} />
|
<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>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemTradesTableRow({ compareListHeading }) {
|
function ItemTradesTableRow({ compareListHeading, href, username, listName }) {
|
||||||
const href = "/user/6/items#list-1";
|
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const onClick = React.useCallback(() => history.push(href), [history, href]);
|
const onClick = React.useCallback(() => history.push(href), [history, href]);
|
||||||
const focusBackground = useColorModeValue("gray.100", "gray.600");
|
const focusBackground = useColorModeValue("gray.100", "gray.600");
|
||||||
|
@ -113,16 +207,15 @@ function ItemTradesTableRow({ compareListHeading }) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="tr"
|
as="tr"
|
||||||
cursor={"pointer"}
|
cursor="pointer"
|
||||||
_hover={{ background: focusBackground }}
|
_hover={{ background: focusBackground }}
|
||||||
_focusWithin={{ background: focusBackground }}
|
_focusWithin={{ background: focusBackground }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<ItemTradesTableCell>Matchu</ItemTradesTableCell>
|
<ItemTradesTableCell overflowWrap="break-word" fontSize="sm">
|
||||||
<ItemTradesTableCell>
|
|
||||||
<Box
|
<Box
|
||||||
as="a"
|
as="a"
|
||||||
href="/user/6/items#list-1"
|
href={href}
|
||||||
className={css`
|
className={css`
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus,
|
&:focus,
|
||||||
|
@ -132,14 +225,17 @@ function ItemTradesTableRow({ compareListHeading }) {
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Top priorities and such so yeah
|
{listName}
|
||||||
</Box>
|
</Box>
|
||||||
</ItemTradesTableCell>
|
</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: "block", sm: "none" }}><1 week</Box>
|
||||||
<Box display={{ base: "none", sm: "block" }}>This week</Box>
|
<Box display={{ base: "none", sm: "block" }}>This week</Box>
|
||||||
</ItemTradesTableCell>
|
</ItemTradesTableCell>
|
||||||
<ItemTradesTableCell height="100%">
|
<ItemTradesTableCell fontSize="xs">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
label={
|
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 }) {
|
function ItemTradesTableCell({ children, as = "td", ...props }) {
|
||||||
const borderColor = useColorModeValue("gray.300", "gray.400");
|
const borderColor = useColorModeValue("gray.300", "gray.400");
|
||||||
const borderColorCss = useToken("colors", borderColor);
|
const borderColorCss = useToken("colors", borderColor);
|
||||||
|
@ -188,7 +303,6 @@ function ItemTradesTableCell({ children, as = "td", ...props }) {
|
||||||
paddingX="4"
|
paddingX="4"
|
||||||
paddingY="2"
|
paddingY="2"
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
fontSize={{ base: "xs", sm: "sm" }}
|
|
||||||
className={css`
|
className={css`
|
||||||
/* Lol sigh, getting this right is way more involved than I wish it
|
/* 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,
|
* were. What I really want is border-collapse and a simple 1px border,
|
||||||
|
|
|
@ -33,6 +33,7 @@ const schema = makeExecutableSchema(
|
||||||
mergeTypeDefsAndResolvers([
|
mergeTypeDefsAndResolvers([
|
||||||
{ typeDefs: rootTypeDefs, resolvers: {} },
|
{ typeDefs: rootTypeDefs, resolvers: {} },
|
||||||
require("./types/AppearanceLayer"),
|
require("./types/AppearanceLayer"),
|
||||||
|
require("./types/ClosetList"),
|
||||||
require("./types/Item"),
|
require("./types/Item"),
|
||||||
require("./types/MutationsForSupport"),
|
require("./types/MutationsForSupport"),
|
||||||
require("./types/Outfit"),
|
require("./types/Outfit"),
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
const DataLoader = require("dataloader");
|
const DataLoader = require("dataloader");
|
||||||
const { normalizeRow } = require("./util");
|
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 buildColorLoader = (db) => {
|
||||||
const colorLoader = new DataLoader(async (colorIds) => {
|
const colorLoader = new DataLoader(async (colorIds) => {
|
||||||
const qs = colorIds.map((_) => "?").join(",");
|
const qs = colorIds.map((_) => "?").join(",");
|
||||||
|
@ -442,6 +455,71 @@ const buildItemTradeCountsLoader = (db) =>
|
||||||
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
{ 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) =>
|
const buildPetTypeLoader = (db, loaders) =>
|
||||||
new DataLoader(async (petTypeIds) => {
|
new DataLoader(async (petTypeIds) => {
|
||||||
const qs = petTypeIds.map((_) => "?").join(",");
|
const qs = petTypeIds.map((_) => "?").join(",");
|
||||||
|
@ -821,7 +899,7 @@ const buildUserClosetHangersLoader = (db) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildUserClosetListsLoader = (db) =>
|
const buildUserClosetListsLoader = (db, loaders) =>
|
||||||
new DataLoader(async (userIds) => {
|
new DataLoader(async (userIds) => {
|
||||||
const qs = userIds.map((_) => "?").join(",");
|
const qs = userIds.map((_) => "?").join(",");
|
||||||
const [rows, _] = await db.execute(
|
const [rows, _] = await db.execute(
|
||||||
|
@ -830,7 +908,11 @@ const buildUserClosetListsLoader = (db) =>
|
||||||
ORDER BY name`,
|
ORDER BY name`,
|
||||||
userIds
|
userIds
|
||||||
);
|
);
|
||||||
|
|
||||||
const entities = rows.map(normalizeRow);
|
const entities = rows.map(normalizeRow);
|
||||||
|
for (const entity of entities) {
|
||||||
|
loaders.closetListLoader.prime(entity.id, entity);
|
||||||
|
}
|
||||||
|
|
||||||
return userIds.map((userId) =>
|
return userIds.map((userId) =>
|
||||||
entities.filter((e) => e.userId === String(userId))
|
entities.filter((e) => e.userId === String(userId))
|
||||||
|
@ -891,6 +973,7 @@ function buildLoaders(db) {
|
||||||
const loaders = {};
|
const loaders = {};
|
||||||
loaders.loadAllPetTypes = loadAllPetTypes(db);
|
loaders.loadAllPetTypes = loadAllPetTypes(db);
|
||||||
|
|
||||||
|
loaders.closetListLoader = buildClosetListLoader(db);
|
||||||
loaders.colorLoader = buildColorLoader(db);
|
loaders.colorLoader = buildColorLoader(db);
|
||||||
loaders.colorTranslationLoader = buildColorTranslationLoader(db);
|
loaders.colorTranslationLoader = buildColorTranslationLoader(db);
|
||||||
loaders.itemLoader = buildItemLoader(db);
|
loaders.itemLoader = buildItemLoader(db);
|
||||||
|
@ -904,6 +987,7 @@ function buildLoaders(db) {
|
||||||
);
|
);
|
||||||
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db);
|
||||||
loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db);
|
loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db);
|
||||||
|
loaders.itemTradesLoader = buildItemTradesLoader(db, loaders);
|
||||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||||
db,
|
db,
|
||||||
|
@ -937,7 +1021,7 @@ function buildLoaders(db) {
|
||||||
loaders.userByNameLoader = buildUserByNameLoader(db);
|
loaders.userByNameLoader = buildUserByNameLoader(db);
|
||||||
loaders.userByEmailLoader = buildUserByEmailLoader(db);
|
loaders.userByEmailLoader = buildUserByEmailLoader(db);
|
||||||
loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db);
|
loaders.userClosetHangersLoader = buildUserClosetHangersLoader(db);
|
||||||
loaders.userClosetListsLoader = buildUserClosetListsLoader(db);
|
loaders.userClosetListsLoader = buildUserClosetListsLoader(db, loaders);
|
||||||
loaders.zoneLoader = buildZoneLoader(db);
|
loaders.zoneLoader = buildZoneLoader(db);
|
||||||
loaders.zoneTranslationLoader = buildZoneTranslationLoader(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!
|
numUsersOfferingThis: Int!
|
||||||
numUsersSeekingThis: 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
|
# 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.
|
# fit the pet, we'll return an empty ItemAppearance with no layers.
|
||||||
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance!
|
appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance!
|
||||||
|
@ -79,12 +83,21 @@ const typeDefs = gql`
|
||||||
PB
|
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 {
|
type ItemSearchResult {
|
||||||
query: String!
|
query: String!
|
||||||
zones: [Zone!]!
|
zones: [Zone!]!
|
||||||
items: [Item!]!
|
items: [Item!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ItemTrade {
|
||||||
|
id: ID!
|
||||||
|
user: User!
|
||||||
|
closetList: ClosetList!
|
||||||
|
}
|
||||||
|
|
||||||
extend type Query {
|
extend type Query {
|
||||||
item(id: ID!): Item
|
item(id: ID!): Item
|
||||||
items(ids: [ID!]!): [Item!]!
|
items(ids: [ID!]!): [Item!]!
|
||||||
|
@ -204,6 +217,39 @@ const resolvers = {
|
||||||
return count;
|
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 (
|
appearanceOn: async (
|
||||||
{ id },
|
{ id },
|
||||||
{ speciesId, colorId },
|
{ speciesId, colorId },
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
const { gql } = require("apollo-server");
|
const { gql } = require("apollo-server");
|
||||||
|
|
||||||
const typeDefs = gql`
|
const typeDefs = gql`
|
||||||
enum OwnsOrWants {
|
|
||||||
OWNS
|
|
||||||
WANTS
|
|
||||||
}
|
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
username: String!
|
username: String!
|
||||||
|
@ -17,27 +12,6 @@ const typeDefs = gql`
|
||||||
itemsTheyWant: [Item!]!
|
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 {
|
extend type Query {
|
||||||
user(id: ID!): User
|
user(id: ID!): User
|
||||||
userByName(name: String!): User
|
userByName(name: String!): User
|
||||||
|
@ -162,10 +136,6 @@ const resolvers = {
|
||||||
.filter((closetList) => isCurrentUser || closetList.visibility >= 1)
|
.filter((closetList) => isCurrentUser || closetList.visibility >= 1)
|
||||||
.map((closetList) => ({
|
.map((closetList) => ({
|
||||||
id: closetList.id,
|
id: closetList.id,
|
||||||
name: closetList.name,
|
|
||||||
description: closetList.description,
|
|
||||||
ownsOrWantsItems: closetList.hangersOwned ? "OWNS" : "WANTS",
|
|
||||||
isDefaultList: false,
|
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
.filter((h) => h.listId === closetList.id)
|
.filter((h) => h.listId === closetList.id)
|
||||||
.map((h) => ({ id: h.itemId })),
|
.map((h) => ({ id: h.itemId })),
|
||||||
|
@ -173,11 +143,9 @@ const resolvers = {
|
||||||
|
|
||||||
if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) {
|
if (isCurrentUser || user.ownedClosetHangersVisibility >= 1) {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-OWNS`,
|
|
||||||
name: "Not in a list",
|
|
||||||
description: null,
|
|
||||||
ownsOrWantsItems: "OWNS",
|
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
|
userId: id,
|
||||||
|
ownsOrWantsItems: "OWNS",
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
.filter((h) => h.listId == null && h.owned)
|
.filter((h) => h.listId == null && h.owned)
|
||||||
.map((h) => ({ id: h.itemId })),
|
.map((h) => ({ id: h.itemId })),
|
||||||
|
@ -186,9 +154,8 @@ const resolvers = {
|
||||||
|
|
||||||
if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) {
|
if (isCurrentUser || user.wantedClosetHangersVisibility >= 1) {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-WANTS`,
|
isDefaultList: true,
|
||||||
name: "Not in a list",
|
userId: id,
|
||||||
description: null,
|
|
||||||
ownsOrWantsItems: "WANTS",
|
ownsOrWantsItems: "WANTS",
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
|
Loading…
Reference in a new issue