Files
tm_panel/src/components/forms/admins/createAdminFormSections.tsx
T
AmirReza Jamali 31d80cd6ae
continuous-integration/drone/push Build is passing
refactor(forms): memoize dynamic form sections with stable ids
Add uuid-based section keys and memoized DynamicSection to reduce re-renders, fix dashboard GSAP scoping, update logo sizing, and temporarily hide notice-type and closing-soon widgets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 09:50:27 +03:30

193 lines
6.2 KiB
TypeScript

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 { v4 as uuidv4 } from "uuid";
import type { CreateAdminFormValues } from "./createAdminForm.types";
const adminSectionId = uuidv4();
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>[] => [
{
id: adminSectionId,
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}
/>
),
},
],
},
];