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";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep";
import useBenefitsStepPresenter, {
IBenefitsStepFields,
} from "./useBenefitsStepPresenter";
const BenefitsStep = () => {
const BenefitsStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
@@ -14,7 +15,13 @@ const BenefitsStep = () => {
append,
remove,
control,
getValues,
} = useBenefitsStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<SectionStep<IBenefitsStepFields>
title="Benefits"
@@ -30,6 +37,8 @@ const BenefitsStep = () => {
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
};
});
BenefitsStep.displayName = "BenefitsStep";
export default BenefitsStep;
@@ -1,10 +1,11 @@
"use client";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep";
import useChallengesStepPresenter, {
IChallengesStepFields,
} from "./useChallengesStepPresenter";
const ChallengesStep = () => {
const ChallengesStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
@@ -14,7 +15,13 @@ const ChallengesStep = () => {
append,
remove,
control,
getValues,
} = useChallengesStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<SectionStep<IChallengesStepFields>
title="Challenges"
@@ -30,11 +37,8 @@ const ChallengesStep = () => {
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
};
});
ChallengesStep.displayName = "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 { TrashIcon } from "@/assets/icons";
import FormFooter from "@/components/ui/FormFooter";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { ChartPreview } from "./ChartPreview";
import useChartStepPresenter from "./useChartStepPresenter";
const ChartStep = () => {
const ChartStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
@@ -18,8 +18,13 @@ const ChartStep = () => {
append,
remove,
watch,
getValues,
} = useChartStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const chartTitle = watch("chart_title");
const chartPoints = watch("chart_points");
@@ -122,11 +127,8 @@ const ChartStep = () => {
<div className="flex flex-col gap-9"></div>
</form>
);
};
});
ChartStep.displayName = "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 { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useContactsStepPresenter from "./useContactsStepPresenter";
const ContactsStep = () => {
const { errors, handleSubmit, onSubmit, register, fields } =
const ContactsStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, fields, getValues } =
useContactsStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<form
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>
</form>
);
};
});
ContactsStep.displayName = "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";
import { forwardRef, useImperativeHandle } from "react";
import SectionStep from "./SectionStep";
import useFeaturesStepPresenter, {
IFeaturesStepFields,
} from "./useFeaturesStepPresenter";
const FeaturesStep = () => {
const FeaturesStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
@@ -14,7 +15,13 @@ const FeaturesStep = () => {
append,
remove,
control,
getValues,
} = useFeaturesStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<SectionStep<IFeaturesStepFields>
title="Features"
@@ -30,11 +37,8 @@ const FeaturesStep = () => {
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
};
});
FeaturesStep.displayName = "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 { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useFooterStepPresenter from "./useFooterStepPresenter";
const FooterStep = () => {
const { errors, handleSubmit, onSubmit, register } =
const FooterStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useFooterStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<form
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>
</form>
);
};
});
FooterStep.displayName = "FooterStep";
export default FooterStep;
FooterStep.collectData = async () => {
const { getValues } = useFooterStepPresenter();
return getValues();
}
@@ -3,10 +3,16 @@ 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 { forwardRef, useImperativeHandle } from "react";
import useHeroStepPresenter from "./useHeroStepPresenter";
const HeroStep = () => {
const { errors, handleSubmit, onSubmit, register } = useHeroStepPresenter();
const HeroStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } = useHeroStepPresenter();
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<form
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>
</form>
);
};
});
HeroStep.collectData = async () => {
const { getValues } = useHeroStepPresenter();
return getValues();
}
HeroStep.displayName = "HeroStep";
export default HeroStep;
@@ -18,6 +18,7 @@ const useBenefitsStepPresenter = () => {
control,
watch,
formState: { errors },
getValues,
} = useForm<IBenefitsStepFields>({
defaultValues: {
benefits: [{ icon: "", title: "", description: "" }],
@@ -33,6 +34,10 @@ const useBenefitsStepPresenter = () => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
return {
register,
handleSubmit,
@@ -43,6 +48,8 @@ const useBenefitsStepPresenter = () => {
remove,
control,
watch,
getValues,
collectData,
};
};
+77 -24
View File
@@ -2,8 +2,10 @@
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Stepper, { Step } from "@/components/ui/Stepper";
import { useCreateCms } from "@/hooks/queries/useCmsQueries";
import { BreadcrumbItem } from "@/types/shared";
import { useRef, useState } from "react";
import BenefitsStep from "./_components/BenefitsStep";
import ChallengesStep from "./_components/ChallengesStep";
import ChartStep from "./_components/ChartStep";
import ContactsStep from "./_components/ContactsStep";
@@ -20,6 +22,9 @@ const Marketing = () => {
const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null);
const { mutate: createCms, isPending } = useCreateCms();
const breadcrumbItems: BreadcrumbItem[] = [
{
href: "/",
@@ -35,63 +40,104 @@ const Marketing = () => {
},
];
const collectStepData = async (stepIndex: number) => {
let stepData = {};
const refs = [heroRef, chartRef, featuresRef, challengesRef, benefitsRef, contactsRef, footerRef];
const collectAllStepsData = async () => {
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const currentRef = refs[stepIndex];
if (currentRef?.current?.collectData) {
stepData = await currentRef.current.collectData();
const allData: any = {
hero: {},
chart: {},
features: {},
challenges: {},
benefits: {},
contacts: {},
footer: {},
};
// Submit data to backend
if (stepData) {
await submitData(stepData, stepIndex);
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 apiUrl = "/api/marketing-data";
console.log(`Submitting data for step ${stepIndex}:`, data);
const handleSubmitAll = async () => {
const allData = await collectAllStepsData();
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[] = [
{
title: "Hero",
description: "Create the hero section",
content: <HeroStep />,
content: <HeroStep ref={heroRef} />,
},
{
title: "Chart",
description: "Create the chart",
content: <ChartStep />,
content: <ChartStep ref={chartRef} />,
},
{
title: "Features",
description: "Add features",
content: <FeaturesStep />,
content: <FeaturesStep ref={featuresRef} />,
},
{
title: "Challenges",
description: "Create challenges section",
content: <ChallengesStep />,
content: <ChallengesStep ref={challengesRef} />,
},
{
title: "Benefits",
description: "create benefits section",
content: <ChallengesStep />,
content: <BenefitsStep ref={benefitsRef} />,
},
{
title: "Contacts",
description: "Create contacts section",
content: <ContactsStep />,
content: <ContactsStep ref={contactsRef} />,
},
{
title: "Footer",
description: "Add footer information",
content: <FooterStep />,
content: <FooterStep ref={footerRef} />,
},
];
return (
<>
<Breadcrumb items={breadcrumbItems} />
@@ -99,11 +145,18 @@ const Marketing = () => {
<Stepper
steps={steps}
currentStep={step}
onStepChange={(newStep) => {
collectStepData(step);
setStep(newStep);
}}
></Stepper>
onStepChange={setStep}
/>
<div className="mt-6 flex justify-end gap-4">
<button
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>
</>
);