A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
at master 3.3 kB view raw
1import { useState } from 'react' 2import { useForm } from 'react-hook-form' 3import { zodResolver } from '@hookform/resolvers/zod' 4import * as z from 'zod' 5import { CreateLinkRequest } from '../types/api' 6import { createShortLink } from '../api/client' 7import { Button } from "@/components/ui/button" 8import { Input } from "@/components/ui/input" 9import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 10import { LinkIcon } from "lucide-react" 11import { 12 Form, 13 FormControl, 14 FormField, 15 FormLabel, 16 FormMessage, 17} from "@/components/ui/form" 18import { useToast } from "@/hooks/use-toast" 19 20const formSchema = z.object({ 21 url: z.string() 22 .min(1, 'URL is required') 23 .url('Must be a valid URL') 24 .refine(val => val.startsWith('http://') || val.startsWith('https://'), { 25 message: 'URL must start with http:// or https://' 26 }), 27 custom_code: z.string() 28 .regex(/^[a-zA-Z0-9_-]{0,32}$/, 'Custom code must contain only letters, numbers, underscores, and hyphens') 29 .optional() 30}) 31 32interface LinkFormProps { 33 onSuccess: () => void; 34} 35 36export function LinkForm({ onSuccess }: LinkFormProps) { 37 const [loading, setLoading] = useState(false) 38 const { toast } = useToast() 39 40 const form = useForm<z.infer<typeof formSchema>>({ 41 resolver: zodResolver(formSchema), 42 defaultValues: { 43 url: '', 44 custom_code: '', 45 }, 46 }) 47 48 const onSubmit = async (values: z.infer<typeof formSchema>) => { 49 try { 50 setLoading(true) 51 await createShortLink(values as CreateLinkRequest) 52 form.reset() 53 onSuccess() // Call the onSuccess callback to trigger refresh 54 toast({ 55 description: "Short link created successfully", 56 }) 57 } catch (err: any) { 58 toast({ 59 variant: "destructive", 60 title: "Error", 61 description: err.response?.data?.error || 'An error occurred', 62 }) 63 } finally { 64 setLoading(false) 65 } 66 } 67 68 return ( 69 <Card className="mb-8"> 70 <CardHeader> 71 <CardTitle>Create Short Link</CardTitle> 72 <CardDescription>Enter a URL to generate a shortened link</CardDescription> 73 </CardHeader> 74 <CardContent> 75 <Form {...form}> 76 <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4 md:flex-row md:items-end"> 77 <FormField 78 control={form.control} 79 name="url" 80 render={({ field }) => ( 81 <div className="flex-1 space-y-2"> 82 <FormLabel>URL</FormLabel> 83 <FormControl> 84 <div className="relative"> 85 <LinkIcon className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /> 86 <Input placeholder="https://example.com" className="pl-9" {...field} /> 87 </div> 88 </FormControl> 89 <FormMessage /> 90 </div> 91 )} 92 /> 93 94 <FormField 95 control={form.control} 96 name="custom_code" 97 render={({ field }) => ( 98 <div className="w-full md:w-1/4 space-y-2"> 99 <FormLabel>Custom Code <span className="text-muted-foreground">(optional)</span></FormLabel> 100 <FormControl> 101 <Input placeholder="custom-code" {...field} /> 102 </FormControl> 103 <FormMessage /> 104 </div> 105 )} 106 /> 107 108 <Button type="submit" disabled={loading} className="md:w-auto"> 109 {loading ? "Creating..." : "Create Short Link"} 110 </Button> 111 </form> 112 </Form> 113 </CardContent> 114 </Card> 115 ) 116}