From b18aa8eb71ce79d1a9f09f846ccb1fa7b066c062 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Wed, 5 Nov 2025 12:44:39 +0330 Subject: [PATCH] refactor(marketing): Abstract form logic into reusable SectionStep component Reduces code duplication in the marketing page forms by creating a generic `SectionStep` component. This new component encapsulates the shared UI and logic for sections that include a title, description, and a dynamic list of fields (e.g., feature cards, pricing points). The `FeaturesStep` component has been refactored to use this new `SectionStep`, resulting in a much cleaner and more maintainable implementation. --- .../marketing/_components/BenefitsStep.tsx | 35 +++++ .../marketing/_components/ChallengesStep.tsx | 35 +++++ .../marketing/_components/FeaturesStep.tsx | 124 ++++------------ src/app/marketing/_components/SectionStep.tsx | 137 ++++++++++++++++++ .../_components/useBenefitsStepPresenter.ts | 49 +++++++ .../_components/useChallengesStepPresenter.ts | 49 +++++++ src/app/marketing/page.tsx | 12 +- 7 files changed, 341 insertions(+), 100 deletions(-) create mode 100644 src/app/marketing/_components/BenefitsStep.tsx create mode 100644 src/app/marketing/_components/ChallengesStep.tsx create mode 100644 src/app/marketing/_components/SectionStep.tsx create mode 100644 src/app/marketing/_components/useBenefitsStepPresenter.ts create mode 100644 src/app/marketing/_components/useChallengesStepPresenter.ts diff --git a/src/app/marketing/_components/BenefitsStep.tsx b/src/app/marketing/_components/BenefitsStep.tsx new file mode 100644 index 0000000..3ca295d --- /dev/null +++ b/src/app/marketing/_components/BenefitsStep.tsx @@ -0,0 +1,35 @@ +"use client"; +import SectionStep from "./SectionStep"; +import useBenefitsStepPresenter, { + IBenefitsStepFields, +} from "./useBenefitsStepPresenter"; + +const BenefitsStep = () => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useBenefitsStepPresenter(); + return ( + + title="Benefits" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="benefits" + defaultValues={{ icon: "", title: "", description: "" }} + /> + ); +}; + +export default BenefitsStep; diff --git a/src/app/marketing/_components/ChallengesStep.tsx b/src/app/marketing/_components/ChallengesStep.tsx new file mode 100644 index 0000000..775f091 --- /dev/null +++ b/src/app/marketing/_components/ChallengesStep.tsx @@ -0,0 +1,35 @@ +"use client"; +import SectionStep from "./SectionStep"; +import useChallengesStepPresenter, { + IChallengesStepFields, +} from "./useChallengesStepPresenter"; + +const ChallengesStep = () => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useChallengesStepPresenter(); + return ( + + title="Challenges" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="challenges" + defaultValues={{ icon: "", title: "", description: "" }} + /> + ); +}; + +export default ChallengesStep; diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/_components/FeaturesStep.tsx index 5fce836..2b19e6e 100644 --- a/src/app/marketing/_components/FeaturesStep.tsx +++ b/src/app/marketing/_components/FeaturesStep.tsx @@ -1,104 +1,34 @@ "use client"; -import { TrashIcon } from "@/assets/icons"; -import InputGroup from "@/components/FormElements/InputGroup"; -import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; -import { ShowcaseSection } from "@/components/Layouts/showcase-section"; -import { FormErrorMessages } from "@/constants/Texts"; -import useFeaturesStepPresenter from "./useFeaturesStepPresenter"; +import SectionStep from "./SectionStep"; +import useFeaturesStepPresenter, { + IFeaturesStepFields, +} from "./useFeaturesStepPresenter"; const FeaturesStep = () => { - const { errors, handleSubmit, onSubmit, register, fields, append, remove } = - useFeaturesStepPresenter(); + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useFeaturesStepPresenter(); return ( -
-
- - - {errors.section_title && ( -

- {errors.section_title.message} -

- )} - -
- - {fields.map((field, index) => ( -
- - - - -
- ))} - -
-
-
+ + title="Features" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="features" + defaultValues={{ icon: "", title: "", description: "" }} + /> ); }; diff --git a/src/app/marketing/_components/SectionStep.tsx b/src/app/marketing/_components/SectionStep.tsx new file mode 100644 index 0000000..c9ec7a3 --- /dev/null +++ b/src/app/marketing/_components/SectionStep.tsx @@ -0,0 +1,137 @@ +"use client"; +import { TrashIcon } from "@/assets/icons"; +import InputGroup from "@/components/FormElements/InputGroup"; +import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; +import { ShowcaseSection } from "@/components/Layouts/showcase-section"; +import { FormErrorMessages } from "@/constants/Texts"; +import { + Control, + FieldErrors, + FieldValues, + Path, + UseFieldArrayAppend, + UseFieldArrayRemove, + UseFormHandleSubmit, + UseFormRegister, +} from "react-hook-form"; + +interface IProps { + title: string; + fields: any[]; + handleSubmit: UseFormHandleSubmit; + onSubmit: (data: T) => void; + register: UseFormRegister; + errors: FieldErrors; + append: UseFieldArrayAppend; + remove: UseFieldArrayRemove; + control: Control; + fieldName: string; + defaultValues: any; +} + +const SectionStep = ({ + title, + fields, + handleSubmit, + onSubmit, + register, + errors, + append, + remove, + fieldName, + defaultValues, +}: IProps) => { + return ( +
+
+ + , { + required: { message: FormErrorMessages.required, value: true }, + })} + label="Section Title" + name="section_title" + required + type="text" + placeholder="Enter Section Title" + /> + {errors.section_title && ( +

+ {errors.section_title.message as string} +

+ )} + )} + name="section_description" + placeholder="Enter Section Description" + /> +
+ + {fields.map((field, index) => ( +
+ , { + required: { + message: FormErrorMessages.required, + value: true, + }, + })} + label="Icon" + name={`${fieldName}.${index}.icon`} + required + type="text" + placeholder="Enter Icon URL or name" + /> + , { + required: { + message: FormErrorMessages.required, + value: true, + }, + })} + label="Title" + name={`${fieldName}.${index}.title`} + required + type="text" + placeholder="Enter Title" + /> + )} + label="Description" + name={`${fieldName}.${index}.description`} + placeholder="Enter Description" + className="sm:col-span-2" + /> + +
+ ))} + +
+
+
+ ); +}; + +export default SectionStep; diff --git a/src/app/marketing/_components/useBenefitsStepPresenter.ts b/src/app/marketing/_components/useBenefitsStepPresenter.ts new file mode 100644 index 0000000..58414a1 --- /dev/null +++ b/src/app/marketing/_components/useBenefitsStepPresenter.ts @@ -0,0 +1,49 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IBenefitsStepFields { + section_title: string; + section_description: string; + benefits: { + icon: string; + title: string; + description: string; + }[]; +} + +const useBenefitsStepPresenter = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + defaultValues: { + benefits: [{ icon: "", title: "", description: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "benefits", + }); + + const onSubmit = (data: IBenefitsStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + append, + remove, + control, + watch, + }; +}; + +export default useBenefitsStepPresenter; diff --git a/src/app/marketing/_components/useChallengesStepPresenter.ts b/src/app/marketing/_components/useChallengesStepPresenter.ts new file mode 100644 index 0000000..0a2785b --- /dev/null +++ b/src/app/marketing/_components/useChallengesStepPresenter.ts @@ -0,0 +1,49 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IChallengesStepFields { + section_title: string; + section_description: string; + challenges: { + icon: string; + title: string; + description: string; + }[]; +} + +const useChallengesStepPresenter = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + defaultValues: { + challenges: [{ icon: "", title: "", description: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "challenges", + }); + + const onSubmit = (data: IChallengesStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + append, + remove, + control, + watch, + }; +}; + +export default useChallengesStepPresenter; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 283ba49..63ad840 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -4,6 +4,7 @@ import Stepper, { Step } from "@/components/ui/Stepper"; import { BreadcrumbItem } from "@/types/shared"; import { useState } from "react"; import { ShowcaseSection } from "../../components/Layouts/showcase-section"; +import ChallengesStep from "./_components/ChallengesStep"; import ChartStep from "./_components/ChartStep"; import FeaturesStep from "./_components/FeaturesStep"; import HeroStep from "./_components/HeroStep"; @@ -37,9 +38,14 @@ const Marketing = () => { content: , }, { - title: "Review", - description: "Review and confirm", - content:
step 4
, + title: "Challenges", + description: "Create challenges section", + content: , + }, + { + title: "Benefits", + description: "create benefits section", + content: , }, ]; return (