From 9bb740e446ae5880eb6b3cefe32f8ea5e15ae30d Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Wed, 17 Jun 2026 12:05:43 +0530 Subject: [PATCH] feat: add zod validation for role and user forms --- package-lock.json | 27 ++++- package.json | 2 + .../(modules)/roles/components/RoleSheet.tsx | 69 ++++++++---- src/app/(modules)/roles/hooks/useRoleForm.ts | 55 ++++++--- src/app/(modules)/roles/page.tsx | 6 + .../(modules)/users/components/UserSheet.tsx | 104 +++++++++--------- src/app/(modules)/users/hooks/useUserForm.ts | 65 ++++++++--- src/app/(modules)/users/page.tsx | 6 + src/components/form/FormField.tsx | 43 ++++++++ src/components/form/index.ts | 1 + 10 files changed, 270 insertions(+), 108 deletions(-) create mode 100644 src/components/form/FormField.tsx create mode 100644 src/components/form/index.ts diff --git a/package-lock.json b/package-lock.json index 8d5b90e..6420be7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@base-ui/react": "^1.5.0", + "@hookform/resolvers": "^5.4.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", @@ -38,6 +39,7 @@ "recharts": "2.15.4", "sonner": "^1.7.4", "tailwind-merge": "^3.3.1", + "zod": "^4.4.3", "zustand": "^5.0.14" }, "devDependencies": { @@ -605,6 +607,18 @@ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@hookform/resolvers": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz", + "integrity": "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2886,6 +2900,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -9826,10 +9846,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index c8251d2..242d4a3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@base-ui/react": "^1.5.0", + "@hookform/resolvers": "^5.4.0", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", @@ -50,6 +51,7 @@ "recharts": "2.15.4", "sonner": "^1.7.4", "tailwind-merge": "^3.3.1", + "zod": "^4.4.3", "zustand": "^5.0.14" }, "devDependencies": { diff --git a/src/app/(modules)/roles/components/RoleSheet.tsx b/src/app/(modules)/roles/components/RoleSheet.tsx index 7d228b0..6bf5d2d 100644 --- a/src/app/(modules)/roles/components/RoleSheet.tsx +++ b/src/app/(modules)/roles/components/RoleSheet.tsx @@ -1,13 +1,14 @@ 'use client'; import type { ComponentProps } from 'react'; -import type { UseFormRegister } from 'react-hook-form'; +import type { FieldErrors, UseFormRegister, UseFormReturn } from 'react-hook-form'; import { Loader2 } from 'lucide-react'; +import { FormField } from '@/components/form'; import { PermissionTree } from '@/components/permission-tree'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; import { Sheet, SheetClose, @@ -26,11 +27,14 @@ interface RoleSheetProps { onOpenChange: (open: boolean) => void; roleId?: number; register: UseFormRegister; + errors: FieldErrors; + touchedFields: UseFormReturn['formState']['touchedFields']; onSubmit: ComponentProps<'form'>['onSubmit']; permissionTree: PermissionTreeItem[]; permissionIds: number[]; onPermissionIdsChange: (ids: number[]) => void; isPermissionsLoading: boolean; + canSubmit: boolean; isSaving: boolean; } @@ -39,13 +43,21 @@ export function RoleSheet({ onOpenChange, roleId, register, + errors, + touchedFields, onSubmit, permissionTree, permissionIds, onPermissionIdsChange, isPermissionsLoading, + canSubmit, isSaving, }: RoleSheetProps) { + const nameErrorMessage = touchedFields.name ? errors.name?.message : undefined; + const displayNameErrorMessage = touchedFields.display_name + ? errors.display_name?.message + : undefined; + return (
-
- + -
-
- + + -
+
-
- -