"use client"; import { SCOPES, type Scope, type ScopePreset, scopePresets, scopesToName, } from "@api/utils/scopes"; import { AnimatedSizeContainer } from "@midday/ui/animated-size-container"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@midday/ui/form"; import { Input } from "@midday/ui/input"; import { SubmitButton } from "@midday/ui/submit-button"; import { Tabs, TabsList, TabsTrigger } from "@midday/ui/tabs"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { z } from "zod/v3"; import { useZodForm } from "@/hooks/use-zod-form"; import { useTokenModalStore } from "@/store/token-modal"; import { useTRPC } from "@/trpc/client"; import { RESOURCES } from "@/utils/scopes"; import { ScopeSelector } from "../scope-selector"; const formSchema = z.object({ id: z.string().optional(), name: z.string().min(2, { message: "Team name must be at least 2 characters.", }), scopes: z.array(z.enum(SCOPES)).default(["apis.all"]), }); type Props = { onSuccess: (key: string | null) => void; }; export function ApiKeyForm({ onSuccess }: Props) { const { data } = useTokenModalStore(); const [preset, setPreset] = useState(() => data?.scopes ? (scopesToName(data.scopes).preset as ScopePreset) : "all_access", ); const trpc = useTRPC(); const queryClient = useQueryClient(); const upsertApiKeyMutation = useMutation( trpc.apiKeys.upsert.mutationOptions({ onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: trpc.apiKeys.get.queryKey(), }); onSuccess(data.key); }, }), ); const form = useZodForm(formSchema, { defaultValues: { id: data?.id ?? undefined, name: data?.name ?? "", scopes: (data?.scopes as Scope[]) ?? ["apis.all"], }, }); // Effect to ensure proper initialization when editing existing API key useEffect(() => { if (data?.scopes) { const detectedPreset = scopesToName(data.scopes).preset as ScopePreset; setPreset(detectedPreset); // If it's restricted, make sure the form has the correct scopes if (detectedPreset === "restricted") { form.setValue("scopes", data.scopes as Scope[], { shouldDirty: true }); } } }, [data?.scopes, form]); // Update form scopes based on preset const updateScopesFromPreset = (newPreset: ScopePreset) => { let newScopes: Scope[] = []; switch (newPreset) { case "all_access": newScopes = ["apis.all"]; break; case "read_only": newScopes = ["apis.read"]; break; case "restricted": { // Keep existing scopes when switching to restricted mode const currentScopes = form.getValues("scopes"); // Get all valid scopes from RESOURCES const validScopes = RESOURCES.flatMap((resource) => resource.scopes.map((scope) => scope.scope), ); // Only keep scopes that are defined in RESOURCES newScopes = currentScopes.filter((scope): scope is Scope => validScopes.some((validScope) => validScope === scope), ); break; } } form.setValue("scopes", newScopes, { shouldDirty: true }); }; const handlePresetChange = (value: string) => { const scopePreset = value as ScopePreset; setPreset(scopePreset); updateScopesFromPreset(scopePreset); }; const handleResourceScopeChange = (resourceKey: string, scope: string) => { if (preset !== "restricted") return; const currentScopes = form.getValues("scopes"); const resource = RESOURCES.find((r) => r.key === resourceKey); if (!resource) return; // Remove any existing scopes for this resource const filteredScopes = currentScopes.filter( (currentScope) => !resource.scopes.some((s) => s.scope === currentScope), ); // Add the new scope if it's not empty const newScopes = scope ? [...filteredScopes, scope as Scope] : filteredScopes; form.setValue("scopes", newScopes, { shouldDirty: true }); }; function onSubmit(values: z.infer) { upsertApiKeyMutation.mutate({ id: values.id, name: values.name, scopes: values.scopes, }); } return (
( Name )} /> {scopePresets.map((scope) => ( {scope.label} ))}

This API key will have{" "} {scopePresets.find((scope) => scope.value === preset)?.description} .

{preset === "restricted" && ( )} {data?.id ? "Update" : "Create"} ); }