From d91492ab66079b15ab84c29d0526f1887d12954d Mon Sep 17 00:00:00 2001 From: Matchu Date: Sat, 19 Jun 2021 09:44:44 -0700 Subject: [PATCH] Editable name/desc on list page Decided to share the ClosetList component and give it a bit of variance, instead of figuring out how to extract all that edit state! --- src/app/UserItemListPage.js | 267 ++++++++++++++++++++++++++---- src/app/UserItemListsIndexPage.js | 216 ++---------------------- 2 files changed, 250 insertions(+), 233 deletions(-) diff --git a/src/app/UserItemListPage.js b/src/app/UserItemListPage.js index 75f7911..21216c5 100644 --- a/src/app/UserItemListPage.js +++ b/src/app/UserItemListPage.js @@ -5,25 +5,34 @@ import { 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 { Heading1, MajorErrorMessage, usePageTitle } from "./util"; -import { gql, useQuery } from "@apollo/client"; +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(); @@ -112,42 +121,240 @@ function UserItemListPage() { - {closetList.name} - - {closetList.creator?.contactNeopetsUsername && ( - - + + ); +} + +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.creator.contactNeopetsUsername} - - - )} - {closetList.creator?.contactNeopetsUsername && ( - - + {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 && ( - {closetList.description} + + {isEditing ? ( +