feat: implement Zod hex color validation and error handling for tenant color settings in wizard and edit forms

This commit is contained in:
Yashwin 2026-05-18 18:22:54 +05:30
parent c927886621
commit 12954e5ba1
3 changed files with 114 additions and 14 deletions

View File

@ -92,13 +92,51 @@ const contactDetailsSchema = z.object({
country: z.string().min(1, "Country is required"),
});
const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
// Step 3: Settings Schema
const settingsSchema = z.object({
enable_sso: z.boolean(),
enable_2fa: z.boolean(),
primary_color: z.string().optional().nullable(),
secondary_color: z.string().optional().nullable(),
accent_color: z.string().optional().nullable(),
primary_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #112868)",
}
),
secondary_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #23DCE1)",
}
),
accent_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #084CC8)",
}
),
});
type TenantDetailsForm = z.infer<typeof tenantDetailsSchema>;
@ -1303,7 +1341,7 @@ const CreateTenantWizard = (): ReactElement => {
type="text"
value={settingsForm.watch("primary_color") || "#112868"}
onChange={(e) =>
settingsForm.setValue("primary_color", e.target.value)
settingsForm.setValue("primary_color", e.target.value, { shouldValidate: true })
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
placeholder="#112868"
@ -1313,11 +1351,14 @@ const CreateTenantWizard = (): ReactElement => {
type="color"
value={settingsForm.watch("primary_color") || "#112868"}
onChange={(e) =>
settingsForm.setValue("primary_color", e.target.value)
settingsForm.setValue("primary_color", e.target.value, { shouldValidate: true })
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.primary_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.primary_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for navigation, headers, and key actions.
</p>
@ -1348,6 +1389,7 @@ const CreateTenantWizard = (): ReactElement => {
settingsForm.setValue(
"secondary_color",
e.target.value,
{ shouldValidate: true }
)
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
@ -1363,11 +1405,15 @@ const CreateTenantWizard = (): ReactElement => {
settingsForm.setValue(
"secondary_color",
e.target.value,
{ shouldValidate: true }
)
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.secondary_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.secondary_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for highlights and supporting elements.
</p>
@ -1396,6 +1442,7 @@ const CreateTenantWizard = (): ReactElement => {
settingsForm.setValue(
"accent_color",
e.target.value,
{ shouldValidate: true }
)
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
@ -1406,11 +1453,14 @@ const CreateTenantWizard = (): ReactElement => {
type="color"
value={settingsForm.watch("accent_color") || "#084CC8"}
onChange={(e) =>
settingsForm.setValue("accent_color", e.target.value)
settingsForm.setValue("accent_color", e.target.value, { shouldValidate: true })
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.accent_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.accent_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for alerts and special notices.
</p>

View File

@ -99,13 +99,51 @@ const contactDetailsSchema = z.object({
country: z.string().min(1, "Country is required"),
});
const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
// Step 3: Settings Schema
const settingsSchema = z.object({
enable_sso: z.boolean(),
enable_2fa: z.boolean(),
primary_color: z.string().optional().nullable(),
secondary_color: z.string().optional().nullable(),
accent_color: z.string().optional().nullable(),
primary_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #112868)",
}
),
secondary_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #23DCE1)",
}
),
accent_color: z
.string()
.optional()
.nullable()
.refine(
(val) => {
if (!val || val.trim() === "") return true;
return hexColorRegex.test(val);
},
{
message: "Must be a valid hex color code (e.g., #084CC8)",
}
),
});
type TenantDetailsForm = z.infer<typeof tenantDetailsSchema>;
@ -1465,7 +1503,7 @@ const EditTenant = (): ReactElement => {
type="text"
value={settingsForm.watch("primary_color") || "#112868"}
onChange={(e) =>
settingsForm.setValue("primary_color", e.target.value)
settingsForm.setValue("primary_color", e.target.value, { shouldValidate: true })
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
placeholder="#112868"
@ -1475,11 +1513,14 @@ const EditTenant = (): ReactElement => {
type="color"
value={settingsForm.watch("primary_color") || "#112868"}
onChange={(e) =>
settingsForm.setValue("primary_color", e.target.value)
settingsForm.setValue("primary_color", e.target.value, { shouldValidate: true })
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.primary_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.primary_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for navigation, headers, and key actions.
</p>
@ -1509,6 +1550,7 @@ const EditTenant = (): ReactElement => {
settingsForm.setValue(
"secondary_color",
e.target.value,
{ shouldValidate: true }
)
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
@ -1524,11 +1566,15 @@ const EditTenant = (): ReactElement => {
settingsForm.setValue(
"secondary_color",
e.target.value,
{ shouldValidate: true }
)
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.secondary_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.secondary_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for highlights and supporting elements.
</p>
@ -1556,6 +1602,7 @@ const EditTenant = (): ReactElement => {
settingsForm.setValue(
"accent_color",
e.target.value,
{ shouldValidate: true }
)
}
className="bg-[#f5f7fa] border border-[#d1d5db] h-10 px-3 py-1 rounded-md text-sm text-[#0f1724] w-full focus:outline-none focus:ring-2 focus:ring-[#112868] focus:border-transparent"
@ -1566,11 +1613,14 @@ const EditTenant = (): ReactElement => {
type="color"
value={settingsForm.watch("accent_color") || "#084CC8"}
onChange={(e) =>
settingsForm.setValue("accent_color", e.target.value)
settingsForm.setValue("accent_color", e.target.value, { shouldValidate: true })
}
className="size-10 rounded-md border border-[#d1d5db] cursor-pointer"
/>
</div>
{settingsForm.formState.errors.accent_color && (
<p className="text-sm text-[#ef4444]">{settingsForm.formState.errors.accent_color.message}</p>
)}
<p className="text-xs font-normal text-[#9ca3af]">
Used for alerts and special notices.
</p>

View File

@ -23,8 +23,8 @@ export const moduleService = {
params.append('search', search);
}
if (orderBy && Array.isArray(orderBy) && orderBy.length === 2) {
params.append('orderBy[]', orderBy[0]);
params.append('orderBy[]', orderBy[1]);
params.append('sort_by', orderBy[0]);
params.append('sort_order', orderBy[1]);
}
const response = await apiClient.get<ModulesResponse>(`/modules?${params.toString()}`);
return response.data;