A social knowledge tool for researchers built on ATProto
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}