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