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