diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1 @@
+{}
diff --git a/package-lock.json b/package-lock.json
index 9b2745b..9021093 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,7 @@
"@tanstack/react-query": "^5.85.6",
"@types/js-cookie": "^3.0.6",
"@types/react-paginate": "^7.1.4",
- "apexcharts": "^4.5.0",
+ "apexcharts": "^4.7.0",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -26,7 +26,7 @@
"next-themes": "^0.4.4",
"nextjs-toploader": "^3.7.15",
"react": "19.0.0",
- "react-apexcharts": "^1.7.0",
+ "react-apexcharts": "^1.8.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.62.0",
"react-intersection-observer": "^9.16.0",
@@ -2353,9 +2353,9 @@
}
},
"node_modules/apexcharts": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-4.5.0.tgz",
- "integrity": "sha512-E7ZkrVqPNBUWy/Rmg8DEIqHNBmElzICE/oxOX5Ekvs2ICQUOK/VkEkMH09JGJu+O/EA0NL31hxlmF+wrwrSLaQ==",
+ "version": "4.7.0",
+ "resolved": "https://registry.npmmirror.com/apexcharts/-/apexcharts-4.7.0.tgz",
+ "integrity": "sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.draggable.js": "^3.0.4",
@@ -6200,10 +6200,10 @@
}
},
"node_modules/react-apexcharts": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.7.0.tgz",
- "integrity": "sha512-03oScKJyNLRf0Oe+ihJxFZliBQM9vW3UWwomVn4YVRTN1jsIR58dLWt0v1sb8RwJVHDMbeHiKQueM0KGpn7nOA==",
- "license": "MIT",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmmirror.com/react-apexcharts/-/react-apexcharts-1.8.0.tgz",
+ "integrity": "sha512-MuEp56gc0NMO2UUgY94fxQzoBE4XEjmcCha4xYY0vJdRrc1yfFFZE4QrCekOt2wcS3nibghzca/q/CbgkAgN5w==",
+ "license": "SEE LICENSE IN LICENSE",
"dependencies": {
"prop-types": "^15.8.1"
},
diff --git a/package.json b/package.json
index cf6d5b2..d456ffe 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"@tanstack/react-query": "^5.85.6",
"@types/js-cookie": "^3.0.6",
"@types/react-paginate": "^7.1.4",
- "apexcharts": "^4.5.0",
+ "apexcharts": "^4.7.0",
"axios": "^1.11.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -27,7 +27,7 @@
"next-themes": "^0.4.4",
"nextjs-toploader": "^3.7.15",
"react": "19.0.0",
- "react-apexcharts": "^1.7.0",
+ "react-apexcharts": "^1.8.0",
"react-dom": "19.0.0",
"react-hook-form": "^7.62.0",
"react-intersection-observer": "^9.16.0",
diff --git a/src/app/contact-us/page.tsx b/src/app/contact-us/page.tsx
new file mode 100644
index 0000000..9276e61
--- /dev/null
+++ b/src/app/contact-us/page.tsx
@@ -0,0 +1,24 @@
+import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
+import ContactUsTable from "@/components/Tables/contact-us";
+import { BreadcrumbItem } from "@/types/shared";
+
+const ContactUsPage = () => {
+ const breadcrumbItems: BreadcrumbItem[] = [
+ {
+ href: "/",
+ name: "Dashboard",
+ },
+ {
+ href: "/contact-us",
+ name: "Contact us",
+ },
+ ];
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default ContactUsPage;
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/BenefitsStep.tsx b/src/app/marketing/_components/BenefitsStep.tsx
new file mode 100644
index 0000000..03d38dc
--- /dev/null
+++ b/src/app/marketing/_components/BenefitsStep.tsx
@@ -0,0 +1,48 @@
+"use client";
+import { forwardRef, useImperativeHandle } from "react";
+import SectionStep from "./SectionStep";
+import useBenefitsStepPresenter, {
+ IBenefitsStepFields,
+} from "./useBenefitsStepPresenter";
+
+interface BenefitsStepProps {
+ initialData?: IBenefitsStepFields;
+}
+
+const BenefitsStep = forwardRef
(({ initialData }, ref) => {
+ const {
+ errors,
+ handleSubmit,
+ onSubmit,
+ register,
+ fields,
+ append,
+ remove,
+ control,
+ getValues,
+ } = useBenefitsStepPresenter(initialData);
+
+ useImperativeHandle(ref, () => ({
+ getData: () => getValues(),
+ }));
+
+ return (
+
+ title="Benefits"
+ fields={fields}
+ handleSubmit={handleSubmit}
+ onSubmit={onSubmit}
+ register={register}
+ errors={errors}
+ append={append}
+ remove={remove}
+ control={control}
+ fieldName="cards"
+ defaultValues={{ icon: "", title: "", description: "" }}
+ />
+ );
+});
+
+BenefitsStep.displayName = "BenefitsStep";
+
+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..7a6c9a4
--- /dev/null
+++ b/src/app/marketing/_components/ChallengesStep.tsx
@@ -0,0 +1,50 @@
+"use client";
+import { forwardRef, useImperativeHandle } from "react";
+import SectionStep from "./SectionStep";
+import useChallengesStepPresenter, {
+ IChallengesStepFields,
+} from "./useChallengesStepPresenter";
+
+interface ChallengesStepProps {
+ initialData?: IChallengesStepFields;
+}
+
+const ChallengesStep = forwardRef(
+ ({ initialData }, ref) => {
+ const {
+ errors,
+ handleSubmit,
+ onSubmit,
+ register,
+ fields,
+ append,
+ remove,
+ control,
+ getValues,
+ } = useChallengesStepPresenter(initialData);
+
+ 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";
+
+export default ChallengesStep;
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..053f0f2
--- /dev/null
+++ b/src/app/marketing/_components/ChartStep.tsx
@@ -0,0 +1,143 @@
+"use client";
+import InputGroup from "@/components/FormElements/InputGroup";
+import { ShowcaseSection } from "@/components/Layouts/showcase-section";
+
+import { TrashIcon } from "@/assets/icons";
+import { FormErrorMessages } from "@/constants/Texts";
+import { forwardRef, useImperativeHandle } from "react";
+import { ChartPreview } from "./ChartPreview";
+import useChartStepPresenter from "./useChartStepPresenter";
+
+interface ChartStepProps {
+ initialData?: any;
+}
+
+const ChartStep = forwardRef(
+ ({ initialData }, ref) => {
+ const {
+ errors,
+ handleSubmit,
+ onSubmit,
+ register,
+ fields,
+ append,
+ remove,
+ watch,
+ getValues,
+ } = useChartStepPresenter(initialData);
+
+ useImperativeHandle(ref, () => ({
+ getData: () => getValues(),
+ }));
+
+ const chartTitle = watch("title");
+ const chartData = watch("data");
+
+ return (
+
+ );
+ },
+);
+
+ChartStep.displayName = "ChartStep";
+
+export default ChartStep;
diff --git a/src/app/marketing/_components/ContactsStep.tsx b/src/app/marketing/_components/ContactsStep.tsx
new file mode 100644
index 0000000..5f1a5d8
--- /dev/null
+++ b/src/app/marketing/_components/ContactsStep.tsx
@@ -0,0 +1,109 @@
+"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 { forwardRef, useImperativeHandle } from "react";
+import useContactsStepPresenter from "./useContactsStepPresenter";
+
+interface ContactsStepProps {
+ initialData?: any;
+}
+
+const ContactsStep = forwardRef(
+ ({ initialData }, ref) => {
+ const { errors, handleSubmit, onSubmit, register, fields, getValues } =
+ useContactsStepPresenter(initialData);
+
+ useImperativeHandle(ref, () => ({
+ getData: () => getValues(),
+ }));
+
+ return (
+
+ );
+ },
+);
+
+ContactsStep.displayName = "ContactsStep";
+
+export default ContactsStep;
diff --git a/src/app/marketing/_components/FeaturesStep.tsx b/src/app/marketing/_components/FeaturesStep.tsx
new file mode 100644
index 0000000..24e6633
--- /dev/null
+++ b/src/app/marketing/_components/FeaturesStep.tsx
@@ -0,0 +1,50 @@
+"use client";
+import { forwardRef, useImperativeHandle } from "react";
+import SectionStep from "./SectionStep";
+import useFeaturesStepPresenter, {
+ IFeaturesStepFields,
+} from "./useFeaturesStepPresenter";
+
+interface FeaturesStepProps {
+ initialData?: IFeaturesStepFields;
+}
+
+const FeaturesStep = forwardRef(
+ ({ initialData }, ref) => {
+ const {
+ errors,
+ handleSubmit,
+ onSubmit,
+ register,
+ fields,
+ append,
+ remove,
+ control,
+ getValues,
+ } = useFeaturesStepPresenter(initialData);
+
+ 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";
+
+export default FeaturesStep;
diff --git a/src/app/marketing/_components/FooterStep.tsx b/src/app/marketing/_components/FooterStep.tsx
new file mode 100644
index 0000000..3c930af
--- /dev/null
+++ b/src/app/marketing/_components/FooterStep.tsx
@@ -0,0 +1,84 @@
+"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 { forwardRef, useImperativeHandle } from "react";
+import useFooterStepPresenter from "./useFooterStepPresenter";
+
+interface FooterStepProps {
+ initialData?: any;
+}
+
+const FooterStep = forwardRef(
+ ({ initialData }, ref) => {
+ const { errors, handleSubmit, onSubmit, register, getValues } =
+ useFooterStepPresenter(initialData);
+
+ useImperativeHandle(ref, () => ({
+ getData: () => getValues(),
+ }));
+
+ return (
+
+ );
+ },
+);
+
+FooterStep.displayName = "FooterStep";
+
+export default FooterStep;
diff --git a/src/app/marketing/_components/HeroStep.tsx b/src/app/marketing/_components/HeroStep.tsx
new file mode 100644
index 0000000..2e7cf2a
--- /dev/null
+++ b/src/app/marketing/_components/HeroStep.tsx
@@ -0,0 +1,146 @@
+"use client";
+import InputGroup from "@/components/FormElements/InputGroup";
+import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
+import { ShowcaseSection } from "@/components/Layouts/showcase-section";
+import { REGEX } from "@/constants/regex";
+import { FormErrorMessages } from "@/constants/Texts";
+import { forwardRef, useImperativeHandle } from "react";
+import useHeroStepPresenter from "./useHeroStepPresenter";
+
+interface HeroStepProps {
+ initialData?: any;
+}
+
+const HeroStep = forwardRef(({ initialData }, ref) => {
+ const { errors, handleSubmit, onSubmit, register, getValues } =
+ useHeroStepPresenter(initialData);
+
+ useImperativeHandle(ref, () => ({
+ getData: () => getValues(),
+ }));
+
+ return (
+
+ );
+});
+
+HeroStep.displayName = "HeroStep";
+
+export default HeroStep;
diff --git a/src/app/marketing/_components/MarketingForm.tsx b/src/app/marketing/_components/MarketingForm.tsx
new file mode 100644
index 0000000..3f161b2
--- /dev/null
+++ b/src/app/marketing/_components/MarketingForm.tsx
@@ -0,0 +1,371 @@
+"use client";
+import { GlobeIcon } from "@/assets/icons";
+import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
+import InputGroup from "@/components/FormElements/InputGroup";
+import { Select } from "@/components/FormElements/select";
+import { ShowcaseSection } from "@/components/Layouts/showcase-section";
+import Stepper, { Step } from "@/components/ui/Stepper";
+import { Countries } from "@/constants/countries";
+import { FormErrorMessages } from "@/constants/Texts";
+import { BreadcrumbItem } from "@/types/shared";
+import { useEffect, useRef, useState } from "react";
+import { useForm } from "react-hook-form";
+import BenefitsStep from "./BenefitsStep";
+import ChallengesStep from "./ChallengesStep";
+import ChartStep from "./ChartStep";
+import ContactsStep from "./ContactsStep";
+import FeaturesStep from "./FeaturesStep";
+import FooterStep from "./FooterStep";
+import HeroStep from "./HeroStep";
+
+interface IPageTypeFields {
+ type: "company" | "organization";
+ language?: string;
+ company_name?: string;
+ country?: string;
+ key: string;
+}
+
+interface MarketingFormProps {
+ breadcrumbItems: BreadcrumbItem[];
+ title: string;
+ onSubmit: (data: any) => void;
+ isPending: boolean;
+ initialData?: any;
+}
+
+const MarketingForm = ({
+ breadcrumbItems,
+ title,
+ onSubmit,
+ isPending,
+ initialData,
+}: MarketingFormProps) => {
+ const [step, setStep] = useState(0);
+ const [formData, setFormData] = useState(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
+
+
+
+ {pageTypeErrors.type && (
+
+ {pageTypeErrors.type.message}
+
+ )}
+
+
+ {pageTypeErrors.language && (
+
+ {pageTypeErrors.language.message}
+
+ )}
+
+
+ {pageTypeErrors.company_name && (
+
+ {pageTypeErrors.company_name.message}
+
+ )}
+
}
+ 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/SectionStep.tsx b/src/app/marketing/_components/SectionStep.tsx
new file mode 100644
index 0000000..087794a
--- /dev/null
+++ b/src/app/marketing/_components/SectionStep.tsx
@@ -0,0 +1,145 @@
+"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 { REGEX } from "@/constants/regex";
+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 (
+
+ );
+};
+
+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..104037b
--- /dev/null
+++ b/src/app/marketing/_components/useBenefitsStepPresenter.ts
@@ -0,0 +1,56 @@
+"use client";
+import { useFieldArray, useForm } from "react-hook-form";
+
+export interface IBenefitsStepFields {
+ title: string;
+ description: string;
+ cards: {
+ icon: string;
+ title: string;
+ description: string;
+ }[];
+}
+
+const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ watch,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData || {
+ cards: [{ icon: "", title: "", description: "" }],
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "cards",
+ });
+
+ const onSubmit = (data: IBenefitsStepFields) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ fields,
+ append,
+ remove,
+ control,
+ watch,
+ getValues,
+ collectData,
+ };
+};
+
+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..f11ee66
--- /dev/null
+++ b/src/app/marketing/_components/useChallengesStepPresenter.ts
@@ -0,0 +1,56 @@
+"use client";
+import { useFieldArray, useForm } from "react-hook-form";
+
+export interface IChallengesStepFields {
+ title: string;
+ description: string;
+ cards: {
+ icon: string;
+ title: string;
+ description: string;
+ }[];
+}
+
+const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ watch,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData || {
+ cards: [{ icon: "", title: "", description: "" }],
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "cards",
+ });
+
+ const onSubmit = (data: IChallengesStepFields) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ fields,
+ append,
+ remove,
+ control,
+ watch,
+ getValues,
+ collectData,
+ };
+};
+
+export default useChallengesStepPresenter;
diff --git a/src/app/marketing/_components/useChartStepPresenter.ts b/src/app/marketing/_components/useChartStepPresenter.ts
new file mode 100644
index 0000000..54bb60d
--- /dev/null
+++ b/src/app/marketing/_components/useChartStepPresenter.ts
@@ -0,0 +1,52 @@
+"use client";
+import { useFieldArray, useForm } from "react-hook-form";
+
+export interface IChartStepFields {
+ title: string;
+ missedAmount: string;
+ data: { key: string; value: number }[];
+}
+
+const useChartStepPresenter = (initialData?: IChartStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ watch,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData || {
+ data: [{ key: "", value: 0 }],
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "data",
+ });
+
+ const onSubmit = (data: IChartStepFields) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ fields,
+ append,
+ remove,
+ control,
+ watch,
+ getValues,
+ collectData,
+ };
+};
+
+export default useChartStepPresenter;
diff --git a/src/app/marketing/_components/useContactsStepPresenter.ts b/src/app/marketing/_components/useContactsStepPresenter.ts
new file mode 100644
index 0000000..72caaf4
--- /dev/null
+++ b/src/app/marketing/_components/useContactsStepPresenter.ts
@@ -0,0 +1,69 @@
+"use client";
+import { useFieldArray, useForm } from "react-hook-form";
+
+export interface IContactsStepFields {
+ title: string;
+ description: string;
+ submitButtonText: string;
+ fields: {
+ name: string;
+ label: string;
+ placeholder: string;
+ required: boolean;
+ }[];
+}
+
+const useContactsStepPresenter = (initialData?: IContactsStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ getValues,
+ formState: { errors },
+ } = useForm({
+ defaultValues: initialData || {
+ fields: [
+ {
+ name: "first_name",
+ label: "",
+ placeholder: "",
+ required: false,
+ },
+ {
+ name: "last_name",
+ label: "",
+ placeholder: "",
+ required: false,
+ },
+ { name: "email", label: "", placeholder: "", required: false },
+ { name: "phone", label: "", placeholder: "", required: false },
+ ],
+ },
+ });
+
+ const { fields } = useFieldArray({
+ control,
+ name: "fields",
+ });
+
+ const onSubmit = (data: IContactsStepFields) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ fields,
+ control,
+ collectData,
+ getValues,
+ };
+};
+
+export default useContactsStepPresenter;
diff --git a/src/app/marketing/_components/useFeaturesStepPresenter.ts b/src/app/marketing/_components/useFeaturesStepPresenter.ts
new file mode 100644
index 0000000..20623a5
--- /dev/null
+++ b/src/app/marketing/_components/useFeaturesStepPresenter.ts
@@ -0,0 +1,56 @@
+"use client";
+import { useFieldArray, useForm } from "react-hook-form";
+
+export interface IFeaturesStepFields {
+ title: string;
+ description: string;
+ cards: {
+ icon: string;
+ title: string;
+ description: string;
+ }[];
+}
+
+const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ control,
+ watch,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData || {
+ cards: [{ icon: "", title: "", description: "" }],
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "cards",
+ });
+
+ const onSubmit = (data: IFeaturesStepFields) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ fields,
+ append,
+ remove,
+ control,
+ watch,
+ getValues,
+ collectData,
+ };
+};
+
+export default useFeaturesStepPresenter;
diff --git a/src/app/marketing/_components/useFooterStepPresenter.ts b/src/app/marketing/_components/useFooterStepPresenter.ts
new file mode 100644
index 0000000..477afca
--- /dev/null
+++ b/src/app/marketing/_components/useFooterStepPresenter.ts
@@ -0,0 +1,39 @@
+import { useForm } from "react-hook-form";
+
+interface FooterFormValues {
+ email: string;
+ phone: string;
+ location: string;
+ tagline: string;
+ copyright: string;
+}
+
+const useFooterStepPresenter = (initialData?: FooterFormValues) => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData,
+ });
+
+ const onSubmit = (data: FooterFormValues) => {
+ console.log(data);
+ };
+
+ const collectData = async () => {
+ return getValues();
+ };
+
+ return {
+ register,
+ handleSubmit,
+ onSubmit,
+ errors,
+ collectData,
+ getValues,
+ };
+};
+
+export default useFooterStepPresenter;
diff --git a/src/app/marketing/_components/useHeroStepPresenter.ts b/src/app/marketing/_components/useHeroStepPresenter.ts
new file mode 100644
index 0000000..a7b826d
--- /dev/null
+++ b/src/app/marketing/_components/useHeroStepPresenter.ts
@@ -0,0 +1,35 @@
+"use client";
+import { useForm } from "react-hook-form";
+
+export interface IHeroStepFields {
+ title: string;
+ description: string;
+ buttonText: string;
+ buttonLink: string;
+ gifFile: string;
+}
+
+const useHeroStepPresenter = (initialData?: IHeroStepFields) => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ getValues,
+ } = useForm({
+ defaultValues: initialData,
+ });
+
+ const onSubmit = (data: IHeroStepFields) => {
+ console.log(data);
+ };
+
+ return {
+ register,
+ handleSubmit,
+ errors,
+ onSubmit,
+ 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..797d119
--- /dev/null
+++ b/src/app/marketing/create/page.tsx
@@ -0,0 +1,45 @@
+"use client";
+import { useCreateCms } from "@/hooks/queries/useCmsQueries";
+import { BreadcrumbItem } from "@/types/shared";
+import MarketingForm from "../_components/MarketingForm";
+
+const Marketing = () => {
+ const { mutate: createCms, isPending } = useCreateCms();
+
+ const breadcrumbItems: BreadcrumbItem[] = [
+ {
+ href: "/",
+ name: "Dashboard",
+ },
+ {
+ href: "/marketing",
+ name: "Marketing",
+ },
+ {
+ href: "/marketing/create",
+ name: "Create Marketing",
+ },
+ ];
+
+ const handleSubmit = (data: any) => {
+ createCms(data, {
+ onSuccess: () => {
+ console.log("CMS created successfully");
+ },
+ onError: (error) => {
+ console.error("Error creating CMS:", error);
+ },
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default Marketing;
diff --git a/src/app/marketing/edit/[id]/page.tsx b/src/app/marketing/edit/[id]/page.tsx
new file mode 100644
index 0000000..d1288e0
--- /dev/null
+++ b/src/app/marketing/edit/[id]/page.tsx
@@ -0,0 +1,56 @@
+"use client";
+import Loading from "@/app/loading";
+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 = ({ params }: IProps) => {
+ const { id } = use(params);
+ const { data, isPending: isLoading } = useGetCmsDetails(id);
+ const { mutate: updateCms, isPending: isUpdating } = useUpdateCms(id);
+
+ const breadcrumbItems: BreadcrumbItem[] = [
+ {
+ href: "/",
+ name: "Dashboard",
+ },
+ {
+ href: "/marketing",
+ name: "Marketing",
+ },
+ {
+ href: `/marketing/${id}`,
+ name: "Edit Marketing",
+ },
+ ];
+
+ 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/app/marketing/page.tsx b/src/app/marketing/page.tsx
new file mode 100644
index 0000000..4515afe
--- /dev/null
+++ b/src/app/marketing/page.tsx
@@ -0,0 +1,24 @@
+import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
+import CmsTable from "@/components/Tables/cms";
+import { BreadcrumbItem } from "@/types/shared";
+
+const MarketingPage = () => {
+ const breadcrumbItems: BreadcrumbItem[] = [
+ {
+ href: "/",
+ name: "Dashboard",
+ },
+ {
+ href: "/marketing",
+ name: "Marketing",
+ },
+ ];
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default MarketingPage;
diff --git a/src/assets/icons.tsx b/src/assets/icons.tsx
index a8e3570..719ef43 100644
--- a/src/assets/icons.tsx
+++ b/src/assets/icons.tsx
@@ -38,6 +38,36 @@ export function CurrencyIcon(props: IconProps) {
);
}
+export function FilterIcon(props: IconProps) {
+ return (
+
+ );
+}
+
export function MoneyIcon(props: IconProps) {
return (
+ );
+}
export function CheckIcon(props: IconProps) {
return (
);
}
+export function CallIncome(props: PropsType) {
+ return (
+
+
+
+
+ );
+}
export function NotificationIcon(props: PropsType) {
return (
;
+ setIsFilterModalOpen: (isOpen: boolean) => void;
+ isMutating: number;
+ handleFilterSubmit: UseFormHandleSubmit;
+ search: (data: any) => void;
+ watch: UseFormWatch;
+ setValue: UseFormSetValue;
+}
+
+const AdminListFilters = ({
+ filterRegister,
+ setIsFilterModalOpen,
+ isMutating,
+ handleFilterSubmit,
+ search,
+ watch,
+ setValue,
+}: AdminListFiltersProps) => {
+ return (
+
+ );
+};
+
+export default AdminListFilters;
diff --git a/src/components/Tables/admins/index.tsx b/src/components/Tables/admins/index.tsx
index 8afe283..c90de0e 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,18 +33,26 @@ const AdminsTable = () => {
setIsChangeStatusModalOpen,
columns,
setCurrentUser,
- pathName,
+ isFilterModalOpen,
+ setIsFilterModalOpen,
changeStatus,
currentUser,
setStatus,
status,
+ setFilterValue,
+ handleFilterSubmit,
+ search,
+ filterRegister,
+ isMutating,
+ watch,
} = useAdminsPresenter();
return (
-
+
setIsFilterModalOpen(true)}
+ createButtonText="Create Admin"
+ />
@@ -56,79 +65,85 @@ const AdminsTable = () => {
{isPending && }
- {allUsers.map((item, index) => {
- return (
-
-
- {index + 1}
-
-
- {item.full_name}
-
-
- {item.email}
-
-
- {item.role}
-
-
- {item.username}
-
-
- {item.status}
-
+ {!allUsers.length ? (
+
+
+ No user
+
+
+ ) : (
+ allUsers.map((item, index) => {
+ return (
+
+
+ {index + 1}
+
+
+ {item?.full_name}
+
+
+ {item?.email}
+
-
-
-
-
-
-
-
-
- );
- })}
+
+ {item?.username}
+
+
+ {item?.status}
+
+
+
+
+
+
+
+
+
+
+ );
+ })
+ )}
{
setStatus={setStatus}
/>
+
+ setIsFilterModalOpen(false)}
+ classNames="w-full xl:w-1/2"
+ showButtons={false}
+ >
+
+
{isModalOpen && (
{
const [isChangeStatusModalOpen, setIsChangeStatusModalOpen] = useState(false);
+ const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [status, setStatus] = useState(
currentUser?.status ?? AdminStatus.ACTIVE,
);
+ const isMutating = useIsMutating();
const router = useRouter();
+ const searchParams = useSearchParams();
+ const {
+ register: filterRegister,
+ handleSubmit: handleFilterSubmit,
+ reset: filterFormReset,
+ watch,
+ setValue: setFilterValue,
+ } = useForm();
+
const {
register,
formState: { errors },
@@ -30,11 +43,22 @@ export const useAdminsPresenter = () => {
status: currentUser?.status,
},
});
- const [params, setParams] = useState>({
- ...apiDefaultParams,
- });
+ const [params, setParams] = useState>({});
const pathName = usePathname();
- const { data, isPending } = useAdminListInfiniteQuery(params);
+
+ useEffect(() => {
+ const newParams: Record = {
+ ...apiDefaultParams,
+ offset: 0,
+ limit: 10,
+ };
+ for (const [key, value] of searchParams.entries()) {
+ newParams[key] = value;
+ }
+ setParams(newParams);
+ filterFormReset(newParams);
+ }, [searchParams, filterFormReset]);
+ const { data, isPending } = useGetAdminListQuery(params);
const { mutate: deleteAdmin } = useDeleteAdmin(() => {
setIsModalOpen(false);
});
@@ -50,23 +74,35 @@ export const useAdminsPresenter = () => {
setIsChangeStatusModalOpen(false);
},
});
- const allUsers = data?.pages.flatMap((page) => page.data.users) ?? [];
+
+ const search = (data: any) => {
+ const newParams = { ...params, ...data, offset: 0 };
+ const cleanedParams = deleteEmptyKeys(newParams);
+ const queryString = new URLSearchParams(cleanedParams).toString();
+ router.push(`${pathName}?${queryString}`);
+ setIsFilterModalOpen(false);
+ };
+ const allUsers = data?.data?.users ?? [];
+ const metadata = data?.meta;
const columns = [
"row",
"full name",
"email",
- "role",
+
"username",
"status",
"actions",
];
return {
allUsers,
+ setFilterValue,
+ metadata,
columns,
isPending,
onConfirmDelete,
isModalOpen,
setIsModalOpen,
+ isMutating,
router,
setCurrentUser,
pathName,
@@ -79,5 +115,12 @@ export const useAdminsPresenter = () => {
register,
errors,
handleSubmit,
+ isFilterModalOpen,
+ setIsFilterModalOpen,
+ setParams,
+ filterRegister,
+ handleFilterSubmit,
+ search,
+ watch,
};
};
diff --git a/src/components/Tables/cms/index.tsx b/src/components/Tables/cms/index.tsx
new file mode 100644
index 0000000..7c0c7d7
--- /dev/null
+++ b/src/components/Tables/cms/index.tsx
@@ -0,0 +1,125 @@
+"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, 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,
+ isModalOpen,
+ setIsModalOpen,
+ currentCms,
+ setCurrentCms,
+ onConfirmDelete,
+ router,
+ pathname,
+ } = useCmsTablePresenter();
+ return (
+
+
+
+
+
+ {columns.map((column) => (
+
+ {column}
+
+ ))}
+
+
+ {isPending && }
+
+
+ {data?.data.cms.map((item, index) => (
+
+
+ {index + 1}
+
+
+ {item.key}
+
+
+ {unixToDate({ unix: item.created_at, hasTime: true })}
+
+
+
+
+
+ {/*
*/}
+
+
+
+ ))}
+
+
+
+ {isModalOpen && (
+
{
+ onConfirmDelete();
+ }}
+ onCancel={() => setIsModalOpen(false)}
+ />
+ )}
+
+ );
+};
+
+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..f432a1b
--- /dev/null
+++ b/src/components/Tables/cms/useCmsTablePresenter.ts
@@ -0,0 +1,36 @@
+"use client";
+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 [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,
+ };
+};
+
+export default useCmsTablePresenter;
diff --git a/src/components/Tables/companies/index.tsx b/src/components/Tables/companies/index.tsx
index 4eb687d..3c10765 100644
--- a/src/components/Tables/companies/index.tsx
+++ b/src/components/Tables/companies/index.tsx
@@ -21,9 +21,7 @@ import Link from "next/link";
import { Tooltip } from "react-tooltip";
import { useCompanyListPresenter } from "./useCompanyListPresenter";
-interface IProps {}
-
-const CompaniesTable = ({}: IProps) => {
+const CompaniesTable = () => {
const {
isPending,
pathName,
@@ -47,7 +45,7 @@ const CompaniesTable = ({}: IProps) => {
{columns.map((columns) => (
{columns}
diff --git a/src/components/Tables/contact-us/index.tsx b/src/components/Tables/contact-us/index.tsx
new file mode 100644
index 0000000..521b02d
--- /dev/null
+++ b/src/components/Tables/contact-us/index.tsx
@@ -0,0 +1,50 @@
+"use client";
+import EmptyListWrapper from "@/components/HOC/EmptyListWrapper";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import TableSkeleton from "@/components/ui/TableSkeleton";
+import { useContactUsListPresenter } from "./useContactUsListPresenter";
+
+const ContactUsTable = () => {
+ const { columns, pathname, router, isPending, data } =
+ useContactUsListPresenter();
+ return (
+
+
+
+
+ {columns.map((columns) => (
+
+ {columns}
+
+ ))}
+
+
+ {isPending && }
+
+
+
+ hjgfjhg
+
+
+
+
+
+ );
+};
+
+export default ContactUsTable;
diff --git a/src/components/Tables/contact-us/useContactUsListPresenter.ts b/src/components/Tables/contact-us/useContactUsListPresenter.ts
new file mode 100644
index 0000000..afc6940
--- /dev/null
+++ b/src/components/Tables/contact-us/useContactUsListPresenter.ts
@@ -0,0 +1,16 @@
+"use client";
+import { useReadAllContactUs } from "@/hooks/queries/useContactUsQuery";
+import { usePathname, useRouter } from "next/navigation";
+export const useContactUsListPresenter = () => {
+ const columns: string[] = ["row", "actions"];
+ const pathname = usePathname();
+ const router = useRouter();
+ const { data, isPending } = useReadAllContactUs();
+ return {
+ columns,
+ pathname,
+ router,
+ isPending,
+ data,
+ };
+};
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 && (
;
+ setIsFilterModalOpen: (isOpen: boolean) => void;
+ isMutating: number;
+ handleFilterSubmit: UseFormHandleSubmit;
+ search: (data: any) => void;
+ watch: UseFormWatch;
+ setValue: UseFormSetValue;
+}
+
+const NotificationHistoryListFilters = ({
+ filterRegister,
+ setIsFilterModalOpen,
+ isMutating,
+ handleFilterSubmit,
+ search,
+}: NotificationHistoryListFiltersProps) => {
+ return (
+
+ );
+};
+
+export default NotificationHistoryListFilters;
diff --git a/src/components/Tables/notification-history/index.tsx b/src/components/Tables/notification-history/index.tsx
index 37b2c7c..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,93 +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,
- })}
-
-
- {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 f8068d1..e34c15a 100644
--- a/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts
+++ b/src/components/Tables/notification-history/useNotificationHistoryTablePresenter.ts
@@ -1,20 +1,26 @@
"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",
"channel",
"created at",
+ "receiver",
"message",
"priority",
"type",
@@ -24,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,
@@ -32,6 +46,14 @@ const useNotificationHistoryTablePresenter = () => {
router,
pathName,
setParams,
+ register,
+ handleSubmit,
+ watch,
+ setValue,
+ isFilterModalOpen,
+ setIsFilterModalOpen,
+ isMutating,
+ search,
};
};
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({
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/Stepper.tsx b/src/components/ui/Stepper.tsx
new file mode 100644
index 0000000..a9c7115
--- /dev/null
+++ b/src/components/ui/Stepper.tsx
@@ -0,0 +1,154 @@
+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;
+ onSubmit?: () => void;
+ isSubmitting?: boolean;
+}
+
+const Stepper: React.FC = ({
+ steps,
+ currentStep: controlledStep,
+ onStepChange,
+ onSubmit,
+ isSubmitting = false,
+}) => {
+ 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) {
+ // On last step, trigger submit
+ if (onSubmit) {
+ onSubmit();
+ }
+ } else if (currentStep < steps.length - 1) {
+ const newStep = currentStep + 1;
+ if (onStepChange) {
+ onStepChange(newStep);
+ } else {
+ setInternalStep(newStep);
+ }
+ }
+ if (window) {
+ window.scroll({ top: 0, behavior: "smooth" });
+ }
+ };
+
+ const prevStep = () => {
+ if (currentStep > 0) {
+ const newStep = currentStep - 1;
+ if (onStepChange) {
+ onStepChange(newStep);
+ } else {
+ setInternalStep(newStep);
+ }
+ }
+ if (window) {
+ window.scroll({ top: 0, behavior: "smooth" });
+ }
+ };
+
+ 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/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()}
>