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:
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user