This repository has no description
4.8 kB
181 lines
1'use client';
2
3import { useState, useMemo } from 'react';
4import { Stack, TextInput, Textarea, Button, Group, Text } from '@mantine/core';
5import { useForm } from '@mantine/form';
6import { ApiClient } from '@/api-client/ApiClient';
7import { UrlMetadataDisplay } from './UrlMetadataDisplay';
8import { useUrlMetadata } from '@/hooks/useUrlMetadata';
9import { CollectionSelector } from './CollectionSelector';
10
11interface UrlCardFormProps {
12 apiClient: ApiClient;
13 userId?: string;
14 onSuccess?: () => void;
15 onCancel?: () => void;
16 initialUrl?: string;
17 showUrlInput?: boolean;
18 submitButtonText?: string;
19 showCollections?: boolean;
20 preSelectedCollectionId?: string | null;
21}
22
23export function UrlCardForm({
24 apiClient,
25 userId,
26 onSuccess,
27 onCancel,
28 initialUrl = '',
29 showUrlInput = true,
30 submitButtonText = 'Save Card',
31 showCollections = true,
32 preSelectedCollectionId,
33}: UrlCardFormProps) {
34 const form = useForm({
35 initialValues: {
36 url: initialUrl,
37 note: '',
38 },
39 });
40
41 const [loading, setLoading] = useState(false);
42 const [error, setError] = useState('');
43 const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>(
44 preSelectedCollectionId ? [preSelectedCollectionId] : [],
45 );
46
47 // URL metadata hook
48 const {
49 metadata,
50 existingCard,
51 loading: metadataLoading,
52 error: metadataError,
53 } = useUrlMetadata({
54 apiClient,
55 url: form.getValues().url,
56 autoFetch: !!form.getValues().url,
57 });
58
59 // Get existing collections for this card (filtered by current user)
60 const existingCollections = useMemo(() => {
61 if (!existingCard || !userId) return [];
62 return existingCard.collections.filter(
63 (collection) => collection.authorId === userId,
64 );
65 }, [existingCard, userId]);
66
67 const handleSubmit = async (e: React.FormEvent) => {
68 e.preventDefault();
69
70 const url = form.getValues().url.trim();
71 if (!url) {
72 setError('URL is required');
73 return;
74 }
75
76 // Basic URL validation
77 try {
78 new URL(url);
79 } catch {
80 setError('Please enter a valid URL');
81 return;
82 }
83
84 setLoading(true);
85 setError('');
86
87 try {
88 await apiClient.addUrlToLibrary({
89 url,
90 note: form.getValues().note.trim() || undefined,
91 collectionIds:
92 selectedCollectionIds.length > 0 ? selectedCollectionIds : undefined,
93 });
94
95 onSuccess?.();
96 } catch (error: any) {
97 console.error('Error saving card:', error);
98 setError(error.message || 'Failed to save card. Please try again.');
99 } finally {
100 setLoading(false);
101 }
102 };
103
104 return (
105 <>
106 <form onSubmit={handleSubmit}>
107 <Stack>
108 {showUrlInput && (
109 <TextInput
110 label="URL"
111 type="url"
112 placeholder="https://example.com"
113 disabled={loading}
114 required
115 key={form.key('url')}
116 {...form.getInputProps('url')}
117 />
118 )}
119
120 {/* URL Metadata Display */}
121 {(form.getValues().url || initialUrl) && (
122 <UrlMetadataDisplay
123 metadata={metadata}
124 isLoading={metadataLoading}
125 currentUrl={form.getValues().url || initialUrl}
126 compact={!showUrlInput}
127 />
128 )}
129
130 {metadataError && (
131 <Text c="red" size="sm">
132 {metadataError}
133 </Text>
134 )}
135
136 <Textarea
137 label="Note (optional)"
138 placeholder="Add a note about this URL..."
139 disabled={loading}
140 rows={3}
141 key={form.key('note')}
142 {...form.getInputProps('note')}
143 />
144
145 {/* Collections Selection */}
146 {showCollections && (
147 <CollectionSelector
148 apiClient={apiClient}
149 userId={userId}
150 selectedCollectionIds={selectedCollectionIds}
151 onSelectionChange={setSelectedCollectionIds}
152 existingCollections={existingCollections}
153 disabled={loading}
154 showCreateOption={true}
155 placeholder="Search collections..."
156 preSelectedCollectionId={preSelectedCollectionId}
157 />
158 )}
159
160 {error && <Text c="red">{error}</Text>}
161
162 <Group>
163 <Button type="submit" loading={loading}>
164 {loading ? 'Saving...' : submitButtonText}
165 </Button>
166 {onCancel && (
167 <Button
168 type="button"
169 variant="outline"
170 onClick={onCancel}
171 disabled={loading}
172 >
173 Cancel
174 </Button>
175 )}
176 </Group>
177 </Stack>
178 </form>
179 </>
180 );
181}