A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
at master 4.7 kB view raw
1// src/components/EditModal.tsx 2import { useState } from 'react'; 3import { useForm } from 'react-hook-form'; 4import { zodResolver } from '@hookform/resolvers/zod'; 5import * as z from 'zod'; 6import { Link } from '../types/api'; 7import { editLink } from '../api/client'; 8import { useToast } from '@/hooks/use-toast'; 9import { 10 Dialog, 11 DialogContent, 12 DialogHeader, 13 DialogTitle, 14 DialogFooter, 15} from '@/components/ui/dialog'; 16import { Button } from '@/components/ui/button'; 17import { Input } from '@/components/ui/input'; 18import { 19 Form, 20 FormControl, 21 FormField, 22 FormItem, 23 FormLabel, 24 FormMessage, 25} from '@/components/ui/form'; 26 27const formSchema = z.object({ 28 url: z 29 .string() 30 .min(1, 'URL is required') 31 .url('Must be a valid URL') 32 .refine((val) => val.startsWith('http://') || val.startsWith('https://'), { 33 message: 'URL must start with http:// or https://', 34 }), 35 custom_code: z 36 .string() 37 .regex(/^[a-zA-Z0-9_-]{1,32}$/, { 38 message: 39 'Custom code must be 1-32 characters and contain only letters, numbers, underscores, and hyphens', 40 }) 41 .optional(), 42}); 43 44interface EditModalProps { 45 isOpen: boolean; 46 onClose: () => void; 47 link: Link; 48 onSuccess: () => void; 49} 50 51export function EditModal({ isOpen, onClose, link, onSuccess }: EditModalProps) { 52 const [loading, setLoading] = useState(false); 53 const { toast } = useToast(); 54 55 const form = useForm<z.infer<typeof formSchema>>({ 56 resolver: zodResolver(formSchema), 57 defaultValues: { 58 url: link.original_url, 59 custom_code: link.short_code, 60 }, 61 }); 62 63 const onSubmit = async (values: z.infer<typeof formSchema>) => { 64 try { 65 setLoading(true); 66 await editLink(link.id, values); 67 toast({ 68 description: 'Link updated successfully', 69 }); 70 onSuccess(); 71 onClose(); 72 } catch (err: unknown) { 73 const error = err as { response?: { data?: { error?: string } } }; 74 toast({ 75 variant: 'destructive', 76 title: 'Error', 77 description: error.response?.data?.error || 'Failed to update link', 78 }); 79 } finally { 80 setLoading(false); 81 } 82 }; 83 84 return ( 85 <Dialog open={isOpen} onOpenChange={onClose}> 86 <DialogContent> 87 <DialogHeader> 88 <DialogTitle>Edit Link</DialogTitle> 89 </DialogHeader> 90 91 <Form {...form}> 92 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> 93 <FormField 94 control={form.control} 95 name="url" 96 render={({ field }) => ( 97 <FormItem> 98 <FormLabel>Destination URL</FormLabel> 99 <FormControl> 100 <Input placeholder="https://example.com" {...field} /> 101 </FormControl> 102 <FormMessage /> 103 </FormItem> 104 )} 105 /> 106 107 <FormField 108 control={form.control} 109 name="custom_code" 110 render={({ field }) => ( 111 <FormItem> 112 <FormLabel>Short Code</FormLabel> 113 <FormControl> 114 <Input placeholder="custom-code" {...field} /> 115 </FormControl> 116 <FormMessage /> 117 </FormItem> 118 )} 119 /> 120 121 <DialogFooter> 122 <Button 123 type="button" 124 variant="outline" 125 onClick={onClose} 126 disabled={loading} 127 > 128 Cancel 129 </Button> 130 <Button type="submit" disabled={loading}> 131 {loading ? 'Saving...' : 'Save Changes'} 132 </Button> 133 </DialogFooter> 134 </form> 135 </Form> 136 </DialogContent> 137 </Dialog> 138 ); 139}