import React from "react";
import { ClassNames } from "@emotion/react";
import {
Box,
Button,
Flex,
Skeleton,
useColorModeValue,
useToken,
} from "@chakra-ui/react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import Link from "next/link";
import { useRouter } from "next/router";
import { Heading2 } from "./util";
import ItemPageLayout from "./ItemPageLayout";
import useCurrentUser from "./components/useCurrentUser";
import { ChevronDownIcon, ChevronUpIcon } from "@chakra-ui/icons";
import Head from "next/head";
export function ItemTradesOfferingPage() {
return (
);
}
export function ItemTradesSeekingPage() {
return (
);
}
function ItemTradesPage({
title,
userHeading,
compareColumnLabel,
tradesQuery,
}) {
const { query } = useRouter();
const { itemId } = query;
const { error, data } = useQuery(
gql`
query ItemTradesPage($itemId: ID!) {
item(id: $itemId) {
id
name
isNc
isPb
thumbnailUrl
description
createdAt
ncTradeValueText
}
}
`,
{ variables: { itemId }, returnPartialData: true }
);
if (error) {
return {error.message};
}
return (
<>
{data?.item?.name && (
{data?.item?.name} | {title} | Dress to Impress
)}
{title}
>
);
}
function ItemTradesTable({
itemId,
userHeading,
compareColumnLabel,
tradesQuery,
}) {
const { isLoggedIn } = useCurrentUser();
const { loading, error, data } = useQuery(tradesQuery, {
variables: { itemId },
context: { sendAuth: true },
});
const [isShowingInactiveTrades, setIsShowingInactiveTrades] = React.useState(
false
);
const shouldShowCompareColumn = isLoggedIn;
// We partially randomize trade sorting, but we want it to stay stable across
// re-renders. To do this, we can use `getTradeSortKey`, which will either
// build a new sort key for the trade, or return the cached one from the
// `tradeSortKeys` map.
const tradeSortKeys = React.useMemo(() => new Map(), []);
const getTradeSortKey = (trade) => {
if (!tradeSortKeys.has(trade.id)) {
tradeSortKeys.set(
trade.id,
getVaguelyRandomizedTradeSortKey(
trade.user.lastTradeActivity,
trade.user.matchingItems.length
)
);
}
return tradeSortKeys.get(trade.id);
};
const allTrades = [...(data?.item?.trades || [])];
// Only trades from users active within the last 6 months are shown by
// default. The user can toggle to the full view, though!
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
const activeTrades = allTrades.filter(
(t) => new Date(t.user.lastTradeActivity) > sixMonthsAgo
);
const trades = isShowingInactiveTrades ? allTrades : activeTrades;
trades.sort((a, b) => getTradeSortKey(b).localeCompare(getTradeSortKey(a)));
const numInactiveTrades = allTrades.length - activeTrades.length;
if (error) {
return {error.message};
}
const minorColumnWidth = {
base: shouldShowCompareColumn ? "23%" : "30%",
md: "20ex",
};
return (
{({ css }) => (
{/* A small wording tweak to fit better on the xsmall screens! */}
Last activeLast edit
{shouldShowCompareColumn && (
{compareColumnLabel}
Matches
)}
{userHeading}
List
{loading && (
<>
>
)}
{!loading &&
trades.length > 0 &&
trades.map((trade) => (
))}
{!loading && trades.length === 0 && (
No trades yet!
)}
{numInactiveTrades > 0 && (
)}
)}
);
}
function ItemTradesTableRow({
href,
username,
listName,
lastTradeActivity,
matchingItems,
shouldShowCompareColumn,
}) {
const { push: pushHistory } = useRouter();
const onClick = React.useCallback(() => pushHistory(href), [
pushHistory,
href,
]);
const focusBackground = useColorModeValue("gray.100", "gray.600");
const sortedMatchingItems = [...matchingItems].sort((a, b) =>
a.name.localeCompare(b.name)
);
return (
{({ css }) => (
{formatVagueDate(lastTradeActivity)}
{shouldShowCompareColumn && (
{matchingItems.length > 0 ? (
{sortedMatchingItems.slice(0, 4).map((item) => (
{item.name}
))}
{matchingItems.length > 4 && (
+ {matchingItems.length - 4} more
)}
) : (
<>
No matchesNone
>
)}
)}
{username}
{listName}
)}
);
}
function ItemTradesTableRowSkeleton({ shouldShowCompareColumn }) {
return (
XXX
{shouldShowCompareColumn && (
X
)}
);
}
function ItemTradesTableCell({ children, as = "td", ...props }) {
const borderColor = useColorModeValue("gray.300", "gray.400");
const borderColorCss = useToken("colors", borderColor);
const borderRadiusCss = useToken("radii", "md");
return (
{({ css }) => (
{children}
)}
);
}
function isThisWeek(date) {
const startOfThisWeek = new Date();
startOfThisWeek.setDate(startOfThisWeek.getDate() - 7);
return date > startOfThisWeek;
}
const shortMonthYearFormatter = new Intl.DateTimeFormat("en", {
month: "short",
year: "numeric",
});
function formatVagueDate(dateString) {
const date = new Date(dateString);
if (isThisWeek(date)) {
return "This week";
}
return shortMonthYearFormatter.format(date);
}
function getVaguelyRandomizedTradeSortKey(dateString, numMatchingItems) {
const date = new Date(dateString);
const hasMatchingItems = numMatchingItems >= 1;
// "This week" sorts after all other dates, but with a random factor! I don't
// want people worrying about gaming themselves up to the very top, just be
// active and trust the system 😅 (I figure that, if you care enough to "game"
// the system by faking activity every week, you probably also care enough to
// be... making real trades every week lmao)
//
// We also prioritize having matches, but we don't bother to sort _how many_
// matches, to decrease the power of gaming with large honeypot lists, and
// because it's hard to judge how good matches are anyway.
if (isThisWeek(date)) {
const matchingItemsKey = hasMatchingItems
? "ZZmatchingZZ"
: "AAnotmatchingAA";
return `ZZZthisweekZZZ-${matchingItemsKey}-${Math.random()}`;
}
return dateString;
}