A social knowledge tool for researchers built on ATProto
1'use client';
2
3import type { UrlCard } from '@/api-client';
4import { Stack } from '@mantine/core';
5import { notifications } from '@mantine/notifications';
6import { 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 useUrlMetadata from '../../lib/queries/useUrlMetadata';
15
16interface SelectableCollectionItem {
17 id: string;
18 name: string;
19 cardCount: number;
20}
21
22interface Props {
23 onClose: () => void;
24 url: string;
25 cardId?: string;
26 note?: string;
27}
28
29export default function AddCardToModalContent(props: Props) {
30 const {
31 data: { metadata },
32 } = useUrlMetadata({ url: props.url });
33 const cardStatus = useGetCardFromMyLibrary({ url: props.url });
34 const isMyCard = props?.cardId === cardStatus.data.card?.id;
35 const [note, setNote] = useState(isMyCard ? props.note : '');
36 const { data, error } = useMyCollections();
37
38 const addCard = useAddCard();
39 const updateCardAssociations = useUpdateCardAssociations();
40
41 if (error) {
42 return <CollectionSelectorError />;
43 }
44
45 const allCollections =
46 data?.pages.flatMap((page) => page.collections ?? []) ?? [];
47
48 const collectionsWithCard = allCollections.filter((c) =>
49 cardStatus.data.collections?.some((col) => col.id === c.id),
50 );
51
52 const [selectedCollections, setSelectedCollections] =
53 useState<SelectableCollectionItem[]>(collectionsWithCard);
54
55 const isSaving = addCard.isPending || updateCardAssociations.isPending;
56
57 const handleUpdateCard = (e: React.FormEvent) => {
58 e.preventDefault();
59
60 const trimmedNote = note?.trimEnd() === '' ? undefined : note;
61
62 const addedCollections = selectedCollections.filter(
63 (c) => !collectionsWithCard.some((original) => original.id === c.id),
64 );
65
66 const removedCollections = collectionsWithCard.filter(
67 (c) => !selectedCollections.some((selected) => selected.id === c.id),
68 );
69
70 const hasNoteChanged = trimmedNote !== props.note;
71 const hasAdded = addedCollections.length > 0;
72 const hasRemoved = removedCollections.length > 0;
73
74 // no change, close modal
75 if (cardStatus.data.card && !hasNoteChanged && !hasAdded && !hasRemoved) {
76 props.onClose();
77 return;
78 }
79
80 // card not yet in library, add it
81 if (!cardStatus.data.card) {
82 addCard.mutate(
83 {
84 url: props.url,
85 note: trimmedNote,
86 collectionIds: selectedCollections.map((c) => c.id),
87 },
88 {
89 onError: () => {
90 notifications.show({ message: 'Could not add card.' });
91 },
92 onSettled: () => {
93 props.onClose();
94 },
95 },
96 );
97 return;
98 }
99
100 // card already in library, update associations instead
101 const updatedCardPayload: {
102 cardId: string;
103 note?: string;
104 addToCollectionIds?: string[];
105 removeFromCollectionIds?: string[];
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 updateCardAssociations.mutate(updatedCardPayload, {
117 onError: () => {
118 notifications.show({ message: 'Could not update card.' });
119 },
120 onSettled: () => {
121 props.onClose();
122 },
123 });
124 };
125
126 return (
127 <Stack justify="space-between">
128 <CardToBeAddedPreview
129 url={props.url}
130 thumbnailUrl={metadata.imageUrl}
131 title={metadata.title}
132 note={isMyCard ? note : cardStatus.data.card?.note?.text}
133 noteId={cardStatus.data.card?.note?.id}
134 onUpdateNote={setNote}
135 onClose={props.onClose}
136 />
137
138 <CollectionSelector
139 isOpen={true}
140 onClose={props.onClose}
141 onCancel={() => {
142 props.onClose();
143 setSelectedCollections(collectionsWithCard);
144 }}
145 onSave={handleUpdateCard}
146 isSaving={isSaving}
147 selectedCollections={selectedCollections}
148 onSelectedCollectionsChange={setSelectedCollections}
149 />
150 </Stack>
151 );
152}