refactor(marketing): Standardize marketing wizard steps with initial data support

- Add support for initial data in all marketing wizard step components
- Update step presenters to accept and handle initial data
- Modify forwardRef implementations to include initialData prop
- Add TypeScript interfaces for step component props
- Improve type safety and data handling across marketing wizard steps
- Prepare components for edit and create workflows with consistent data management
This commit is contained in:
AmirReza Jamali
2025-11-11 10:43:32 +03:30
parent b444f6a08f
commit bd79b7ed14
19 changed files with 842 additions and 688 deletions
@@ -5,7 +5,11 @@ import useBenefitsStepPresenter, {
IBenefitsStepFields,
} from "./useBenefitsStepPresenter";
const BenefitsStep = forwardRef((props, ref) => {
interface BenefitsStepProps {
initialData?: IBenefitsStepFields;
}
const BenefitsStep = forwardRef<unknown, BenefitsStepProps>(({ initialData }, ref) => {
const {
errors,
handleSubmit,
@@ -16,7 +20,7 @@ const BenefitsStep = forwardRef((props, ref) => {
remove,
control,
getValues,
} = useBenefitsStepPresenter();
} = useBenefitsStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
@@ -5,39 +5,45 @@ import useChallengesStepPresenter, {
IChallengesStepFields,
} from "./useChallengesStepPresenter";
const ChallengesStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
control,
getValues,
} = useChallengesStepPresenter();
interface ChallengesStepProps {
initialData?: IChallengesStepFields;
}
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const ChallengesStep = forwardRef<unknown, ChallengesStepProps>(
({ initialData }, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
control,
getValues,
} = useChallengesStepPresenter(initialData);
return (
<SectionStep<IChallengesStepFields>
title="Challenges"
fields={fields}
handleSubmit={handleSubmit}
onSubmit={onSubmit}
register={register}
errors={errors}
append={append}
remove={remove}
control={control}
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
});
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<SectionStep<IChallengesStepFields>
title="Challenges"
fields={fields}
handleSubmit={handleSubmit}
onSubmit={onSubmit}
register={register}
errors={errors}
append={append}
remove={remove}
control={control}
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
},
);
ChallengesStep.displayName = "ChallengesStep";
+125 -119
View File
@@ -8,129 +8,135 @@ import { forwardRef, useImperativeHandle } from "react";
import { ChartPreview } from "./ChartPreview";
import useChartStepPresenter from "./useChartStepPresenter";
const ChartStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
watch,
getValues,
} = useChartStepPresenter();
interface ChartStepProps {
initialData?: any;
}
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const ChartStep = forwardRef<unknown, ChartStepProps>(
({ initialData }, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
watch,
getValues,
} = useChartStepPresenter(initialData);
const chartTitle = watch("title");
const chartData = watch("data");
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"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-9">
<ShowcaseSection
title="Chart Information"
className="flex flex-col gap-5"
>
<InputGroup
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Chart Title"
name="title"
required
type="text"
placeholder="Enter Chart Title"
/>
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.title.message}
</p>
)}
<InputGroup
{...register("missedAmount", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Missed Amount"
name="missedAmount"
required
type="text"
placeholder="Enter Missed Amount"
/>
{errors.missedAmount && (
<p className="mt-1 text-sm text-red-500">
{errors.missedAmount.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Chart Preview">
<ChartPreview
series={
chartData
?.filter((p) => p.key && p.value)
.map((p) => ({ x: p.key, y: p.value })) || []
}
title={chartTitle}
/>
</ShowcaseSection>
<ShowcaseSection title="Chart Points" className="flex flex-col gap-5">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-4">
<InputGroup
{...register(`data.${index}.key`, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="Key"
name={`data.${index}.key`}
required
type="text"
placeholder="Enter key"
/>
<InputGroup
{...register(`data.${index}.value`, {
required: {
message: FormErrorMessages.required,
value: true,
},
valueAsNumber: true,
})}
label="Value"
name={`data.${index}.value`}
required
type="number"
placeholder="Enter value"
/>
<button
type="button"
onClick={() => remove(index)}
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium text-white hover:bg-opacity-90"
>
<TrashIcon />
</button>
</div>
))}
<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({ key: "", value: 0 })}
const chartTitle = watch("title");
const chartData = watch("data");
return (
<form
className="items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7 sm:grid-cols-2"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-9">
<ShowcaseSection
title="Chart Information"
className="flex flex-col gap-5"
>
Add point
</button>
</ShowcaseSection>
</div>
<div className="flex flex-col gap-9"></div>
</form>
);
});
<InputGroup
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Chart Title"
name="title"
required
type="text"
placeholder="Enter Chart Title"
/>
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.title.message}
</p>
)}
<InputGroup
{...register("missedAmount", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Missed Amount"
name="missedAmount"
required
type="text"
placeholder="Enter Missed Amount"
/>
{errors.missedAmount && (
<p className="mt-1 text-sm text-red-500">
{errors.missedAmount.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Chart Preview">
<ChartPreview
series={
chartData
?.filter((p) => p.key && p.value)
.map((p) => ({ x: p.key, y: p.value })) || []
}
title={chartTitle}
/>
</ShowcaseSection>
<ShowcaseSection title="Chart Points" className="flex flex-col gap-5">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-4">
<InputGroup
{...register(`data.${index}.key`, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="Key"
name={`data.${index}.key`}
required
type="text"
placeholder="Enter key"
/>
<InputGroup
{...register(`data.${index}.value`, {
required: {
message: FormErrorMessages.required,
value: true,
},
valueAsNumber: true,
})}
label="Value"
name={`data.${index}.value`}
required
type="number"
placeholder="Enter value"
/>
<button
type="button"
onClick={() => remove(index)}
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium text-white hover:bg-opacity-90"
>
<TrashIcon />
</button>
</div>
))}
<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({ key: "", value: 0 })}
>
Add point
</button>
</ShowcaseSection>
</div>
<div className="flex flex-col gap-9"></div>
</form>
);
},
);
ChartStep.displayName = "ChartStep";
+87 -78
View File
@@ -7,93 +7,102 @@ import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useContactsStepPresenter from "./useContactsStepPresenter";
const ContactsStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, fields, getValues } =
useContactsStepPresenter();
interface ContactsStepProps {
initialData?: any;
}
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const ContactsStep = forwardRef<unknown, ContactsStepProps>(
({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, fields, getValues } =
useContactsStepPresenter(initialData);
return (
<form
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7"
onSubmit={handleSubmit(onSubmit)}
>
<ShowcaseSection title="Contacts Section" className="flex flex-col gap-5">
<div className="flex flex-col gap-9">
<InputGroup
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Title"
name="title"
required
type="text"
placeholder="Enter Title"
/>
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.title.message as string}
</p>
)}
<TextAreaGroup
label="Description"
{...register("description")}
name="description"
placeholder="Enter Description"
/>
<InputGroup
{...register("submitButtonText")}
label="Submit Button Text"
name="submitButtonText"
type="text"
placeholder="Enter Submit Button Text"
/>
</div>
</ShowcaseSection>
<ShowcaseSection title="Form Fields" className="flex flex-col gap-5">
{fields.map((field, index) => (
<div
key={field.id}
className="grid grid-cols-1 items-center gap-4 sm:grid-cols-2"
>
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"
onSubmit={handleSubmit(onSubmit)}
>
<ShowcaseSection
title="Contacts Section"
className="flex flex-col gap-5"
>
<div className="flex flex-col gap-9">
<InputGroup
{...register(`fields.${index}.name`)}
label="Field Name"
name={`fields.${index}.name`}
{...register("title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Title"
name="title"
required
type="text"
placeholder="Field Name"
disabled
placeholder="Enter Title"
/>
{errors.title && (
<p className="mt-1 text-sm text-red-500">
{errors.title.message as string}
</p>
)}
<TextAreaGroup
label="Description"
{...register("description")}
name="description"
placeholder="Enter Description"
/>
<InputGroup
{...register(`fields.${index}.label`)}
label="Label"
name={`fields.${index}.label`}
{...register("submitButtonText")}
label="Submit Button Text"
name="submitButtonText"
type="text"
placeholder="Enter Label"
/>
<InputGroup
{...register(`fields.${index}.placeholder`)}
label="Placeholder"
name={`fields.${index}.placeholder`}
type="text"
placeholder="Enter Placeholder"
/>
<Checkbox
{...register(`fields.${index}.required`)}
label="Required"
minimal
withIcon="check"
withBg
name={`fields.${index}.required`}
placeholder="Enter Submit Button Text"
/>
</div>
))}
</ShowcaseSection>
</form>
);
});
</ShowcaseSection>
<ShowcaseSection title="Form Fields" className="flex flex-col gap-5">
{fields.map((field, index) => (
<div
key={field.id}
className="grid grid-cols-1 items-center gap-4 sm:grid-cols-2"
>
<InputGroup
{...register(`fields.${index}.name`)}
label="Field Name"
name={`fields.${index}.name`}
type="text"
placeholder="Field Name"
disabled
/>
<InputGroup
{...register(`fields.${index}.label`)}
label="Label"
name={`fields.${index}.label`}
type="text"
placeholder="Enter Label"
/>
<InputGroup
{...register(`fields.${index}.placeholder`)}
label="Placeholder"
name={`fields.${index}.placeholder`}
type="text"
placeholder="Enter Placeholder"
/>
<Checkbox
{...register(`fields.${index}.required`)}
label="Required"
minimal
withIcon="check"
withBg
name={`fields.${index}.required`}
/>
</div>
))}
</ShowcaseSection>
</form>
);
},
);
ContactsStep.displayName = "ContactsStep";
+37 -31
View File
@@ -5,39 +5,45 @@ import useFeaturesStepPresenter, {
IFeaturesStepFields,
} from "./useFeaturesStepPresenter";
const FeaturesStep = forwardRef((props, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
control,
getValues,
} = useFeaturesStepPresenter();
interface FeaturesStepProps {
initialData?: IFeaturesStepFields;
}
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const FeaturesStep = forwardRef<unknown, FeaturesStepProps>(
({ initialData }, ref) => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
control,
getValues,
} = useFeaturesStepPresenter(initialData);
return (
<SectionStep<IFeaturesStepFields>
title="Features"
fields={fields}
handleSubmit={handleSubmit}
onSubmit={onSubmit}
register={register}
errors={errors}
append={append}
remove={remove}
control={control}
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
});
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
return (
<SectionStep<IFeaturesStepFields>
title="Features"
fields={fields}
handleSubmit={handleSubmit}
onSubmit={onSubmit}
register={register}
errors={errors}
append={append}
remove={remove}
control={control}
fieldName="cards"
defaultValues={{ icon: "", title: "", description: "" }}
/>
);
},
);
FeaturesStep.displayName = "FeaturesStep";
+71 -65
View File
@@ -6,73 +6,79 @@ import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useFooterStepPresenter from "./useFooterStepPresenter";
const FooterStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useFooterStepPresenter();
interface FooterStepProps {
initialData?: any;
}
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const FooterStep = forwardRef<unknown, FooterStepProps>(
({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useFooterStepPresenter(initialData);
return (
<form
className="grid grid-cols-1 items-start gap-9 rounded-xl bg-gray-1 p-10 dark:bg-gray-7"
onSubmit={handleSubmit(onSubmit)}
>
<ShowcaseSection title="Footer Section" className="flex flex-col gap-5">
<div className="flex flex-col gap-9">
<InputGroup
{...register("email", {
required: { message: FormErrorMessages.required, value: true },
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "Invalid email format",
},
})}
label="Email"
name="email"
required
type="email"
placeholder="Enter Email"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">
{errors.email.message as string}
</p>
)}
<InputGroup
{...register("phone")}
label="Phone Number"
name="phone"
type="text"
placeholder="Enter Phone Number"
/>
<InputGroup
{...register("location")}
label="Location"
name="location"
type="text"
placeholder="Enter Location"
/>
<TextAreaGroup
label="Tagline"
{...register("tagline")}
name="tagline"
placeholder="Enter Tagline"
/>
<InputGroup
{...register("copyright")}
label="Copyright"
name="copyright"
type="text"
placeholder="Enter Copyright Text"
/>
</div>
</ShowcaseSection>
</form>
);
});
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"
onSubmit={handleSubmit(onSubmit)}
>
<ShowcaseSection title="Footer Section" className="flex flex-col gap-5">
<div className="flex flex-col gap-9">
<InputGroup
{...register("email", {
required: { message: FormErrorMessages.required, value: true },
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "Invalid email format",
},
})}
label="Email"
name="email"
required
type="email"
placeholder="Enter Email"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">
{errors.email.message as string}
</p>
)}
<InputGroup
{...register("phone")}
label="Phone Number"
name="phone"
type="text"
placeholder="Enter Phone Number"
/>
<InputGroup
{...register("location")}
label="Location"
name="location"
type="text"
placeholder="Enter Location"
/>
<TextAreaGroup
label="Tagline"
{...register("tagline")}
name="tagline"
placeholder="Enter Tagline"
/>
<InputGroup
{...register("copyright")}
label="Copyright"
name="copyright"
type="text"
placeholder="Enter Copyright Text"
/>
</div>
</ShowcaseSection>
</form>
);
},
);
FooterStep.displayName = "FooterStep";
export default FooterStep;
export default FooterStep;
+12 -4
View File
@@ -7,8 +7,13 @@ import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import useHeroStepPresenter from "./useHeroStepPresenter";
const HeroStep = forwardRef((props, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } = useHeroStepPresenter();
interface HeroStepProps {
initialData?: any;
}
const HeroStep = forwardRef<unknown, HeroStepProps>(({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useHeroStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
@@ -16,7 +21,7 @@ const HeroStep = forwardRef((props, ref) => {
return (
<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"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-9">
@@ -114,7 +119,10 @@ const HeroStep = forwardRef((props, ref) => {
<InputGroup
{...register("gifFile", {
required: { message: FormErrorMessages.required, value: true },
pattern: {message: FormErrorMessages.invalidPattern, value: REGEX.URL}
pattern: {
message: FormErrorMessages.invalidPattern,
value: REGEX.URL,
},
})}
label="GIF File URL"
name="gifFile"
@@ -0,0 +1,371 @@
"use client";
import { GlobeIcon } from "@/assets/icons";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Stepper, { Step } from "@/components/ui/Stepper";
import { Countries } from "@/constants/countries";
import { FormErrorMessages } from "@/constants/Texts";
import { BreadcrumbItem } from "@/types/shared";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import BenefitsStep from "./BenefitsStep";
import ChallengesStep from "./ChallengesStep";
import ChartStep from "./ChartStep";
import ContactsStep from "./ContactsStep";
import FeaturesStep from "./FeaturesStep";
import FooterStep from "./FooterStep";
import HeroStep from "./HeroStep";
interface IPageTypeFields {
type: "company" | "organization";
language?: string;
company_name?: string;
country?: string;
key: string;
}
interface MarketingFormProps {
breadcrumbItems: BreadcrumbItem[];
title: string;
onSubmit: (data: any) => void;
isPending: boolean;
initialData?: any;
}
const MarketingForm = ({
breadcrumbItems,
title,
onSubmit,
isPending,
initialData,
}: MarketingFormProps) => {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState<any>(initialData || {});
const heroRef = useRef<any>(null);
const chartRef = useRef<any>(null);
const featuresRef = useRef<any>(null);
const challengesRef = useRef<any>(null);
const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null);
const {
register: registerPageType,
watch: watchPageType,
formState: { errors: pageTypeErrors },
getValues: getPageTypeValues,
setValue,
reset,
} = useForm<IPageTypeFields>({
defaultValues: {
type: "company",
},
});
useEffect(() => {
if (initialData) {
const formValues = {
type: initialData.type || "company",
language: initialData.language || "",
company_name: initialData.company_name || "",
country: initialData.country || "",
key: initialData.key || "",
};
reset(formValues);
setFormData(initialData);
}
}, [initialData, reset]);
const pageType = watchPageType("type");
const isCompany = pageType === "company";
const saveCurrentStepData = async () => {
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
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 () => {
await saveCurrentStepData();
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const allData: any = { ...formData };
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"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 handleSubmitAll = async () => {
const allData = await collectAllStepsData();
const pageTypeData = getPageTypeValues();
const finalData = {
...allData,
type: pageTypeData.type,
key: pageTypeData.key,
...(pageTypeData.type === "company" && {
language: pageTypeData.language,
company_name: pageTypeData.company_name,
country: pageTypeData.country,
}),
};
onSubmit(finalData);
};
const steps: Step[] = [
{
title: "Hero",
description: "Create the hero section",
content: <></>,
},
{
title: "Chart",
description: "Create the chart",
content: <></>,
},
{
title: "Features",
description: "Add features",
content: <></>,
},
{
title: "Challenges",
description: "Create challenges section",
content: <></>,
},
{
title: "Benefits",
description: "create benefits section",
content: <></>,
},
{
title: "Contacts",
description: "Create contacts section",
content: <></>,
},
{
title: "Footer",
description: "Add footer information",
content: <></>,
},
];
const handleStepChange = async (newStep: number) => {
await saveCurrentStepData();
setStep(newStep);
};
return (
<>
<Breadcrumb items={breadcrumbItems} />
<ShowcaseSection title={title}>
<div className="mb-6 rounded-xl bg-gray-1 p-6 dark:bg-gray-7">
<h3 className="mb-4 text-lg font-semibold text-dark dark:text-white">
Page Configuration
</h3>
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
<Select
{...registerPageType("type", {
required: { message: FormErrorMessages.required, value: true },
})}
name="type"
label="Page Type"
required
items={[
{ value: "company", label: "Company" },
{ value: "organization", label: "Organization" },
]}
/>
{pageTypeErrors.type && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.type.message}
</p>
)}
<InputGroup
{...registerPageType("language", {
required: isCompany
? { message: FormErrorMessages.required, value: true }
: false,
minLength: isCompany
? { value: 2, message: FormErrorMessages.minLength(2) }
: undefined,
maxLength: isCompany
? { value: 10, message: FormErrorMessages.maxLength(10) }
: undefined,
})}
label="Language"
name="language"
type="text"
placeholder="Enter Language (e.g., en, ar)"
disabled={!isCompany}
required={isCompany}
/>
{pageTypeErrors.language && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.language.message}
</p>
)}
<InputGroup
{...registerPageType("company_name", {
required: isCompany
? { message: FormErrorMessages.required, value: true }
: false,
minLength: isCompany
? { value: 2, message: FormErrorMessages.minLength(2) }
: undefined,
maxLength: isCompany
? { value: 200, message: FormErrorMessages.maxLength(200) }
: undefined,
})}
label="Company Name"
name="company_name"
type="text"
placeholder="Enter Company Name"
disabled={!isCompany}
required={isCompany}
/>
{pageTypeErrors.company_name && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.company_name.message}
</p>
)}
<Select
{...registerPageType("country")}
name="country"
label="Country"
items={Countries}
defaultValue="USA"
clearable
prefixIcon={<GlobeIcon />}
disabled={!isCompany}
required={isCompany}
placeholder="Please select country"
onClear={() => setValue("country", undefined)}
/>
{pageTypeErrors.country && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.country.message}
</p>
)}
<InputGroup
{...registerPageType("key", {
required: {
value: true,
message: FormErrorMessages.required,
},
})}
label="Page key"
name="key"
placeholder="Please enter page key"
type="text"
/>
{pageTypeErrors.key && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.key.message}
</p>
)}
</div>
</div>
<div style={{ display: step === 0 ? "block" : "none" }}>
<HeroStep ref={heroRef} initialData={formData.hero} />
</div>
<div style={{ display: step === 1 ? "block" : "none" }}>
<ChartStep ref={chartRef} initialData={formData.chart} />
</div>
<div style={{ display: step === 2 ? "block" : "none" }}>
<FeaturesStep ref={featuresRef} initialData={formData.features} />
</div>
<div style={{ display: step === 3 ? "block" : "none" }}>
<ChallengesStep
ref={challengesRef}
initialData={formData.challenges}
/>
</div>
<div style={{ display: step === 4 ? "block" : "none" }}>
<BenefitsStep ref={benefitsRef} initialData={formData.advantages} />
</div>
<div style={{ display: step === 5 ? "block" : "none" }}>
<ContactsStep ref={contactsRef} initialData={formData.contact} />
</div>
<div style={{ display: step === 6 ? "block" : "none" }}>
<FooterStep ref={footerRef} initialData={formData.footer} />
</div>
<Stepper
steps={steps}
currentStep={step}
onStepChange={handleStepChange}
onSubmit={handleSubmitAll}
isSubmitting={isPending}
/>
</ShowcaseSection>
</>
);
};
export default MarketingForm;
@@ -11,7 +11,7 @@ export interface IBenefitsStepFields {
}[];
}
const useBenefitsStepPresenter = () => {
const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
const {
register,
handleSubmit,
@@ -20,7 +20,7 @@ const useBenefitsStepPresenter = () => {
formState: { errors },
getValues,
} = useForm<IBenefitsStepFields>({
defaultValues: {
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
},
});
@@ -11,7 +11,7 @@ export interface IChallengesStepFields {
}[];
}
const useChallengesStepPresenter = () => {
const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
const {
register,
handleSubmit,
@@ -20,7 +20,7 @@ const useChallengesStepPresenter = () => {
formState: { errors },
getValues,
} = useForm<IChallengesStepFields>({
defaultValues: {
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
},
});
@@ -7,7 +7,7 @@ export interface IChartStepFields {
data: { key: string; value: number }[];
}
const useChartStepPresenter = () => {
const useChartStepPresenter = (initialData?: IChartStepFields) => {
const {
register,
handleSubmit,
@@ -16,7 +16,7 @@ const useChartStepPresenter = () => {
formState: { errors },
getValues,
} = useForm<IChartStepFields>({
defaultValues: {
defaultValues: initialData || {
data: [{ key: "", value: 0 }],
},
});
@@ -13,7 +13,7 @@ export interface IContactsStepFields {
}[];
}
const useContactsStepPresenter = () => {
const useContactsStepPresenter = (initialData?: IContactsStepFields) => {
const {
register,
handleSubmit,
@@ -21,7 +21,7 @@ const useContactsStepPresenter = () => {
getValues,
formState: { errors },
} = useForm<IContactsStepFields>({
defaultValues: {
defaultValues: initialData || {
fields: [
{
name: "first_name",
@@ -11,7 +11,7 @@ export interface IFeaturesStepFields {
}[];
}
const useFeaturesStepPresenter = () => {
const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
const {
register,
handleSubmit,
@@ -20,7 +20,7 @@ const useFeaturesStepPresenter = () => {
formState: { errors },
getValues,
} = useForm<IFeaturesStepFields>({
defaultValues: {
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
},
});
@@ -8,13 +8,15 @@ interface FooterFormValues {
copyright: string;
}
const useFooterStepPresenter = () => {
const useFooterStepPresenter = (initialData?: FooterFormValues) => {
const {
register,
handleSubmit,
formState: { errors },
getValues,
} = useForm<FooterFormValues>();
} = useForm<FooterFormValues>({
defaultValues: initialData,
});
const onSubmit = (data: FooterFormValues) => {
console.log(data);
@@ -34,4 +36,4 @@ const useFooterStepPresenter = () => {
};
};
export default useFooterStepPresenter;
export default useFooterStepPresenter;
@@ -9,13 +9,15 @@ export interface IHeroStepFields {
gifFile: string;
}
const useHeroStepPresenter = () => {
const useHeroStepPresenter = (initialData?: IHeroStepFields) => {
const {
register,
handleSubmit,
formState: { errors },
getValues,
} = useForm<IHeroStepFields>();
} = useForm<IHeroStepFields>({
defaultValues: initialData,
});
const onSubmit = (data: IHeroStepFields) => {
console.log(data);
+9 -334
View File
@@ -1,58 +1,9 @@
"use client";
import { GlobeIcon } from "@/assets/icons";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Stepper, { Step } from "@/components/ui/Stepper";
import { Countries } from "@/constants/countries";
import { FormErrorMessages } from "@/constants/Texts";
import { useCreateCms } from "@/hooks/queries/useCmsQueries";
import { BreadcrumbItem } from "@/types/shared";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import BenefitsStep from "../_components/BenefitsStep";
import ChallengesStep from "../_components/ChallengesStep";
import ChartStep from "../_components/ChartStep";
import ContactsStep from "../_components/ContactsStep";
import FeaturesStep from "../_components/FeaturesStep";
import FooterStep from "../_components/FooterStep";
import HeroStep from "../_components/HeroStep";
interface IPageTypeFields {
type: "company" | "organization";
language?: string;
company_name?: string;
country?: string;
key: string;
}
import MarketingForm from "../_components/MarketingForm";
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);
const challengesRef = useRef<any>(null);
const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null);
const {
register: registerPageType,
watch: watchPageType,
formState: { errors: pageTypeErrors },
getValues: getPageTypeValues,
setValue,
} = useForm<IPageTypeFields>({
defaultValues: {
type: "company",
},
});
const pageType = watchPageType("type");
const isCompany = pageType === "company";
const { mutate: createCms, isPending } = useCreateCms();
const breadcrumbItems: BreadcrumbItem[] = [
@@ -70,101 +21,8 @@ const Marketing = () => {
},
];
const saveCurrentStepData = async () => {
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
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",
"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) {
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 handleSubmitAll = async () => {
const allData = await collectAllStepsData();
const pageTypeData = getPageTypeValues();
const finalData = {
...allData,
type: pageTypeData.type,
key: pageTypeData.key,
...(pageTypeData.type === "company" && {
language: pageTypeData.language,
company_name: pageTypeData.company_name,
country: pageTypeData.country,
}),
};
console.log("All collected data:", finalData);
createCms(finalData, {
const handleSubmit = (data: any) => {
createCms(data, {
onSuccess: () => {
console.log("CMS created successfully");
},
@@ -174,196 +32,13 @@ const Marketing = () => {
});
};
const steps: Step[] = [
{
title: "Hero",
description: "Create the hero section",
content: <></>,
},
{
title: "Chart",
description: "Create the chart",
content: <></>,
},
{
title: "Features",
description: "Add features",
content: <></>,
},
{
title: "Challenges",
description: "Create challenges section",
content: <></>,
},
{
title: "Benefits",
description: "create benefits section",
content: <></>,
},
{
title: "Contacts",
description: "Create contacts section",
content: <></>,
},
{
title: "Footer",
description: "Add footer information",
content: <></>,
},
];
const handleStepChange = async (newStep: number) => {
await saveCurrentStepData();
setStep(newStep);
};
return (
<>
<Breadcrumb items={breadcrumbItems} />
<ShowcaseSection title="Create Marketing page">
{/* Page Type Selection */}
<div className="mb-6 rounded-xl bg-gray-1 p-6 dark:bg-gray-7">
<h3 className="mb-4 text-lg font-semibold text-dark dark:text-white">
Page Configuration
</h3>
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
<Select
{...registerPageType("type", {
required: { message: FormErrorMessages.required, value: true },
})}
name="type"
label="Page Type"
required
items={[
{ value: "company", label: "Company" },
{ value: "organization", label: "Organization" },
]}
/>
{pageTypeErrors.type && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.type.message}
</p>
)}
<InputGroup
{...registerPageType("language", {
required: isCompany
? { message: FormErrorMessages.required, value: true }
: false,
minLength: isCompany
? { value: 2, message: FormErrorMessages.minLength(2) }
: undefined,
maxLength: isCompany
? { value: 10, message: FormErrorMessages.maxLength(10) }
: undefined,
})}
label="Language"
name="language"
type="text"
placeholder="Enter Language (e.g., en, ar)"
disabled={!isCompany}
required={isCompany}
/>
{pageTypeErrors.language && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.language.message}
</p>
)}
<InputGroup
{...registerPageType("company_name", {
required: isCompany
? { message: FormErrorMessages.required, value: true }
: false,
minLength: isCompany
? { value: 2, message: FormErrorMessages.minLength(2) }
: undefined,
maxLength: isCompany
? { value: 200, message: FormErrorMessages.maxLength(200) }
: undefined,
})}
label="Company Name"
name="company_name"
type="text"
placeholder="Enter Company Name"
disabled={!isCompany}
required={isCompany}
/>
{pageTypeErrors.company_name && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.company_name.message}
</p>
)}
<Select
{...registerPageType("country")}
name="country"
label="Country"
items={Countries}
defaultValue="USA"
clearable
prefixIcon={<GlobeIcon />}
disabled={!isCompany}
required={isCompany}
placeholder="Please select country"
onClear={() => setValue("country", undefined)}
/>
{pageTypeErrors.country && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.country.message}
</p>
)}
<InputGroup
{...registerPageType("key", {
required: {
value: true,
message: FormErrorMessages.required,
},
})}
label="Page key"
name="key"
placeholder="Please enter page key"
type="text"
/>
{pageTypeErrors.key && (
<p className="mt-1 text-sm text-red-500">
{pageTypeErrors.key.message}
</p>
)}
</div>
</div>
{/* 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={handleStepChange}
onSubmit={handleSubmitAll}
isSubmitting={isPending}
/>
</ShowcaseSection>
</>
<MarketingForm
breadcrumbItems={breadcrumbItems}
title="Create Marketing page"
onSubmit={handleSubmit}
isPending={isPending}
/>
);
};
+31 -8
View File
@@ -1,15 +1,19 @@
"use client";
import Loading from "@/app/loading";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { useGetCmsDetails } from "@/hooks/queries/useCmsQueries";
import { useGetCmsDetails, useUpdateCms } from "@/hooks/queries/useCmsQueries";
import { BreadcrumbItem } from "@/types/shared";
import { use } from "react";
import MarketingForm from "../../_components/MarketingForm";
interface IProps {
params: Promise<{ id: string }>;
}
const EditMarketingPage = async ({ params }: IProps) => {
const EditMarketingPage = ({ params }: IProps) => {
const { id } = use(params);
const { data, isPending } = useGetCmsDetails(id);
const { data, isPending: isLoading } = useGetCmsDetails(id);
const { mutate: updateCms, isPending: isUpdating } = useUpdateCms(id);
const breadcrumbItems: BreadcrumbItem[] = [
{
href: "/",
@@ -24,10 +28,29 @@ const EditMarketingPage = async ({ params }: IProps) => {
name: "Edit Marketing",
},
];
if (isPending) return <Loading />;
const handleSubmit = (formData: any) => {
updateCms(formData, {
onSuccess: () => {
console.log("CMS updated successfully");
},
onError: (error) => {
console.error("Error updating CMS:", error);
},
});
};
if (isLoading || !data?.data) return <Loading />;
return (
<>
<Breadcrumb items={breadcrumbItems} />
</>
<MarketingForm
breadcrumbItems={breadcrumbItems}
title="Edit Marketing page"
onSubmit={handleSubmit}
isPending={isUpdating}
initialData={data.data}
/>
);
};
export default EditMarketingPage;