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:
@@ -0,0 +1,188 @@
|
||||
import { PasswordIcon } from "@/assets/icons";
|
||||
import {
|
||||
createPhoneFieldRules,
|
||||
PhoneNumberInput,
|
||||
} from "@/components/FormElements/PhoneNumberInput";
|
||||
import type { DynamicFormSection } from "@/components/forms/DynamicFormBuilder";
|
||||
import FileImageUploadCard from "@/components/ui/FileImageUploadCard";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { Controller, type UseFormRegisterReturn } from "react-hook-form";
|
||||
import type { CreateAdminFormValues } from "./createAdminForm.types";
|
||||
|
||||
interface CreateAdminFormSectionsOptions {
|
||||
editMode?: boolean;
|
||||
passwordFieldInputType: "password" | "text";
|
||||
setPasswordFieldInputType: Dispatch<SetStateAction<"password" | "text">>;
|
||||
profileImageRegister: UseFormRegisterReturn;
|
||||
isUploadingImage: boolean;
|
||||
uploadProgress: number;
|
||||
previewUrl: string | null;
|
||||
selectedFileName: string;
|
||||
selectedFileSize: number | null;
|
||||
clearSelectedProfileImage: () => void;
|
||||
}
|
||||
|
||||
export const createAdminFormSections = ({
|
||||
editMode,
|
||||
passwordFieldInputType,
|
||||
setPasswordFieldInputType,
|
||||
profileImageRegister,
|
||||
isUploadingImage,
|
||||
uploadProgress,
|
||||
previewUrl,
|
||||
selectedFileName,
|
||||
selectedFileSize,
|
||||
clearSelectedProfileImage,
|
||||
}: CreateAdminFormSectionsOptions): DynamicFormSection<CreateAdminFormValues>[] => [
|
||||
{
|
||||
title: "Input Fields",
|
||||
fields: [
|
||||
{
|
||||
kind: "input",
|
||||
name: "full_name",
|
||||
label: "Full name",
|
||||
type: "text",
|
||||
placeholder: "Enter full name (5-100 characters)",
|
||||
required: true,
|
||||
dataCy: "admin-form-full-name-input",
|
||||
rules: {
|
||||
required: { message: FormErrorMessages.required, value: true },
|
||||
minLength: { value: 5, message: FormErrorMessages.minLength(5) },
|
||||
maxLength: { value: 100, message: FormErrorMessages.maxLength(100) },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "input",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
type: "text",
|
||||
placeholder: "Letters and numbers only (3-30)",
|
||||
autoComplete: "off",
|
||||
required: true,
|
||||
dataCy: "admin-form-username-input",
|
||||
rules: {
|
||||
required: { value: true, message: FormErrorMessages.required },
|
||||
pattern: {
|
||||
value: /^[a-zA-Z0-9]+$/,
|
||||
message: FormErrorMessages.alphaNumeric,
|
||||
},
|
||||
minLength: { value: 3, message: FormErrorMessages.minLength(3) },
|
||||
maxLength: { value: 30, message: FormErrorMessages.maxLength(30) },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "input",
|
||||
name: "email",
|
||||
label: "Email",
|
||||
type: "email",
|
||||
placeholder: "you@example.com",
|
||||
autoComplete: "off",
|
||||
required: true,
|
||||
dataCy: "admin-form-email-input",
|
||||
rules: {
|
||||
required: { value: true, message: FormErrorMessages.required },
|
||||
pattern: {
|
||||
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||
message: FormErrorMessages.invalidEmail,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "input",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
type: passwordFieldInputType,
|
||||
placeholder: "At least 8 characters",
|
||||
autoComplete: "new-password",
|
||||
required: true,
|
||||
hidden: editMode,
|
||||
dataCy: "admin-form-password-input",
|
||||
icon: (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setPasswordFieldInputType(
|
||||
passwordFieldInputType === "password" ? "text" : "password",
|
||||
)
|
||||
}
|
||||
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"
|
||||
aria-label={
|
||||
passwordFieldInputType === "password"
|
||||
? "Show password"
|
||||
: "Hide password"
|
||||
}
|
||||
>
|
||||
<PasswordIcon className="pointer-events-none" />
|
||||
</button>
|
||||
),
|
||||
rules: {
|
||||
required: { value: true, message: FormErrorMessages.required },
|
||||
minLength: { value: 8, message: FormErrorMessages.minLength(8) },
|
||||
maxLength: { value: 128, message: FormErrorMessages.maxLength(128) },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "custom",
|
||||
name: "phone",
|
||||
render: ({ control, errors }) => (
|
||||
<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}
|
||||
dataCy="admin-form-phone-input"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
kind: "input",
|
||||
name: "department",
|
||||
label: "Department",
|
||||
type: "text",
|
||||
dataCy: "admin-form-department-input",
|
||||
rules: {
|
||||
minLength: { value: 2, message: FormErrorMessages.minLength(2) },
|
||||
maxLength: { value: 100, message: FormErrorMessages.maxLength(100) },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "input",
|
||||
name: "position",
|
||||
label: "Position",
|
||||
type: "text",
|
||||
dataCy: "admin-form-position-input",
|
||||
rules: {
|
||||
minLength: { value: 2, message: FormErrorMessages.minLength(2) },
|
||||
maxLength: { value: 100, message: FormErrorMessages.maxLength(100) },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "custom",
|
||||
name: "profile_image",
|
||||
render: ({ error }) => (
|
||||
<FileImageUploadCard
|
||||
label="Profile image"
|
||||
inputId="profile-image-upload"
|
||||
fileRegister={profileImageRegister}
|
||||
isLoading={isUploadingImage}
|
||||
uploadProgress={uploadProgress}
|
||||
previewUrl={previewUrl}
|
||||
selectedFileName={selectedFileName}
|
||||
selectedFileSize={selectedFileSize}
|
||||
onClear={clearSelectedProfileImage}
|
||||
errorMessage={error}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user