refactor(marketing): Standardize step components and data structure

- Rename field names across step components for consistency
- Update chart step to use more generic data structure
- Modify input field names to follow consistent naming convention
- Refactor chart step to use key-value pairs instead of x-y coordinates
- Update form field names in multiple marketing wizard steps
- Improve type consistency and naming across marketing wizard components
This commit is contained in:
AmirReza Jamali
2025-11-09 16:04:52 +03:30
parent 574a3a20ce
commit 4f7b258533
18 changed files with 283 additions and 159 deletions
+2
View File
@@ -0,0 +1,2 @@
{
}
@@ -33,7 +33,7 @@ const BenefitsStep = forwardRef((props, ref) => {
append={append}
remove={remove}
control={control}
fieldName="benefits"
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
@@ -33,7 +33,7 @@ const ChallengesStep = forwardRef((props, ref) => {
append={append}
remove={remove}
control={control}
fieldName="challenges"
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
@@ -25,8 +25,8 @@ const ChartStep = forwardRef((props, ref) => {
getData: () => getValues(),
}));
const chartTitle = watch("chart_title");
const chartPoints = watch("chart_points");
const chartTitle = watch("title");
const chartData = watch("data");
return (
<form
@@ -39,40 +39,43 @@ const ChartStep = forwardRef((props, ref) => {
className="flex flex-col gap-5"
>
<InputGroup
{...register("chart_title", {
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Chart Title"
name="chart_title"
name="title"
required
type="text"
placeholder="Enter Chart Title"
/>
{errors.chart_title && (
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.chart_title.message}
{errors.title.message}
</p>
)}
<InputGroup
{...register("lost_amount", {
{...register("missedAmount", {
required: { message: FormErrorMessages.required, value: true },
valueAsNumber: true,
})}
label="Lost Amount"
name="lost_amount"
label="Missed Amount"
name="missedAmount"
required
type="number"
placeholder="Enter Lost Amount"
type="text"
placeholder="Enter Missed Amount"
/>
{errors.lost_amount && (
{errors.missedAmount && (
<p className="mt-1 text-sm text-red-500">
{errors.lost_amount.message}
{errors.missedAmount.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Chart Preview">
<ChartPreview
series={chartPoints.filter((p) => p.x && p.y)}
series={
chartData
?.filter((p) => p.key && p.value)
.map((p) => ({ x: p.key, y: p.value })) || []
}
title={chartTitle}
/>
</ShowcaseSection>
@@ -80,31 +83,31 @@ const ChartStep = forwardRef((props, ref) => {
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-4">
<InputGroup
{...register(`chart_points.${index}.x`, {
{...register(`data.${index}.key`, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="X-axis"
name={`chart_points.${index}.x`}
label="Key"
name={`data.${index}.key`}
required
type="text"
placeholder="Enter X-axis value"
placeholder="Enter key"
/>
<InputGroup
{...register(`chart_points.${index}.y`, {
{...register(`data.${index}.value`, {
required: {
message: FormErrorMessages.required,
value: true,
},
valueAsNumber: true,
})}
label="Y-axis"
name={`chart_points.${index}.y`}
label="Value"
name={`data.${index}.value`}
required
type="number"
placeholder="Enter Y-axis value"
placeholder="Enter value"
/>
<button
type="button"
@@ -118,7 +121,7 @@ const ChartStep = forwardRef((props, ref) => {
<button
type="button"
className="mt-6 flex w-fit items-center justify-center rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90"
onClick={() => append({ x: "", y: 0 })}
onClick={() => append({ key: "", value: 0 })}
>
Add point
</button>
@@ -44,11 +44,11 @@ const ContactsStep = forwardRef((props, ref) => {
placeholder="Enter Description"
/>
<InputGroup
{...register("submit_button_title")}
label="Submit Button Title"
name="submit_button_title"
{...register("submitButtonText")}
label="Submit Button Text"
name="submitButtonText"
type="text"
placeholder="Enter Submit Button Title"
placeholder="Enter Submit Button Text"
/>
</div>
</ShowcaseSection>
@@ -59,9 +59,9 @@ const ContactsStep = forwardRef((props, ref) => {
className="grid grid-cols-1 items-center gap-4 sm:grid-cols-2"
>
<InputGroup
{...register(`fields.${index}.field_name`)}
{...register(`fields.${index}.name`)}
label="Field Name"
name={`fields.${index}.field_name`}
name={`fields.${index}.name`}
type="text"
placeholder="Field Name"
disabled
@@ -33,7 +33,7 @@ const FeaturesStep = forwardRef((props, ref) => {
append={append}
remove={remove}
control={control}
fieldName="features"
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
@@ -41,18 +41,18 @@ const FooterStep = forwardRef((props, ref) => {
</p>
)}
<InputGroup
{...register("phoneNumber")}
{...register("phone")}
label="Phone Number"
name="phoneNumber"
name="phone"
type="text"
placeholder="Enter Phone Number"
/>
<InputGroup
{...register("address")}
label="Address"
name="address"
{...register("location")}
label="Location"
name="location"
type="text"
placeholder="Enter Address"
placeholder="Enter Location"
/>
<TextAreaGroup
label="Tagline"
@@ -23,27 +23,6 @@ const HeroStep = forwardRef((props, ref) => {
title="Hero Information"
className="flex flex-col gap-5"
>
<InputGroup
{...register("key", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
message: FormErrorMessages.minLength(2),
},
maxLength: {
value: 200,
message: FormErrorMessages.maxLength(200),
},
})}
label="Key"
name="key"
required
type="text"
placeholder="Enter Key"
/>
{errors.key && (
<p className="mt-1 text-sm text-red-500">{errors.key.message}</p>
)}
<InputGroup
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
@@ -86,7 +65,7 @@ const HeroStep = forwardRef((props, ref) => {
</p>
)}
<InputGroup
{...register("button_text", {
{...register("buttonText", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
@@ -98,18 +77,18 @@ const HeroStep = forwardRef((props, ref) => {
},
})}
label="Button Text"
name="button_text"
name="buttonText"
required
type="text"
placeholder="Enter Button Text"
/>
{errors.button_text && (
{errors.buttonText && (
<p className="mt-1 text-sm text-red-500">
{errors.button_text.message}
{errors.buttonText.message}
</p>
)}
<InputGroup
{...register("button_link", {
{...register("buttonLink", {
required: { message: FormErrorMessages.required, value: true },
minLength: {
value: 2,
@@ -121,14 +100,29 @@ const HeroStep = forwardRef((props, ref) => {
},
})}
label="Button Link"
name="button_link"
name="buttonLink"
required
type="url"
placeholder="Enter Button Link"
/>
{errors.button_link && (
{errors.buttonLink && (
<p className="mt-1 text-sm text-red-500">
{errors.button_link.message}
{errors.buttonLink.message}
</p>
)}
<InputGroup
{...register("gifFile", {
required: { message: FormErrorMessages.required, value: true },
})}
label="GIF File URL"
name="gifFile"
required
type="text"
placeholder="Enter GIF File URL"
/>
{errors.gifFile && (
<p className="mt-1 text-sm text-red-500">
{errors.gifFile.message}
</p>
)}
</ShowcaseSection>
@@ -5,14 +5,14 @@ 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,
Control,
FieldErrors,
FieldValues,
Path,
UseFieldArrayAppend,
UseFieldArrayRemove,
UseFormHandleSubmit,
UseFormRegister,
} from "react-hook-form";
interface IProps<T extends FieldValues> {
@@ -52,24 +52,24 @@ const SectionStep = <T extends FieldValues>({
className="flex flex-col gap-5"
>
<InputGroup
{...register("section_title" as Path<T>, {
{...register("title" as Path<T>, {
required: { message: FormErrorMessages.required, value: true },
})}
label="Section Title"
name="section_title"
name="title"
required
type="text"
placeholder="Enter Section Title"
/>
{errors.section_title && (
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.section_title.message as string}
{errors.title.message as string}
</p>
)}
<TextAreaGroup
label="Section Description"
{...register("section_description" as Path<T>)}
name="section_description"
{...register("description" as Path<T>)}
name="description"
placeholder="Enter Section Description"
/>
</ShowcaseSection>
@@ -80,35 +80,35 @@ const SectionStep = <T extends FieldValues>({
className="grid grid-cols-1 gap-4 sm:grid-cols-2"
>
<InputGroup
{...register(`${fieldName}.${index}.icon` as Path<T>, {
{...register(`cards.${index}.icon` as Path<T>, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="Icon"
name={`${fieldName}.${index}.icon`}
name={`cards.${index}.icon`}
required
type="text"
placeholder="Enter Icon URL or name"
/>
<InputGroup
{...register(`${fieldName}.${index}.title` as Path<T>, {
{...register(`cards.${index}.title` as Path<T>, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="Title"
name={`${fieldName}.${index}.title`}
name={`cards.${index}.title`}
required
type="text"
placeholder="Enter Title"
/>
<TextAreaGroup
{...register(`${fieldName}.${index}.description` as Path<T>)}
{...register(`cards.${index}.description` as Path<T>)}
label="Description"
name={`${fieldName}.${index}.description`}
name={`cards.${index}.description`}
placeholder="Enter Description"
className="sm:col-span-2"
/>
@@ -2,9 +2,9 @@
import { useFieldArray, useForm } from "react-hook-form";
export interface IBenefitsStepFields {
section_title: string;
section_description: string;
benefits: {
title: string;
description: string;
cards: {
icon: string;
title: string;
description: string;
@@ -21,13 +21,13 @@ const useBenefitsStepPresenter = () => {
getValues,
} = useForm<IBenefitsStepFields>({
defaultValues: {
benefits: [{ icon: "", title: "", description: "" }],
cards: [{ icon: "", title: "", description: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "benefits",
name: "cards",
});
const onSubmit = (data: IBenefitsStepFields) => {
@@ -2,9 +2,9 @@
import { useFieldArray, useForm } from "react-hook-form";
export interface IChallengesStepFields {
section_title: string;
section_description: string;
challenges: {
title: string;
description: string;
cards: {
icon: string;
title: string;
description: string;
@@ -18,16 +18,16 @@ const useChallengesStepPresenter = () => {
control,
watch,
formState: { errors },
getValues
getValues,
} = useForm<IChallengesStepFields>({
defaultValues: {
challenges: [{ icon: "", title: "", description: "" }],
cards: [{ icon: "", title: "", description: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "challenges",
name: "cards",
});
const onSubmit = (data: IChallengesStepFields) => {
@@ -49,8 +49,8 @@ const useChallengesStepPresenter = () => {
control,
watch,
getValues,
collectData
};
collectData,
};
};
export default useChallengesStepPresenter;
@@ -2,9 +2,9 @@
import { useFieldArray, useForm } from "react-hook-form";
export interface IChartStepFields {
chart_title: string;
lost_amount: number;
chart_points: { x: string; y: number }[];
title: string;
missedAmount: string;
data: { key: string; value: number }[];
}
const useChartStepPresenter = () => {
@@ -14,16 +14,16 @@ const useChartStepPresenter = () => {
control,
watch,
formState: { errors },
getValues
getValues,
} = useForm<IChartStepFields>({
defaultValues: {
chart_points: [{ x: "", y: 0 }],
data: [{ key: "", value: 0 }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "chart_points",
name: "data",
});
const onSubmit = (data: IChartStepFields) => {
@@ -45,7 +45,7 @@ const useChartStepPresenter = () => {
control,
watch,
getValues,
collectData
collectData,
};
};
@@ -4,9 +4,9 @@ import { useFieldArray, useForm } from "react-hook-form";
export interface IContactsStepFields {
title: string;
description: string;
submit_button_title: string;
submitButtonText: string;
fields: {
field_name: string;
name: string;
label: string;
placeholder: string;
required: boolean;
@@ -19,25 +19,24 @@ const useContactsStepPresenter = () => {
handleSubmit,
control,
getValues,
formState: { errors },
} = useForm<IContactsStepFields>({
defaultValues: {
fields: [
{
field_name: "first_name",
name: "first_name",
label: "",
placeholder: "",
required: false,
},
{
field_name: "last_name",
name: "last_name",
label: "",
placeholder: "",
required: false,
},
{ field_name: "email", label: "", placeholder: "", required: false },
{ field_name: "phone", label: "", placeholder: "", required: false },
{ name: "email", label: "", placeholder: "", required: false },
{ name: "phone", label: "", placeholder: "", required: false },
],
},
});
@@ -2,9 +2,9 @@
import { useFieldArray, useForm } from "react-hook-form";
export interface IFeaturesStepFields {
section_title: string;
section_description: string;
features: {
title: string;
description: string;
cards: {
icon: string;
title: string;
description: string;
@@ -18,16 +18,16 @@ const useFeaturesStepPresenter = () => {
control,
watch,
formState: { errors },
getValues
getValues,
} = useForm<IFeaturesStepFields>({
defaultValues: {
features: [{ icon: "", title: "", description: "" }],
cards: [{ icon: "", title: "", description: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "features",
name: "cards",
});
const onSubmit = (data: IFeaturesStepFields) => {
@@ -49,8 +49,8 @@ const useFeaturesStepPresenter = () => {
control,
watch,
getValues,
collectData
};
collectData,
};
};
export default useFeaturesStepPresenter;
@@ -2,10 +2,10 @@ import { useForm } from "react-hook-form";
interface FooterFormValues {
email: string;
phoneNumber?: string;
address?: string;
tagline?: string;
copyright?: string;
phone: string;
location: string;
tagline: string;
copyright: string;
}
const useFooterStepPresenter = () => {
@@ -13,7 +13,7 @@ const useFooterStepPresenter = () => {
register,
handleSubmit,
formState: { errors },
getValues
getValues,
} = useForm<FooterFormValues>();
const onSubmit = (data: FooterFormValues) => {
@@ -29,9 +29,9 @@ const useFooterStepPresenter = () => {
handleSubmit,
onSubmit,
errors,
collectData,
getValues,
};
collectData,
getValues,
};
};
export default useFooterStepPresenter;
@@ -2,11 +2,11 @@
import { useForm } from "react-hook-form";
export interface IHeroStepFields {
key: string;
title: string;
description: string;
button_text: string;
button_link: string;
buttonText: string;
buttonLink: string;
gifFile: string;
}
const useHeroStepPresenter = () => {
@@ -30,9 +30,4 @@ const useHeroStepPresenter = () => {
};
};
(useHeroStepPresenter as any).collectData = async () => {
const { getValues } = useForm<IHeroStepFields>();
return getValues();
};
export default useHeroStepPresenter;
+80 -20
View File
@@ -15,6 +15,7 @@ import HeroStep from "./_components/HeroStep";
const Marketing = () => {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState<any>({});
const heroRef = useRef<any>(null);
const chartRef = useRef<any>(null);
const featuresRef = useRef<any>(null);
@@ -40,7 +41,7 @@ const Marketing = () => {
},
];
const collectAllStepsData = async () => {
const saveCurrentStepData = async () => {
const refs = [
heroRef,
chartRef,
@@ -51,26 +52,57 @@ const Marketing = () => {
footerRef,
];
const allData: any = {
hero: {},
chart: {},
features: {},
challenges: {},
benefits: {},
contacts: {},
footer: {},
};
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"footer",
];
const currentRef = refs[step];
if (currentRef?.current?.getData) {
try {
const data = await currentRef.current.getData();
setFormData((prev: any) => ({
...prev,
[keys[step]]: data,
}));
} catch (error) {
console.error(`Error collecting data from step ${step}:`, error);
}
}
};
const collectAllStepsData = async () => {
// First save current step data
await saveCurrentStepData();
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const allData: any = { ...formData };
const keys = [
"hero",
"chart",
"features",
"challenges",
"benefits",
"contacts",
"advantages",
"contact",
"footer",
];
// Collect any remaining data from refs
for (let i = 0; i < refs.length; i++) {
const ref = refs[i];
if (ref?.current?.getData) {
@@ -104,48 +136,76 @@ const Marketing = () => {
{
title: "Hero",
description: "Create the hero section",
content: <HeroStep ref={heroRef} />,
content: <></>,
},
{
title: "Chart",
description: "Create the chart",
content: <ChartStep ref={chartRef} />,
content: <></>,
},
{
title: "Features",
description: "Add features",
content: <FeaturesStep ref={featuresRef} />,
content: <></>,
},
{
title: "Challenges",
description: "Create challenges section",
content: <ChallengesStep ref={challengesRef} />,
content: <></>,
},
{
title: "Benefits",
description: "create benefits section",
content: <BenefitsStep ref={benefitsRef} />,
content: <></>,
},
{
title: "Contacts",
description: "Create contacts section",
content: <ContactsStep ref={contactsRef} />,
content: <></>,
},
{
title: "Footer",
description: "Add footer information",
content: <FooterStep ref={footerRef} />,
content: <></>,
},
];
const handleStepChange = async (newStep: number) => {
await saveCurrentStepData();
setStep(newStep);
};
return (
<>
<Breadcrumb items={breadcrumbItems} />
<ShowcaseSection title="Create Marketing page">
{/* Render all steps but only show the current one */}
<div style={{ display: step === 0 ? "block" : "none" }}>
<HeroStep ref={heroRef} />
</div>
<div style={{ display: step === 1 ? "block" : "none" }}>
<ChartStep ref={chartRef} />
</div>
<div style={{ display: step === 2 ? "block" : "none" }}>
<FeaturesStep ref={featuresRef} />
</div>
<div style={{ display: step === 3 ? "block" : "none" }}>
<ChallengesStep ref={challengesRef} />
</div>
<div style={{ display: step === 4 ? "block" : "none" }}>
<BenefitsStep ref={benefitsRef} />
</div>
<div style={{ display: step === 5 ? "block" : "none" }}>
<ContactsStep ref={contactsRef} />
</div>
<div style={{ display: step === 6 ? "block" : "none" }}>
<FooterStep ref={footerRef} />
</div>
<Stepper
steps={steps}
currentStep={step}
onStepChange={setStep}
onStepChange={handleStepChange}
/>
<div className="mt-6 flex justify-end gap-4">
<button
+71
View File
@@ -1,5 +1,75 @@
import { z } from "zod";
export const CmsCredentials = z.object({ advantages: z.object({
cards: z.array(
z.object({
description: z.string(),
icon: z.string(),
title: z.string(),
}),
),
description: z.string(),
title: z.string(),
}),
challenges: z.object({
cards: z.array(
z.object({
description: z.string(),
icon: z.string(),
title: z.string(),
}),
),
description: z.string(),
title: z.string(),
}),
chart: z.object({
data: z.array(
z.object({
key: z.string(),
value: z.number(),
}),
),
missedAmount: z.string(),
title: z.string(),
}),
contact: z.object({
description: z.string(),
fields: z.array(
z.object({
label: z.string(),
name: z.string(),
placeholder: z.string(),
required: z.boolean(),
}),
),
submitButtonText: z.string(),
title: z.string(),
}),
features: z.object({
cards: z.array(
z.object({
description: z.string(),
icon: z.string(),
title: z.string(),
}),
),
description: z.string(),
title: z.string(),
}),
footer: z.object({
copyright: z.string(),
email: z.string(),
location: z.string(),
phone: z.string(),
tagline: z.string(),
}),
hero: z.object({
buttonLink: z.string(),
buttonText: z.string(),
description: z.string(),
gifFile: z.string(),
title: z.string(),
})})
const CmsSchema = z.object({
cms: z.array(
z.object({
@@ -82,3 +152,4 @@ const CmsSchema = z.object({
),
});
export type TCmsResponse = z.infer<typeof CmsSchema>;
export type TCmsCredentials = z.infer<typeof CmsCredentials>