import React from "react"; import { Badge, Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, Center, Flex, HStack, Input, Textarea, useToast, Wrap, WrapItem, } from "@chakra-ui/react"; import { ArrowForwardIcon, CheckIcon, ChevronRightIcon, EditIcon, EmailIcon, } from "@chakra-ui/icons"; import { gql, useMutation, useQuery } from "@apollo/client"; import { Link, useParams } from "react-router-dom"; import { HashLink } from "react-router-hash-link"; import { Heading1, Heading3, MajorErrorMessage, usePageTitle } from "./util"; import HangerSpinner from "./components/HangerSpinner"; import MarkdownAndSafeHTML from "./components/MarkdownAndSafeHTML"; import ItemCard from "./components/ItemCard"; import useCurrentUser from "./components/useCurrentUser"; import useSupport from "./WardrobePage/support/useSupport"; import WIPCallout from "./components/WIPCallout"; function UserItemListPage() { const { listId } = useParams(); const currentUser = useCurrentUser(); const { loading, error, data } = useQuery( gql` query UserItemListPage($listId: ID!) { closetList(id: $listId) { id name description ownsOrWantsItems creator { id username contactNeopetsUsername } items { id isNc isPb name thumbnailUrl currentUserOwnsThis currentUserWantsThis } } } `, { variables: { listId }, context: { sendAuth: true } } ); const closetList = data?.closetList; usePageTitle(closetList?.name); if (loading) { return (
); } if (error) { return ; } if (!closetList) { return ; } const { creator, ownsOrWantsItems } = closetList; // NOTE: `currentUser` should have resolved by now, because the GraphQL query // sends authorization, which requires the current user to load first! const isCurrentUser = currentUser.id === creator.id; let linkBackText; let linkBackPath; if (ownsOrWantsItems === "OWNS") { linkBackText = `Items ${creator.username} owns`; linkBackPath = `/user/${creator.id}/lists#owned-items`; } else if (ownsOrWantsItems === "WANTS") { linkBackText = `Items ${creator.username} wants`; linkBackPath = `/user/${creator.id}/lists#wanted-items`; } else { throw new Error(`unexpected ownsOrWantsItems value: ${ownsOrWantsItems}`); } return ( } > {creator.username}'s lists {linkBackText} ); } export function ClosetList({ closetList, isCurrentUser, headingVariant = "list-item", }) { const { isSupportUser, supportSecret } = useSupport(); const toast = useToast(); // When this mounts, scroll it into view if it matches the location hash. // This works around the fact that, while the browser tries to do this // natively on page load, the list might not be mounted yet! const anchorId = `list-${closetList.id}`; React.useEffect(() => { if (document.location.hash === "#" + anchorId) { document.getElementById(anchorId).scrollIntoView(); } }, [anchorId]); const [ sendSaveChangesMutation, { loading: loadingSaveChanges }, ] = useMutation( gql` mutation ClosetList_Edit( $closetListId: ID! $name: String! $description: String! # Support users can edit any list, if they provide the secret. If you're # editing your own list, this will be empty, and that's okay. $supportSecret: String ) { editClosetList( closetListId: $closetListId name: $name description: $description supportSecret: $supportSecret ) { id name description } } `, { context: { sendAuth: true } } ); const [isEditing, setIsEditing] = React.useState(false); const [editableName, setEditableName] = React.useState(closetList.name); const [editableDescription, setEditableDescription] = React.useState( closetList.description ); const hasChanges = editableName !== closetList.name || editableDescription !== closetList.description; const onSaveChanges = () => { if (!hasChanges) { setIsEditing(false); return; } sendSaveChangesMutation({ variables: { closetListId: closetList.id, name: editableName, description: editableDescription, supportSecret, }, }) .then(() => { setIsEditing(false); toast({ status: "success", title: "Changes saved!", }); }) .catch((err) => { console.error(err); toast({ status: "error", title: "Sorry, we couldn't save this list 😖", description: "Check your connection and try again.", }); }); }; const Heading = headingVariant === "top-level" ? Heading1 : Heading3; return ( {headingVariant !== "hidden" && (isEditing ? ( setEditableName(e.target.value)} maxWidth="20ch" // Shift left by our own padding/border, for alignment with the // original title paddingX="0.75rem" marginLeft="calc(-0.75rem - 1px)" boxShadow="sm" lineHeight="1.2" // HACK: Idk, the height stuff is really getting away from me, // this is close enough :/ height="1.2em" /> ) : ( {closetList.isDefaultList || headingVariant === "top-level" ? ( closetList.name ) : ( {closetList.name} )} ))} {(isCurrentUser || isSupportUser) && !closetList.isDefaultList && (isEditing ? ( <> WIP: Can only edit text for now! ) : ( ))} {headingVariant === "top-level" && ( {closetList.creator?.contactNeopetsUsername && ( {closetList.creator.contactNeopetsUsername} )} {closetList.creator?.contactNeopetsUsername && ( Neomail )} )} {(closetList.description || isEditing) && ( {isEditing ? (