diff --git a/public/images/checkmark.svg b/public/images/checkmark.svg new file mode 100644 index 000000000..90773421d --- /dev/null +++ b/public/images/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/app/(landing-routes)/pricing/page.tsx b/src/app/(landing-routes)/pricing/page.tsx new file mode 100644 index 000000000..ddc871d6a --- /dev/null +++ b/src/app/(landing-routes)/pricing/page.tsx @@ -0,0 +1,538 @@ +"use client"; + +import Image from "next/image"; +import { useState } from "react"; + +import FaqAccordion from "~/components/layouts/accordion/FaqsAccordion"; +import { Button } from "~/components/ui/button"; +import { faqData } from "~/constants/faqsdata"; + +// + +const handleButtonClickTest = () => { + alert("Contact Button Click Test"); +}; + +// + +export default function Pricing() { + const [toggle, setToggle] = useState(1); + + return ( + <> +
+
+

+ Pricing +

+ +

+ Simple and Affordable{" "} + Pricing Plan +

+

+ Our flexible plans are designed to scale with your business. We have + a plan for you. +

+
+ +
+
setToggle(1)} + className={`flex h-[50px] w-[190px] cursor-pointer items-center justify-center rounded-md ${toggle === 1 ? "bg-white font-medium" : ""}`} + data-testid="monthly-toggle" + > + Monthly +
+
setToggle(2)} + className={`flex h-[50px] w-[190px] cursor-pointer items-center justify-center rounded-md ${toggle === 2 ? "bg-white font-medium" : ""}`} + data-testid="annual-toggle" + > + Annual(save 20%) +
+
+ +
+ {toggle === 1 ? ( + <> +
+

+ Basic +

+

+ $800 /month +

+

+ The essensitals to provide your best work for clients. +

+
+ + 2 Projects +
+
+ + Up to 100 subscribers +
+
+ + Basic analytics +
+
+ + 24-hour support response time +
+
+ + Marketing advisor +
+
+ + Custom integration +
+ +
+ +
+

+ Premium +

+

+ $3,000 /month +

+

+ The essensitals to provide your best work for clients. +

+
+ + 2 Projects +
+
+ + Up to 100 subscribers +
+
+ + Basic analytics +
+
+ + 24-hour support response time +
+
+ + Marketing advisor +
+
+ + Custom integration +
+ +
+ + ) : ( + <> +
+

+ Basic +

+

+ $500 /month +

+

+ The essensitals to provide your best work for clients. +

+
+ + 2 Projects +
+
+ + Up to 100 subscribers +
+
+ + Basic analytics +
+
+ + 24-hour support response time +
+
+ + Marketing advisor +
+
+ + Custom integration +
+ +
+ +
+

+ Premium +

+

+ $2,000 /month +

+

+ The essensitals to provide your best work for clients. +

+
+ + 2 Projects +
+
+ + Up to 100 subscribers +
+
+ + Basic analytics +
+
+ + 24-hour support response time +
+
+ + Marketing advisor +
+
+ + Custom integration +
+ +
+ + )} +
+
+ +
+
+
+
+

+ Frequently Asked Questions +

+ +

+ We couldn’t answer your question? +

