"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Calendar as CalendarIcon } from "@openstatus/icons"; import { Button } from "@openstatus/ui/components/ui/button"; import { Calendar } from "@openstatus/ui/components/ui/calendar"; import { DialogFooter } from "@openstatus/ui/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@openstatus/ui/components/ui/form"; import { Input } from "@openstatus/ui/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@openstatus/ui/components/ui/popover"; import { RadioGroup, RadioGroupItem, } from "@openstatus/ui/components/ui/radio-group"; import { Textarea } from "@openstatus/ui/components/ui/textarea"; import { cn } from "@openstatus/ui/lib/utils"; import { isTRPCClientError } from "@trpc/client"; import { format, parse } from "date-fns"; import { useTransition } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; export const schema = z.object({ name: z.string().trim().min(1, "Name is required"), description: z.string().optional(), expiresAt: z.string().optional(), // Single-value radio. The wire format on the create-key API is // an array (`scopes: Scope[]`) so per-resource scopes can land // additively later, but the v1 dashboard surface only ever picks // one of two options — flat enum here, lift to array on submit. // // Duplicates `apiKeySettableScopes` from `@openstatus/db` — the // dashboard's client bundle can't reach into the db package // (drizzle pulls in node-only deps via the schema barrel). The // services input schema validates whatever the form sends, so // drift here surfaces as a parse error on submit, not a silent // mismatch. Keep this list in sync with // `packages/db/src/schema/api-keys/constants.ts`. scope: z.enum(["read", "write"]), }); export type FormValues = z.infer; interface ApiKeyFormProps { defaultValues?: Partial; onSubmit: (values: FormValues) => Promise; onCancel?: () => void; submitDisabled?: boolean; } export function ApiKeyForm({ defaultValues, onSubmit, onCancel, submitDisabled, }: ApiKeyFormProps) { const [isPending, startTransition] = useTransition(); const form = useForm({ resolver: zodResolver(schema), defaultValues: { name: defaultValues?.name ?? "", description: defaultValues?.description ?? "", expiresAt: defaultValues?.expiresAt ?? "", // Default Read-only: AI agents are the most common new use // case for keys, and read-only is the safer starting point. // CI/CD users actively pick "Read & write." scope: defaultValues?.scope ?? "read", }, }); function submitAction(values: FormValues) { if (isPending) return; startTransition(async () => { try { const promise = onSubmit(values); toast.promise(promise, { loading: "Creating...", success: () => "Created", error: (error) => { if (isTRPCClientError(error)) { return error.message; } return "Failed to create API key"; }, }); await promise; form.reset(); } catch (error) { console.error(error); } }); } return (
( Name )} /> ( Description