"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Add, Check, Close, Copy } from "@openstatus/icons"; import { Button } from "@openstatus/ui/components/ui/button"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@openstatus/ui/components/ui/form"; import { Input } from "@openstatus/ui/components/ui/input"; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, } from "@openstatus/ui/components/ui/input-group"; import { useCopyToClipboard } from "@openstatus/ui/hooks/use-copy-to-clipboard"; import { cn } from "@openstatus/ui/lib/utils"; import { isTRPCClientError } from "@trpc/client"; import React, { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { EmptyStateContainer, EmptyStateTitle, } from "@/components/content/empty-state"; import { FormCardContent, FormCardSeparator, } from "@/components/forms/form-card"; import { useFormSheetDirty } from "@/components/forms/form-sheet"; import { CheckboxTree } from "@/components/ui/checkbox-tree"; const schema = z.object({ name: z.string().min(1, "Name is required"), token: z.string(), monitors: z.array(z.number()), metadata: z .array( z.object({ key: z.string().min(1, "Key is required").max(64), value: z.string().max(256), }), ) .max(20, "At most 20 metadata entries") .refine( (rows) => new Set(rows.map((r) => r.key)).size === rows.length, "Metadata keys must be unique", ), }); export type FormValues = z.infer; export function FormPrivateLocation({ defaultValues, onSubmit, className, monitors, ...props }: Omit, "onSubmit"> & { defaultValues?: FormValues; monitors: { id: number; name: string; url: string }[]; onSubmit: (values: FormValues) => Promise; }) { const form = useForm({ resolver: zodResolver(schema), defaultValues: defaultValues ?? { name: "", token: crypto.randomUUID(), monitors: [], metadata: [], }, }); const [isPending, startTransition] = useTransition(); const { copy, isCopied } = useCopyToClipboard(); const { setIsDirty } = useFormSheetDirty(); const formIsDirty = form.formState.isDirty; React.useEffect(() => { setIsDirty(formIsDirty); }, [formIsDirty, setIsDirty]); function submitAction(values: FormValues) { if (isPending) return; startTransition(async () => { try { const promise = onSubmit(values); toast.promise(promise, { loading: "Saving...", success: () => "Saved", error: (error) => { if (isTRPCClientError(error)) { return error.message; } return "Failed to save"; }, }); await promise; } catch (error) { console.error(error); } }); } return (
( Name )} /> ( Token { copy(field.value, { successMessage: "Token copied to clipboard", }); }} > {isCopied ? : } )} /> ( Monitors Connected monitors will be automatically activated for the private location. {monitors.length ? ( ({ id: m.id, label: m.name, })), }, ]} value={field.value ?? []} onValueChange={field.onChange} /> ) : ( No monitors found )} )} /> ( Metadata Optional key/value labels to store custom information about this location. {field.value.map((entry, index) => (
{ const next = [...field.value]; next[index] = { ...next[index], key: e.target.value }; field.onChange(next); }} /> { const next = [...field.value]; next[index] = { ...next[index], value: e.target.value }; field.onChange(next); }} />
))}
)} />
); }