A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
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}