diff --git a/apps/web/app/routes/ws/policies/_components/create/PolicyFormContext.tsx b/apps/web/app/routes/ws/policies/_components/create/PolicyFormContext.tsx index 76cdc3e92..000158ba3 100644 --- a/apps/web/app/routes/ws/policies/_components/create/PolicyFormContext.tsx +++ b/apps/web/app/routes/ws/policies/_components/create/PolicyFormContext.tsx @@ -2,9 +2,13 @@ import type { UseFormReturn } from "react-hook-form"; import { createContext, useContext } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router"; +import { toast } from "sonner"; import { z } from "zod"; +import { trpc } from "~/api/trpc"; import { Form } from "~/components/ui/form"; +import { useWorkspace } from "~/components/WorkspaceProvider"; const selectorSchema = z.object({ cel: z.string().min(1, "CEL expression is required"), @@ -13,7 +17,7 @@ const selectorSchema = z.object({ export const policyCreateFormSchema = z.object({ name: z.string().min(1, "Policy name is required"), description: z.string().optional(), - priority: z.number().min(0, "Priority must be greater than 0"), + priority: z.number().min(0, "Priority must be 0 or greater"), enabled: z.boolean().default(true), target: z.object({ deploymentSelector: selectorSchema, @@ -33,6 +37,7 @@ export type PolicyCreateFormSchema = z.infer; type PolicyFormContextType = { form: UseFormReturn; + isSubmitting: boolean; }; const PolicyFormContext = createContext(null); @@ -49,10 +54,14 @@ export function usePolicyCreateForm() { export const PolicyCreateFormContextProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { - const form = useForm({ + const { workspace } = useWorkspace(); + const navigate = useNavigate(); + const utils = trpc.useUtils(); + const form = useForm({ resolver: zodResolver(policyCreateFormSchema), defaultValues: { name: "", + description: "", priority: 0, enabled: true, target: { @@ -63,12 +72,41 @@ export const PolicyCreateFormContextProvider: React.FC<{ }, }); - const onSubmit = form.handleSubmit(() => { - // todo + const createPolicyMutation = trpc.policies.create.useMutation({ + onSuccess: () => { + toast.success("Policy created successfully"); + void utils.policies.list.invalidate({ workspaceId: workspace.id }); + form.reset(); + navigate(`/${workspace.slug}/policies`); + }, + onError: (error: unknown) => { + const message = + error && + typeof error === "object" && + "message" in error && + typeof error.message === "string" + ? error.message + : "Failed to create policy"; + toast.error(message); + }, }); + const onSubmit = form.handleSubmit(async (data) => { + await createPolicyMutation.mutateAsync({ + workspaceId: workspace.id, + name: data.name, + description: data.description?.trim() || undefined, + priority: data.priority, + enabled: data.enabled, + target: data.target, + anyApproval: data.anyApproval, + }); + }); + + const isSubmitting = createPolicyMutation.isPending; + return ( - +
{children}
diff --git a/apps/web/app/routes/ws/policies/page.create.tsx b/apps/web/app/routes/ws/policies/page.create.tsx index 65f6c98e1..ead681336 100644 --- a/apps/web/app/routes/ws/policies/page.create.tsx +++ b/apps/web/app/routes/ws/policies/page.create.tsx @@ -1,4 +1,22 @@ +import { Loader2Icon } from "lucide-react"; +import { Link } from "react-router"; + +import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "~/components/ui/form"; +import { Input } from "~/components/ui/input"; +import { Switch } from "~/components/ui/switch"; +import { Textarea } from "~/components/ui/textarea"; +import { useWorkspace } from "~/components/WorkspaceProvider"; +import CelExpressionInput from "../_components/CelExpiressionInput"; +import { usePolicyCreateForm } from "./_components/create/PolicyFormContext"; export function meta() { return [ @@ -11,16 +29,262 @@ export function meta() { } export default function PageCreate() { + const { workspace } = useWorkspace(); + const { form, isSubmitting } = usePolicyCreateForm(); + const anyApprovalEnabled = form.watch("anyApproval") != null; + + const handleApprovalToggle = (checked: boolean) => { + if (checked) { + form.setValue( + "anyApproval", + { minApprovals: 1 }, + { shouldDirty: true, shouldValidate: true }, + ); + } else { + form.setValue("anyApproval", undefined, { + shouldDirty: true, + shouldValidate: true, + }); + form.clearErrors("anyApproval"); + } + }; + return ( Create New Policy - {/* TODO: Add policy creation form here */} -

- Policy creation form coming soon... -

+
+
+

Basic information

+ + ( + + Name + + + + A short, descriptive policy name + + + )} + /> + + ( + + Description (Optional) + +