This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / webapp / features / cards / components / addCardToModal / AddCardToModalContent.tsx
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}