Openstatus www.openstatus.dev
at main 172 lines 4.9 kB view raw
1"use client"; 2 3import { Checkbox } from "@/components/ui/checkbox"; 4import { 5 FormControl, 6 FormDescription, 7 FormField, 8 FormItem, 9 FormLabel, 10 FormMessage, 11} from "@/components/ui/form"; 12 13import { Form } from "@/components/ui/form"; 14import { Input } from "@/components/ui/input"; 15import { Label } from "@/components/ui/label"; 16import { cn } from "@/lib/utils"; 17import { zodResolver } from "@hookform/resolvers/zod"; 18import { useTransition } from "react"; 19import { useForm } from "react-hook-form"; 20import { toast } from "sonner"; 21import { z } from "zod"; 22 23const schema = z.object({ 24 name: z.string(), 25 provider: z.enum([ 26 "slack", 27 "discord", 28 "email", 29 "sms", 30 "webhook", 31 "opsgenie", 32 "pagerduty", 33 "ntfy", 34 "telegram", 35 "whatsapp", 36 "google-chat", 37 ]), 38 data: z.record(z.string(), z.string()).or(z.string()), 39 monitors: z.array(z.number()), 40}); 41 42export type FormValues = z.infer<typeof schema>; 43 44export function NotifierForm({ 45 defaultValues, 46 className, 47 onSubmit, 48 monitors, 49 ...props 50}: Omit<React.ComponentProps<"form">, "onSubmit"> & { 51 defaultValues?: FormValues; 52 onSubmit?: (values: FormValues) => Promise<void> | void; 53 monitors: { id: number; name: string }[]; 54}) { 55 const form = useForm<FormValues>({ 56 resolver: zodResolver(schema), 57 defaultValues: defaultValues ?? { 58 name: "", 59 data: { 60 webhook: "", 61 }, 62 monitors: [], 63 }, 64 }); 65 const [isPending, startTransition] = useTransition(); 66 67 function submitAction(values: FormValues) { 68 if (isPending) return; 69 70 startTransition(async () => { 71 try { 72 const promise = new Promise((resolve) => setTimeout(resolve, 1000)); 73 toast.promise(promise, { 74 loading: "Saving...", 75 success: () => JSON.stringify(values), 76 error: "Failed to save", 77 }); 78 await promise; 79 onSubmit?.(values); 80 } catch (error) { 81 console.error(error); 82 } 83 }); 84 } 85 86 return ( 87 <Form {...form}> 88 <form 89 id="notifier-form" 90 className={cn("grid gap-4", className)} 91 onSubmit={form.handleSubmit(submitAction)} 92 {...props} 93 > 94 <FormField 95 control={form.control} 96 name="name" 97 render={({ field }) => ( 98 <FormItem> 99 <FormLabel>Name</FormLabel> 100 <FormControl> 101 <Input placeholder="My Notifier" {...field} /> 102 </FormControl> 103 <FormMessage /> 104 <FormDescription> 105 Enter a descriptive name for your notifier. 106 </FormDescription> 107 </FormItem> 108 )} 109 /> 110 <FormField 111 control={form.control} 112 name="data.webhook" 113 render={({ field }) => ( 114 <FormItem> 115 <FormLabel>Webhook URL</FormLabel> 116 <FormControl> 117 <Input placeholder="https://example.com/webhook" {...field} /> 118 </FormControl> 119 <FormMessage /> 120 </FormItem> 121 )} 122 /> 123 <FormField 124 control={form.control} 125 name="monitors" 126 render={({ field }) => ( 127 <FormItem> 128 <FormLabel>Monitors</FormLabel> 129 <FormDescription> 130 Select the monitors you want to notify. 131 </FormDescription> 132 <div className="grid gap-3"> 133 <div className="flex items-center gap-2"> 134 <FormControl> 135 <Checkbox 136 id="all" 137 checked={field.value?.length === monitors.length} 138 onCheckedChange={(checked) => { 139 field.onChange( 140 checked ? monitors.map((m) => m.id) : [], 141 ); 142 }} 143 /> 144 </FormControl> 145 <Label htmlFor="all">Select all</Label> 146 </div> 147 {monitors.map((item) => ( 148 <div key={item.id} className="flex items-center gap-2"> 149 <FormControl> 150 <Checkbox 151 id={String(item.id)} 152 checked={field.value?.includes(item.id)} 153 onCheckedChange={(checked) => { 154 const newValue = checked 155 ? [...(field.value || []), item.id] 156 : field.value?.filter((id) => id !== item.id); 157 field.onChange(newValue); 158 }} 159 /> 160 </FormControl> 161 <Label htmlFor={String(item.id)}>{item.name}</Label> 162 </div> 163 ))} 164 </div> 165 <FormMessage /> 166 </FormItem> 167 )} 168 /> 169 </form> 170 </Form> 171 ); 172}