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:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,6 +4,7 @@ import Stepper, { Step } from "@/components/ui/Stepper";
|
|||||||
import { BreadcrumbItem } from "@/types/shared";
|
import { BreadcrumbItem } from "@/types/shared";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ShowcaseSection } from "../../components/Layouts/showcase-section";
|
import { ShowcaseSection } from "../../components/Layouts/showcase-section";
|
||||||
|
import ChartStep from "./_components/ChartStep";
|
||||||
import HeroStep from "./_components/HeroStep";
|
import HeroStep from "./_components/HeroStep";
|
||||||
|
|
||||||
const Marketing = () => {
|
const Marketing = () => {
|
||||||
@@ -25,9 +26,9 @@ const Marketing = () => {
|
|||||||
content: <HeroStep />,
|
content: <HeroStep />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Profile",
|
title: "Chart",
|
||||||
description: "Setup your profile",
|
description: "Create the chart",
|
||||||
content: <div>step 2</div>,
|
content: <ChartStep />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Preferences",
|
title: "Preferences",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type ButtonProps = HTMLAttributes<HTMLButtonElement> &
|
|||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
|
type?: "button" | "submit" | "reset";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Button({
|
export function Button({
|
||||||
|
|||||||
Reference in New Issue
Block a user