From 5b73389d649f8edb14c82da7c2dbdb587578fbfb Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Sun, 12 Oct 2025 15:20:52 +0330 Subject: [PATCH 01/21] feat(admins): add filter functionality to admins list This commit introduces filtering capabilities to the administrators table, allowing users to refine the list based on specific criteria. A new `ListHeader` component has been implemented, which now contains the "Create Admin" button and a new "Filter" button. Clicking the filter button opens a modal containing the `AdminListFilters` form. The state for managing the filter modal's visibility is handled within the `useAdminsPresenter` hook. A new `FilterIcon` has also been added to support this feature. --- src/assets/icons.tsx | 29 +++++++++ .../Tables/admins/AdminListFilters.tsx | 59 +++++++++++++++++++ src/components/Tables/admins/index.tsx | 23 ++++++-- .../admins/useAdminListFilterPresenter.ts | 10 ++++ .../Tables/admins/useAdminsPresenter.ts | 3 + src/components/Tables/customers/index.tsx | 40 +++++++------ .../Tables/notification-history/index.tsx | 1 + .../useNotificationHistoryTablePresenter.ts | 1 + src/components/ui/ListHeader.tsx | 45 ++++++++++++++ src/components/ui/modal.tsx | 4 +- src/hooks/queries/useNotificationQueries.ts | 2 + 11 files changed, 191 insertions(+), 26 deletions(-) create mode 100644 src/components/Tables/admins/AdminListFilters.tsx create mode 100644 src/components/Tables/admins/useAdminListFilterPresenter.ts create mode 100644 src/components/ui/ListHeader.tsx diff --git a/src/assets/icons.tsx b/src/assets/icons.tsx index a8e3570..4ac130f 100644 --- a/src/assets/icons.tsx +++ b/src/assets/icons.tsx @@ -38,6 +38,35 @@ export function CurrencyIcon(props: IconProps) { ); } +export function FilterIcon(props: IconProps) { + return ( + + + + + ); +} export function MoneyIcon(props: IconProps) { return ( { + const { register } = useAdminListFilterPresenter(); + return ( +
+ + + + + + +
+ ); +}; + +export default AdminListFilters; diff --git a/src/components/Tables/admins/index.tsx b/src/components/Tables/admins/index.tsx index 8afe283..1735331 100644 --- a/src/components/Tables/admins/index.tsx +++ b/src/components/Tables/admins/index.tsx @@ -2,8 +2,8 @@ import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons"; import ConfirmationModal from "@/components/ui/ConfirmationModal"; +import ListHeader from "@/components/ui/ListHeader"; import Modal from "@/components/ui/modal"; -import { _TooltipDefaultParams } from "@/constants/tooltip"; import Status from "@/components/ui/Status"; import { Table, @@ -15,8 +15,9 @@ import { } from "@/components/ui/table"; import TableSkeleton from "@/components/ui/TableSkeleton"; import { AdminStatus } from "@/constants/enums"; -import Link from "next/link"; +import { _TooltipDefaultParams } from "@/constants/tooltip"; import { Tooltip } from "react-tooltip"; +import AdminListFilters from "./AdminListFilters"; import ChangeStatusModalContent from "./ChangeStatusModalContent"; import { useAdminsPresenter } from "./useAdminsPresenter"; @@ -32,7 +33,8 @@ const AdminsTable = () => { setIsChangeStatusModalOpen, columns, setCurrentUser, - pathName, + isFilterModalOpen, + setIsFilterModalOpen, changeStatus, currentUser, setStatus, @@ -41,9 +43,10 @@ const AdminsTable = () => { return (
- + setIsFilterModalOpen(true)} + createButtonText="Create Admin" + /> @@ -143,6 +146,14 @@ const AdminsTable = () => { setStatus={setStatus} /> + + setIsFilterModalOpen(false)} + classNames="w-full" + > + + {isModalOpen && ( { + const { register } = useForm(); + return { + register, + }; +}; + +export default useAdminListFilterPresenter; diff --git a/src/components/Tables/admins/useAdminsPresenter.ts b/src/components/Tables/admins/useAdminsPresenter.ts index 8f63241..1f4cca8 100644 --- a/src/components/Tables/admins/useAdminsPresenter.ts +++ b/src/components/Tables/admins/useAdminsPresenter.ts @@ -14,6 +14,7 @@ import { useForm } from "react-hook-form"; export const useAdminsPresenter = () => { const [isChangeStatusModalOpen, setIsChangeStatusModalOpen] = useState(false); + const [isFilterModalOpen, setIsFilterModalOpen] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [status, setStatus] = useState( @@ -79,5 +80,7 @@ export const useAdminsPresenter = () => { register, errors, handleSubmit, + isFilterModalOpen, + setIsFilterModalOpen, }; }; diff --git a/src/components/Tables/customers/index.tsx b/src/components/Tables/customers/index.tsx index 3797bc1..057de63 100644 --- a/src/components/Tables/customers/index.tsx +++ b/src/components/Tables/customers/index.tsx @@ -154,25 +154,27 @@ const CustomersTable = () => { ))}
- { - if (!selectedCompanies || !currentCustomer) { - toast.error("Invalid credentials"); - return; - } - assignSelectedCompanies({ - credentials: selectedCompanies, - id: currentCustomer?.id ?? "", - }); - }} - isOpen={isAssignCompanyModalOpen} - onClose={() => setIsAssignCompanyModalOpen(false)} - > - - +
+ { + if (!selectedCompanies || !currentCustomer) { + toast.error("Invalid credentials"); + return; + } + assignSelectedCompanies({ + credentials: selectedCompanies, + id: currentCustomer?.id ?? "", + }); + }} + isOpen={isAssignCompanyModalOpen} + onClose={() => setIsAssignCompanyModalOpen(false)} + > + + +
{isModalOpen && ( { unix: item.created_at, })} + {item.recipient?.full_name} {truncateString(item.message, 20, 20)} diff --git a/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts b/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts index f8068d1..139fb5c 100644 --- a/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts +++ b/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts @@ -15,6 +15,7 @@ const useNotificationHistoryTablePresenter = () => { "title", "channel", "created at", + "receiver", "message", "priority", "type", diff --git a/src/components/ui/ListHeader.tsx b/src/components/ui/ListHeader.tsx new file mode 100644 index 0000000..adfa251 --- /dev/null +++ b/src/components/ui/ListHeader.tsx @@ -0,0 +1,45 @@ +"use client"; +import { FilterIcon } from "@/assets/icons"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import IsVisible from "./IsVisible"; + +interface IProps { + hasCreate?: boolean; + hasFilter?: boolean; + createButtonText?: string; + filterButtonText?: string; + onFilter?: () => void; +} + +const ListHeader = ({ + hasCreate = true, + hasFilter = true, + createButtonText = "Create", + filterButtonText = "Filter", + onFilter, +}: IProps) => { + const pathName = usePathname(); + return ( +
+ + + + + + +
+ ); +}; + +export default ListHeader; diff --git a/src/components/ui/modal.tsx b/src/components/ui/modal.tsx index ae6fe36..e065c6b 100644 --- a/src/components/ui/modal.tsx +++ b/src/components/ui/modal.tsx @@ -12,6 +12,7 @@ interface ModalProps { confirmText?: string; cancelText?: string; showButtons?: boolean; + classNames?: string; } const Modal: React.FC = ({ @@ -22,6 +23,7 @@ const Modal: React.FC = ({ confirmText = "confirm", cancelText = "close", showButtons = true, + classNames, }) => { const [mounted, setMounted] = useState(false); const isMutating = useIsMutating(); @@ -49,7 +51,7 @@ const Modal: React.FC = ({ onClick={onClose} >
e.stopPropagation()} > + )} +
diff --git a/src/components/Tables/admins/AdminListFilters.tsx b/src/components/Tables/admins/AdminListFilters.tsx index edd3f84..c871356 100644 --- a/src/components/Tables/admins/AdminListFilters.tsx +++ b/src/components/Tables/admins/AdminListFilters.tsx @@ -1,57 +1,95 @@ import InputGroup from "@/components/FormElements/InputGroup"; import { Select } from "@/components/FormElements/select"; -import { AdminRoles, NotificationChannels } from "@/constants/enums"; +import { AdminRoles, AdminStatus } from "@/constants/enums"; import { getEnumAsArray } from "@/utils/shared"; -import useAdminListFilterPresenter from "./useAdminListFilterPresenter"; +import { + FieldValues, + UseFormHandleSubmit, + UseFormRegister, +} from "react-hook-form"; -interface IProps {} +interface AdminListFiltersProps { + filterRegister: UseFormRegister; + setIsFilterModalOpen: (isOpen: boolean) => void; + isMutating: number; + handleFilterSubmit: UseFormHandleSubmit; + search: (data: any) => void; +} -const AdminListFilters = ({}: IProps) => { - const { register } = useAdminListFilterPresenter(); +const AdminListFilters = ({ + filterRegister, + setIsFilterModalOpen, + isMutating, + handleFilterSubmit, + search, +}: AdminListFiltersProps) => { return ( -
+ + {/* + /> */} setValue("status", undefined)} /> */} - + + +
+ + +
+ + ); +}; + +export default NotificationHistoryListFilters; diff --git a/src/components/Tables/notification-history/index.tsx b/src/components/Tables/notification-history/index.tsx index 4b246cb..bdf1f1b 100644 --- a/src/components/Tables/notification-history/index.tsx +++ b/src/components/Tables/notification-history/index.tsx @@ -14,10 +14,12 @@ import { import TableSkeleton from "@/components/ui/TableSkeleton"; import { _TooltipDefaultParams } from "@/constants/tooltip"; import { truncateString, unixToDate } from "@/utils/shared"; -import Link from "next/link"; import { Tooltip } from "react-tooltip"; import Boolean from "@/components/ui/Boolean"; +import ListHeader from "@/components/ui/ListHeader"; +import Modal from "@/components/ui/modal"; +import NotificationHistoryListFilters from "./NotificationHistoryListFilters"; import useNotificationHistoryTablePresenter from "./useNotificationHistoryTablePresenter"; const NotificationHistoryTable = () => { @@ -28,94 +30,122 @@ const NotificationHistoryTable = () => { notificationHistory, pathName, router, + isFilterModalOpen, + setIsFilterModalOpen, setParams, + handleSubmit, + register, + setValue, + watch, + search, + isMutating, } = useNotificationHistoryTablePresenter(); return ( - - - - - - {columns.map((column) => ( - - {column} - - ))} - - - {isLoading && } - - {!notificationHistory?.length ? ( + <> + + setIsFilterModalOpen(true)} + /> +
+ - -

No notifications

-
+ {columns.map((column) => ( + + {column} + + ))}
- ) : ( - notificationHistory.map((item, index) => ( - - {index + 1} - {item.title} - {item.event_type} - - {unixToDate({ - hasTime: true, - unix: item.created_at, - })} - - {item.recipient?.full_name} - - {truncateString(item.message, 20, 20)} - - - {item.priority} - - - {item.type} - - - - - - {item.status} - - - +
+ {isLoading && } + + {!notificationHistory?.length ? ( + + +

No notifications

- )) - )} -
-
- { - setParams((currentParams) => ({ - ...currentParams, - offset: e.selected * (metadata?.limit ?? 10), - limit: metadata?.limit ?? 10, - })); - }} - /> -
+ ) : ( + notificationHistory.map((item, index) => ( + + {index + 1} + {item.title} + {item.event_type} + + {unixToDate({ + hasTime: true, + unix: item.created_at, + })} + + + {item.recipient?.full_name} + + + {truncateString(item.message, 20, 20)} + + + {item.priority} + + + {item.type} + + + + + + {item.status} + + + + + + )) + )} + + + { + setParams((currentParams) => ({ + ...currentParams, + offset: e.selected * (metadata?.limit ?? 10), + limit: metadata?.limit ?? 10, + })); + }} + /> + + setIsFilterModalOpen(false)} + showButtons={false} + > + + + ); }; diff --git a/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts b/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts index 139fb5c..e34c15a 100644 --- a/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts +++ b/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts @@ -1,15 +1,20 @@ "use client"; import { apiDefaultParams } from "@/constants/Api_Params"; import { useGetNotificationHistoryQuery } from "@/hooks/queries/useNotificationQueries"; +import { deleteEmptyKeys } from "@/utils/shared"; +import { useIsMutating } from "@tanstack/react-query"; import { usePathname, useRouter } from "next/navigation"; import { useState } from "react"; +import { useForm } from "react-hook-form"; const useNotificationHistoryTablePresenter = () => { + const { register, handleSubmit, watch, setValue } = useForm(); + const [isFilterModalOpen, setIsFilterModalOpen] = useState(false); const [params, setParams] = useState>({ ...apiDefaultParams, }); const { data, isLoading } = useGetNotificationHistoryQuery(params); - + const isMutating = useIsMutating(); const columns: string[] = [ "row", "title", @@ -25,6 +30,14 @@ const useNotificationHistoryTablePresenter = () => { ]; const pathName = usePathname(); const router = useRouter(); + const search = (data: any) => { + const newParams = { ...params, ...data, offset: 0 }; + const cleanedParams = deleteEmptyKeys(newParams); + setParams(cleanedParams); + const queryString = new URLSearchParams(cleanedParams).toString(); + router.push(`${pathName}?${queryString}`); + setIsFilterModalOpen(false); + }; return { notificationHistory: data?.data, metadata: data?.meta, @@ -33,6 +46,14 @@ const useNotificationHistoryTablePresenter = () => { router, pathName, setParams, + register, + handleSubmit, + watch, + setValue, + isFilterModalOpen, + setIsFilterModalOpen, + isMutating, + search, }; }; diff --git a/src/components/ui/Stepper.tsx b/src/components/ui/Stepper.tsx new file mode 100644 index 0000000..0e0ee71 --- /dev/null +++ b/src/components/ui/Stepper.tsx @@ -0,0 +1,135 @@ +import { CheckIcon } from "@/assets/icons"; +import React, { ReactNode, useState } from "react"; +export interface Step { + title: string; + description?: string; + content: ReactNode; +} + +interface StepperProps { + steps: Step[]; + currentStep?: number; + onStepChange?: (step: number) => void; +} + +const Stepper: React.FC = ({ + steps, + currentStep: controlledStep, + onStepChange, +}) => { + const [internalStep, setInternalStep] = useState(0); + const currentStep = + controlledStep !== undefined ? controlledStep : internalStep; + + const handleStepClick = (index: number) => { + if (index <= currentStep) { + if (onStepChange) { + onStepChange(index); + } else { + setInternalStep(index); + } + } + }; + + const nextStep = () => { + if (currentStep < steps.length - 1) { + const newStep = currentStep + 1; + if (onStepChange) { + onStepChange(newStep); + } else { + setInternalStep(newStep); + } + } + }; + + const prevStep = () => { + if (currentStep > 0) { + const newStep = currentStep - 1; + if (onStepChange) { + onStepChange(newStep); + } else { + setInternalStep(newStep); + } + } + }; + + return ( +
+
+ {steps.map((step, index) => ( + +
+ +
+
+ {step.title} +
+ {step.description && ( +
+ {step.description} +
+ )} +
+
+ {index < steps.length - 1 && ( +
+ )} + + ))} +
+ +
{steps[currentStep].content}
+ +
+ + +
+
+ ); +}; + +export default Stepper; diff --git a/src/contexts/User.ctx.tsx b/src/contexts/User.ctx.tsx index ba12f2f..4b1ad75 100644 --- a/src/contexts/User.ctx.tsx +++ b/src/contexts/User.ctx.tsx @@ -14,12 +14,9 @@ import React, { } from "react"; import { useRouter } from "next/navigation"; -import { z } from "zod"; import { ILoginResponse, IUser, UserSchema } from "../lib/api/types"; import { COOKIE_KEYS } from "../lib/shared/cookies"; - - interface UserContextType { user: IUser | null; accessToken: string | null; @@ -27,7 +24,7 @@ interface UserContextType { isLoading: boolean; setAuthState: (data: ILoginResponse) => void; logout: () => void; - setUser:Dispatch> + setUser: Dispatch>; } const UserContext = createContext(undefined); @@ -48,7 +45,7 @@ export const UserProvider: React.FC = ({ children }) => { Cookies.remove(COOKIE_KEYS.access_token); Cookies.remove(COOKIE_KEYS.refresh_token); Cookies.remove(COOKIE_KEYS.user); - router.refresh(); + window.location.href = "/auth/sign-in"; }, [router]); useEffect(() => { try { diff --git a/src/hooks/queries/useContactUsQuery.ts b/src/hooks/queries/useContactUsQuery.ts new file mode 100644 index 0000000..f1b834d --- /dev/null +++ b/src/hooks/queries/useContactUsQuery.ts @@ -0,0 +1,13 @@ +import { API_ENDPOINTS } from "@/lib/api"; +import { contactUsService } from "@/lib/api/services/contact-us-service"; +import { useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; + +export const useReadAllContactUs = () => { + const queryKey = useMemo(() => [API_ENDPOINTS.CONTACT_US.READ_ALL], []); + + return useQuery({ + queryKey, + queryFn: () => contactUsService.getAllContactUs(), + }); +}; diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index d999277..3e90ec3 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -58,4 +58,7 @@ export const API_ENDPOINTS = { MY_NOTIFICATIONS: "notifications/my", MARK_AS_SEEN: (id: string) => `notifications/mark-seen/${id}`, }, + CONTACT_US: { + READ_ALL: "contacts", + }, } as const; diff --git a/src/lib/api/services/contact-us-service.ts b/src/lib/api/services/contact-us-service.ts new file mode 100644 index 0000000..5bbe2c1 --- /dev/null +++ b/src/lib/api/services/contact-us-service.ts @@ -0,0 +1,16 @@ +import api from "../axios"; +import { API_ENDPOINTS } from "../endpoints"; + +export const contactUsService = { + getAllContactUs: async () => { + try { + return (await api.get(API_ENDPOINTS.CONTACT_US.READ_ALL)).data; + } catch (error) { + console.error( + "ERROR caught in contact-us service => getAllContactUs", + error, + ); + throw error; + } + }, +}; diff --git a/src/utils/timeframe-extractor.ts b/src/utils/timeframe-extractor.ts index 6869bfa..bbb1237 100644 --- a/src/utils/timeframe-extractor.ts +++ b/src/utils/timeframe-extractor.ts @@ -6,4 +6,4 @@ export function createTimeFrameExtractor( ?.split(",") .find((value) => value.includes(sectionKey)); }; -} +} \ No newline at end of file From 634de17f2351b0be1c3c3d7b364569090ca55365 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 4 Nov 2025 15:30:36 +0330 Subject: [PATCH 06/21] 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. --- .../marketing/_components/ChartPreview.tsx | 101 ++++++++++++++ src/app/marketing/_components/ChartStep.tsx | 127 ++++++++++++++++++ .../_components/useChartStepPresenter.ts | 45 +++++++ src/app/marketing/page.tsx | 7 +- src/components/ui-elements/button.tsx | 1 + 5 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 src/app/marketing/_components/ChartPreview.tsx create mode 100644 src/app/marketing/_components/ChartStep.tsx create mode 100644 src/app/marketing/_components/useChartStepPresenter.ts diff --git a/src/app/marketing/_components/ChartPreview.tsx b/src/app/marketing/_components/ChartPreview.tsx new file mode 100644 index 0000000..560e29d --- /dev/null +++ b/src/app/marketing/_components/ChartPreview.tsx @@ -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 ( +
+ +
+ ); +} diff --git a/src/app/marketing/_components/ChartStep.tsx b/src/app/marketing/_components/ChartStep.tsx new file mode 100644 index 0000000..23d455c --- /dev/null +++ b/src/app/marketing/_components/ChartStep.tsx @@ -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 ( +
+
+ + + {errors.chart_title && ( +

+ {errors.chart_title.message} +

+ )} + + {errors.lost_amount && ( +

+ {errors.lost_amount.message} +

+ )} +
+ + p.x && p.y)} + title={chartTitle} + /> + + + {fields.map((field, index) => ( +
+ + + +
+ ))} + +
+
+
+
+ ); +}; + +export default ChartStep; diff --git a/src/app/marketing/_components/useChartStepPresenter.ts b/src/app/marketing/_components/useChartStepPresenter.ts new file mode 100644 index 0000000..8e2cf01 --- /dev/null +++ b/src/app/marketing/_components/useChartStepPresenter.ts @@ -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({ + 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; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index ff2d2a2..035eb02 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -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: , }, { - title: "Profile", - description: "Setup your profile", - content:
step 2
, + title: "Chart", + description: "Create the chart", + content: , }, { title: "Preferences", diff --git a/src/components/ui-elements/button.tsx b/src/components/ui-elements/button.tsx index a66c168..1b6ae90 100644 --- a/src/components/ui-elements/button.tsx +++ b/src/components/ui-elements/button.tsx @@ -37,6 +37,7 @@ type ButtonProps = HTMLAttributes & VariantProps & { label: string; icon?: React.ReactNode; + type?: "button" | "submit" | "reset"; }; export function Button({ From 90ca4054d81a7ae38365db3c5a78d40b1529802c Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 4 Nov 2025 17:04:08 +0330 Subject: [PATCH 07/21] feat(marketing): add features step to wizard This commit replaces the placeholder "Preferences" step in the marketing page's multi-step process with a new, functional "Features" step. The `FeaturesStep` component is now imported and rendered as the third step, allowing users to add features. The step's title and description have been updated to reflect this change. --- .../marketing/_components/FeaturesStep.tsx | 105 ++++++++++++++++++ .../_components/useFeaturesStepPresenter.ts | 49 ++++++++ src/app/marketing/page.tsx | 7 +- 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 src/app/marketing/_components/FeaturesStep.tsx create mode 100644 src/app/marketing/_components/useFeaturesStepPresenter.ts diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/_components/FeaturesStep.tsx new file mode 100644 index 0000000..5fce836 --- /dev/null +++ b/src/app/marketing/_components/FeaturesStep.tsx @@ -0,0 +1,105 @@ +"use client"; +import { TrashIcon } from "@/assets/icons"; +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 useFeaturesStepPresenter from "./useFeaturesStepPresenter"; + +const FeaturesStep = () => { + const { errors, handleSubmit, onSubmit, register, fields, append, remove } = + useFeaturesStepPresenter(); + return ( +
+
+ + + {errors.section_title && ( +

+ {errors.section_title.message} +

+ )} + +
+ + {fields.map((field, index) => ( +
+ + + + +
+ ))} + +
+
+
+ ); +}; + +export default FeaturesStep; diff --git a/src/app/marketing/_components/useFeaturesStepPresenter.ts b/src/app/marketing/_components/useFeaturesStepPresenter.ts new file mode 100644 index 0000000..c6aa47f --- /dev/null +++ b/src/app/marketing/_components/useFeaturesStepPresenter.ts @@ -0,0 +1,49 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IFeaturesStepFields { + section_title: string; + section_description: string; + features: { + icon: string; + title: string; + description: string; + }[]; +} + +const useFeaturesStepPresenter = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + defaultValues: { + features: [{ icon: "", title: "", description: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "features", + }); + + const onSubmit = (data: IFeaturesStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + append, + remove, + control, + watch, + }; +}; + +export default useFeaturesStepPresenter; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 035eb02..283ba49 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -5,6 +5,7 @@ import { BreadcrumbItem } from "@/types/shared"; import { useState } from "react"; import { ShowcaseSection } from "../../components/Layouts/showcase-section"; import ChartStep from "./_components/ChartStep"; +import FeaturesStep from "./_components/FeaturesStep"; import HeroStep from "./_components/HeroStep"; const Marketing = () => { @@ -31,9 +32,9 @@ const Marketing = () => { content: , }, { - title: "Preferences", - description: "Choose preferences", - content:
step 3
, + title: "Features", + description: "Add features", + content: , }, { title: "Review", From b18aa8eb71ce79d1a9f09f846ccb1fa7b066c062 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Wed, 5 Nov 2025 12:44:39 +0330 Subject: [PATCH 08/21] refactor(marketing): Abstract form logic into reusable SectionStep component Reduces code duplication in the marketing page forms by creating a generic `SectionStep` component. This new component encapsulates the shared UI and logic for sections that include a title, description, and a dynamic list of fields (e.g., feature cards, pricing points). The `FeaturesStep` component has been refactored to use this new `SectionStep`, resulting in a much cleaner and more maintainable implementation. --- .../marketing/_components/BenefitsStep.tsx | 35 +++++ .../marketing/_components/ChallengesStep.tsx | 35 +++++ .../marketing/_components/FeaturesStep.tsx | 124 ++++------------ src/app/marketing/_components/SectionStep.tsx | 137 ++++++++++++++++++ .../_components/useBenefitsStepPresenter.ts | 49 +++++++ .../_components/useChallengesStepPresenter.ts | 49 +++++++ src/app/marketing/page.tsx | 12 +- 7 files changed, 341 insertions(+), 100 deletions(-) create mode 100644 src/app/marketing/_components/BenefitsStep.tsx create mode 100644 src/app/marketing/_components/ChallengesStep.tsx create mode 100644 src/app/marketing/_components/SectionStep.tsx create mode 100644 src/app/marketing/_components/useBenefitsStepPresenter.ts create mode 100644 src/app/marketing/_components/useChallengesStepPresenter.ts diff --git a/src/app/marketing/_components/BenefitsStep.tsx b/src/app/marketing/_components/BenefitsStep.tsx new file mode 100644 index 0000000..3ca295d --- /dev/null +++ b/src/app/marketing/_components/BenefitsStep.tsx @@ -0,0 +1,35 @@ +"use client"; +import SectionStep from "./SectionStep"; +import useBenefitsStepPresenter, { + IBenefitsStepFields, +} from "./useBenefitsStepPresenter"; + +const BenefitsStep = () => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useBenefitsStepPresenter(); + return ( + + title="Benefits" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="benefits" + defaultValues={{ icon: "", title: "", description: "" }} + /> + ); +}; + +export default BenefitsStep; diff --git a/src/app/marketing/_components/ChallengesStep.tsx b/src/app/marketing/_components/ChallengesStep.tsx new file mode 100644 index 0000000..775f091 --- /dev/null +++ b/src/app/marketing/_components/ChallengesStep.tsx @@ -0,0 +1,35 @@ +"use client"; +import SectionStep from "./SectionStep"; +import useChallengesStepPresenter, { + IChallengesStepFields, +} from "./useChallengesStepPresenter"; + +const ChallengesStep = () => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useChallengesStepPresenter(); + return ( + + title="Challenges" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="challenges" + defaultValues={{ icon: "", title: "", description: "" }} + /> + ); +}; + +export default ChallengesStep; diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/_components/FeaturesStep.tsx index 5fce836..2b19e6e 100644 --- a/src/app/marketing/_components/FeaturesStep.tsx +++ b/src/app/marketing/_components/FeaturesStep.tsx @@ -1,104 +1,34 @@ "use client"; -import { TrashIcon } from "@/assets/icons"; -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 useFeaturesStepPresenter from "./useFeaturesStepPresenter"; +import SectionStep from "./SectionStep"; +import useFeaturesStepPresenter, { + IFeaturesStepFields, +} from "./useFeaturesStepPresenter"; const FeaturesStep = () => { - const { errors, handleSubmit, onSubmit, register, fields, append, remove } = - useFeaturesStepPresenter(); + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + } = useFeaturesStepPresenter(); return ( -
-
- - - {errors.section_title && ( -

- {errors.section_title.message} -

- )} - -
- - {fields.map((field, index) => ( -
- - - - -
- ))} - -
-
-
+ + title="Features" + fields={fields} + handleSubmit={handleSubmit} + onSubmit={onSubmit} + register={register} + errors={errors} + append={append} + remove={remove} + control={control} + fieldName="features" + defaultValues={{ icon: "", title: "", description: "" }} + /> ); }; diff --git a/src/app/marketing/_components/SectionStep.tsx b/src/app/marketing/_components/SectionStep.tsx new file mode 100644 index 0000000..c9ec7a3 --- /dev/null +++ b/src/app/marketing/_components/SectionStep.tsx @@ -0,0 +1,137 @@ +"use client"; +import { TrashIcon } from "@/assets/icons"; +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 { + Control, + FieldErrors, + FieldValues, + Path, + UseFieldArrayAppend, + UseFieldArrayRemove, + UseFormHandleSubmit, + UseFormRegister, +} from "react-hook-form"; + +interface IProps { + title: string; + fields: any[]; + handleSubmit: UseFormHandleSubmit; + onSubmit: (data: T) => void; + register: UseFormRegister; + errors: FieldErrors; + append: UseFieldArrayAppend; + remove: UseFieldArrayRemove; + control: Control; + fieldName: string; + defaultValues: any; +} + +const SectionStep = ({ + title, + fields, + handleSubmit, + onSubmit, + register, + errors, + append, + remove, + fieldName, + defaultValues, +}: IProps) => { + return ( +
+
+ + , { + required: { message: FormErrorMessages.required, value: true }, + })} + label="Section Title" + name="section_title" + required + type="text" + placeholder="Enter Section Title" + /> + {errors.section_title && ( +

+ {errors.section_title.message as string} +

+ )} + )} + name="section_description" + placeholder="Enter Section Description" + /> +
+ + {fields.map((field, index) => ( +
+ , { + required: { + message: FormErrorMessages.required, + value: true, + }, + })} + label="Icon" + name={`${fieldName}.${index}.icon`} + required + type="text" + placeholder="Enter Icon URL or name" + /> + , { + required: { + message: FormErrorMessages.required, + value: true, + }, + })} + label="Title" + name={`${fieldName}.${index}.title`} + required + type="text" + placeholder="Enter Title" + /> + )} + label="Description" + name={`${fieldName}.${index}.description`} + placeholder="Enter Description" + className="sm:col-span-2" + /> + +
+ ))} + +
+
+
+ ); +}; + +export default SectionStep; diff --git a/src/app/marketing/_components/useBenefitsStepPresenter.ts b/src/app/marketing/_components/useBenefitsStepPresenter.ts new file mode 100644 index 0000000..58414a1 --- /dev/null +++ b/src/app/marketing/_components/useBenefitsStepPresenter.ts @@ -0,0 +1,49 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IBenefitsStepFields { + section_title: string; + section_description: string; + benefits: { + icon: string; + title: string; + description: string; + }[]; +} + +const useBenefitsStepPresenter = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + defaultValues: { + benefits: [{ icon: "", title: "", description: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "benefits", + }); + + const onSubmit = (data: IBenefitsStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + append, + remove, + control, + watch, + }; +}; + +export default useBenefitsStepPresenter; diff --git a/src/app/marketing/_components/useChallengesStepPresenter.ts b/src/app/marketing/_components/useChallengesStepPresenter.ts new file mode 100644 index 0000000..0a2785b --- /dev/null +++ b/src/app/marketing/_components/useChallengesStepPresenter.ts @@ -0,0 +1,49 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IChallengesStepFields { + section_title: string; + section_description: string; + challenges: { + icon: string; + title: string; + description: string; + }[]; +} + +const useChallengesStepPresenter = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + defaultValues: { + challenges: [{ icon: "", title: "", description: "" }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "challenges", + }); + + const onSubmit = (data: IChallengesStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + append, + remove, + control, + watch, + }; +}; + +export default useChallengesStepPresenter; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 283ba49..63ad840 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -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 ChallengesStep from "./_components/ChallengesStep"; import ChartStep from "./_components/ChartStep"; import FeaturesStep from "./_components/FeaturesStep"; import HeroStep from "./_components/HeroStep"; @@ -37,9 +38,14 @@ const Marketing = () => { content: , }, { - title: "Review", - description: "Review and confirm", - content:
step 4
, + title: "Challenges", + description: "Create challenges section", + content: , + }, + { + title: "Benefits", + description: "create benefits section", + content: , }, ]; return ( From ad9323659402b6c51aaa12f7881c86d1f7b13695 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Wed, 5 Nov 2025 12:47:53 +0330 Subject: [PATCH 09/21] feat(stepper): add scroll to top on step change When navigating between steps, the new content could appear below the fold, requiring the user to scroll up manually. This change introduces an automatic smooth scroll to the top of the page whenever the user navigates to the next or previous step. This improves the user experience by ensuring the start of the new step's content is always visible. --- src/components/ui/Stepper.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/ui/Stepper.tsx b/src/components/ui/Stepper.tsx index 0e0ee71..93bbe61 100644 --- a/src/components/ui/Stepper.tsx +++ b/src/components/ui/Stepper.tsx @@ -40,6 +40,9 @@ const Stepper: React.FC = ({ setInternalStep(newStep); } } + if (window) { + window.scroll({ top: 0, behavior: "smooth" }); + } }; const prevStep = () => { @@ -51,6 +54,9 @@ const Stepper: React.FC = ({ setInternalStep(newStep); } } + if (window) { + window.scroll({ top: 0, behavior: "smooth" }); + } }; return ( From c2a14953d0236ca9fb70aa2334abee137b7c81a8 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Wed, 5 Nov 2025 15:39:16 +0330 Subject: [PATCH 10/21] feat(marketing): add contacts section and improve checkbox UI This commit introduces a new "Contacts" section to the marketing page, which is now the fifth step in the page's flow. Additionally, the Checkbox component has been enhanced with a smooth transition effect on state change. This provides better visual feedback and a more polished user experience. --- src/app/forms/form-elements/page.tsx | 4 +- .../form-layout/_components/sign-in-form.tsx | 2 +- .../marketing/_components/ContactsStep.tsx | 92 +++++++++++++++++++ .../_components/useContactsStepPresenter.ts | 62 +++++++++++++ src/app/marketing/page.tsx | 8 +- src/components/FormElements/checkbox.tsx | 2 +- 6 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 src/app/marketing/_components/ContactsStep.tsx create mode 100644 src/app/marketing/_components/useContactsStepPresenter.ts diff --git a/src/app/forms/form-elements/page.tsx b/src/app/forms/form-elements/page.tsx index 6d8e987..ff616c7 100644 --- a/src/app/forms/form-elements/page.tsx +++ b/src/app/forms/form-elements/page.tsx @@ -113,9 +113,9 @@ export default function FormElementsPage() { title="Checkbox and radio" className="space-y-5.5 !p-6.5" > - + {/* - + */} diff --git a/src/app/forms/form-layout/_components/sign-in-form.tsx b/src/app/forms/form-layout/_components/sign-in-form.tsx index 1be902a..a5f8aff 100644 --- a/src/app/forms/form-layout/_components/sign-in-form.tsx +++ b/src/app/forms/form-layout/_components/sign-in-form.tsx @@ -21,7 +21,7 @@ export function SignInForm() { /> */}
- + {/* */} Forgot password? diff --git a/src/app/marketing/_components/ContactsStep.tsx b/src/app/marketing/_components/ContactsStep.tsx new file mode 100644 index 0000000..bbad051 --- /dev/null +++ b/src/app/marketing/_components/ContactsStep.tsx @@ -0,0 +1,92 @@ +"use client"; +import { Checkbox } from "@/components/FormElements/checkbox"; +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 useContactsStepPresenter from "./useContactsStepPresenter"; + +const ContactsStep = () => { + const { errors, handleSubmit, onSubmit, register, fields } = + useContactsStepPresenter(); + return ( +
+ +
+ + {errors.title && ( +

+ {errors.title.message as string} +

+ )} + + +
+
+ + {fields.map((field, index) => ( +
+ + + + +
+ ))} +
+
+ ); +}; + +export default ContactsStep; diff --git a/src/app/marketing/_components/useContactsStepPresenter.ts b/src/app/marketing/_components/useContactsStepPresenter.ts new file mode 100644 index 0000000..b899130 --- /dev/null +++ b/src/app/marketing/_components/useContactsStepPresenter.ts @@ -0,0 +1,62 @@ +"use client"; +import { useFieldArray, useForm } from "react-hook-form"; + +export interface IContactsStepFields { + title: string; + description: string; + submit_button_title: string; + fields: { + field_name: string; + label: string; + placeholder: string; + required: boolean; + }[]; +} + +const useContactsStepPresenter = () => { + const { + register, + handleSubmit, + control, + formState: { errors }, + } = useForm({ + defaultValues: { + fields: [ + { + field_name: "first_name", + label: "", + placeholder: "", + required: false, + }, + { + field_name: "last_name", + label: "", + placeholder: "", + required: false, + }, + { field_name: "email", label: "", placeholder: "", required: false }, + { field_name: "phone", label: "", placeholder: "", required: false }, + ], + }, + }); + + const { fields } = useFieldArray({ + control, + name: "fields", + }); + + const onSubmit = (data: IContactsStepFields) => { + console.log(data); + }; + + return { + register, + handleSubmit, + errors, + onSubmit, + fields, + control, + }; +}; + +export default useContactsStepPresenter; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 63ad840..90bb94d 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -6,11 +6,12 @@ import { useState } from "react"; import { ShowcaseSection } from "../../components/Layouts/showcase-section"; import ChallengesStep from "./_components/ChallengesStep"; import ChartStep from "./_components/ChartStep"; +import ContactsStep from "./_components/ContactsStep"; import FeaturesStep from "./_components/FeaturesStep"; import HeroStep from "./_components/HeroStep"; const Marketing = () => { - const [step, setStep] = useState(0); + const [step, setStep] = useState(5); const breadcrumbItems: BreadcrumbItem[] = [ { href: "/", @@ -47,6 +48,11 @@ const Marketing = () => { description: "create benefits section", content: , }, + { + title: "Contacts", + description: "Create contacts section", + content: , + }, ]; return ( <> diff --git a/src/components/FormElements/checkbox.tsx b/src/components/FormElements/checkbox.tsx index d644f89..00e7784 100644 --- a/src/components/FormElements/checkbox.tsx +++ b/src/components/FormElements/checkbox.tsx @@ -43,7 +43,7 @@ export function Checkbox({
*]:block", + "mr-2 flex size-5 items-center justify-center rounded border border-dark-5 peer-checked:border-primary dark:border-dark-6 peer-checked:[&>*]:block transition-all duration-300", withBg ? "peer-checked:bg-primary [&>*]:text-white" : "peer-checked:bg-gray-2 dark:peer-checked:bg-transparent", From f9bd7fce4b32bad7919ceaf5c88691ae9530a298 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Wed, 5 Nov 2025 16:16:53 +0330 Subject: [PATCH 11/21] feat(marketing): add footer step with form inputs for email, phone, address, tagline, and copyright --- src/app/marketing/_components/FooterStep.tsx | 72 +++++++++++++++++++ .../_components/useFooterStepPresenter.ts | 30 ++++++++ src/app/marketing/page.tsx | 6 ++ 3 files changed, 108 insertions(+) create mode 100644 src/app/marketing/_components/FooterStep.tsx create mode 100644 src/app/marketing/_components/useFooterStepPresenter.ts diff --git a/src/app/marketing/_components/FooterStep.tsx b/src/app/marketing/_components/FooterStep.tsx new file mode 100644 index 0000000..ffdd5b3 --- /dev/null +++ b/src/app/marketing/_components/FooterStep.tsx @@ -0,0 +1,72 @@ +"use client"; +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 useFooterStepPresenter from "./useFooterStepPresenter"; + + +const FooterStep = () => { + const { errors, handleSubmit, onSubmit, register } = + useFooterStepPresenter(); + + return ( +
+ +
+ + {errors.email && ( +

+ {errors.email.message as string} +

+ )} + + + + +
+
+
+ ); +}; + +export default FooterStep; \ No newline at end of file diff --git a/src/app/marketing/_components/useFooterStepPresenter.ts b/src/app/marketing/_components/useFooterStepPresenter.ts new file mode 100644 index 0000000..0e42083 --- /dev/null +++ b/src/app/marketing/_components/useFooterStepPresenter.ts @@ -0,0 +1,30 @@ +import { useForm } from "react-hook-form"; + +interface FooterFormValues { + email: string; + phoneNumber?: string; + address?: string; + tagline?: string; + copyright?: string; +} + +const useFooterStepPresenter = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const onSubmit = (data: FooterFormValues) => { + console.log(data); + }; + + return { + register, + handleSubmit, + onSubmit, + errors, + }; +}; + +export default useFooterStepPresenter; \ No newline at end of file diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 90bb94d..183e9e4 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -8,6 +8,7 @@ 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"; const Marketing = () => { @@ -53,6 +54,11 @@ const Marketing = () => { description: "Create contacts section", content: , }, + { + title: "Footer", + description: "Add footer information", + content: , + }, ]; return ( <> From cdc8cde6a490559f2b7ec8b7d4eb8de496590dbf Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Sun, 9 Nov 2025 14:53:36 +0330 Subject: [PATCH 12/21] feat(marketing): Refactor marketing wizard steps and add data collection methods - Moved all marketing wizard step components to `create/_components` directory - Added `collectData` static method to each step component for data retrieval - Reorganized file structure to improve component modularity - Prepared steps for dynamic data collection in wizard workflow - Added VSCode settings file for project configuration - Introduced new CMS-related components and services - Updated API endpoints and added new query hooks --- .../{ => create}/_components/BenefitsStep.tsx | 0 .../_components/ChallengesStep.tsx | 5 + .../{ => create}/_components/ChartPreview.tsx | 0 .../{ => create}/_components/ChartStep.tsx | 5 + .../{ => create}/_components/ContactsStep.tsx | 10 ++ .../{ => create}/_components/FeaturesStep.tsx | 5 + .../{ => create}/_components/FooterStep.tsx | 7 +- .../{ => create}/_components/HeroStep.tsx | 5 + .../{ => create}/_components/SectionStep.tsx | 0 .../_components/useBenefitsStepPresenter.ts | 0 .../_components/useChallengesStepPresenter.ts | 9 +- .../_components/useChartStepPresenter.ts | 7 ++ .../_components/useContactsStepPresenter.ts | 8 ++ .../_components/useFeaturesStepPresenter.ts | 9 +- .../_components/useFooterStepPresenter.ts | 9 +- .../_components/useHeroStepPresenter.ts | 7 ++ src/app/marketing/create/page.tsx | 112 ++++++++++++++++++ src/app/marketing/page.tsx | 61 +--------- src/components/Auth/Signin/index.tsx | 2 - src/components/HOC/EmptyListWrapper.tsx | 30 +++++ src/components/Tables/cms/index.tsx | 39 ++++++ .../Tables/cms/useCmsTablePresenter.ts | 15 +++ src/hooks/queries/useCmsQueries.ts | 22 ++++ src/lib/api/endpoints.ts | 3 + src/lib/api/services/cms-service.ts | 23 ++++ src/lib/api/types/TCms.ts | 84 +++++++++++++ 26 files changed, 414 insertions(+), 63 deletions(-) rename src/app/marketing/{ => create}/_components/BenefitsStep.tsx (100%) rename src/app/marketing/{ => create}/_components/ChallengesStep.tsx (86%) rename src/app/marketing/{ => create}/_components/ChartPreview.tsx (100%) rename src/app/marketing/{ => create}/_components/ChartStep.tsx (97%) rename src/app/marketing/{ => create}/_components/ContactsStep.tsx (92%) rename src/app/marketing/{ => create}/_components/FeaturesStep.tsx (86%) rename src/app/marketing/{ => create}/_components/FooterStep.tsx (93%) rename src/app/marketing/{ => create}/_components/HeroStep.tsx (97%) rename src/app/marketing/{ => create}/_components/SectionStep.tsx (100%) rename src/app/marketing/{ => create}/_components/useBenefitsStepPresenter.ts (100%) rename src/app/marketing/{ => create}/_components/useChallengesStepPresenter.ts (89%) rename src/app/marketing/{ => create}/_components/useChartStepPresenter.ts (87%) rename src/app/marketing/{ => create}/_components/useContactsStepPresenter.ts (91%) rename src/app/marketing/{ => create}/_components/useFeaturesStepPresenter.ts (88%) rename src/app/marketing/{ => create}/_components/useFooterStepPresenter.ts (82%) rename src/app/marketing/{ => create}/_components/useHeroStepPresenter.ts (76%) create mode 100644 src/app/marketing/create/page.tsx create mode 100644 src/components/HOC/EmptyListWrapper.tsx create mode 100644 src/components/Tables/cms/index.tsx create mode 100644 src/components/Tables/cms/useCmsTablePresenter.ts create mode 100644 src/hooks/queries/useCmsQueries.ts create mode 100644 src/lib/api/services/cms-service.ts create mode 100644 src/lib/api/types/TCms.ts diff --git a/src/app/marketing/_components/BenefitsStep.tsx b/src/app/marketing/create/_components/BenefitsStep.tsx similarity index 100% rename from src/app/marketing/_components/BenefitsStep.tsx rename to src/app/marketing/create/_components/BenefitsStep.tsx diff --git a/src/app/marketing/_components/ChallengesStep.tsx b/src/app/marketing/create/_components/ChallengesStep.tsx similarity index 86% rename from src/app/marketing/_components/ChallengesStep.tsx rename to src/app/marketing/create/_components/ChallengesStep.tsx index 775f091..9ed7a3f 100644 --- a/src/app/marketing/_components/ChallengesStep.tsx +++ b/src/app/marketing/create/_components/ChallengesStep.tsx @@ -33,3 +33,8 @@ const ChallengesStep = () => { }; export default ChallengesStep; + +ChallengesStep.collectData = async () => { + const { getValues } = useChallengesStepPresenter(); + return getValues(); +} diff --git a/src/app/marketing/_components/ChartPreview.tsx b/src/app/marketing/create/_components/ChartPreview.tsx similarity index 100% rename from src/app/marketing/_components/ChartPreview.tsx rename to src/app/marketing/create/_components/ChartPreview.tsx diff --git a/src/app/marketing/_components/ChartStep.tsx b/src/app/marketing/create/_components/ChartStep.tsx similarity index 97% rename from src/app/marketing/_components/ChartStep.tsx rename to src/app/marketing/create/_components/ChartStep.tsx index 23d455c..d69733d 100644 --- a/src/app/marketing/_components/ChartStep.tsx +++ b/src/app/marketing/create/_components/ChartStep.tsx @@ -125,3 +125,8 @@ const ChartStep = () => { }; export default ChartStep; + +ChartStep.collectData = async () => { + const { getValues } = useChartStepPresenter(); + return getValues(); +} diff --git a/src/app/marketing/_components/ContactsStep.tsx b/src/app/marketing/create/_components/ContactsStep.tsx similarity index 92% rename from src/app/marketing/_components/ContactsStep.tsx rename to src/app/marketing/create/_components/ContactsStep.tsx index bbad051..4f11b2e 100644 --- a/src/app/marketing/_components/ContactsStep.tsx +++ b/src/app/marketing/create/_components/ContactsStep.tsx @@ -90,3 +90,13 @@ const ContactsStep = () => { }; export default ContactsStep; + +ContactsStep.collectData = async () => { + const { getValues } = useContactsStepPresenter(); + return getValues(); +} + +ContactsStep.collectData = async () => { + const { getValues } = useContactsStepPresenter(); + return getValues(); +} diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/create/_components/FeaturesStep.tsx similarity index 86% rename from src/app/marketing/_components/FeaturesStep.tsx rename to src/app/marketing/create/_components/FeaturesStep.tsx index 2b19e6e..7e592b6 100644 --- a/src/app/marketing/_components/FeaturesStep.tsx +++ b/src/app/marketing/create/_components/FeaturesStep.tsx @@ -33,3 +33,8 @@ const FeaturesStep = () => { }; export default FeaturesStep; + +FeaturesStep.collectData = async () => { + const { getValues } = useFeaturesStepPresenter(); + return getValues(); +} diff --git a/src/app/marketing/_components/FooterStep.tsx b/src/app/marketing/create/_components/FooterStep.tsx similarity index 93% rename from src/app/marketing/_components/FooterStep.tsx rename to src/app/marketing/create/_components/FooterStep.tsx index ffdd5b3..02733ca 100644 --- a/src/app/marketing/_components/FooterStep.tsx +++ b/src/app/marketing/create/_components/FooterStep.tsx @@ -69,4 +69,9 @@ const FooterStep = () => { ); }; -export default FooterStep; \ No newline at end of file +export default FooterStep; + +FooterStep.collectData = async () => { + const { getValues } = useFooterStepPresenter(); + return getValues(); +} \ No newline at end of file diff --git a/src/app/marketing/_components/HeroStep.tsx b/src/app/marketing/create/_components/HeroStep.tsx similarity index 97% rename from src/app/marketing/_components/HeroStep.tsx rename to src/app/marketing/create/_components/HeroStep.tsx index 6425656..4fb4c94 100644 --- a/src/app/marketing/_components/HeroStep.tsx +++ b/src/app/marketing/create/_components/HeroStep.tsx @@ -131,4 +131,9 @@ const HeroStep = () => { ); }; +HeroStep.collectData = async () => { + const { getValues } = useHeroStepPresenter(); + return getValues(); +} + export default HeroStep; diff --git a/src/app/marketing/_components/SectionStep.tsx b/src/app/marketing/create/_components/SectionStep.tsx similarity index 100% rename from src/app/marketing/_components/SectionStep.tsx rename to src/app/marketing/create/_components/SectionStep.tsx diff --git a/src/app/marketing/_components/useBenefitsStepPresenter.ts b/src/app/marketing/create/_components/useBenefitsStepPresenter.ts similarity index 100% rename from src/app/marketing/_components/useBenefitsStepPresenter.ts rename to src/app/marketing/create/_components/useBenefitsStepPresenter.ts diff --git a/src/app/marketing/_components/useChallengesStepPresenter.ts b/src/app/marketing/create/_components/useChallengesStepPresenter.ts similarity index 89% rename from src/app/marketing/_components/useChallengesStepPresenter.ts rename to src/app/marketing/create/_components/useChallengesStepPresenter.ts index 0a2785b..91576df 100644 --- a/src/app/marketing/_components/useChallengesStepPresenter.ts +++ b/src/app/marketing/create/_components/useChallengesStepPresenter.ts @@ -18,6 +18,7 @@ const useChallengesStepPresenter = () => { control, watch, formState: { errors }, + getValues } = useForm({ defaultValues: { challenges: [{ icon: "", title: "", description: "" }], @@ -33,6 +34,10 @@ const useChallengesStepPresenter = () => { console.log(data); }; + const collectData = async () => { + return getValues(); + }; + return { register, handleSubmit, @@ -43,7 +48,9 @@ const useChallengesStepPresenter = () => { remove, control, watch, - }; + getValues, + collectData + }; }; export default useChallengesStepPresenter; diff --git a/src/app/marketing/_components/useChartStepPresenter.ts b/src/app/marketing/create/_components/useChartStepPresenter.ts similarity index 87% rename from src/app/marketing/_components/useChartStepPresenter.ts rename to src/app/marketing/create/_components/useChartStepPresenter.ts index 8e2cf01..c533546 100644 --- a/src/app/marketing/_components/useChartStepPresenter.ts +++ b/src/app/marketing/create/_components/useChartStepPresenter.ts @@ -14,6 +14,7 @@ const useChartStepPresenter = () => { control, watch, formState: { errors }, + getValues } = useForm({ defaultValues: { chart_points: [{ x: "", y: 0 }], @@ -29,6 +30,10 @@ const useChartStepPresenter = () => { console.log(data); }; + const collectData = async () => { + return getValues(); + }; + return { register, handleSubmit, @@ -39,6 +44,8 @@ const useChartStepPresenter = () => { remove, control, watch, + getValues, + collectData }; }; diff --git a/src/app/marketing/_components/useContactsStepPresenter.ts b/src/app/marketing/create/_components/useContactsStepPresenter.ts similarity index 91% rename from src/app/marketing/_components/useContactsStepPresenter.ts rename to src/app/marketing/create/_components/useContactsStepPresenter.ts index b899130..297b3cd 100644 --- a/src/app/marketing/_components/useContactsStepPresenter.ts +++ b/src/app/marketing/create/_components/useContactsStepPresenter.ts @@ -18,6 +18,8 @@ const useContactsStepPresenter = () => { register, handleSubmit, control, + getValues, + formState: { errors }, } = useForm({ defaultValues: { @@ -49,6 +51,10 @@ const useContactsStepPresenter = () => { console.log(data); }; + const collectData = async () => { + return getValues(); + }; + return { register, handleSubmit, @@ -56,6 +62,8 @@ const useContactsStepPresenter = () => { onSubmit, fields, control, + collectData, + getValues, }; }; diff --git a/src/app/marketing/_components/useFeaturesStepPresenter.ts b/src/app/marketing/create/_components/useFeaturesStepPresenter.ts similarity index 88% rename from src/app/marketing/_components/useFeaturesStepPresenter.ts rename to src/app/marketing/create/_components/useFeaturesStepPresenter.ts index c6aa47f..1dcca68 100644 --- a/src/app/marketing/_components/useFeaturesStepPresenter.ts +++ b/src/app/marketing/create/_components/useFeaturesStepPresenter.ts @@ -18,6 +18,7 @@ const useFeaturesStepPresenter = () => { control, watch, formState: { errors }, + getValues } = useForm({ defaultValues: { features: [{ icon: "", title: "", description: "" }], @@ -33,6 +34,10 @@ const useFeaturesStepPresenter = () => { console.log(data); }; + const collectData = async () => { + return getValues(); + }; + return { register, handleSubmit, @@ -43,7 +48,9 @@ const useFeaturesStepPresenter = () => { remove, control, watch, - }; + getValues, + collectData + }; }; export default useFeaturesStepPresenter; diff --git a/src/app/marketing/_components/useFooterStepPresenter.ts b/src/app/marketing/create/_components/useFooterStepPresenter.ts similarity index 82% rename from src/app/marketing/_components/useFooterStepPresenter.ts rename to src/app/marketing/create/_components/useFooterStepPresenter.ts index 0e42083..30a9459 100644 --- a/src/app/marketing/_components/useFooterStepPresenter.ts +++ b/src/app/marketing/create/_components/useFooterStepPresenter.ts @@ -13,18 +13,25 @@ const useFooterStepPresenter = () => { register, handleSubmit, formState: { errors }, + getValues } = useForm(); const onSubmit = (data: FooterFormValues) => { console.log(data); }; + const collectData = async () => { + return getValues(); + }; + return { register, handleSubmit, onSubmit, errors, - }; + collectData, + getValues, + }; }; export default useFooterStepPresenter; \ No newline at end of file diff --git a/src/app/marketing/_components/useHeroStepPresenter.ts b/src/app/marketing/create/_components/useHeroStepPresenter.ts similarity index 76% rename from src/app/marketing/_components/useHeroStepPresenter.ts rename to src/app/marketing/create/_components/useHeroStepPresenter.ts index 4bd3934..5221b49 100644 --- a/src/app/marketing/_components/useHeroStepPresenter.ts +++ b/src/app/marketing/create/_components/useHeroStepPresenter.ts @@ -14,6 +14,7 @@ const useHeroStepPresenter = () => { register, handleSubmit, formState: { errors }, + getValues, } = useForm(); const onSubmit = (data: IHeroStepFields) => { @@ -25,7 +26,13 @@ const useHeroStepPresenter = () => { handleSubmit, errors, onSubmit, + getValues, }; }; +(useHeroStepPresenter as any).collectData = async () => { + const { getValues } = useForm(); + return getValues(); +}; + export default useHeroStepPresenter; diff --git a/src/app/marketing/create/page.tsx b/src/app/marketing/create/page.tsx new file mode 100644 index 0000000..079af26 --- /dev/null +++ b/src/app/marketing/create/page.tsx @@ -0,0 +1,112 @@ +"use client"; +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import { ShowcaseSection } from "@/components/Layouts/showcase-section"; +import Stepper, { Step } from "@/components/ui/Stepper"; +import { BreadcrumbItem } from "@/types/shared"; +import { useRef, useState } from "react"; +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"; + +const Marketing = () => { + const [step, setStep] = useState(0); + const heroRef = useRef(null); + const chartRef = useRef(null); + const featuresRef = useRef(null); + const challengesRef = useRef(null); + const benefitsRef = useRef(null); + const contactsRef = useRef(null); + const footerRef = useRef(null); + const breadcrumbItems: BreadcrumbItem[] = [ + { + href: "/", + name: "Dashboard", + }, + { + href: "/marketing", + name: "Marketing", + }, + { + href: "/marketing/create", + name: "Create Marketing", + }, + ]; + + const collectStepData = async (stepIndex: number) => { + let stepData = {}; + const refs = [heroRef, chartRef, featuresRef, challengesRef, benefitsRef, contactsRef, footerRef]; + + const currentRef = refs[stepIndex]; + if (currentRef?.current?.collectData) { + stepData = await currentRef.current.collectData(); + + // Submit data to backend + if (stepData) { + await submitData(stepData, stepIndex); + } + } + }; + + const submitData = async (data: any, stepIndex: number) => { + const apiUrl = "/api/marketing-data"; + console.log(`Submitting data for step ${stepIndex}:`, data); + }; + + 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: , + }, + ]; + return ( + <> + + + { + collectStepData(step); + setStep(newStep); + }} + > + + + ); +}; + +export default Marketing; diff --git a/src/app/marketing/page.tsx b/src/app/marketing/page.tsx index 183e9e4..4515afe 100644 --- a/src/app/marketing/page.tsx +++ b/src/app/marketing/page.tsx @@ -1,18 +1,8 @@ -"use client"; import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; -import Stepper, { Step } from "@/components/ui/Stepper"; +import CmsTable from "@/components/Tables/cms"; import { BreadcrumbItem } from "@/types/shared"; -import { useState } from "react"; -import { ShowcaseSection } from "../../components/Layouts/showcase-section"; -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"; -const Marketing = () => { - const [step, setStep] = useState(5); +const MarketingPage = () => { const breadcrumbItems: BreadcrumbItem[] = [ { href: "/", @@ -23,55 +13,12 @@ const Marketing = () => { name: "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: , - }, - ]; return ( <> - - - + ); }; -export default Marketing; +export default MarketingPage; diff --git a/src/components/Auth/Signin/index.tsx b/src/components/Auth/Signin/index.tsx index 00e75f3..f289398 100644 --- a/src/components/Auth/Signin/index.tsx +++ b/src/components/Auth/Signin/index.tsx @@ -1,5 +1,3 @@ -import Link from "next/link"; -import GoogleSigninButton from "../GoogleSigninButton"; import SigninWithPassword from "../SigninWithPassword"; export default function Signin() { diff --git a/src/components/HOC/EmptyListWrapper.tsx b/src/components/HOC/EmptyListWrapper.tsx new file mode 100644 index 0000000..76e421f --- /dev/null +++ b/src/components/HOC/EmptyListWrapper.tsx @@ -0,0 +1,30 @@ +import { TableCell, TableRow } from "@/components/ui/table"; +import { ReactNode } from "react"; + +interface EmptyListWrapperProps { + list: T[]; + emptyMessage: string; + columns: string[]; + children: ReactNode; +} + +function EmptyListWrapper({ + list, + emptyMessage, + columns, + children, +}: EmptyListWrapperProps) { + if (!list.length) { + return ( + + +

{emptyMessage}

+
+
+ ); + } + + return <>{children}; +} + +export default EmptyListWrapper; diff --git a/src/components/Tables/cms/index.tsx b/src/components/Tables/cms/index.tsx new file mode 100644 index 0000000..f8d64ed --- /dev/null +++ b/src/components/Tables/cms/index.tsx @@ -0,0 +1,39 @@ +"use client"; +import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; +import ListHeader from "@/components/ui/ListHeader"; +import { Table, TableBody, TableHead } from "@/components/ui/table"; +import TableSkeleton from "@/components/ui/TableSkeleton"; +import { TableHeader, TableRow } from "../../ui/table"; +import useCmsTablePresenter from "./useCmsTablePresenter"; + +const CmsTable = () => { + const { columns, data, isPending } = useCmsTablePresenter(); + return ( +
+ + + + + {columns.map((column) => ( + + {column} + + ))} + + + {isPending && } + + + Hollo + + +
+
+ ); +}; + +export default CmsTable; diff --git a/src/components/Tables/cms/useCmsTablePresenter.ts b/src/components/Tables/cms/useCmsTablePresenter.ts new file mode 100644 index 0000000..faaa018 --- /dev/null +++ b/src/components/Tables/cms/useCmsTablePresenter.ts @@ -0,0 +1,15 @@ +"use client"; +import { useGetCmsList } from "@/hooks/queries/useCmsQueries"; + +const useCmsTablePresenter = () => { + const columns = ["row", "title", "created at"]; + const { isPending, data } = useGetCmsList(); + + return { + columns, + isPending, + data, + }; +}; + +export default useCmsTablePresenter; diff --git a/src/hooks/queries/useCmsQueries.ts b/src/hooks/queries/useCmsQueries.ts new file mode 100644 index 0000000..c9fcb6c --- /dev/null +++ b/src/hooks/queries/useCmsQueries.ts @@ -0,0 +1,22 @@ +import { API_ENDPOINTS } from "@/lib/api"; +import { cmsService } from "@/lib/api/services/cms-service"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; + +export const useGetCmsList = () => { + const queryKey = useMemo(() => [API_ENDPOINTS.CMS.BASE()], []); + return useQuery({ + queryKey, + queryFn: cmsService.getCms, + }); +}; +export const useCreateCms = () => { + const mutationKey = useMemo( + () => [API_ENDPOINTS.CMS.BASE(), "Create cms"], + [], + ); + return useMutation({ + mutationKey, + mutationFn: cmsService.createCms, + }); +}; diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index 3e90ec3..790d215 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -3,6 +3,9 @@ export const API_ENDPOINTS = { POSTS: { READ_ALL: "posts ", }, + CMS: { + BASE: (id?: string) => (id ? `cms/${id}` : "cms"), + }, USER: { LOGIN: "profile/login", LOGOUT: "profile/logout", diff --git a/src/lib/api/services/cms-service.ts b/src/lib/api/services/cms-service.ts new file mode 100644 index 0000000..233074b --- /dev/null +++ b/src/lib/api/services/cms-service.ts @@ -0,0 +1,23 @@ +import api from "../axios"; +import { API_ENDPOINTS } from "../endpoints"; +import { ApiResponse } from "../types"; +import { TCmsResponse } from "../types/TCms"; + +export const cmsService = { + getCms: async (): Promise> => { + try { + return (await api.get(API_ENDPOINTS.CMS.BASE())).data; + } catch (error) { + console.error("Error Caught in CMS Service => Get cms", error); + throw error; + } + }, + createCms: async (data: any) => { + try { + return (await api.post(API_ENDPOINTS.CMS.BASE(), data)).data; + } catch (error) { + console.error("Error Caught in CMS Service => Create cms", error); + throw error; + } + }, +}; diff --git a/src/lib/api/types/TCms.ts b/src/lib/api/types/TCms.ts new file mode 100644 index 0000000..9a52f47 --- /dev/null +++ b/src/lib/api/types/TCms.ts @@ -0,0 +1,84 @@ +import { z } from "zod"; + +const CmsSchema = z.object({ + cms: z.array( + 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(), + }), + created_at: z.number(), + updated_at: z.number(), + id: z.string(), + key: z.string(), + type: z.string(), + }), + ), +}); +export type TCmsResponse = z.infer; From 574a3a20ce406a08caca829065013a3f2ad77adc Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Sun, 9 Nov 2025 15:02:26 +0330 Subject: [PATCH 13/21] 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 --- .../create/_components/BenefitsStep.tsx | 13 ++- .../create/_components/ChallengesStep.tsx | 18 +-- .../create/_components/ChartStep.tsx | 18 +-- .../create/_components/ContactsStep.tsx | 24 ++-- .../create/_components/FeaturesStep.tsx | 18 +-- .../create/_components/FooterStep.tsx | 19 ++-- .../marketing/create/_components/HeroStep.tsx | 17 +-- .../_components/useBenefitsStepPresenter.ts | 7 ++ src/app/marketing/create/page.tsx | 105 +++++++++++++----- 9 files changed, 160 insertions(+), 79 deletions(-) diff --git a/src/app/marketing/create/_components/BenefitsStep.tsx b/src/app/marketing/create/_components/BenefitsStep.tsx index 3ca295d..9265bba 100644 --- a/src/app/marketing/create/_components/BenefitsStep.tsx +++ b/src/app/marketing/create/_components/BenefitsStep.tsx @@ -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 ( title="Benefits" @@ -30,6 +37,8 @@ const BenefitsStep = () => { defaultValues={{ icon: "", title: "", description: "" }} /> ); -}; +}); + +BenefitsStep.displayName = "BenefitsStep"; export default BenefitsStep; diff --git a/src/app/marketing/create/_components/ChallengesStep.tsx b/src/app/marketing/create/_components/ChallengesStep.tsx index 9ed7a3f..4cca940 100644 --- a/src/app/marketing/create/_components/ChallengesStep.tsx +++ b/src/app/marketing/create/_components/ChallengesStep.tsx @@ -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 ( 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(); -} diff --git a/src/app/marketing/create/_components/ChartStep.tsx b/src/app/marketing/create/_components/ChartStep.tsx index d69733d..14d5eb5 100644 --- a/src/app/marketing/create/_components/ChartStep.tsx +++ b/src/app/marketing/create/_components/ChartStep.tsx @@ -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 = () => {
); -}; +}); + +ChartStep.displayName = "ChartStep"; export default ChartStep; - -ChartStep.collectData = async () => { - const { getValues } = useChartStepPresenter(); - return getValues(); -} diff --git a/src/app/marketing/create/_components/ContactsStep.tsx b/src/app/marketing/create/_components/ContactsStep.tsx index 4f11b2e..1ab4257 100644 --- a/src/app/marketing/create/_components/ContactsStep.tsx +++ b/src/app/marketing/create/_components/ContactsStep.tsx @@ -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 (
{
); -}; +}); + +ContactsStep.displayName = "ContactsStep"; export default ContactsStep; - -ContactsStep.collectData = async () => { - const { getValues } = useContactsStepPresenter(); - return getValues(); -} - -ContactsStep.collectData = async () => { - const { getValues } = useContactsStepPresenter(); - return getValues(); -} diff --git a/src/app/marketing/create/_components/FeaturesStep.tsx b/src/app/marketing/create/_components/FeaturesStep.tsx index 7e592b6..394a237 100644 --- a/src/app/marketing/create/_components/FeaturesStep.tsx +++ b/src/app/marketing/create/_components/FeaturesStep.tsx @@ -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 ( 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(); -} diff --git a/src/app/marketing/create/_components/FooterStep.tsx b/src/app/marketing/create/_components/FooterStep.tsx index 02733ca..002d50f 100644 --- a/src/app/marketing/create/_components/FooterStep.tsx +++ b/src/app/marketing/create/_components/FooterStep.tsx @@ -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 (
{
); -}; +}); -export default FooterStep; +FooterStep.displayName = "FooterStep"; -FooterStep.collectData = async () => { - const { getValues } = useFooterStepPresenter(); - return getValues(); -} \ No newline at end of file +export default FooterStep; \ No newline at end of file diff --git a/src/app/marketing/create/_components/HeroStep.tsx b/src/app/marketing/create/_components/HeroStep.tsx index 4fb4c94..431039d 100644 --- a/src/app/marketing/create/_components/HeroStep.tsx +++ b/src/app/marketing/create/_components/HeroStep.tsx @@ -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 (
{
); -}; +}); -HeroStep.collectData = async () => { - const { getValues } = useHeroStepPresenter(); - return getValues(); -} +HeroStep.displayName = "HeroStep"; export default HeroStep; diff --git a/src/app/marketing/create/_components/useBenefitsStepPresenter.ts b/src/app/marketing/create/_components/useBenefitsStepPresenter.ts index 58414a1..922c832 100644 --- a/src/app/marketing/create/_components/useBenefitsStepPresenter.ts +++ b/src/app/marketing/create/_components/useBenefitsStepPresenter.ts @@ -18,6 +18,7 @@ const useBenefitsStepPresenter = () => { control, watch, formState: { errors }, + getValues, } = useForm({ 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, }; }; diff --git a/src/app/marketing/create/page.tsx b/src/app/marketing/create/page.tsx index 079af26..42c6507 100644 --- a/src/app/marketing/create/page.tsx +++ b/src/app/marketing/create/page.tsx @@ -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(null); const contactsRef = useRef(null); const footerRef = useRef(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 currentRef = refs[stepIndex]; - if (currentRef?.current?.collectData) { - stepData = await currentRef.current.collectData(); - - // Submit data to backend - if (stepData) { - await submitData(stepData, stepIndex); + const collectAllStepsData = async () => { + const refs = [ + heroRef, + chartRef, + featuresRef, + challengesRef, + benefitsRef, + contactsRef, + footerRef, + ]; + + const allData: any = { + hero: {}, + chart: {}, + features: {}, + challenges: {}, + benefits: {}, + contacts: {}, + footer: {}, + }; + + 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: , + content: , }, { title: "Chart", description: "Create the chart", - content: , + content: , }, { title: "Features", description: "Add features", - content: , + content: , }, { title: "Challenges", description: "Create challenges section", - content: , + content: , }, { title: "Benefits", description: "create benefits section", - content: , + content: , }, { title: "Contacts", description: "Create contacts section", - content: , + content: , }, { title: "Footer", description: "Add footer information", - content: , + content: , }, ]; + return ( <> @@ -99,11 +145,18 @@ const Marketing = () => { { - collectStepData(step); - setStep(newStep); - }} - > + onStepChange={setStep} + /> +
+ +
); From 4f7b258533ec8cfd2af3c9f03da77e3f9e04c90f Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Sun, 9 Nov 2025 16:04:52 +0330 Subject: [PATCH 14/21] 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 --- .vscode/settings.json | 2 + .../create/_components/BenefitsStep.tsx | 2 +- .../create/_components/ChallengesStep.tsx | 2 +- .../create/_components/ChartStep.tsx | 51 ++++----- .../create/_components/ContactsStep.tsx | 12 +-- .../create/_components/FeaturesStep.tsx | 2 +- .../create/_components/FooterStep.tsx | 12 +-- .../marketing/create/_components/HeroStep.tsx | 52 ++++----- .../create/_components/SectionStep.tsx | 40 +++---- .../_components/useBenefitsStepPresenter.ts | 10 +- .../_components/useChallengesStepPresenter.ts | 16 +-- .../_components/useChartStepPresenter.ts | 14 +-- .../_components/useContactsStepPresenter.ts | 13 ++- .../_components/useFeaturesStepPresenter.ts | 16 +-- .../_components/useFooterStepPresenter.ts | 16 +-- .../_components/useHeroStepPresenter.ts | 11 +- src/app/marketing/create/page.tsx | 100 ++++++++++++++---- src/lib/api/types/TCms.ts | 71 +++++++++++++ 18 files changed, 283 insertions(+), 159 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/src/app/marketing/create/_components/BenefitsStep.tsx b/src/app/marketing/create/_components/BenefitsStep.tsx index 9265bba..1b3e527 100644 --- a/src/app/marketing/create/_components/BenefitsStep.tsx +++ b/src/app/marketing/create/_components/BenefitsStep.tsx @@ -33,7 +33,7 @@ const BenefitsStep = forwardRef((props, ref) => { append={append} remove={remove} control={control} - fieldName="benefits" + fieldName="cards" defaultValues={{ icon: "", title: "", description: "" }} /> ); diff --git a/src/app/marketing/create/_components/ChallengesStep.tsx b/src/app/marketing/create/_components/ChallengesStep.tsx index 4cca940..8bdc53e 100644 --- a/src/app/marketing/create/_components/ChallengesStep.tsx +++ b/src/app/marketing/create/_components/ChallengesStep.tsx @@ -33,7 +33,7 @@ const ChallengesStep = forwardRef((props, ref) => { append={append} remove={remove} control={control} - fieldName="challenges" + fieldName="cards" defaultValues={{ icon: "", title: "", description: "" }} /> ); diff --git a/src/app/marketing/create/_components/ChartStep.tsx b/src/app/marketing/create/_components/ChartStep.tsx index 14d5eb5..f34bddd 100644 --- a/src/app/marketing/create/_components/ChartStep.tsx +++ b/src/app/marketing/create/_components/ChartStep.tsx @@ -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 (
{ className="flex flex-col gap-5" > - {errors.chart_title && ( + {errors.title && (

- {errors.chart_title.message} + {errors.title.message}

)} - {errors.lost_amount && ( + {errors.missedAmount && (

- {errors.lost_amount.message} + {errors.missedAmount.message}

)} p.x && p.y)} + series={ + chartData + ?.filter((p) => p.key && p.value) + .map((p) => ({ x: p.key, y: p.value })) || [] + } title={chartTitle} /> @@ -80,31 +83,31 @@ const ChartStep = forwardRef((props, ref) => { {fields.map((field, index) => (
diff --git a/src/app/marketing/create/_components/ContactsStep.tsx b/src/app/marketing/create/_components/ContactsStep.tsx index 1ab4257..a5172d1 100644 --- a/src/app/marketing/create/_components/ContactsStep.tsx +++ b/src/app/marketing/create/_components/ContactsStep.tsx @@ -44,11 +44,11 @@ const ContactsStep = forwardRef((props, ref) => { placeholder="Enter Description" />
@@ -59,9 +59,9 @@ const ContactsStep = forwardRef((props, ref) => { className="grid grid-cols-1 items-center gap-4 sm:grid-cols-2" > { append={append} remove={remove} control={control} - fieldName="features" + fieldName="cards" defaultValues={{ icon: "", title: "", description: "" }} /> ); diff --git a/src/app/marketing/create/_components/FooterStep.tsx b/src/app/marketing/create/_components/FooterStep.tsx index 002d50f..1834211 100644 --- a/src/app/marketing/create/_components/FooterStep.tsx +++ b/src/app/marketing/create/_components/FooterStep.tsx @@ -41,18 +41,18 @@ const FooterStep = forwardRef((props, ref) => {

)} { title="Hero Information" className="flex flex-col gap-5" > - - {errors.key && ( -

{errors.key.message}

- )} {

)} { }, })} label="Button Text" - name="button_text" + name="buttonText" required type="text" placeholder="Enter Button Text" /> - {errors.button_text && ( + {errors.buttonText && (

- {errors.button_text.message} + {errors.buttonText.message}

)} { }, })} label="Button Link" - name="button_link" + name="buttonLink" required type="url" placeholder="Enter Button Link" /> - {errors.button_link && ( + {errors.buttonLink && (

- {errors.button_link.message} + {errors.buttonLink.message} +

+ )} + + {errors.gifFile && ( +

+ {errors.gifFile.message}

)} diff --git a/src/app/marketing/create/_components/SectionStep.tsx b/src/app/marketing/create/_components/SectionStep.tsx index c9ec7a3..0f1d610 100644 --- a/src/app/marketing/create/_components/SectionStep.tsx +++ b/src/app/marketing/create/_components/SectionStep.tsx @@ -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 { @@ -52,24 +52,24 @@ const SectionStep = ({ className="flex flex-col gap-5" > , { + {...register("title" as Path, { 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 && (

- {errors.section_title.message as string} + {errors.title.message as string}

)} )} - name="section_description" + {...register("description" as Path)} + name="description" placeholder="Enter Section Description" /> @@ -80,35 +80,35 @@ const SectionStep = ({ className="grid grid-cols-1 gap-4 sm:grid-cols-2" > , { + {...register(`cards.${index}.icon` as Path, { 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" /> , { + {...register(`cards.${index}.title` as Path, { required: { message: FormErrorMessages.required, value: true, }, })} label="Title" - name={`${fieldName}.${index}.title`} + name={`cards.${index}.title`} required type="text" placeholder="Enter Title" /> )} + {...register(`cards.${index}.description` as Path)} label="Description" - name={`${fieldName}.${index}.description`} + name={`cards.${index}.description`} placeholder="Enter Description" className="sm:col-span-2" /> diff --git a/src/app/marketing/create/_components/useBenefitsStepPresenter.ts b/src/app/marketing/create/_components/useBenefitsStepPresenter.ts index 922c832..ee8d9c1 100644 --- a/src/app/marketing/create/_components/useBenefitsStepPresenter.ts +++ b/src/app/marketing/create/_components/useBenefitsStepPresenter.ts @@ -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({ defaultValues: { - benefits: [{ icon: "", title: "", description: "" }], + cards: [{ icon: "", title: "", description: "" }], }, }); const { fields, append, remove } = useFieldArray({ control, - name: "benefits", + name: "cards", }); const onSubmit = (data: IBenefitsStepFields) => { diff --git a/src/app/marketing/create/_components/useChallengesStepPresenter.ts b/src/app/marketing/create/_components/useChallengesStepPresenter.ts index 91576df..fe23048 100644 --- a/src/app/marketing/create/_components/useChallengesStepPresenter.ts +++ b/src/app/marketing/create/_components/useChallengesStepPresenter.ts @@ -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({ 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; diff --git a/src/app/marketing/create/_components/useChartStepPresenter.ts b/src/app/marketing/create/_components/useChartStepPresenter.ts index c533546..18a2797 100644 --- a/src/app/marketing/create/_components/useChartStepPresenter.ts +++ b/src/app/marketing/create/_components/useChartStepPresenter.ts @@ -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({ 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, }; }; diff --git a/src/app/marketing/create/_components/useContactsStepPresenter.ts b/src/app/marketing/create/_components/useContactsStepPresenter.ts index 297b3cd..0101dce 100644 --- a/src/app/marketing/create/_components/useContactsStepPresenter.ts +++ b/src/app/marketing/create/_components/useContactsStepPresenter.ts @@ -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({ 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 }, ], }, }); diff --git a/src/app/marketing/create/_components/useFeaturesStepPresenter.ts b/src/app/marketing/create/_components/useFeaturesStepPresenter.ts index 1dcca68..dfc581c 100644 --- a/src/app/marketing/create/_components/useFeaturesStepPresenter.ts +++ b/src/app/marketing/create/_components/useFeaturesStepPresenter.ts @@ -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({ 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; diff --git a/src/app/marketing/create/_components/useFooterStepPresenter.ts b/src/app/marketing/create/_components/useFooterStepPresenter.ts index 30a9459..fde1b0d 100644 --- a/src/app/marketing/create/_components/useFooterStepPresenter.ts +++ b/src/app/marketing/create/_components/useFooterStepPresenter.ts @@ -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(); const onSubmit = (data: FooterFormValues) => { @@ -29,9 +29,9 @@ const useFooterStepPresenter = () => { handleSubmit, onSubmit, errors, - collectData, - getValues, - }; + collectData, + getValues, + }; }; export default useFooterStepPresenter; \ No newline at end of file diff --git a/src/app/marketing/create/_components/useHeroStepPresenter.ts b/src/app/marketing/create/_components/useHeroStepPresenter.ts index 5221b49..5e7d74f 100644 --- a/src/app/marketing/create/_components/useHeroStepPresenter.ts +++ b/src/app/marketing/create/_components/useHeroStepPresenter.ts @@ -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(); - return getValues(); -}; - export default useHeroStepPresenter; diff --git a/src/app/marketing/create/page.tsx b/src/app/marketing/create/page.tsx index 42c6507..ce4fb8f 100644 --- a/src/app/marketing/create/page.tsx +++ b/src/app/marketing/create/page.tsx @@ -15,6 +15,7 @@ import HeroStep from "./_components/HeroStep"; const Marketing = () => { const [step, setStep] = useState(0); + const [formData, setFormData] = useState({}); const heroRef = useRef(null); const chartRef = useRef(null); const featuresRef = useRef(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: , + content: <>, }, { title: "Chart", description: "Create the chart", - content: , + content: <>, }, { title: "Features", description: "Add features", - content: , + content: <>, }, { title: "Challenges", description: "Create challenges section", - content: , + content: <>, }, { title: "Benefits", description: "create benefits section", - content: , + content: <>, }, { title: "Contacts", description: "Create contacts section", - content: , + content: <>, }, { title: "Footer", description: "Add footer information", - content: , + content: <>, }, ]; + const handleStepChange = async (newStep: number) => { + await saveCurrentStepData(); + setStep(newStep); + }; + return ( <> + {/* Render all steps but only show the current one */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
); diff --git a/src/components/FormElements/select.tsx b/src/components/FormElements/select.tsx index 374c891..d57662a 100644 --- a/src/components/FormElements/select.tsx +++ b/src/components/FormElements/select.tsx @@ -64,13 +64,15 @@ export function Select({ }, [controlledValue]); const handleClear = () => { - setValue(""); + if (controlledValue === undefined) { + setValue(""); + } setIsOptionSelected(false); onClear?.(); if (onChange) { const event = { - target: { value: "" }, - } as ChangeEvent; + target: { value: "", name }, + } as unknown as ChangeEvent; onChange(event); } }; @@ -113,11 +115,7 @@ export function Select({ clearable && value && "pr-20", )} > - {placeholder && ( - - )} + {placeholder && } {items.map((item) => (
diff --git a/src/constants/countries.ts b/src/constants/countries.ts new file mode 100644 index 0000000..c726709 --- /dev/null +++ b/src/constants/countries.ts @@ -0,0 +1,258 @@ +export const Countries = [ + { value: "AF", label: "Afghanistan" }, + { value: "AX", label: "Åland Islands" }, + { value: "AL", label: "Albania" }, + { value: "DZ", label: "Algeria" }, + { value: "AS", label: "American Samoa" }, + { value: "AD", label: "Andorra" }, + { value: "AO", label: "Angola" }, + { value: "AI", label: "Anguilla" }, + { value: "AQ", label: "Antarctica" }, + { value: "AG", label: "Antigua and Barbuda" }, + { value: "AR", label: "Argentina" }, + { value: "AM", label: "Armenia" }, + { value: "AW", label: "Aruba" }, + { value: "AU", label: "Australia" }, + { value: "AT", label: "Austria" }, + { value: "AZ", label: "Azerbaijan" }, + { value: "BS", label: "Bahamas (The)" }, + { value: "BH", label: "Bahrain" }, + { value: "BD", label: "Bangladesh" }, + { value: "BB", label: "Barbados" }, + { value: "BY", label: "Belarus" }, + { value: "BE", label: "Belgium" }, + { value: "BZ", label: "Belize" }, + { value: "BJ", label: "Benin" }, + { value: "BM", label: "Bermuda" }, + { value: "BT", label: "Bhutan" }, + { value: "BO", label: "Bolivia (Plurinational State of)" }, + { value: "BQ", label: "Bonaire, Sint Eustatius and Saba" }, + { value: "BA", label: "Bosnia and Herzegovina" }, + { value: "BW", label: "Botswana" }, + { value: "BV", label: "Bouvet Island" }, + { value: "BR", label: "Brazil" }, + { value: "IO", label: "British Indian Ocean Territory" }, + { value: "BN", label: "Brunei Darussalam" }, + { value: "BG", label: "Bulgaria" }, + { value: "BF", label: "Burkina Faso" }, + { value: "BI", label: "Burundi" }, + { value: "CV", label: "Cabo Verde" }, + { value: "KH", label: "Cambodia" }, + { value: "CM", label: "Cameroon" }, + { value: "CA", label: "Canada" }, + { value: "KY", label: "Cayman Islands" }, + { value: "CF", label: "Central African Republic" }, + { value: "TD", label: "Chad" }, + { value: "CL", label: "Chile" }, + { value: "CN", label: "China" }, + { value: "CX", label: "Christmas Island" }, + { value: "CC", label: "Cocos (Keeling) Islands" }, + { value: "CO", label: "Colombia" }, + { value: "KM", label: "Comoros" }, + { value: "CG", label: "Congo (Republic of the)" }, + { value: "CD", label: "Congo (Democratic Republic of the)" }, + { value: "CK", label: "Cook Islands" }, + { value: "CR", label: "Costa Rica" }, + { value: "CI", label: "Côte d’Ivoire" }, + { value: "HR", label: "Croatia" }, + { value: "CU", label: "Cuba" }, + { value: "CW", label: "Curaçao" }, + { value: "CY", label: "Cyprus" }, + { value: "CZ", label: "Czechia" }, + { value: "DK", label: "Denmark" }, + { value: "DJ", label: "Djibouti" }, + { value: "DM", label: "Dominica" }, + { value: "DO", label: "Dominican Republic" }, + { value: "EC", label: "Ecuador" }, + { value: "EG", label: "Egypt" }, + { value: "SV", label: "El Salvador" }, + { value: "GQ", label: "Equatorial Guinea" }, + { value: "ER", label: "Eritrea" }, + { value: "EE", label: "Estonia" }, + { value: "SZ", label: "Eswatini" }, + { value: "ET", label: "Ethiopia" }, + { value: "FK", label: "Falkland Islands (Malvinas)" }, + { value: "FO", label: "Faroe Islands" }, + { value: "FJ", label: "Fiji" }, + { value: "FI", label: "Finland" }, + { value: "FR", label: "France" }, + { value: "GF", label: "French Guiana" }, + { value: "PF", label: "French Polynesia" }, + { value: "TF", label: "French Southern Territories" }, + { value: "GA", label: "Gabon" }, + { value: "GM", label: "Gambia (The)" }, + { value: "GE", label: "Georgia" }, + { value: "DE", label: "Germany" }, + { value: "GH", label: "Ghana" }, + { value: "GI", label: "Gibraltar" }, + { value: "GR", label: "Greece" }, + { value: "GL", label: "Greenland" }, + { value: "GD", label: "Grenada" }, + { value: "GP", label: "Guadeloupe" }, + { value: "GU", label: "Guam" }, + { value: "GT", label: "Guatemala" }, + { value: "GG", label: "Guernsey" }, + { value: "GN", label: "Guinea" }, + { value: "GW", label: "Guinea-Bissau" }, + { value: "GY", label: "Guyana" }, + { value: "HT", label: "Haiti" }, + { value: "HM", label: "Heard Island and McDonald Islands" }, + { value: "VA", label: "Holy See (Vatican City State)" }, + { value: "HN", label: "Honduras" }, + { value: "HK", label: "Hong Kong" }, + { value: "HU", label: "Hungary" }, + { value: "IS", label: "Iceland" }, + { value: "IN", label: "India" }, + { value: "ID", label: "Indonesia" }, + { value: "IR", label: "Iran" }, + { value: "IQ", label: "Iraq" }, + { value: "IE", label: "Ireland" }, + { value: "IM", label: "Isle of Man" }, + { value: "IL", label: "Israel" }, + { value: "IT", label: "Italy" }, + { value: "JM", label: "Jamaica" }, + { value: "JP", label: "Japan" }, + { value: "JE", label: "Jersey" }, + { value: "JO", label: "Jordan" }, + { value: "KZ", label: "Kazakhstan" }, + { value: "KE", label: "Kenya" }, + { value: "KI", label: "Kiribati" }, + { value: "KP", label: "Korea (Democratic People’s Republic of)" }, + { value: "KR", label: "Korea (Republic of)" }, + { value: "KW", label: "Kuwait" }, + { value: "KG", label: "Kyrgyzstan" }, + { value: "LA", label: "Lao People’s Democratic Republic" }, + { value: "LV", label: "Latvia" }, + { value: "LB", label: "Lebanon" }, + { value: "LS", label: "Lesotho" }, + { value: "LR", label: "Liberia" }, + { value: "LY", label: "Libya" }, + { value: "LI", label: "Liechtenstein" }, + { value: "LT", label: "Lithuania" }, + { value: "LU", label: "Luxembourg" }, + { value: "MO", label: "Macao" }, + { value: "MG", label: "Madagascar" }, + { value: "MW", label: "Malawi" }, + { value: "MY", label: "Malaysia" }, + { value: "MV", label: "Maldives" }, + { value: "ML", label: "Mali" }, + { value: "MT", label: "Malta" }, + { value: "MH", label: "Marshall Islands" }, + { value: "MQ", label: "Martinique" }, + { value: "MR", label: "Mauritania" }, + { value: "MU", label: "Mauritius" }, + { value: "YT", label: "Mayotte" }, + { value: "MX", label: "Mexico" }, + { value: "FM", label: "Micronesia (Federated States of)" }, + { value: "MD", label: "Moldova (Republic of)" }, + { value: "MC", label: "Monaco" }, + { value: "MN", label: "Mongolia" }, + { value: "ME", label: "Montenegro" }, + { value: "MS", label: "Montserrat" }, + { value: "MA", label: "Morocco" }, + { value: "MZ", label: "Mozambique" }, + { value: "MM", label: "Myanmar" }, + { value: "NA", label: "Namibia" }, + { value: "NR", label: "Nauru" }, + { value: "NP", label: "Nepal" }, + { value: "NL", label: "Netherlands (The)" }, + { value: "NC", label: "New Caledonia" }, + { value: "NZ", label: "New Zealand" }, + { value: "NI", label: "Nicaragua" }, + { value: "NE", label: "Niger (The)" }, + { value: "NG", label: "Nigeria" }, + { value: "NU", label: "Niue" }, + { value: "NF", label: "Norfolk Island" }, + { value: "MK", label: "North Macedonia" }, + { value: "MP", label: "Northern Mariana Islands" }, + { value: "NO", label: "Norway" }, + { value: "OM", label: "Oman" }, + { value: "PK", label: "Pakistan" }, + { value: "PW", label: "Palau" }, + { value: "PS", label: "Palestine, State of" }, + { value: "PA", label: "Panama" }, + { value: "PG", label: "Papua New Guinea" }, + { value: "PY", label: "Paraguay" }, + { value: "PE", label: "Peru" }, + { value: "PH", label: "Philippines (The)" }, + { value: "PN", label: "Pitcairn" }, + { value: "PL", label: "Poland" }, + { value: "PT", label: "Portugal" }, + { value: "PR", label: "Puerto Rico" }, + { value: "QA", label: "Qatar" }, + { value: "RE", label: "Réunion" }, + { value: "RO", label: "Romania" }, + { value: "RU", label: "Russian Federation (The)" }, + { value: "RW", label: "Rwanda" }, + { value: "BL", label: "Saint Barthélemy" }, + { value: "SH", label: "Saint Helena, Ascension and Tristan da Cunha" }, + { value: "KN", label: "Saint Kitts and Nevis" }, + { value: "LC", label: "Saint Lucia" }, + { value: "MF", label: "Saint Martin (French part)" }, + { value: "PM", label: "Saint Pierre and Miquelon" }, + { value: "VC", label: "Saint Vincent and the Grenadines" }, + { value: "WS", label: "Samoa" }, + { value: "SM", label: "San Marino" }, + { value: "ST", label: "Sao Tome and Principe" }, + { value: "SA", label: "Saudi Arabia" }, + { value: "SN", label: "Senegal" }, + { value: "RS", label: "Serbia" }, + { value: "SC", label: "Seychelles" }, + { value: "SL", label: "Sierra Leone" }, + { value: "SG", label: "Singapore" }, + { value: "SX", label: "Sint Maarten (Dutch part)" }, + { value: "SK", label: "Slovakia" }, + { value: "SI", label: "Slovenia" }, + { value: "SB", label: "Solomon Islands" }, + { value: "SO", label: "Somalia" }, + { value: "ZA", label: "South Africa" }, + { value: "GS", label: "South Georgia and the South Sandwich Islands" }, + { value: "SS", label: "South Sudan" }, + { value: "ES", label: "Spain" }, + { value: "LK", label: "Sri Lanka" }, + { value: "SD", label: "Sudan (The)" }, + { value: "SR", label: "Suriname" }, + { value: "SJ", label: "Svalbard and Jan Mayen" }, + { value: "SE", label: "Sweden" }, + { value: "CH", label: "Switzerland" }, + { value: "SY", label: "Syrian Arab Republic" }, + { value: "TW", label: "Taiwan (Province of China)" }, + { value: "TJ", label: "Tajikistan" }, + { value: "TZ", label: "Tanzania (United Republic of)" }, + { value: "TH", label: "Thailand" }, + { value: "TL", label: "Timor-Leste" }, + { value: "TG", label: "Togo" }, + { value: "TK", label: "Tokelau" }, + { value: "TO", label: "Tonga" }, + { value: "TT", label: "Trinidad and Tobago" }, + { value: "TN", label: "Tunisia" }, + { value: "TR", label: "Türkiye" }, + { value: "TM", label: "Turkmenistan" }, + { value: "TC", label: "Turks and Caicos Islands" }, + { value: "TV", label: "Tuvalu" }, + { value: "UG", label: "Uganda" }, + { value: "UA", label: "Ukraine" }, + { value: "AE", label: "United Arab Emirates" }, + { + value: "GB", + label: "United Kingdom of Great Britain and Northern Ireland", + }, + { value: "US", label: "United States of America" }, + { value: "UM", label: "United States Minor Outlying Islands" }, + { value: "UY", label: "Uruguay" }, + { value: "UZ", label: "Uzbekistan" }, + { value: "VU", label: "Vanuatu" }, + { value: "VE", label: "Venezuela (Bolivarian Republic of)" }, + { value: "VN", label: "Viet Nam" }, + { value: "VG", label: "Virgin Islands (British)" }, + { value: "VI", label: "Virgin Islands (U.S.)" }, + { value: "WF", label: "Wallis and Futuna" }, + { + value: "EH", + label: + "Western Sahara (Sahrawi Arab Democratic Republic claimed by Morocco)", + }, + { value: "YE", label: "Yemen" }, + { value: "ZM", label: "Zambia" }, + { value: "ZW", label: "Zimbabwe" }, +]; From b444f6a08f26a2380814fa3bd076b9b9fd65ee1a Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 11 Nov 2025 10:12:21 +0330 Subject: [PATCH 19/21] feat(cms): Add CMS table with delete and edit functionality - Implement CMS table with row listing and pagination - Add delete functionality for CMS entries with confirmation modal - Introduce edit button to navigate to edit page for specific CMS entry - Update page type form to include new 'key' field for CMS entries - Modify CMS service and types to support new data structure - Enhance table presenter with additional actions and state management Resolves data management and user interaction improvements for CMS module --- src/app/marketing/create/page.tsx | 23 ++++- src/components/Tables/cms/index.tsx | 92 ++++++++++++++++++- .../Tables/cms/useCmsTablePresenter.ts | 27 +++++- src/components/Tables/contact-us/index.tsx | 5 +- src/hooks/queries/useCmsQueries.ts | 31 ++++++- src/lib/api/services/cms-service.ts | 8 ++ src/lib/api/types/TCms.ts | 3 +- 7 files changed, 178 insertions(+), 11 deletions(-) diff --git a/src/app/marketing/create/page.tsx b/src/app/marketing/create/page.tsx index 632ed8a..6d641ce 100644 --- a/src/app/marketing/create/page.tsx +++ b/src/app/marketing/create/page.tsx @@ -24,6 +24,7 @@ interface IPageTypeFields { language?: string; company_name?: string; country?: string; + key: string; } const Marketing = () => { @@ -152,7 +153,8 @@ const Marketing = () => { const finalData = { ...allData, - page_type: pageTypeData.type, + type: pageTypeData.type, + key: pageTypeData.key, ...(pageTypeData.type === "company" && { language: pageTypeData.language, company_name: pageTypeData.company_name, @@ -229,7 +231,7 @@ const Marketing = () => { {...registerPageType("type", { required: { message: FormErrorMessages.required, value: true }, })} - name="page_type" + name="type" label="Page Type" required items={[ @@ -310,6 +312,23 @@ const Marketing = () => { {pageTypeErrors.country.message}

)} + + {pageTypeErrors.key && ( +

+ {pageTypeErrors.key.message} +

+ )}
diff --git a/src/components/Tables/cms/index.tsx b/src/components/Tables/cms/index.tsx index 956a230..7c0c7d7 100644 --- a/src/components/Tables/cms/index.tsx +++ b/src/components/Tables/cms/index.tsx @@ -1,13 +1,29 @@ "use client"; +import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; +import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ListHeader from "@/components/ui/ListHeader"; -import { Table, TableBody, TableHead } from "@/components/ui/table"; +import { Table, TableBody, TableCell, TableHead } from "@/components/ui/table"; import TableSkeleton from "@/components/ui/TableSkeleton"; +import { _TooltipDefaultParams } from "@/constants/tooltip"; +import { unixToDate } from "@/utils/shared"; +import { Tooltip } from "react-tooltip"; import { TableHeader, TableRow } from "../../ui/table"; import useCmsTablePresenter from "./useCmsTablePresenter"; const CmsTable = () => { - const { columns, data, isPending } = useCmsTablePresenter(); + const { + columns, + data, + isPending, + isModalOpen, + setIsModalOpen, + currentCms, + setCurrentCms, + onConfirmDelete, + router, + pathname, + } = useCmsTablePresenter(); return (
@@ -28,10 +44,80 @@ const CmsTable = () => { columns={columns} emptyMessage="No Cms were found" > - Cms list will be added + {data?.data.cms.map((item, index) => ( + + + {index + 1} + + + {item.key} + + + {unixToDate({ unix: item.created_at, hasTime: true })} + + +
+ + + {/* */} +
+
+
+ ))} + {isModalOpen && ( + { + onConfirmDelete(); + }} + onCancel={() => setIsModalOpen(false)} + /> + )}
); }; diff --git a/src/components/Tables/cms/useCmsTablePresenter.ts b/src/components/Tables/cms/useCmsTablePresenter.ts index faaa018..f432a1b 100644 --- a/src/components/Tables/cms/useCmsTablePresenter.ts +++ b/src/components/Tables/cms/useCmsTablePresenter.ts @@ -1,14 +1,35 @@ "use client"; -import { useGetCmsList } from "@/hooks/queries/useCmsQueries"; +import { useDeleteCms, useGetCmsList } from "@/hooks/queries/useCmsQueries"; +import { TCms } from "@/lib/api/types/TCms"; +import { usePathname, useRouter } from "next/navigation"; +import { useState } from "react"; const useCmsTablePresenter = () => { - const columns = ["row", "title", "created at"]; + const [isModalOpen, setIsModalOpen] = useState(false); + const [currentCms, setCurrentCms] = useState(null); + const columns = ["row", "key", "created at", "actions"]; const { isPending, data } = useGetCmsList(); - + const router = useRouter(); + const pathname = usePathname(); + const { mutate: deleteCms } = useDeleteCms(() => { + setIsModalOpen(false); + setCurrentCms(null); + }); + const onConfirmDelete = () => { + if (!currentCms?.id) return; + deleteCms(currentCms.id); + }; return { columns, isPending, data, + isModalOpen, + setIsModalOpen, + currentCms, + setCurrentCms, + onConfirmDelete, + router, + pathname, }; }; diff --git a/src/components/Tables/contact-us/index.tsx b/src/components/Tables/contact-us/index.tsx index 796a9da..521b02d 100644 --- a/src/components/Tables/contact-us/index.tsx +++ b/src/components/Tables/contact-us/index.tsx @@ -3,6 +3,7 @@ import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; import { Table, TableBody, + TableCell, TableHead, TableHeader, TableRow, @@ -36,7 +37,9 @@ const ContactUsTable = () => { columns={columns} emptyMessage="No contact us is submitted" > - Contact us table will be initialized here + +
hjgfjhg
+
diff --git a/src/hooks/queries/useCmsQueries.ts b/src/hooks/queries/useCmsQueries.ts index fc26074..9c91ee6 100644 --- a/src/hooks/queries/useCmsQueries.ts +++ b/src/hooks/queries/useCmsQueries.ts @@ -1,7 +1,9 @@ import { API_ENDPOINTS } from "@/lib/api"; import { cmsService } from "@/lib/api/services/cms-service"; -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; import { useMemo } from "react"; +import { toast } from "react-toastify"; export const useGetCmsList = () => { const queryKey = useMemo(() => [API_ENDPOINTS.CMS.BASE()], []); @@ -18,6 +20,8 @@ export const useGetCmsDetails = (id: string) => { }); }; export const useCreateCms = () => { + const router = useRouter(); + const queryClient = useQueryClient(); const mutationKey = useMemo( () => [API_ENDPOINTS.CMS.BASE(), "Create cms"], [], @@ -25,5 +29,30 @@ export const useCreateCms = () => { return useMutation({ mutationKey, mutationFn: cmsService.createCms, + onSuccess: ({ data }: { data: { message: string } }) => { + toast.success(data.message); + router.push("/marketing"); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.CMS.BASE()], + }); + }, + }); +}; +export const useDeleteCms = (successCallback?: () => void) => { + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.CMS.BASE(), "Delete CMS"], + [], + ); + return useMutation({ + mutationKey, + mutationFn: cmsService.deleteCms, + onSuccess: ({ data }: { data: { message: string } }) => { + toast.success(data.message); + successCallback?.(); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.CMS.BASE()], + }); + }, }); }; diff --git a/src/lib/api/services/cms-service.ts b/src/lib/api/services/cms-service.ts index 3dcfaa8..0548978 100644 --- a/src/lib/api/services/cms-service.ts +++ b/src/lib/api/services/cms-service.ts @@ -28,4 +28,12 @@ export const cmsService = { throw error; } }, + deleteCms: async (id: string) => { + try { + return (await api.delete(API_ENDPOINTS.CMS.BASE(id))).data; + } catch (error) { + console.error("Error caught in CMS Service => delete CMS: ", error); + throw error; + } + }, }; diff --git a/src/lib/api/types/TCms.ts b/src/lib/api/types/TCms.ts index 4b4c0da..b8d25d0 100644 --- a/src/lib/api/types/TCms.ts +++ b/src/lib/api/types/TCms.ts @@ -155,6 +155,7 @@ export const cmsListResponseSchema = z.object({ export const cmsDetailsResponse = z.object({ cms: CmsSchema, }); +export type TCms = z.infer; export type TCmsListResponse = z.infer; -export type TCmsDetailsResponse = z.infer +export type TCmsDetailsResponse = z.infer; export type TCmsCredentials = z.infer; From bd79b7ed143c926abb2d9452d01ef0c370eac5cb Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 11 Nov 2025 10:43:32 +0330 Subject: [PATCH 20/21] 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 --- .../marketing/_components/BenefitsStep.tsx | 8 +- .../marketing/_components/ChallengesStep.tsx | 68 ++-- src/app/marketing/_components/ChartStep.tsx | 244 ++++++------ .../marketing/_components/ContactsStep.tsx | 165 ++++---- .../marketing/_components/FeaturesStep.tsx | 68 ++-- src/app/marketing/_components/FooterStep.tsx | 136 ++++--- src/app/marketing/_components/HeroStep.tsx | 16 +- .../marketing/_components/MarketingForm.tsx | 371 ++++++++++++++++++ .../_components/useBenefitsStepPresenter.ts | 4 +- .../_components/useChallengesStepPresenter.ts | 4 +- .../_components/useChartStepPresenter.ts | 4 +- .../_components/useContactsStepPresenter.ts | 4 +- .../_components/useFeaturesStepPresenter.ts | 4 +- .../_components/useFooterStepPresenter.ts | 8 +- .../_components/useHeroStepPresenter.ts | 6 +- src/app/marketing/create/page.tsx | 343 +--------------- src/app/marketing/edit/[id]/page.tsx | 39 +- src/hooks/queries/useCmsQueries.ts | 22 ++ src/lib/api/services/cms-service.ts | 16 +- 19 files changed, 842 insertions(+), 688 deletions(-) create mode 100644 src/app/marketing/_components/MarketingForm.tsx diff --git a/src/app/marketing/_components/BenefitsStep.tsx b/src/app/marketing/_components/BenefitsStep.tsx index 1b3e527..03d38dc 100644 --- a/src/app/marketing/_components/BenefitsStep.tsx +++ b/src/app/marketing/_components/BenefitsStep.tsx @@ -5,7 +5,11 @@ import useBenefitsStepPresenter, { IBenefitsStepFields, } from "./useBenefitsStepPresenter"; -const BenefitsStep = forwardRef((props, ref) => { +interface BenefitsStepProps { + initialData?: IBenefitsStepFields; +} + +const BenefitsStep = forwardRef(({ initialData }, ref) => { const { errors, handleSubmit, @@ -16,7 +20,7 @@ const BenefitsStep = forwardRef((props, ref) => { remove, control, getValues, - } = useBenefitsStepPresenter(); + } = useBenefitsStepPresenter(initialData); useImperativeHandle(ref, () => ({ getData: () => getValues(), diff --git a/src/app/marketing/_components/ChallengesStep.tsx b/src/app/marketing/_components/ChallengesStep.tsx index 8bdc53e..7a6c9a4 100644 --- a/src/app/marketing/_components/ChallengesStep.tsx +++ b/src/app/marketing/_components/ChallengesStep.tsx @@ -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( + ({ initialData }, ref) => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + getValues, + } = useChallengesStepPresenter(initialData); - return ( - - 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 ( + + 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"; diff --git a/src/app/marketing/_components/ChartStep.tsx b/src/app/marketing/_components/ChartStep.tsx index f34bddd..053f0f2 100644 --- a/src/app/marketing/_components/ChartStep.tsx +++ b/src/app/marketing/_components/ChartStep.tsx @@ -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( + ({ 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 ( - -
- - - {errors.title && ( -

- {errors.title.message} -

- )} - - {errors.missedAmount && ( -

- {errors.missedAmount.message} -

- )} -
- - p.key && p.value) - .map((p) => ({ x: p.key, y: p.value })) || [] - } - title={chartTitle} - /> - - - {fields.map((field, index) => ( -
- - - -
- ))} - -
-
-
- - ); -}); + + {errors.title && ( +

+ {errors.title.message} +

+ )} + + {errors.missedAmount && ( +

+ {errors.missedAmount.message} +

+ )} + + + p.key && p.value) + .map((p) => ({ x: p.key, y: p.value })) || [] + } + title={chartTitle} + /> + + + {fields.map((field, index) => ( +
+ + + +
+ ))} + +
+ +
+ + ); + }, +); ChartStep.displayName = "ChartStep"; diff --git a/src/app/marketing/_components/ContactsStep.tsx b/src/app/marketing/_components/ContactsStep.tsx index a5172d1..5f1a5d8 100644 --- a/src/app/marketing/_components/ContactsStep.tsx +++ b/src/app/marketing/_components/ContactsStep.tsx @@ -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( + ({ initialData }, ref) => { + const { errors, handleSubmit, onSubmit, register, fields, getValues } = + useContactsStepPresenter(initialData); - return ( -
- -
- - {errors.title && ( -

- {errors.title.message as string} -

- )} - - -
-
- - {fields.map((field, index) => ( -
+ useImperativeHandle(ref, () => ({ + getData: () => getValues(), + })); + + return ( + + +
+ {errors.title && ( +

+ {errors.title.message as string} +

+ )} + - -
- ))} -
- - ); -}); + + + {fields.map((field, index) => ( +
+ + + + +
+ ))} +
+ + ); + }, +); ContactsStep.displayName = "ContactsStep"; diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/_components/FeaturesStep.tsx index 23ecc43..24e6633 100644 --- a/src/app/marketing/_components/FeaturesStep.tsx +++ b/src/app/marketing/_components/FeaturesStep.tsx @@ -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( + ({ initialData }, ref) => { + const { + errors, + handleSubmit, + onSubmit, + register, + fields, + append, + remove, + control, + getValues, + } = useFeaturesStepPresenter(initialData); - return ( - - 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 ( + + 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"; diff --git a/src/app/marketing/_components/FooterStep.tsx b/src/app/marketing/_components/FooterStep.tsx index 1834211..3c930af 100644 --- a/src/app/marketing/_components/FooterStep.tsx +++ b/src/app/marketing/_components/FooterStep.tsx @@ -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( + ({ initialData }, ref) => { + const { errors, handleSubmit, onSubmit, register, getValues } = + useFooterStepPresenter(initialData); - return ( -
- -
- - {errors.email && ( -

- {errors.email.message as string} -

- )} - - - - -
-
-
- ); -}); + useImperativeHandle(ref, () => ({ + getData: () => getValues(), + })); + + return ( +
+ +
+ + {errors.email && ( +

+ {errors.email.message as string} +

+ )} + + + + +
+
+
+ ); + }, +); FooterStep.displayName = "FooterStep"; -export default FooterStep; \ No newline at end of file +export default FooterStep; diff --git a/src/app/marketing/_components/HeroStep.tsx b/src/app/marketing/_components/HeroStep.tsx index fd42846..2e7cf2a 100644 --- a/src/app/marketing/_components/HeroStep.tsx +++ b/src/app/marketing/_components/HeroStep.tsx @@ -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(({ initialData }, ref) => { + const { errors, handleSubmit, onSubmit, register, getValues } = + useHeroStepPresenter(initialData); useImperativeHandle(ref, () => ({ getData: () => getValues(), @@ -16,7 +21,7 @@ const HeroStep = forwardRef((props, ref) => { return (
@@ -114,7 +119,10 @@ const HeroStep = forwardRef((props, ref) => { void; + isPending: boolean; + initialData?: any; +} + +const MarketingForm = ({ + breadcrumbItems, + title, + onSubmit, + isPending, + initialData, +}: MarketingFormProps) => { + const [step, setStep] = useState(0); + const [formData, setFormData] = useState(initialData || {}); + const heroRef = useRef(null); + const chartRef = useRef(null); + const featuresRef = useRef(null); + const challengesRef = useRef(null); + const benefitsRef = useRef(null); + const contactsRef = useRef(null); + const footerRef = useRef(null); + + const { + register: registerPageType, + watch: watchPageType, + formState: { errors: pageTypeErrors }, + getValues: getPageTypeValues, + setValue, + reset, + } = useForm({ + 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 ( + <> + + +
+

+ Page Configuration +

+
+ } + disabled={!isCompany} + required={isCompany} + placeholder="Please select country" + onClear={() => setValue("country", undefined)} + /> + {pageTypeErrors.country && ( +

+ {pageTypeErrors.country.message} +

+ )} + + {pageTypeErrors.key && ( +

+ {pageTypeErrors.key.message} +

+ )} +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+ + ); +}; + +export default MarketingForm; diff --git a/src/app/marketing/_components/useBenefitsStepPresenter.ts b/src/app/marketing/_components/useBenefitsStepPresenter.ts index ee8d9c1..104037b 100644 --- a/src/app/marketing/_components/useBenefitsStepPresenter.ts +++ b/src/app/marketing/_components/useBenefitsStepPresenter.ts @@ -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({ - defaultValues: { + defaultValues: initialData || { cards: [{ icon: "", title: "", description: "" }], }, }); diff --git a/src/app/marketing/_components/useChallengesStepPresenter.ts b/src/app/marketing/_components/useChallengesStepPresenter.ts index fe23048..f11ee66 100644 --- a/src/app/marketing/_components/useChallengesStepPresenter.ts +++ b/src/app/marketing/_components/useChallengesStepPresenter.ts @@ -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({ - defaultValues: { + defaultValues: initialData || { cards: [{ icon: "", title: "", description: "" }], }, }); diff --git a/src/app/marketing/_components/useChartStepPresenter.ts b/src/app/marketing/_components/useChartStepPresenter.ts index 18a2797..54bb60d 100644 --- a/src/app/marketing/_components/useChartStepPresenter.ts +++ b/src/app/marketing/_components/useChartStepPresenter.ts @@ -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({ - defaultValues: { + defaultValues: initialData || { data: [{ key: "", value: 0 }], }, }); diff --git a/src/app/marketing/_components/useContactsStepPresenter.ts b/src/app/marketing/_components/useContactsStepPresenter.ts index 0101dce..72caaf4 100644 --- a/src/app/marketing/_components/useContactsStepPresenter.ts +++ b/src/app/marketing/_components/useContactsStepPresenter.ts @@ -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({ - defaultValues: { + defaultValues: initialData || { fields: [ { name: "first_name", diff --git a/src/app/marketing/_components/useFeaturesStepPresenter.ts b/src/app/marketing/_components/useFeaturesStepPresenter.ts index dfc581c..20623a5 100644 --- a/src/app/marketing/_components/useFeaturesStepPresenter.ts +++ b/src/app/marketing/_components/useFeaturesStepPresenter.ts @@ -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({ - defaultValues: { + defaultValues: initialData || { cards: [{ icon: "", title: "", description: "" }], }, }); diff --git a/src/app/marketing/_components/useFooterStepPresenter.ts b/src/app/marketing/_components/useFooterStepPresenter.ts index fde1b0d..477afca 100644 --- a/src/app/marketing/_components/useFooterStepPresenter.ts +++ b/src/app/marketing/_components/useFooterStepPresenter.ts @@ -8,13 +8,15 @@ interface FooterFormValues { copyright: string; } -const useFooterStepPresenter = () => { +const useFooterStepPresenter = (initialData?: FooterFormValues) => { const { register, handleSubmit, formState: { errors }, getValues, - } = useForm(); + } = useForm({ + defaultValues: initialData, + }); const onSubmit = (data: FooterFormValues) => { console.log(data); @@ -34,4 +36,4 @@ const useFooterStepPresenter = () => { }; }; -export default useFooterStepPresenter; \ No newline at end of file +export default useFooterStepPresenter; diff --git a/src/app/marketing/_components/useHeroStepPresenter.ts b/src/app/marketing/_components/useHeroStepPresenter.ts index 5e7d74f..a7b826d 100644 --- a/src/app/marketing/_components/useHeroStepPresenter.ts +++ b/src/app/marketing/_components/useHeroStepPresenter.ts @@ -9,13 +9,15 @@ export interface IHeroStepFields { gifFile: string; } -const useHeroStepPresenter = () => { +const useHeroStepPresenter = (initialData?: IHeroStepFields) => { const { register, handleSubmit, formState: { errors }, getValues, - } = useForm(); + } = useForm({ + defaultValues: initialData, + }); const onSubmit = (data: IHeroStepFields) => { console.log(data); diff --git a/src/app/marketing/create/page.tsx b/src/app/marketing/create/page.tsx index 6d641ce..797d119 100644 --- a/src/app/marketing/create/page.tsx +++ b/src/app/marketing/create/page.tsx @@ -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({}); - const heroRef = useRef(null); - const chartRef = useRef(null); - const featuresRef = useRef(null); - const challengesRef = useRef(null); - const benefitsRef = useRef(null); - const contactsRef = useRef(null); - const footerRef = useRef(null); - - const { - register: registerPageType, - watch: watchPageType, - formState: { errors: pageTypeErrors }, - getValues: getPageTypeValues, - setValue, - } = useForm({ - 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 ( - <> - - - {/* Page Type Selection */} -
-

- Page Configuration -

-
- } - disabled={!isCompany} - required={isCompany} - placeholder="Please select country" - onClear={() => setValue("country", undefined)} - /> - {pageTypeErrors.country && ( -

- {pageTypeErrors.country.message} -

- )} - - {pageTypeErrors.key && ( -

- {pageTypeErrors.key.message} -

- )} -
-
- - {/* Render all steps but only show the current one */} -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- - -
- + ); }; diff --git a/src/app/marketing/edit/[id]/page.tsx b/src/app/marketing/edit/[id]/page.tsx index 676faae..d1288e0 100644 --- a/src/app/marketing/edit/[id]/page.tsx +++ b/src/app/marketing/edit/[id]/page.tsx @@ -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 ; + + 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 ; + return ( - <> - - + ); }; + +export default EditMarketingPage; diff --git a/src/hooks/queries/useCmsQueries.ts b/src/hooks/queries/useCmsQueries.ts index 9c91ee6..c415cb2 100644 --- a/src/hooks/queries/useCmsQueries.ts +++ b/src/hooks/queries/useCmsQueries.ts @@ -56,3 +56,25 @@ export const useDeleteCms = (successCallback?: () => void) => { }, }); }; +export const useUpdateCms = (id: string) => { + const router = useRouter(); + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.CMS.BASE(id), "Update cms"], + [id], + ); + return useMutation({ + mutationKey, + mutationFn: (data: any) => cmsService.updateCms({ id, data }), + onSuccess: () => { + toast.success("CMS updated successfully"); + router.push("/marketing"); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.CMS.BASE()], + }); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.CMS.BASE(id)], + }); + }, + }); +}; diff --git a/src/lib/api/services/cms-service.ts b/src/lib/api/services/cms-service.ts index 0548978..05fb6d8 100644 --- a/src/lib/api/services/cms-service.ts +++ b/src/lib/api/services/cms-service.ts @@ -1,7 +1,7 @@ import api from "../axios"; import { API_ENDPOINTS } from "../endpoints"; import { ApiResponse } from "../types"; -import { TCmsListResponse } from "../types/TCms"; +import { TCms, TCmsListResponse } from "../types/TCms"; export const cmsService = { getCms: async (): Promise> => { @@ -36,4 +36,18 @@ export const cmsService = { throw error; } }, + updateCms: async ({ + id, + data, + }: { + id: string; + data: any; + }): Promise<{ data: TCms; message: string }> => { + try { + return (await api.put(API_ENDPOINTS.CMS.BASE(id), data)).data; + } catch (error) { + console.error("Error Caught in CMS Service => Update cms", error); + throw error; + } + }, }; From e75932d249c084ff87ce730deffd2421c8b002df Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 11 Nov 2025 10:52:45 +0330 Subject: [PATCH 21/21] refactor(sidebar): Remove commented out Contact Us navigation item - Commented out the "Contact Us" navigation item in sidebar data - Removed unused navigation entry to clean up sidebar configuration - Maintains existing navigation structure while reducing clutter --- src/components/Layouts/sidebar/data/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Layouts/sidebar/data/index.ts b/src/components/Layouts/sidebar/data/index.ts index 5e0e837..3c781b2 100644 --- a/src/components/Layouts/sidebar/data/index.ts +++ b/src/components/Layouts/sidebar/data/index.ts @@ -73,13 +73,13 @@ export const NAV_DATA: NavData = [ icon: Icons.NotificationIcon, items: [], }, - { - title: "Contact Us", - url: "/contact-us", - icon: Icons.CallIncome, - items: [], - visible: false, - }, + // { + // title: "Contact Us", + // url: "/contact-us", + // icon: Icons.CallIncome, + // items: [], + // visible: false, + // }, { title: "Marketing", url: "/marketing",