+ + +
+ + +
+
+
+ + ); +} diff --git a/src/components/layouts/accordion/FaqsAccordion.tsx b/src/components/layouts/accordion/FaqsAccordion.tsx new file mode 100644 index 000000000..35841a8ac --- /dev/null +++ b/src/components/layouts/accordion/FaqsAccordion.tsx @@ -0,0 +1,57 @@ +import clsx from "clsx"; + +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "~/components/ui/accordion"; + +interface FaqAccordionProperties { + faqs: { question: string; content: string }[]; // Define the shape of faq objects + containerClassName?: string; + triggerClassName?: string; + contentClassName?: string; +} + +const FaqAccordion = ({ + faqs, + triggerClassName, + contentClassName, + containerClassName, +}: FaqAccordionProperties) => { + return ( +
+ + {faqs?.map((faq, index) => ( + + + {faq.question} + + + {faq.content} + + + ))} + +
+ ); +}; + +export default FaqAccordion; diff --git a/src/components/layouts/accordion/TopicAccordion.tsx b/src/components/layouts/accordion/TopicAccordion.tsx new file mode 100644 index 000000000..f286ed4fd --- /dev/null +++ b/src/components/layouts/accordion/TopicAccordion.tsx @@ -0,0 +1,42 @@ +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "~/components/ui/accordion"; +import { topics } from "~/constants/topicsdata"; + +const TopicsAccordions = () => { + return ( + + {topics.map((topic, index) => ( + + + {topic.question} + + + {topic.answer} + + + ))} + + ); +}; + +export default TopicsAccordions; diff --git a/src/components/layouts/careercard/CareerCard.tsx b/src/components/layouts/careercard/CareerCard.tsx new file mode 100644 index 000000000..74a556aa1 --- /dev/null +++ b/src/components/layouts/careercard/CareerCard.tsx @@ -0,0 +1,65 @@ +import { FC } from "react"; + +import { Card, CardContent, CardFooter } from "~/components/ui/card"; +import { Skeleton } from "~/components/ui/skeleton"; +import CustomButton from "../Button/button"; + +interface CareerCardProperties { + isLoading: boolean; + jobTitle?: string; + location?: string; + description?: string; + amount?: string; +} + +const CareerCard: FC = ({ + isLoading, + jobTitle, + location, + description, + amount, +}) => { + return ( + + + {isLoading ? ( +
+ +
+ + +
+
+ ) : ( + <> +

{jobTitle}

+

{location}

+

{description}

+ + )} +
+ + + {isLoading ? ( + <> + + + + ) : ( + <> + + {amount} + /month + + View Details + + )} + +
+ ); +}; + +export default CareerCard; diff --git a/src/components/layouts/careercard/index.tsx b/src/components/layouts/careercard/index.tsx new file mode 100644 index 000000000..737247a3b --- /dev/null +++ b/src/components/layouts/careercard/index.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { FC, useEffect, useState } from "react"; + +import CareerCard from "./CareerCard"; + +const CareerCardParent: FC = () => { + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const timer = setTimeout(() => { + setIsLoading(false); + }, 2000); + + return () => clearTimeout(timer); + }, []); + + return ( + + ); +}; + +export default CareerCardParent; diff --git a/src/components/layouts/navbar/index.tsx b/src/components/layouts/navbar/index.tsx index 1aa285f11..f2f17be2b 100644 --- a/src/components/layouts/navbar/index.tsx +++ b/src/components/layouts/navbar/index.tsx @@ -9,7 +9,7 @@ import Logo from "~/components/common/logo"; const navlinks = [ { route: "Home", link: "/" }, - { route: "Pricing", link: "/" }, + { route: "Pricing", link: "/pricing" }, { route: "Features", link: "/" }, ]; diff --git a/src/components/layouts/pagination/Pagination.tsx b/src/components/layouts/pagination/Pagination.tsx new file mode 100644 index 000000000..518f63d46 --- /dev/null +++ b/src/components/layouts/pagination/Pagination.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { + Pagination as PaginationComponent, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "~/components/ui/pagination"; + +export interface PaginationProperties { + total: number; + pageSize: number; + currentPage: number; + activeVariant?: "default" | "outline"; + navigationVariant?: "semibold" | "medium"; + onChange?: (page: number) => void; +} + +const Pagination = ({ + total, + pageSize, + currentPage, + activeVariant = "default", + navigationVariant = "medium", + onChange, +}: PaginationProperties) => { + const totalPages = Math.ceil(total / pageSize); + + const handleChange = (page: number) => { + if (onChange) { + onChange(page); + } + }; + + const getPageNumbers = () => { + const pages = []; + const showEllipsis = totalPages > 3; + + if (showEllipsis === false) { + for (let index = 1; index <= totalPages; index++) { + pages.push(index); + } + } else { + pages.push(1); + if (currentPage > 4) { + pages.push("..."); + } + + const firstPage = Math.max(2, currentPage - 2); + const lastPage = Math.min(totalPages - 1, currentPage + 2); + + for (let index = firstPage; index <= lastPage; index++) { + pages.push(index); + } + + if (currentPage < totalPages - 3) { + pages.push("..."); + } + pages.push(totalPages); + } + + return pages; + }; + + return ( + + + + currentPage > 1 && handleChange(currentPage - 1)} + className={`${ + navigationVariant === "semibold" + ? "text-xl font-semibold leading-normal" + : "text-base font-medium leading-6" + } ${ + currentPage > 1 + ? "cursor-pointer" + : "cursor-not-allowed text-stroke-colors-stroke hover:bg-transparent hover:text-stroke-colors-stroke" + }`} + href={currentPage > 1 ? `?page=${currentPage - 1}` : "#"} + /> + + {getPageNumbers().map((page, index) => + typeof page === "number" ? ( + handleChange(page)}> + + {page} + + + ) : ( + + {page} + + ), + )} + + currentPage < totalPages && handleChange(currentPage + 1) + } + > + + + + + ); +}; + +export default Pagination; diff --git a/src/constants/faqsdata.ts b/src/constants/faqsdata.ts new file mode 100644 index 000000000..3ef833759 --- /dev/null +++ b/src/constants/faqsdata.ts @@ -0,0 +1,17 @@ +export const faqData = [ + { + question: "What is the purpose of this application?", + content: + "This application is designed to help users manage their tasks efficiently by providing tools for scheduling, tracking progress, and setting reminders.", + }, + { + question: "How do I reset my password?", + content: + "To reset your password, go to the login page and click on the 'Forgot Password' link. Follow the instructions to receive a password reset email and create a new password.", + }, + { + question: "Can I use this application on multiple devices?", + content: + "Yes, the application is accessible from multiple devices including desktops, tablets, and smartphones. Your data will be synced across all devices, ensuring a seamless experience.", + }, +]; diff --git a/src/constants/templateList.ts b/src/constants/templateList.ts new file mode 100644 index 000000000..72d424ee8 --- /dev/null +++ b/src/constants/templateList.ts @@ -0,0 +1,27 @@ +const templateTableList = [ + { id: 1, name: "Alani", image: "" }, + { id: 2, name: "Alani1", image: "" }, + { id: 3, name: "Alani2", image: "" }, + { id: 4, name: "Alani3", image: "" }, + { id: 5, name: "Alani4", image: "" }, + { id: 6, name: "Alani5", image: "" }, + { id: 7, name: "Alani6", image: "" }, + { id: 8, name: "Alani7", image: "" }, + { id: 9, name: "Alani8", image: "" }, + { id: 10, name: "Alani9", image: "" }, + { id: 11, name: "Alani10", image: "" }, + { id: 12, name: "Alani11", image: "" }, + { id: 13, name: "Alani12", image: "" }, + { id: 14, name: "Alani13", image: "" }, + { id: 15, name: "Alani14", image: "" }, + { id: 16, name: "Alani15", image: "" }, + { id: 17, name: "Alani16", image: "" }, + { id: 18, name: "Alani17", image: "" }, + { id: 19, name: "Alani18", image: "" }, + { id: 20, name: "Alani19", image: "" }, + { id: 21, name: "Alani20", image: "" }, + { id: 22, name: "Alani21", image: "" }, + { id: 23, name: "Alani22", image: "" }, +]; + +export default templateTableList; diff --git a/src/constants/topicsdata.ts b/src/constants/topicsdata.ts new file mode 100644 index 000000000..e5b61e58a --- /dev/null +++ b/src/constants/topicsdata.ts @@ -0,0 +1,47 @@ +export const topics = [ + { + id: "item-1", + question: "What is EcoClean?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, + { + id: "item-2", + question: "How does EcoClean work?", + answer: "Lorem ipsum dolor sit amet consectetur elit.", + }, + { + id: "item-3", + question: "What are the key features of EcoClean?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, + { + id: "item-4", + question: "Who can benefit from using EcoClean?", + answer: "Lorem ipsum dolor sit amet consectetur elit.", + }, + { + id: "item-5", + question: "What are the system requirements for EcoClean?", + answer: "Lorem ipsum dolor sit amet consectetur elit.", + }, + { + id: "item-6", + question: "How do I use EcoClean?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, + { + id: "item-7", + question: "How do I store EcoClean?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, + { + id: "item-8", + question: "How much does EcoClean cost?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, + { + id: "item-9", + question: "Are there any discounts available?", + answer: "Lorem ipsum dolor sit amet, consectetur elit.", + }, +]; diff --git a/src/test/pricingpage/pricing.test.tsx b/src/test/pricingpage/pricing.test.tsx new file mode 100644 index 000000000..e8e232bcb --- /dev/null +++ b/src/test/pricingpage/pricing.test.tsx @@ -0,0 +1,38 @@ +import { fireEvent, render, screen } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import Pricing from "~/app/(landing-routes)/pricing/page"; + +// + +describe("pricing Component", () => { + it("renders the pricing header", () => { + expect.assertions(1); + render(); + const pricingHeader = screen.getByTestId("pricing-header"); + expect(pricingHeader).toBeInTheDocument(); + }); + + it("toggles between monthly and annual plans", () => { + expect.assertions(2); + render(); + const monthlyToggle = screen.getByTestId("monthly-toggle"); + const annualToggle = screen.getByTestId("annual-toggle"); + + fireEvent.click(annualToggle); + const annualCard = screen.getByTestId("basic-card-annual"); + expect(annualCard).toBeInTheDocument(); + + fireEvent.click(monthlyToggle); + const monthlyCard = screen.getByTestId("basic-card-monthly"); + expect(monthlyCard).toBeInTheDocument(); + }); + + it("renders FAQ section", () => { + expect.assertions(1); + render(); + const faqSection = screen.getByTestId("faq-section"); + expect(faqSection).toBeInTheDocument(); + }); +});