diff --git a/public/signup/icons/bi_facebook.svg b/public/signup/icons/bi_facebook.svg new file mode 100644 index 000000000..e2c617c85 --- /dev/null +++ b/public/signup/icons/bi_facebook.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/signup/icons/flat-color-icons_google.svg b/public/signup/icons/flat-color-icons_google.svg new file mode 100644 index 000000000..c5ba07eee --- /dev/null +++ b/public/signup/icons/flat-color-icons_google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/(admin)/_components/unread-notification-card/UnreadNotificationCard.tsx b/src/app/(admin)/_components/unread-notification-card/UnreadNotificationCard.tsx index bb6a741c4..fd62ebbe3 100644 --- a/src/app/(admin)/_components/unread-notification-card/UnreadNotificationCard.tsx +++ b/src/app/(admin)/_components/unread-notification-card/UnreadNotificationCard.tsx @@ -1,7 +1,7 @@ import { BellRing } from "lucide-react"; import { FC } from "react"; -import CustomButton from "~/components/common/button/button"; +import CustomButton from "~/components/common/common-button/common-button"; import { Card, CardContent, diff --git a/src/app/(admin)/admin/email/generate-with-html/layout.tsx b/src/app/(admin)/admin/email/generate-with-html/layout.tsx index 40661a9dd..d2555f97f 100644 --- a/src/app/(admin)/admin/email/generate-with-html/layout.tsx +++ b/src/app/(admin)/admin/email/generate-with-html/layout.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode } from "react"; -import { Breadcrumb } from "~/components/common/breadcrumb/breadcrumb"; +import { Breadcrumb } from "~/components/common/breadcrumb"; interface IProperties { children: ReactNode; diff --git a/src/app/(admin)/admin/email/generate-with-html/preview-template/_component.tsx/index.tsx b/src/app/(admin)/admin/email/generate-with-html/preview-template/_component.tsx/index.tsx index eb0adb782..b217c58e3 100644 --- a/src/app/(admin)/admin/email/generate-with-html/preview-template/_component.tsx/index.tsx +++ b/src/app/(admin)/admin/email/generate-with-html/preview-template/_component.tsx/index.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { Breadcrumb } from "~/components/common/breadcrumb"; -import { Button } from "~/components/common/button"; +import { Button } from "~/components/common/common-button"; import PageHeader from "../../../_components/page-header"; import { templateOne } from "./template-example"; import TemplateViewer from "./TemplateViewer"; diff --git a/src/app/(admin)/admin/email/generate-with-html/preview-template/page.test.tsx b/src/app/(admin)/admin/email/generate-with-html/preview-template/page.testx.tsx similarity index 100% rename from src/app/(admin)/admin/email/generate-with-html/preview-template/page.test.tsx rename to src/app/(admin)/admin/email/generate-with-html/preview-template/page.testx.tsx diff --git a/src/app/(auth-routes)/reset-password/verify-otp/page.tsx b/src/app/(auth-routes)/reset-password/verify-otp/page.tsx new file mode 100644 index 000000000..a21d4da99 --- /dev/null +++ b/src/app/(auth-routes)/reset-password/verify-otp/page.tsx @@ -0,0 +1,39 @@ +"use client"; + +import CustomButton from "~/components/common/common-button/common-button"; +import { InputOtp } from "~/components/common/Input-otp"; + +export default function VerifyCodePage() { + return ( + <> +
+
+

+ Verification Code +

+

+ Confirm the OTP sent to{" "} + ellafedora@gmail.com and + enter the Verification code that was sent. Code expires in{" "} + 00:59 +

+
+ +
+ +
+
+ + Verify{" "} + +

+ Didn't recieve any code?{" "} + + Resend OTP + +

+
+
+ + ); +} diff --git a/src/app/(auth-routes)/sign-up/_component/SignupForm.tsx b/src/app/(auth-routes)/sign-up/_component/SignupForm.tsx new file mode 100644 index 000000000..0778c51ee --- /dev/null +++ b/src/app/(auth-routes)/sign-up/_component/SignupForm.tsx @@ -0,0 +1,211 @@ +"use client"; + +import { Eye, EyeOff } from "lucide-react"; +import { useState } from "react"; +import { z, ZodType } from "zod"; + +import CustomButton from "~/components/common/common-button/common-button"; + +interface IData { + fullName: string; + email: string; + password: string; +} + +interface TestFormProperties { + onSubmit: (data: IData) => void; +} + +const schemaRegister = z.object({ + fullName: z.string().regex(/^['A-Za-z-]+(?: ['A-Za-z-]+)*$/, { + message: "Enter your full name", + }), + email: z.string().email({ + message: "Enter a valid email format", + }), + password: z + .string() + .min(8, { + message: "Password must be at least 8 characters long.", + }) + .max(20, { + message: "Password should not be more than 20 characters long.", + }), +}); + +type SchemaRegisterType = z.infer; + +type ErrorsType = Partial>; + +const SignupForm = ({ onSubmit }: TestFormProperties) => { + const [formData, setFormData] = useState({ + fullName: "", + email: "", + password: "", + }); + const [errors, setErrors] = useState({}); + const [passwordVisible, setPasswordVisible] = useState(false); + + const handleChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + + if (name in schemaRegister.shape) { + setFormData({ ...formData, [name]: value }); + + const fieldSchema = schemaRegister.shape[ + name as keyof typeof schemaRegister.shape + ] as ZodType; + const result = fieldSchema.safeParse(value); + + if (result.success) { + setErrors((previousErrors) => { + const remainingErrors = { ...previousErrors }; + delete remainingErrors[name as keyof SchemaRegisterType]; + return remainingErrors; + }); + } else { + setErrors((previousErrors) => ({ + ...previousErrors, + [name]: result.error.errors[0].message, + })); + } + } + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + const result = schemaRegister.safeParse(formData); + + if (result.success) { + setErrors({}); + onSubmit(formData); + } else { + const newErrors: ErrorsType = {}; + for (const error of result.error.errors) { + const fieldName = error.path[0] as keyof SchemaRegisterType; + newErrors[fieldName] = error.message; + } + setErrors(newErrors); + } + }; + return ( +
+
+
+ + +
+ {errors.fullName && ( + + {errors.fullName} + + )} +
+
+
+ + +
+ {errors.email && ( + + {errors.email} + + )} +
+
+
+ + + {passwordVisible ? ( + setPasswordVisible(!passwordVisible)} + /> + ) : ( + setPasswordVisible(!passwordVisible)} + /> + )} +
+ {errors.password && ( + + {errors.password} + + )} +
+ + Create Account + +
+ ); +}; + +export default SignupForm; diff --git a/src/app/(auth-routes)/sign-up/page.test.tsx b/src/app/(auth-routes)/sign-up/page.test.tsx new file mode 100644 index 000000000..a803b9276 --- /dev/null +++ b/src/app/(auth-routes)/sign-up/page.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import SignupForm from "./_component/SignupForm"; +import SignUp from "./page"; + +describe("form", () => { + const handleSubmit = vi.fn(); + + it("should render the form fields", () => { + expect.hasAssertions(); + render(); + + expect(screen.getByLabelText("Full Name")).toBeInTheDocument(); + expect(screen.getByLabelText("Email Address")).toBeInTheDocument(); + expect(screen.getByLabelText("Password")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Create Account" }), + ).toBeInTheDocument(); + + expect(screen.getByLabelText("Full Name")).toHaveAttribute("type", "text"); + expect(screen.getByLabelText("Email Address")).toHaveAttribute( + "type", + "email", + ); + expect(screen.getByLabelText("Password")).toHaveAttribute( + "type", + "password", + ); + }); + + it("should not submit the form if required fields are empty", async () => { + expect.hasAssertions(); + render(); + + await userEvent.click( + screen.getByRole("button", { name: "Create Account" }), + ); + expect(handleSubmit).not.toHaveBeenCalled(); + }); + + it("should submit the form with correct values", async () => { + expect.hasAssertions(); + render(); + + await userEvent.type(screen.getByLabelText("Full Name"), "John Doe"); + await userEvent.type( + screen.getByLabelText("Email Address"), + "johndoe@example.com", + ); + await userEvent.type(screen.getByLabelText("Password"), "securepassword"); + await userEvent.click( + screen.getByRole("button", { name: "Create Account" }), + ); + + expect(handleSubmit).toHaveBeenCalledTimes(1); + expect(handleSubmit).toHaveBeenCalledWith({ + fullName: "John Doe", + email: "johndoe@example.com", + password: "securepassword", + }); + }); +}); diff --git a/src/app/(auth-routes)/sign-up/page.tsx b/src/app/(auth-routes)/sign-up/page.tsx index 2582a31cb..73128d826 100644 --- a/src/app/(auth-routes)/sign-up/page.tsx +++ b/src/app/(auth-routes)/sign-up/page.tsx @@ -1,5 +1,65 @@ -const SignUp = () => { - return
SignUp
; +"use client"; + +import Image from "next/image"; +import Link from "next/link"; + +import SignupForm from "./_component/SignupForm"; + +const page = () => { + interface IData { + fullName: string; + email: string; + password: string; + } + return ( +
+
+
+

+ Sign Up +

+

+ Create an account to get started with us. +

+
+ + +
+ +

+ Already Have An Account?{" "} + + Login + +

+
+
+
+ ); }; -export default SignUp; +export default page; diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx deleted file mode 100644 index 8abb411bd..000000000 --- a/src/app/(dashboard)/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const layout = () => { - return
layout
; -}; - -export default layout; diff --git a/src/app/(dashboard)/products/page.test.tsx b/src/app/(landing-routes)/contact-us/page.test.tsx similarity index 100% rename from src/app/(dashboard)/products/page.test.tsx rename to src/app/(landing-routes)/contact-us/page.test.tsx diff --git a/src/app/(landing-routes)/contact-us/page.tsx b/src/app/(landing-routes)/contact-us/page.tsx new file mode 100644 index 000000000..db4d38750 --- /dev/null +++ b/src/app/(landing-routes)/contact-us/page.tsx @@ -0,0 +1,5 @@ +const page = () => { + return
page
; +}; + +export default page; diff --git a/src/app/(legal)/privacy-policy/page.tsx b/src/app/(legal)/privacy-policy/page.tsx new file mode 100644 index 000000000..cfa85f6d3 --- /dev/null +++ b/src/app/(legal)/privacy-policy/page.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useEffect } from "react"; + +import { Breadcrumb } from "~/components/common/breadcrumb"; +import SubPageHero from "~/components/external_static_page/SubPageHero"; +import PrivacyPolicyContent from "~/components/layouts/Legal/PrivacyPolicy/PrivacyPolicyContent"; +import TableOfContent from "~/components/layouts/Legal/TableOfContent"; +import privacyPolicyData, { + getTableOfContents, +} from "../../../components/layouts/Legal/PrivacyPolicy/constants/privacyPolicyData"; + +export default function PrivacyPolicy() { + const tableOfContents = getTableOfContents(privacyPolicyData); + + useEffect(() => { + const scrollClasses = [ + "scroll-smooth", + "scroll-pt-24", + "md:scroll-pt-[108px]", + ]; + + const htmlElement = document.documentElement; + htmlElement.classList.add(...scrollClasses); + + return () => { + htmlElement.classList.remove(...scrollClasses); + }; + }, []); + + return ( +
+ +
+ +
+ + +
+
+
+ ); +} diff --git a/src/app/(legal)/terms/page.tsx b/src/app/(legal)/terms/page.tsx new file mode 100644 index 000000000..1d0777ae0 --- /dev/null +++ b/src/app/(legal)/terms/page.tsx @@ -0,0 +1,30 @@ +import { Breadcrumb } from "~/components/common/breadcrumb"; +import SubPageHero from "~/components/external_static_page/SubPageHero"; +import Main from "~/components/layouts/Legal/Terms&Conditions/Main"; + +const TermsConditions = () => { + return ( +
+ +
+ +
+
+
+ ); +}; + +export default TermsConditions; diff --git a/src/app/(dashboard)/home/page.tsx b/src/app/(user-dashboard)/home/page.tsx similarity index 100% rename from src/app/(dashboard)/home/page.tsx rename to src/app/(user-dashboard)/home/page.tsx diff --git a/src/app/(user-dashboard)/layout.tsx b/src/app/(user-dashboard)/layout.tsx new file mode 100644 index 000000000..6c6e99f4d --- /dev/null +++ b/src/app/(user-dashboard)/layout.tsx @@ -0,0 +1,20 @@ +import { Suspense } from "react"; + +// import AdminNavbar from "~/components/superadminlayout/navbar/AdminNavbar"; + +export default function AdminLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {/*
*/} + {/* */} + +
+ {children} +
+
+ ); +} diff --git a/src/app/(dashboard)/products/_components/product-body-shadcn.tsx b/src/app/(user-dashboard)/products/_components/product-body-shadcn.tsx similarity index 100% rename from src/app/(dashboard)/products/_components/product-body-shadcn.tsx rename to src/app/(user-dashboard)/products/_components/product-body-shadcn.tsx diff --git a/src/app/(dashboard)/products/_components/product-content.tsx b/src/app/(user-dashboard)/products/_components/product-content.tsx similarity index 98% rename from src/app/(dashboard)/products/_components/product-content.tsx rename to src/app/(user-dashboard)/products/_components/product-content.tsx index dcbfbfe87..ead789ca2 100644 --- a/src/app/(dashboard)/products/_components/product-content.tsx +++ b/src/app/(user-dashboard)/products/_components/product-content.tsx @@ -88,6 +88,11 @@ const ProductContent = ({ return; } }, []); + useEffect(() => { + document.title = `Products - ${subset.length} Product${ + subset.length > 1 ? "s" : "" + }`; + }, [subset.length]); return (
diff --git a/src/app/(dashboard)/products/_components/product-detail-modal.tsx b/src/app/(user-dashboard)/products/_components/product-detail-modal.tsx similarity index 100% rename from src/app/(dashboard)/products/_components/product-detail-modal.tsx rename to src/app/(user-dashboard)/products/_components/product-detail-modal.tsx diff --git a/src/app/(dashboard)/products/_components/product-detail-view.tsx b/src/app/(user-dashboard)/products/_components/product-detail-view.tsx similarity index 100% rename from src/app/(dashboard)/products/_components/product-detail-view.tsx rename to src/app/(user-dashboard)/products/_components/product-detail-view.tsx diff --git a/src/app/(dashboard)/products/_components/product-filter.tsx b/src/app/(user-dashboard)/products/_components/product-filter.tsx similarity index 87% rename from src/app/(dashboard)/products/_components/product-filter.tsx rename to src/app/(user-dashboard)/products/_components/product-filter.tsx index 84161f045..282f04079 100644 --- a/src/app/(dashboard)/products/_components/product-filter.tsx +++ b/src/app/(user-dashboard)/products/_components/product-filter.tsx @@ -90,13 +90,13 @@ const ProductFilter = ({ variant="outline" size="icon" className={cn( - "flex items-center gap-x-2 bg-transparent text-sm hover:!bg-black hover:text-white disabled:!opacity-85", + "flex items-center gap-x-2 bg-transparent p-1 text-sm hover:!bg-black hover:text-white disabled:!opacity-85 md:rounded-[6px] md:p-2", view === "grid" ? "bg-black text-white transition-all duration-300 hover:!opacity-80 active:scale-90" : "", )} > - +
@@ -120,7 +120,7 @@ const ProductFilter = ({ variant="outline" className="relative grid w-8 place-items-center gap-x-2 bg-transparent text-sm min-[500px]:flex min-[500px]:w-fit min-[500px]:items-center" > - + Filter @@ -131,7 +131,7 @@ const ProductFilter = ({ animate={{ opacity: 1, y: 0, x: 0 }} exit={{ opacity: 0, y: -20, x: 20 }} ref={filterReference} - className="absolute -bottom-[12rem] right-0 z-30 flex w-full max-w-[185px] flex-col gap-y-1 rounded-[6px] border border-gray-300 bg-white/80 shadow-[0px_1px_18px_0px_rgba(10,_57,_176,_0.12)] backdrop-blur-sm" + className="absolute -bottom-[12rem] right-0 z-30 flex w-[150px] flex-col gap-y-1 rounded-[6px] border border-gray-300 bg-white/80 shadow-[0px_1px_18px_0px_rgba(10,_57,_176,_0.12)] backdrop-blur-sm min-[500px]:-bottom-[14rem] sm:w-full sm:max-w-[185px]" > Filters @@ -141,7 +141,7 @@ const ProductFilter = ({ variant="ghost" key={filter.value} className={cn( - "flex h-8 cursor-pointer items-center justify-start gap-x-2 px-2 py-2 text-sm md:px-4", + "flex h-8 cursor-pointer items-center justify-start gap-x-2 px-2 py-1 text-xs min-[500px]:py-2 min-[500px]:text-sm md:px-4", active_filter === filter.value ? "text-black" : "text-neutral-dark-1", @@ -154,7 +154,7 @@ const ProductFilter = ({ > diff --git a/src/app/(dashboard)/products/_components/product-header.tsx b/src/app/(user-dashboard)/products/_components/product-header.tsx similarity index 100% rename from src/app/(dashboard)/products/_components/product-header.tsx rename to src/app/(user-dashboard)/products/_components/product-header.tsx diff --git a/src/app/(dashboard)/products/data/product.mock.ts b/src/app/(user-dashboard)/products/data/product.mock.ts similarity index 100% rename from src/app/(dashboard)/products/data/product.mock.ts rename to src/app/(user-dashboard)/products/data/product.mock.ts diff --git a/src/app/(landing-routes)/faqs/page.test.tsx b/src/app/(user-dashboard)/products/page.test.tsx similarity index 100% rename from src/app/(landing-routes)/faqs/page.test.tsx rename to src/app/(user-dashboard)/products/page.test.tsx diff --git a/src/app/(dashboard)/products/page.tsx b/src/app/(user-dashboard)/products/page.tsx similarity index 99% rename from src/app/(dashboard)/products/page.tsx rename to src/app/(user-dashboard)/products/page.tsx index f02c68212..0e216d51e 100644 --- a/src/app/(dashboard)/products/page.tsx +++ b/src/app/(user-dashboard)/products/page.tsx @@ -49,6 +49,7 @@ const ProductPage = () => { document.removeEventListener("keydown", handleEscape); }; }, []); + return (
diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(user-dashboard)/settings/page.tsx similarity index 100% rename from src/app/(dashboard)/settings/page.tsx rename to src/app/(user-dashboard)/settings/page.tsx diff --git a/src/components/common/CareerCard/CareerCard.tsx b/src/components/common/CareerCard/CareerCard.tsx index a83bbe5c9..18879a073 100644 --- a/src/components/common/CareerCard/CareerCard.tsx +++ b/src/components/common/CareerCard/CareerCard.tsx @@ -2,8 +2,7 @@ import { FC } from "react"; import { Card, CardContent, CardFooter } from "~/components/ui/card"; import { Skeleton } from "~/components/ui/skeleton"; -import CustomButton from "../button/button"; - +import CustomButton from "../common-button/common-button"; interface CareerCardProperties { isLoading: boolean; diff --git a/src/components/common/Input-otp/index.tsx b/src/components/common/Input-otp/index.tsx new file mode 100644 index 000000000..c761fcf47 --- /dev/null +++ b/src/components/common/Input-otp/index.tsx @@ -0,0 +1,20 @@ +import { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from "~/components/ui/input-otp"; + +export function InputOtp() { + return ( + + + + + + + + + + + ); +} diff --git a/src/components/common/breadcrumb/breadcrumb.tsx b/src/components/common/breadcrumb/breadcrumb.tsx index e1e769631..cbdb44d44 100644 --- a/src/components/common/breadcrumb/breadcrumb.tsx +++ b/src/components/common/breadcrumb/breadcrumb.tsx @@ -1,118 +1,125 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { Fragment } from "react"; - +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; import { - Breadcrumb as BreadcrumbBase, - BreadcrumbEllipsis, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "~/components/common/breadcrumb"; + forwardRef, + type ComponentProps, + type ComponentPropsWithoutRef, + type ReactNode, +} from "react"; + import { cn } from "~/lib/utils"; -type PagesList = { - /** Specifies the page name */ - name: string; - /** Specifies the page URL */ - href: string; - /** Specifies if the page is the current page */ - isCurrent?: boolean; -}; +const Breadcrumb = forwardRef< + HTMLElement, + ComponentPropsWithoutRef<"nav"> & { + separator?: ReactNode; + } +>(({ ...properties }, reference) => ( +