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 ( +
+
+ + + {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"; + +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 ( +
+ +
+ + {errors.title && ( +

+ {errors.title.message as string} +

+ )} + + +
+
+ + {fields.map((field, index) => ( +
+ + + + +
+ ))} +
+
+ ); + }, +); + +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 ( +
+ +
+ + {errors.email && ( +

+ {errors.email.message as string} +

+ )} + + + + +
+
+
+ ); + }, +); + +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 ( +
+
+ + + {errors.title && ( +

{errors.title.message}

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

+ {errors.description.message} +

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

+ {errors.buttonText.message} +

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

+ {errors.buttonLink.message} +

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

+ {errors.gifFile.message} +

+ )} +
+
+
+ ); +}); + +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 +

+
+ } + 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 ( +
+
+ + , { + required: { message: FormErrorMessages.required, value: true }, + })} + label="Section Title" + name="title" + required + type="text" + placeholder="Enter Section Title" + /> + {errors.title && ( +

+ {errors.title.message as string} +

+ )} + )} + name="description" + placeholder="Enter Section Description" + /> +
+ + {fields.map((field, index) => ( +
+ , { + required: { + message: FormErrorMessages.required, + value: true, + }, + pattern: { + message: FormErrorMessages.invalidPattern, + value: REGEX.URL, + }, + })} + label="Icon" + name={`cards.${index}.icon`} + required + type="text" + placeholder="Enter Icon URL" + /> + , { + required: { + message: FormErrorMessages.required, + value: true, + }, + })} + label="Title" + name={`cards.${index}.title`} + required + type="text" + placeholder="Enter Title" + /> + )} + label="Description" + name={`cards.${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..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 ( *]: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", diff --git a/src/components/FormElements/select.tsx b/src/components/FormElements/select.tsx index 22f482f..d57662a 100644 --- a/src/components/FormElements/select.tsx +++ b/src/components/FormElements/select.tsx @@ -3,7 +3,7 @@ import { ChevronUpIcon } from "@/assets/icons"; import { cn } from "@/lib/utils"; import { ILabelValue } from "@/types/shared"; -import { useId, useState, type ChangeEvent } from "react"; +import { useEffect, useId, useState, type ChangeEvent } from "react"; import { type FieldErrors, type FieldValues, @@ -22,8 +22,11 @@ type PropsType = { register?: UseFormRegister; errors?: FieldErrors; required?: boolean; + clearable?: boolean; + onClear?: () => void; + value?: string; } & ( - | { placeholder?: string; defaultValue: string } + | { placeholder?: string; defaultValue?: string } | { placeholder: string; defaultValue?: string } ) & Partial; @@ -42,13 +45,38 @@ export function Select({ onChange, onBlur, ref, + clearable = false, + onClear, + value: controlledValue, ...props }: PropsType) { const id = useId(); const [isOptionSelected, setIsOptionSelected] = useState(!!defaultValue); + const [value, setValue] = useState(defaultValue || ""); const error = errors && errors[name]; + useEffect(() => { + if (controlledValue !== undefined) { + setValue(controlledValue); + setIsOptionSelected(!!controlledValue); + } + }, [controlledValue]); + + const handleClear = () => { + if (controlledValue === undefined) { + setValue(""); + } + setIsOptionSelected(false); + onClear?.(); + if (onChange) { + const event = { + target: { value: "", name }, + } as unknown as ChangeEvent; + onChange(event); + } + }; + return (
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/Layouts/header/user-info/index.tsx b/src/components/Layouts/header/user-info/index.tsx index c73f290..6622771 100644 --- a/src/components/Layouts/header/user-info/index.tsx +++ b/src/components/Layouts/header/user-info/index.tsx @@ -107,6 +107,7 @@ export function UserInfo() { onClick={() => { setIsOpen(false); logout(); + }} > diff --git a/src/components/Layouts/sidebar/data/index.ts b/src/components/Layouts/sidebar/data/index.ts index 8366822..3c781b2 100644 --- a/src/components/Layouts/sidebar/data/index.ts +++ b/src/components/Layouts/sidebar/data/index.ts @@ -1,3 +1,4 @@ +import { GlobeSearch } from "@/assets/icons"; import { z } from "zod"; import * as Icons from "../icons"; @@ -72,6 +73,19 @@ export const NAV_DATA: NavData = [ icon: Icons.NotificationIcon, items: [], }, + // { + // title: "Contact Us", + // url: "/contact-us", + // icon: Icons.CallIncome, + // items: [], + // visible: false, + // }, + { + title: "Marketing", + url: "/marketing", + icon: GlobeSearch, + items: [], + }, ], }, ]; diff --git a/src/components/Layouts/sidebar/icons.tsx b/src/components/Layouts/sidebar/icons.tsx index d0a177e..8ba9248 100644 --- a/src/components/Layouts/sidebar/icons.tsx +++ b/src/components/Layouts/sidebar/icons.tsx @@ -19,6 +19,32 @@ export function ChevronUp(props: PropsType) { ); } +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 ( +
+