import React from "react"; import { Badge, Box, Flex, Popover, PopoverArrow, PopoverContent, PopoverTrigger, Portal, Select, Skeleton, Spinner, Tooltip, useToast, VStack, } from "@chakra-ui/react"; import { ExternalLinkIcon, ChevronRightIcon } from "@chakra-ui/icons"; import { gql, useMutation } from "@apollo/client"; import { ItemBadgeList, ItemKindBadge, ItemThumbnail, } from "./components/ItemCard"; import { Heading1 } from "./util"; import useSupport from "./WardrobePage/support/useSupport"; function ItemPageLayout({ children, item, isEmbedded }) { return ( {children} ); } function ItemPageHeader({ item, isEmbedded }) { return ( {item?.name || "Item name here"} ); } /** * SubtleSkeleton hides the skeleton animation until a second has passed, and * doesn't fade in the content if it loads near-instantly. This helps avoid * flash-of-content stuff! * * For plain Skeletons, we often use instead. But * that pattern doesn't work as well for wrapper skeletons where we're using * placeholder content for layout: we don't want the delay if the content * really _is_ present! */ export function SubtleSkeleton({ isLoaded, ...props }) { const [shouldFadeIn, setShouldFadeIn] = React.useState(false); const [shouldShowSkeleton, setShouldShowSkeleton] = React.useState(false); React.useEffect(() => { const t = setTimeout(() => { if (!isLoaded) { setShouldFadeIn(true); } }, 150); return () => clearTimeout(t); }); React.useEffect(() => { const t = setTimeout(() => setShouldShowSkeleton(true), 500); return () => clearTimeout(t); }); return ( ); } function ItemPageBadges({ item, isEmbedded }) { const searchBadgesAreLoaded = item?.name != null && item?.isNc != null; const shouldShowOwls = useShouldShowOwls(); return ( { // If the createdAt date is null (loaded and empty), hide the badge. item.createdAt !== null && ( {item.createdAt && } ) } Classic DTI Jellyneo {item.isNc && shouldShowOwls && ( {item.ncTradeValueText && ( OWLS: {item.ncTradeValueText} )} )} {!item?.isNc && !item?.isPb && ( Trade Post )} {!item?.isNc && !item?.isPb && ( Auctions )} ); } function ItemKindBadgeWithSupportTools({ item }) { const { isSupportUser, supportSecret } = useSupport(); const toast = useToast(); const ncRef = React.useRef(null); const isNcAutoDetectedFromRarity = item.rarityIndex === 500 || item.rarityIndex === 0; const [mutate, { loading }] = useMutation(gql` mutation ItemPageSupportSetIsManuallyNc( $itemId: ID! $isManuallyNc: Boolean! $supportSecret: String! ) { setItemIsManuallyNc( itemId: $itemId isManuallyNc: $isManuallyNc supportSecret: $supportSecret ) { id isNc isManuallyNc } } `); if (isSupportUser && item.rarityIndex != null && item.isManuallyNc != null) { // TODO: Could code-split this into a SupportOnly file... return ( NC: {loading && } PB: Support ); } return ; } const LinkBadge = React.forwardRef( ({ children, href, isEmbedded, ...props }, ref) => { return ( {children} { // We also change the icon to signal whether this will launch in a new // window or not! isEmbedded ? ( ) : ( ) } ); } ); const fullDateFormatter = new Intl.DateTimeFormat("en-US", { dateStyle: "long", }); const monthYearFormatter = new Intl.DateTimeFormat("en-US", { month: "short", year: "numeric", }); const monthDayYearFormatter = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", }); function ShortTimestamp({ when }) { const date = new Date(when); // To find the start of last month, take today, then set its date to the 1st // and its time to midnight (the start of this month), and subtract one // month. (JS handles negative months and rolls them over correctly.) const startOfLastMonth = new Date(); startOfLastMonth.setDate(1); startOfLastMonth.setHours(0); startOfLastMonth.setMinutes(0); startOfLastMonth.setSeconds(0); startOfLastMonth.setMilliseconds(0); startOfLastMonth.setMonth(startOfLastMonth.getMonth() - 1); const dateIsOlderThanLastMonth = date < startOfLastMonth; return ( {dateIsOlderThanLastMonth ? monthYearFormatter.format(date) : monthDayYearFormatter.format(date)} ); } const SHOULD_SHOW_OWLS = false; /** * useShouldShowOwls will return false until the user types "~owls" on the * page, after which the ~owls badge will appear. * * We also keep the value in a global, so it'll stick if you go search for * another item too! */ function useShouldShowOwls() { const [mostRecentKeys, setMostRecentKeys] = React.useState([]); const [shouldShowOwls, setShouldShowOwls] = React.useState(SHOULD_SHOW_OWLS); React.useEffect(() => { const onKeyPress = (e) => { const newMostRecentKeys = [...mostRecentKeys, e.key].slice(-5); if (newMostRecentKeys.join("") === "~owls") { SHOULD_SHOW_OWLS = true; setShouldShowOwls(true); } setMostRecentKeys(newMostRecentKeys); }; window.addEventListener("keypress", onKeyPress); return () => window.removeEventListener("keypress", onKeyPress); }); return shouldShowOwls; } export default ItemPageLayout;