feat(forms): implement dynamic form builder for admin and company creation
continuous-integration/drone/push Build is passing

- Introduced DynamicFormBuilder component to streamline form creation for admin and company categories.
- Refactored CreateAdmin and CreateCompany components to utilize the new dynamic form structure.
- Added createAdminFormSections and createCompanyFormSections functions to define form fields and validation rules.
- Enhanced form handling with improved error management and dynamic field rendering.

This update enhances maintainability and scalability of forms across the application.
This commit is contained in:
AmirReza Jamali
2026-06-21 09:03:51 +03:30
parent 5a6a5346c6
commit 90a42b9fc5
22 changed files with 1717 additions and 1392 deletions
+24 -210
View File
@@ -1,21 +1,9 @@
"use client";
import { EyeIcon, GlobeIcon } from "@/assets/icons";
import InputGroup from "@/components/FormElements/InputGroup";
import MultiSelect from "@/components/FormElements/MultiSelect";
import {
createPhoneFieldRules,
PhoneNumberInput,
} from "@/components/FormElements/PhoneNumberInput";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import DynamicFormBuilder from "@/components/forms/DynamicFormBuilder";
import FormFooter from "@/components/ui/FormFooter";
import { AdminRoles } from "@/constants/enums";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
import { CreateCustomerCredentials } from "@/lib/api";
import { getEnumAsArray } from "@/utils/shared";
import { Controller } from "react-hook-form";
import type { CreateCustomerCredentials } from "@/lib/api";
import { createCustomerFormSections } from "./customerFormSections";
import useCreateCustomerPresenter from "./useCreateCustomerPresenter";
interface IProps {
@@ -37,202 +25,28 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
setShowPassword,
} = useCreateCustomerPresenter({ defaultValues, editMode, id });
const companyItems =
companies?.map((company) => ({
value: company.id,
label: company.name,
})) ?? [];
return (
<form className="grid grid-cols-1 gap-9" onSubmit={handleSubmit(onSubmit)}>
<ShowcaseSection
title="Create Customer"
className="grid grid-cols-1 gap-5 md:grid-cols-2"
>
<div className="flex flex-col gap-2">
<InputGroup
{...register("username", {
required: { value: true, message: FormErrorMessages.required },
minLength: { value: 3, message: FormErrorMessages.minLength(3) },
maxLength: {
value: 30,
message: FormErrorMessages.maxLength(30),
},
pattern: {
value: REGEX.USERNAME,
message: FormErrorMessages.invalidPattern,
},
})}
name="username"
label="Username"
type="text"
placeholder="Unique login, letters and numbers only"
required
/>
{errors.username && (
<p className="mt-1 text-sm text-red-500">
{errors.username.message}
</p>
)}
</div>
<div className="flex flex-col gap-2">
<MultiSelect
label="Select companies"
value={watch("companies")}
items={
companies
? companies?.map((item) => ({
value: item.id,
label: item.name,
}))
: []
}
{...register("companies")}
errors={errors}
placeholder=""
/>
</div>
<div className="flex flex-col gap-2">
<InputGroup
{...register("email", {
required: { value: true, message: FormErrorMessages.required },
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: FormErrorMessages.invalidEmail,
},
})}
name="email"
label="Email"
type="email"
required
placeholder="name@company.com"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">{errors.email.message}</p>
)}
</div>
<div className="flex flex-col">
<Select
label="Role"
{...register("role", {
required: { message: FormErrorMessages.required, value: true },
})}
name="role"
value={watch("role")}
items={getEnumAsArray(AdminRoles)}
placeholder="Choose a role"
required
/>
</div>
{!editMode && (
<div className="flex flex-col gap-2">
<InputGroup
{...register("password", {
required: { value: true, message: FormErrorMessages.required },
minLength: {
value: 8,
message: FormErrorMessages.minLength(8),
},
maxLength: {
value: 128,
message: FormErrorMessages.maxLength(128),
},
})}
disabled={editMode}
name="password"
icon={
<button
className="absolute right-4.5 top-1/2 z-10 flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded text-dark-5 transition hover:text-primary dark:text-white/75 [&>svg]:!static [&>svg]:!size-5 [&>svg]:!translate-y-0"
onClick={() => {
setShowPassword(!showPassword);
}}
>
<EyeIcon />
</button>
}
iconPosition="right"
label="Password"
type={showPassword ? "text" : "password"}
placeholder="At least 8 characters"
required={!editMode}
/>
{errors.password && (
<p className="mt-1 text-sm text-red-500">
{errors.password.message}
</p>
)}
</div>
)}
<div className="flex flex-col gap-2">
<InputGroup
{...register("full_name", {
minLength: { value: 2, message: FormErrorMessages.minLength(2) },
maxLength: {
value: 100,
message: FormErrorMessages.maxLength(100),
},
})}
name="full_name"
label="Full name"
type="text"
/>
{errors.full_name && (
<p className="mt-1 text-sm text-red-500">
{errors.full_name.message}
</p>
)}
</div>
<div className="flex flex-col gap-2">
<Controller
name="phone"
control={control}
rules={createPhoneFieldRules({ required: false })}
render={({ field }) => (
<PhoneNumberInput
label="Phone"
value={field.value ?? ""}
onChange={field.onChange}
onBlur={field.onBlur}
error={errors.phone?.message as string | undefined}
/>
)}
/>
</div>
<div className="flex flex-col gap-2">
<Select
{...register("type", {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
name="type"
label="Type"
required
value={watch("type")}
placeholder="Choose customer type"
prefixIcon={<GlobeIcon />}
items={[
{
label: "Individual",
value: "individual",
},
{
label: "Company",
value: "company",
},
{
label: "Government",
value: "government",
},
]}
/>
{errors.type && (
<p className="mt-1 text-sm text-red-500">{errors.type.message}</p>
)}
</div>
<div className="md:col-span-2">
<FormFooter />
</div>
</ShowcaseSection>
</form>
<DynamicFormBuilder<CreateCustomerCredentials>
sections={createCustomerFormSections({
editMode,
companyItems,
watch,
showPassword,
setShowPassword,
})}
register={register}
control={control}
errors={errors}
handleSubmit={handleSubmit}
onSubmit={onSubmit}
footer={<FormFooter />}
/>
);
};