feat(forms): implement dynamic form builder for admin and company creation
continuous-integration/drone/push Build is passing
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:
@@ -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 />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user