Use the main app for outfit deletion, too

This commit is contained in:
Emi Matchu 2023-11-02 17:39:26 -07:00
parent d32c6459b0
commit 629706a182
3 changed files with 41 additions and 36 deletions

View file

@ -36,19 +36,13 @@ import {
} from "@chakra-ui/icons"; } from "@chakra-ui/icons";
import { CSSTransition, TransitionGroup } from "react-transition-group"; import { CSSTransition, TransitionGroup } from "react-transition-group";
import { import { Delay, ErrorMessage, Heading1, Heading2 } from "../util";
Delay,
ErrorMessage,
getGraphQLErrorMessage,
Heading1,
Heading2,
} from "../util";
import Item, { ItemListContainer, ItemListSkeleton } from "./Item"; import Item, { ItemListContainer, ItemListSkeleton } from "./Item";
import { BiRename } from "react-icons/bi"; import { BiRename } from "react-icons/bi";
import { IoCloudUploadOutline } from "react-icons/io5"; import { IoCloudUploadOutline } from "react-icons/io5";
import { MdMoreVert } from "react-icons/md"; import { MdMoreVert } from "react-icons/md";
import { buildOutfitUrl } from "./useOutfitState"; import { buildOutfitUrl } from "./useOutfitState";
import { gql, useMutation } from "@apollo/client"; import { useDeleteOutfitMutation } from "../loaders/outfits";
/** /**
* ItemsPanel shows the items in the current outfit, and lets the user toggle * ItemsPanel shows the items in the current outfit, and lets the user toggle
@ -455,25 +449,7 @@ function DeleteOutfitMenuItem({ outfitState }) {
const { id, name } = outfitState; const { id, name } = outfitState;
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const [sendDeleteOutfitMutation, { loading, error }] = useMutation( const { status, error, mutateAsync } = useDeleteOutfitMutation();
gql`
mutation DeleteOutfitMenuItem($id: ID!) {
deleteOutfit(id: $id)
}
`,
{
context: { sendAuth: true },
update(cache) {
// Once this is deleted, evict it from the local cache, and "garbage
// collect" to force all queries referencing this outfit to reload the
// next time we see them. (This is especially important since we're
// about to redirect to the user outfits page, which shouldn't show
// the outfit anymore!)
cache.evict(`Outfit:${id}`);
cache.gc();
},
},
);
return ( return (
<> <>
@ -489,10 +465,9 @@ function DeleteOutfitMenuItem({ outfitState }) {
We'll delete this data and remove it from your list of outfits. We'll delete this data and remove it from your list of outfits.
Links and image embeds pointing to this outfit will break. Is that Links and image embeds pointing to this outfit will break. Is that
okay? okay?
{error && ( {status === "error" && (
<ErrorMessage marginTop="1em"> <ErrorMessage marginTop="1em">
Error deleting outfit: "{getGraphQLErrorMessage(error)}". Try Error deleting outfit: "{error.message}". Try again?
again?
</ErrorMessage> </ErrorMessage>
)} )}
</ModalBody> </ModalBody>
@ -502,7 +477,7 @@ function DeleteOutfitMenuItem({ outfitState }) {
<Button <Button
colorScheme="red" colorScheme="red"
onClick={() => onClick={() =>
sendDeleteOutfitMutation({ variables: { id } }) mutateAsync(id)
.then(() => { .then(() => {
window.location = "/your-outfits"; window.location = "/your-outfits";
}) })
@ -510,7 +485,9 @@ function DeleteOutfitMenuItem({ outfitState }) {
/* handled in error UI */ /* handled in error UI */
}) })
} }
isLoading={loading} // We continue to show the loading spinner in the success case,
// while we redirect away!
isLoading={status === "pending" || status === "success"}
> >
Delete Delete
</Button> </Button>

View file

@ -3,8 +3,6 @@ import { useToast } from "@chakra-ui/react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { useDebounce } from "../util"; import { useDebounce } from "../util";
import useCurrentUser from "../components/useCurrentUser"; import useCurrentUser from "../components/useCurrentUser";
import gql from "graphql-tag";
import { useMutation } from "@apollo/client";
import { outfitStatesAreEqual } from "./useOutfitState"; import { outfitStatesAreEqual } from "./useOutfitState";
import { useSaveOutfitMutation } from "../loaders/outfits"; import { useSaveOutfitMutation } from "../loaders/outfits";

View file

@ -8,7 +8,7 @@ export function useSavedOutfit(id, options) {
}); });
} }
export function useSaveOutfitMutation(options) { export function useSaveOutfitMutation(options = {}) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
@ -16,7 +16,24 @@ export function useSaveOutfitMutation(options) {
mutationFn: saveOutfit, mutationFn: saveOutfit,
onSuccess: (outfit) => { onSuccess: (outfit) => {
queryClient.setQueryData(["outfits", outfit.id], outfit); queryClient.setQueryData(["outfits", outfit.id], outfit);
if (options.onSuccess) {
options.onSuccess(outfit); options.onSuccess(outfit);
}
},
});
}
export function useDeleteOutfitMutation(options = {}) {
const queryClient = useQueryClient();
return useMutation({
...options,
mutationFn: deleteOutfit,
onSuccess: (emptyData, id, context) => {
queryClient.invalidateQueries({ queryKey: ["outfits", String(id)] });
if (options.onSuccess) {
options.onSuccess(emptyData, id, context);
}
}, },
}); });
} }
@ -80,6 +97,19 @@ async function saveOutfit({
return res.json().then(normalizeOutfit); return res.json().then(normalizeOutfit);
} }
async function deleteOutfit(id) {
const res = await fetch(`/outfits/${encodeURIComponent(id)}.json`, {
method: "DELETE",
headers: {
"X-CSRF-Token": getCSRFToken(),
},
});
if (!res.ok) {
throw new Error(`deleting outfit failed: ${res.status} ${res.statusText}`);
}
}
function normalizeOutfit(outfit) { function normalizeOutfit(outfit) {
return { return {
id: String(outfit.id), id: String(outfit.id),