This repository has no description
5.3 kB
167 lines
1'use client';
2
3import type { UrlCard } from '@semble/types';
4import { Stack } from '@mantine/core';
5import { notifications } from '@mantine/notifications';
6import { Suspense, useState } from 'react';
7import CollectionSelectorError from '@/features/collections/components/collectionSelector/Error.CollectionSelector';
8import CardToBeAddedPreview from '@/features/cards/components/cardToBeAddedPreview/CardToBeAddedPreview';
9import CollectionSelector from '@/features/collections/components/collectionSelector/CollectionSelector';
10import useGetCardFromMyLibrary from '@/features/cards/lib/queries/useGetCardFromMyLibrary';
11import useMyCollections from '@/features/collections/lib/queries/useMyCollections';
12import useUpdateCardAssociations from '@/features/cards/lib/mutations/useUpdateCardAssociations';
13import useAddCard from '@/features/cards/lib/mutations/useAddCard';
14import { track } from '@vercel/analytics';
15import AddCardActions from '../addCardActions/AddCardActions';
16import CollectionSelectorSkeleton from '@/features/collections/components/collectionSelector/Skeleton.CollectionSelector';
17
18interface SelectableCollectionItem {
19 id: string;
20 name: string;
21 cardCount: number;
22}
23
24interface Props {
25 onClose: () => void;
26 url: string;
27 cardId?: string;
28 note?: string;
29 cardContent?: UrlCard['cardContent'];
30 viaCardId?: string;
31}
32
33export default function AddCardToModalContent(props: Props) {
34 const cardStatus = useGetCardFromMyLibrary({ url: props.url });
35 const isMyCard = props?.cardId === cardStatus.data.card?.id;
36 const [note, setNote] = useState(isMyCard ? props.note : '');
37 const { data, error } = useMyCollections();
38
39 const addCard = useAddCard();
40 const updateCardAssociations = useUpdateCardAssociations();
41
42 if (error) {
43 return <CollectionSelectorError />;
44 }
45
46 const allCollections =
47 data?.pages.flatMap((page) => page.collections ?? []) ?? [];
48
49 const collectionsWithCard = allCollections.filter((c) =>
50 cardStatus.data.collections?.some((col) => col.id === c.id),
51 );
52
53 const [selectedCollections, setSelectedCollections] =
54 useState<SelectableCollectionItem[]>(collectionsWithCard);
55
56 const isSaving = addCard.isPending || updateCardAssociations.isPending;
57
58 const handleUpdateCard = (e: React.FormEvent) => {
59 e.preventDefault();
60 track('add or update existing card');
61
62 const trimmedNote = note?.trimEnd() === '' ? undefined : note;
63
64 const addedCollections = selectedCollections.filter(
65 (c) => !collectionsWithCard.some((original) => original.id === c.id),
66 );
67
68 const removedCollections = collectionsWithCard.filter(
69 (c) => !selectedCollections.some((selected) => selected.id === c.id),
70 );
71
72 const hasNoteChanged = trimmedNote !== props.note;
73 const hasAdded = addedCollections.length > 0;
74 const hasRemoved = removedCollections.length > 0;
75
76 // no change, close modal
77 if (cardStatus.data.card && !hasNoteChanged && !hasAdded && !hasRemoved) {
78 props.onClose();
79 return;
80 }
81
82 // card not yet in library, add it
83 if (!cardStatus.data.card) {
84 addCard.mutate(
85 {
86 url: props.url,
87 note: trimmedNote,
88 collectionIds: selectedCollections.map((c) => c.id),
89 viaCardId: props.viaCardId,
90 },
91 {
92 onError: () => {
93 notifications.show({ message: 'Could not add card.' });
94 },
95 onSettled: () => {
96 props.onClose();
97 },
98 },
99 );
100 return;
101 }
102
103 // card already in library, update associations instead
104 const updatedCardPayload: {
105 cardId: string;
106 note?: string;
107 addToCollectionIds?: string[];
108 removeFromCollectionIds?: string[];
109 } = { cardId: cardStatus.data.card.id };
110
111 if (hasNoteChanged) updatedCardPayload.note = trimmedNote;
112 if (hasAdded)
113 updatedCardPayload.addToCollectionIds = addedCollections.map((c) => c.id);
114 if (hasRemoved)
115 updatedCardPayload.removeFromCollectionIds = removedCollections.map(
116 (c) => c.id,
117 );
118
119 updateCardAssociations.mutate(
120 {
121 ...updatedCardPayload,
122 viaCardId: props.viaCardId,
123 },
124 {
125 onError: () => {
126 notifications.show({ message: 'Could not update card.' });
127 },
128 onSettled: () => {
129 props.onClose();
130 },
131 },
132 );
133 };
134
135 return (
136 <Stack justify="space-between">
137 <Stack gap={'xs'}>
138 <CardToBeAddedPreview
139 url={props.url}
140 title={props.cardContent?.title}
141 imageUrl={props.cardContent?.imageUrl}
142 />
143 <AddCardActions
144 note={isMyCard ? note : cardStatus.data.card?.note?.text}
145 noteId={cardStatus.data.card?.note?.id}
146 onUpdateNote={setNote}
147 onClose={props.onClose}
148 />
149 </Stack>
150
151 <Suspense fallback={<CollectionSelectorSkeleton />}>
152 <CollectionSelector
153 isOpen={true}
154 onClose={props.onClose}
155 onCancel={() => {
156 props.onClose();
157 setSelectedCollections(collectionsWithCard);
158 }}
159 onSave={handleUpdateCard}
160 isSaving={isSaving}
161 selectedCollections={selectedCollections}
162 onSelectedCollectionsChange={setSelectedCollections}
163 />
164 </Suspense>
165 </Stack>
166 );
167}