A social knowledge tool for researchers built on ATProto
at main 4.7 kB view raw
1'use client'; 2 3import { useState, useEffect, useMemo, useCallback } from 'react'; 4import { ApiClient } from '@/api-client/ApiClient'; 5import { Button, Group, Modal, Stack, Text } from '@mantine/core'; 6import { CollectionSelector } from './CollectionSelector'; 7import { getUrlCardView } from '@/features/cards/lib/dal'; 8 9interface Collection { 10 id: string; 11 name: string; 12 description?: string; 13 cardCount: number; 14 authorId: string; 15} 16 17interface AddToCollectionModalProps { 18 cardId: string; 19 isOpen: boolean; 20 onClose: () => void; 21 onSuccess?: () => void; 22} 23 24export function AddToCollectionModal({ 25 cardId, 26 isOpen, 27 onClose, 28 onSuccess, 29}: AddToCollectionModalProps) { 30 const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>( 31 [], 32 ); 33 const [submitting, setSubmitting] = useState(false); 34 const [error, setError] = useState(''); 35 const [card, setCard] = useState<any>(null); 36 const [loading, setLoading] = useState(false); 37 38 // Create API client instance 39 const apiClient = new ApiClient( 40 process.env.NEXT_PUBLIC_API_BASE_URL || 'http://127.0.0.1:3000', 41 ); 42 43 // Get existing collections for this card 44 const existingCollections = useMemo(() => { 45 if (!card) return []; 46 return card.collections || []; 47 }, [card]); 48 49 const fetchCard = useCallback(async () => { 50 // Create API client instance 51 const apiClient = new ApiClient( 52 process.env.NEXT_PUBLIC_API_BASE_URL || 'http://127.0.0.1:3000', 53 ); 54 55 try { 56 setLoading(true); 57 setError(''); 58 const response = await getUrlCardView(cardId); 59 setCard(response); 60 } catch (error: any) { 61 console.error('Error fetching card:', error); 62 setError(error.message || 'Failed to load card details'); 63 } finally { 64 setLoading(false); 65 } 66 }, [cardId]); 67 68 useEffect(() => { 69 if (isOpen) { 70 fetchCard(); 71 } 72 }, [isOpen, cardId, fetchCard]); 73 74 const handleSubmit = async () => { 75 if (selectedCollectionIds.length === 0) { 76 setError('Please select at least one collection'); 77 return; 78 } 79 80 setSubmitting(true); 81 setError(''); 82 83 try { 84 // Create API client instance 85 const apiClient = new ApiClient( 86 process.env.NEXT_PUBLIC_API_BASE_URL || 'http://127.0.0.1:3000', 87 ); 88 89 // Add card to all selected collections in a single request 90 await apiClient.addCardToCollection({ 91 cardId, 92 collectionIds: selectedCollectionIds, 93 }); 94 95 // Success 96 onSuccess?.(); 97 onClose(); 98 setSelectedCollectionIds([]); 99 } catch (error: any) { 100 console.error('Error adding card to collections:', error); 101 setError(error.message || 'Failed to add card to collections'); 102 } finally { 103 setSubmitting(false); 104 } 105 }; 106 107 const handleClose = () => { 108 if (!submitting) { 109 onClose(); 110 setSelectedCollectionIds([]); 111 setError(''); 112 } 113 }; 114 115 if (!isOpen) return null; 116 117 return ( 118 <Modal 119 opened={isOpen} 120 onClose={handleClose} 121 title="Add to Collections" 122 centered 123 > 124 <Stack p="sm"> 125 {loading ? ( 126 <Text size="sm" c="dimmed" ta="center" py="md"> 127 Loading card details... 128 </Text> 129 ) : error ? ( 130 <Stack align="center"> 131 <Text c="red">{error}</Text> 132 <Button onClick={fetchCard} variant="outline" size="sm"> 133 Try Again 134 </Button> 135 </Stack> 136 ) : ( 137 <> 138 <CollectionSelector 139 apiClient={apiClient} 140 selectedCollectionIds={selectedCollectionIds} 141 onSelectionChange={setSelectedCollectionIds} 142 existingCollections={existingCollections} 143 disabled={submitting} 144 showCreateOption={true} 145 placeholder="Search collections to add..." 146 /> 147 148 {error && ( 149 <Text c="red" size="sm"> 150 {error} 151 </Text> 152 )} 153 154 <Group gap={'xs'} grow> 155 <Button 156 onClick={handleSubmit} 157 disabled={submitting || selectedCollectionIds.length === 0} 158 loading={submitting} 159 > 160 {submitting 161 ? 'Adding...' 162 : `Add to ${selectedCollectionIds.length} Collection${selectedCollectionIds.length !== 1 && 's'}`} 163 </Button> 164 <Button 165 variant="outline" 166 onClick={handleClose} 167 disabled={submitting} 168 > 169 Cancel 170 </Button> 171 </Group> 172 </> 173 )} 174 </Stack> 175 </Modal> 176 ); 177}