A social knowledge tool for researchers built on ATProto
at main 152 lines 4.8 kB view raw
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}