refactor(marketing): Standardize step components with forwardRef and data collection

- Add forwardRef to all marketing wizard step components
- Implement useImperativeHandle for consistent data collection across steps
- Remove redundant collectData static methods
- Add displayName to each component for better debugging
- Simplify data retrieval pattern using ref-based getData method
- Remove duplicate code and improve component consistency
- Ensure each step component follows the same structural pattern
This commit is contained in:
AmirReza Jamali
2025-11-09 15:02:26 +03:30
parent cdc8cde6a4
commit 574a3a20ce
9 changed files with 160 additions and 79 deletions
@@ -1,10 +1,11 @@
"use client"; "use client";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep"; import SectionStep from "./SectionStep";
import useBenefitsStepPresenter, { import useBenefitsStepPresenter, {
IBenefitsStepFields, IBenefitsStepFields,
} from "./useBenefitsStepPresenter"; } from "./useBenefitsStepPresenter";
const BenefitsStep = () => { const BenefitsStep = forwardRef((props, ref) => {
const { const {
errors, errors,
handleSubmit, handleSubmit,
@@ -14,7 +15,13 @@ const BenefitsStep = () => {
append, append,
remove, remove,
control, control,
getValues,
} = useBenefitsStepPresenter(); } = useBenefitsStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<SectionStep<IBenefitsStepFields> <SectionStep<IBenefitsStepFields>
title="Benefits" title="Benefits"
@@ -30,6 +37,8 @@ const BenefitsStep = () => {
defaultValues={{ icon: "", title: "", description: "" }} defaultValues={{ icon: "", title: "", description: "" }}
/> />
); );
}; });
BenefitsStep.displayName = "BenefitsStep";
export default BenefitsStep; export default BenefitsStep;
@@ -1,10 +1,11 @@
"use client"; "use client";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep"; import SectionStep from "./SectionStep";
import useChallengesStepPresenter, { import useChallengesStepPresenter, {
IChallengesStepFields, IChallengesStepFields,
} from "./useChallengesStepPresenter"; } from "./useChallengesStepPresenter";
const ChallengesStep = () => { const ChallengesStep = forwardRef((props, ref) => {
const { const {
errors, errors,
handleSubmit, handleSubmit,
@@ -14,7 +15,13 @@ const ChallengesStep = () => {
append, append,
remove, remove,
control, control,
getValues,
} = useChallengesStepPresenter(); } = useChallengesStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<SectionStep<IChallengesStepFields> <SectionStep<IChallengesStepFields>
title="Challenges" title="Challenges"
@@ -30,11 +37,8 @@ const ChallengesStep = () => {
defaultValues={{ icon: "", title: "", description: "" }} defaultValues={{ icon: "", title: "", description: "" }}
/> />
); );
}; });
ChallengesStep.displayName = "ChallengesStep";
export default ChallengesStep; export default ChallengesStep;
ChallengesStep.collectData = async () => {
const { getValues } = useChallengesStepPresenter();
return getValues();
}
@@ -3,12 +3,12 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { TrashIcon } from "@/assets/icons"; import { TrashIcon } from "@/assets/icons";
import FormFooter from "@/components/ui/FormFooter";
import { FormErrorMessages } from "@/constants/Texts"; import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { ChartPreview } from "./ChartPreview"; import { ChartPreview } from "./ChartPreview";
import useChartStepPresenter from "./useChartStepPresenter"; import useChartStepPresenter from "./useChartStepPresenter";
const ChartStep = () => { const ChartStep = forwardRef((props, ref) => {
const { const {
errors, errors,
handleSubmit, handleSubmit,
@@ -18,8 +18,13 @@ const ChartStep = () => {
append, append,
remove, remove,
watch, watch,
getValues,
} = useChartStepPresenter(); } = useChartStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const chartTitle = watch("chart_title"); const chartTitle = watch("chart_title");
const chartPoints = watch("chart_points"); const chartPoints = watch("chart_points");
@@ -122,11 +127,8 @@ const ChartStep = () => {
<div className="flex flex-col gap-9"></div> <div className="flex flex-col gap-9"></div>
</form> </form>
); );
}; });
ChartStep.displayName = "ChartStep";
export default ChartStep; export default ChartStep;
ChartStep.collectData = async () => {
const { getValues } = useChartStepPresenter();
return getValues();
}
@@ -4,11 +4,17 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts"; import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useContactsStepPresenter from "./useContactsStepPresenter"; import useContactsStepPresenter from "./useContactsStepPresenter";
const ContactsStep = () => { const ContactsStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, fields } = const { errors, handleSubmit, onSubmit, register, fields, getValues } =
useContactsStepPresenter(); useContactsStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<form <form
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7" className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7"
@@ -87,16 +93,8 @@ const ContactsStep = () => {
</ShowcaseSection> </ShowcaseSection>
</form> </form>
); );
}; });
ContactsStep.displayName = "ContactsStep";
export default ContactsStep; export default ContactsStep;
ContactsStep.collectData = async () => {
const { getValues } = useContactsStepPresenter();
return getValues();
}
ContactsStep.collectData = async () => {
const { getValues } = useContactsStepPresenter();
return getValues();
}
@@ -1,10 +1,11 @@
"use client"; "use client";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep"; import SectionStep from "./SectionStep";
import useFeaturesStepPresenter, { import useFeaturesStepPresenter, {
IFeaturesStepFields, IFeaturesStepFields,
} from "./useFeaturesStepPresenter"; } from "./useFeaturesStepPresenter";
const FeaturesStep = () => { const FeaturesStep = forwardRef((props, ref) => {
const { const {
errors, errors,
handleSubmit, handleSubmit,
@@ -14,7 +15,13 @@ const FeaturesStep = () => {
append, append,
remove, remove,
control, control,
getValues,
} = useFeaturesStepPresenter(); } = useFeaturesStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<SectionStep<IFeaturesStepFields> <SectionStep<IFeaturesStepFields>
title="Features" title="Features"
@@ -30,11 +37,8 @@ const FeaturesStep = () => {
defaultValues={{ icon: "", title: "", description: "" }} defaultValues={{ icon: "", title: "", description: "" }}
/> />
); );
}; });
FeaturesStep.displayName = "FeaturesStep";
export default FeaturesStep; export default FeaturesStep;
FeaturesStep.collectData = async () => {
const { getValues } = useFeaturesStepPresenter();
return getValues();
}
@@ -3,13 +3,17 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts"; import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useFooterStepPresenter from "./useFooterStepPresenter"; import useFooterStepPresenter from "./useFooterStepPresenter";
const FooterStep = forwardRef((props, ref) => {
const FooterStep = () => { const { errors, handleSubmit, onSubmit, register, getValues } =
const { errors, handleSubmit, onSubmit, register } =
useFooterStepPresenter(); useFooterStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<form <form
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7" className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7"
@@ -67,11 +71,8 @@ const FooterStep = () => {
</ShowcaseSection> </ShowcaseSection>
</form> </form>
); );
}; });
export default FooterStep; FooterStep.displayName = "FooterStep";
FooterStep.collectData = async () => { export default FooterStep;
const { getValues } = useFooterStepPresenter();
return getValues();
}
@@ -3,10 +3,16 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts"; import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useHeroStepPresenter from "./useHeroStepPresenter"; import useHeroStepPresenter from "./useHeroStepPresenter";
const HeroStep = () => { const HeroStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register } = useHeroStepPresenter(); const { errors, handleSubmit, onSubmit, register, getValues } = useHeroStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return ( return (
<form <form
className=" items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7 sm:grid-cols-2" className=" items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7 sm:grid-cols-2"
@@ -129,11 +135,8 @@ const HeroStep = () => {
</div> </div>
</form> </form>
); );
}; });
HeroStep.collectData = async () => { HeroStep.displayName = "HeroStep";
const { getValues } = useHeroStepPresenter();
return getValues();
}
export default HeroStep; export default HeroStep;
@@ -18,6 +18,7 @@ const useBenefitsStepPresenter = () => {
control, control,
watch, watch,
formState: { errors }, formState: { errors },
getValues,
} = useForm<IBenefitsStepFields>({ } = useForm<IBenefitsStepFields>({
defaultValues: { defaultValues: {
benefits: [{ icon: "", title: "", description: "" }], benefits: [{ icon: "", title: "", description: "" }],
@@ -33,6 +34,10 @@ const useBenefitsStepPresenter = () => {
console.log(data); console.log(data);
}; };
const collectData = async () => {
return getValues();
};
return { return {
register, register,
handleSubmit, handleSubmit,
@@ -43,6 +48,8 @@ const useBenefitsStepPresenter = () => {
remove, remove,
control, control,
watch, watch,
getValues,
collectData,
}; };
}; };
+79 -26
View File
@@ -2,8 +2,10 @@
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Stepper, { Step } from "@/components/ui/Stepper"; import Stepper, { Step } from "@/components/ui/Stepper";
import { useCreateCms } from "@/hooks/queries/useCmsQueries";
import { BreadcrumbItem } from "@/types/shared"; import { BreadcrumbItem } from "@/types/shared";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import BenefitsStep from "./_components/BenefitsStep";
import ChallengesStep from "./_components/ChallengesStep"; import ChallengesStep from "./_components/ChallengesStep";
import ChartStep from "./_components/ChartStep"; import ChartStep from "./_components/ChartStep";
import ContactsStep from "./_components/ContactsStep"; import ContactsStep from "./_components/ContactsStep";
@@ -20,6 +22,9 @@ const Marketing = () => {
const benefitsRef = useRef<any>(null); const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null); const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null); const footerRef = useRef<any>(null);
const { mutate: createCms, isPending } = useCreateCms();
const breadcrumbItems: BreadcrumbItem[] = [ const breadcrumbItems: BreadcrumbItem[] = [
{ {
href: "/", href: "/",
@@ -35,63 +40,104 @@ const Marketing = () => {
}, },
]; ];
const collectStepData = async (stepIndex: number) => { const collectAllStepsData = async () => {
let stepData = {}; const refs = [
const refs = [heroRef, chartRef, featuresRef, challengesRef, benefitsRef, contactsRef, footerRef]; heroRef,
chartRef,
const currentRef = refs[stepIndex]; featuresRef,
if (currentRef?.current?.collectData) { challengesRef,
stepData = await currentRef.current.collectData(); benefitsRef,
contactsRef,
// Submit data to backend footerRef,
if (stepData) { ];
await submitData(stepData, stepIndex);
const allData: any = {
hero: {},
chart: {},
features: {},
challenges: {},
benefits: {},
contacts: {},
footer: {},
};
const keys = [
"hero",
"chart",
"features",
"challenges",
"benefits",
"contacts",
"footer",
];
for (let i = 0; i < refs.length; i++) {
const ref = refs[i];
if (ref?.current?.getData) {
try {
const data = await ref.current.getData();
allData[keys[i]] = data;
} catch (error) {
console.error(`Error collecting data from step ${i}:`, error);
}
} }
} }
return allData;
}; };
const submitData = async (data: any, stepIndex: number) => { const handleSubmitAll = async () => {
const apiUrl = "/api/marketing-data"; const allData = await collectAllStepsData();
console.log(`Submitting data for step ${stepIndex}:`, data); console.log("All collected data:", allData);
createCms(allData, {
onSuccess: () => {
console.log("CMS created successfully");
},
onError: (error) => {
console.error("Error creating CMS:", error);
},
});
}; };
const steps: Step[] = [ const steps: Step[] = [
{ {
title: "Hero", title: "Hero",
description: "Create the hero section", description: "Create the hero section",
content: <HeroStep />, content: <HeroStep ref={heroRef} />,
}, },
{ {
title: "Chart", title: "Chart",
description: "Create the chart", description: "Create the chart",
content: <ChartStep />, content: <ChartStep ref={chartRef} />,
}, },
{ {
title: "Features", title: "Features",
description: "Add features", description: "Add features",
content: <FeaturesStep />, content: <FeaturesStep ref={featuresRef} />,
}, },
{ {
title: "Challenges", title: "Challenges",
description: "Create challenges section", description: "Create challenges section",
content: <ChallengesStep />, content: <ChallengesStep ref={challengesRef} />,
}, },
{ {
title: "Benefits", title: "Benefits",
description: "create benefits section", description: "create benefits section",
content: <ChallengesStep />, content: <BenefitsStep ref={benefitsRef} />,
}, },
{ {
title: "Contacts", title: "Contacts",
description: "Create contacts section", description: "Create contacts section",
content: <ContactsStep />, content: <ContactsStep ref={contactsRef} />,
}, },
{ {
title: "Footer", title: "Footer",
description: "Add footer information", description: "Add footer information",
content: <FooterStep />, content: <FooterStep ref={footerRef} />,
}, },
]; ];
return ( return (
<> <>
<Breadcrumb items={breadcrumbItems} /> <Breadcrumb items={breadcrumbItems} />
@@ -99,11 +145,18 @@ const Marketing = () => {
<Stepper <Stepper
steps={steps} steps={steps}
currentStep={step} currentStep={step}
onStepChange={(newStep) => { onStepChange={setStep}
collectStepData(step); />
setStep(newStep); <div className="mt-6 flex justify-end gap-4">
}} <button
></Stepper> type="button"
onClick={handleSubmitAll}
disabled={isPending}
className="rounded-lg bg-primary px-6 py-3 font-medium text-white hover:bg-opacity-90 disabled:opacity-50"
>
{isPending ? "Submitting..." : "Submit All"}
</button>
</div>
</ShowcaseSection> </ShowcaseSection>
</> </>
); );