This repository has no description
0

Configure Feed

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

semble / src / webapp / components / UrlCardForm.tsx
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}