Openstatus
www.openstatus.dev
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}