feat(marketing): Implement chart creation step

This commit introduces the second step in the marketing page's stepper, which allows users to create a chart. The previous placeholder content has been replaced with the new `ChartStep` component.

To support form functionality within this new step, the `Button` component has been updated to accept a `type` attribute (`button`, `submit`, `reset`), enabling proper form submission handling.
This commit is contained in:
AmirReza Jamali
2025-11-04 15:30:36 +03:30
parent 2d664795ad
commit 634de17f23
5 changed files with 278 additions and 3 deletions
@@ -0,0 +1,101 @@
"use client";
import { useIsMobile } from "@/hooks/use-mobile";
import type { ApexOptions } from "apexcharts";
import dynamic from "next/dynamic";
type PropsType = {
series: { x: string; y: number }[];
title: string;
};
const Chart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
export function ChartPreview({ series, title }: PropsType) {
const isMobile = useIsMobile();
const options: ApexOptions = {
legend: {
show: false,
},
colors: ["#5750F1"],
chart: {
height: 310,
type: "area",
toolbar: {
show: false,
},
fontFamily: "inherit",
},
fill: {
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
},
},
responsive: [
{
breakpoint: 1024,
options: {
chart: {
height: 300,
},
},
},
{
breakpoint: 1366,
options: {
chart: {
height: 320,
},
},
},
],
stroke: {
curve: "smooth",
width: isMobile ? 2 : 3,
},
grid: {
strokeDashArray: 5,
yaxis: {
lines: {
show: true,
},
},
},
dataLabels: {
enabled: false,
},
tooltip: {
marker: {
show: true,
},
},
xaxis: {
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
};
return (
<div className="-ml-4 -mr-5 h-[310px]">
<Chart
options={options}
series={[
{
name: title,
data: series,
},
]}
type="area"
height={310}
/>
</div>
);
}
+127
View File
@@ -0,0 +1,127 @@
"use client";
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 { ChartPreview } from "./ChartPreview";
import useChartStepPresenter from "./useChartStepPresenter";
const ChartStep = () => {
const {
errors,
handleSubmit,
onSubmit,
register,
fields,
append,
remove,
watch,
} = useChartStepPresenter();
const chartTitle = watch("chart_title");
const chartPoints = watch("chart_points");
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("chart_title", {
required: { message: FormErrorMessages.required, value: true },
})}
label="Chart Title"
name="chart_title"
required
type="text"
placeholder="Enter Chart Title"
/>
{errors.chart_title && (
<p className="mt-1 text-sm text-red-500">
{errors.chart_title.message}
</p>
)}
<InputGroup
{...register("lost_amount", {
required: { message: FormErrorMessages.required, value: true },
valueAsNumber: true,
})}
label="Lost Amount"
name="lost_amount"
required
type="number"
placeholder="Enter Lost Amount"
/>
{errors.lost_amount && (
<p className="mt-1 text-sm text-red-500">
{errors.lost_amount.message}
</p>
)}
</ShowcaseSection>
<ShowcaseSection title="Chart Preview">
<ChartPreview
series={chartPoints.filter((p) => p.x && p.y)}
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(`chart_points.${index}.x`, {
required: {
message: FormErrorMessages.required,
value: true,
},
})}
label="X-axis"
name={`chart_points.${index}.x`}
required
type="text"
placeholder="Enter X-axis value"
/>
<InputGroup
{...register(`chart_points.${index}.y`, {
required: {
message: FormErrorMessages.required,
value: true,
},
valueAsNumber: true,
})}
label="Y-axis"
name={`chart_points.${index}.y`}
required
type="number"
placeholder="Enter Y-axis 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({ x: "", y: 0 })}
>
Add point
</button>
</ShowcaseSection>
</div>
<div className="flex flex-col gap-9"></div>
</form>
);
};
export default ChartStep;
@@ -0,0 +1,45 @@
"use client";
import { useFieldArray, useForm } from "react-hook-form";
export interface IChartStepFields {
chart_title: string;
lost_amount: number;
chart_points: { x: string; y: number }[];
}
const useChartStepPresenter = () => {
const {
register,
handleSubmit,
control,
watch,
formState: { errors },
} = useForm<IChartStepFields>({
defaultValues: {
chart_points: [{ x: "", y: 0 }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "chart_points",
});
const onSubmit = (data: IChartStepFields) => {
console.log(data);
};
return {
register,
handleSubmit,
errors,
onSubmit,
fields,
append,
remove,
control,
watch,
};
};
export default useChartStepPresenter;
+4 -3
View File
@@ -4,6 +4,7 @@ import Stepper, { Step } from "@/components/ui/Stepper";
import { BreadcrumbItem } from "@/types/shared";
import { useState } from "react";
import { ShowcaseSection } from "../../components/Layouts/showcase-section";
import ChartStep from "./_components/ChartStep";
import HeroStep from "./_components/HeroStep";
const Marketing = () => {
@@ -25,9 +26,9 @@ const Marketing = () => {
content: <HeroStep />,
},
{
title: "Profile",
description: "Setup your profile",
content: <div>step 2</div>,
title: "Chart",
description: "Create the chart",
content: <ChartStep />,
},
{
title: "Preferences",
+1
View File
@@ -37,6 +37,7 @@ type ButtonProps = HTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants> & {
label: string;
icon?: React.ReactNode;
type?: "button" | "submit" | "reset";
};
export function Button({