From b626754f5dffd9c35afed1314cce8c908ebab44c Mon Sep 17 00:00:00 2001 From: The Hanifah Date: Tue, 30 Jul 2024 13:36:42 +0100 Subject: [PATCH] feat: career and help-center/ startlight --- package.json | 3 +- pnpm-lock.yaml | 32 ++++++- src/app/(auth-routes)/login/page.tsx | 59 ++++++++++-- src/app/(auth-routes)/register/page.tsx | 81 +++++++++++----- .../(landing-routes)/career/[slug]/page.tsx | 27 +++--- src/app/(landing-routes)/career/page.tsx | 94 ++++++++++++++++--- src/app/(landing-routes)/help-center/page.tsx | 48 +++++----- .../layouts/pagination/Pagination.tsx | 56 ++++++++--- .../layouts/pagination/PaginationHook.tsx | 38 ++++++++ 9 files changed, 344 insertions(+), 94 deletions(-) create mode 100644 src/components/layouts/pagination/PaginationHook.tsx diff --git a/package.json b/package.json index 96bd2a8f4..7d34b4ceb 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@react-email/tailwind": "^0.0.18", "@t3-oss/env-nextjs": "^0.10.1", "@types/jest-axe": "^3.5.9", + "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cookies-next": "^4.2.1", @@ -49,7 +50,7 @@ "lenis": "^1.1.6", "lucide-react": "^0.400.0", "moment": "^2.30.1", - "next": "14.2.5", + "next": "^14.2.5", "next-auth": "5.0.0-beta.19", "next-nprogress-bar": "^2.3.13", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1161db10..5613c3769 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: '@types/jest-axe': specifier: ^3.5.9 version: 3.5.9 + axios: + specifier: ^1.7.2 + version: 1.7.2 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -99,7 +102,7 @@ importers: specifier: ^2.30.1 version: 2.30.1 next: - specifier: 14.2.5 + specifier: ^14.2.5 version: 14.2.5(@babel/core@7.24.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-auth: specifier: 5.0.0-beta.19 @@ -2410,6 +2413,9 @@ packages: resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==} engines: {node: '>=4'} + axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -3189,6 +3195,15 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -4307,6 +4322,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -7627,6 +7645,14 @@ snapshots: axe-core@4.9.1: {} + axios@1.7.2: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@3.1.1: dependencies: deep-equal: 2.2.3 @@ -8639,6 +8665,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.6: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -9715,6 +9743,8 @@ snapshots: proto-list@1.2.4: {} + proxy-from-env@1.1.0: {} + psl@1.9.0: {} punycode@2.3.1: {} diff --git a/src/app/(auth-routes)/login/page.tsx b/src/app/(auth-routes)/login/page.tsx index cd79f013a..e960e7700 100644 --- a/src/app/(auth-routes)/login/page.tsx +++ b/src/app/(auth-routes)/login/page.tsx @@ -1,6 +1,7 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; +import axios from "axios"; import { Eye, EyeOff, ShieldCheck } from "lucide-react"; import { useRouter } from "next-nprogress-bar"; import Image from "next/image"; @@ -10,7 +11,7 @@ import { useEffect, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import * as z from "zod"; -import { loginUser } from "~/actions/login"; +// import { loginUser } from "~/actions/login"; import LoadingSpinner from "~/components/miscellaneous/loading-spinner"; import { Button } from "~/components/ui/button"; import { Checkbox } from "~/components/ui/checkbox"; @@ -23,8 +24,9 @@ import { FormMessage, } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; +import { toast } from "~/components/ui/use-toast"; import { useUser } from "~/hooks/user/use-user"; -import { simulateDelay } from "~/lib/utils"; +// import { simulateDelay } from "~/lib/utils"; import Facebook from "../../../../public/images/facebook.svg"; import Google from "../../../../public/images/google.svg"; @@ -70,13 +72,52 @@ const LoginPage = () => { const onSubmit = async (values: z.infer) => { startTransition(async () => { - await simulateDelay(3); - await loginUser(values); - updateUser({ email: values.email, name: values.email.split("@")[0] }); - if (callback_url) { - router.push(callback_url); - } else { - router.push("/"); + try { + const response = await axios.post( + "https://deployment.api-nestjs.boilerplate.hng.tech/api/v1/auth/login", + { + email: values.email, + password: values.password, + }, + ); + + if (response.status === 200) { + // Login successful + const userData = response.data; + updateUser(userData); + // eslint-disable-next-line no-console + console.log(userData); + + localStorage.setItem("token", userData.token); + + toast({ + title: "Success", + description: "Logged in successfully!", + variant: "default", + }); + + if (callback_url) { + router.push(callback_url); + } else { + router.push("/"); + } + } + } catch (error) { + if (axios.isAxiosError(error)) { + toast({ + title: "Error", + description: + error.response?.data?.message || + "Login failed. Please try again.", + variant: "destructive", + }); + } else { + toast({ + title: "Error", + description: "An unexpected error occurred. Please try again.", + variant: "destructive", + }); + } } }); }; diff --git a/src/app/(auth-routes)/register/page.tsx b/src/app/(auth-routes)/register/page.tsx index 2c4d59ced..9295f189b 100644 --- a/src/app/(auth-routes)/register/page.tsx +++ b/src/app/(auth-routes)/register/page.tsx @@ -2,9 +2,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { DialogContent, DialogTitle } from "@radix-ui/react-dialog"; +import axios from "axios"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; +import { SubmitHandler, useForm } from "react-hook-form"; import { z } from "zod"; import CustomButton from "~/components/common/common-button/common-button"; @@ -41,7 +43,10 @@ const formSchema = z.object({ type FormData = z.infer; const SignUp = () => { - const [apiUrl, setApiUrl] = useState(""); + const router = useRouter(); + const [apiUrl, setApiUrl] = useState( + "https://deployment.api-nestjs.boilerplate.hng.tech/api/v1/auth/register", + ); const { toast } = useToast(); useEffect(() => { @@ -49,6 +54,8 @@ const SignUp = () => { try { const url = await getApiUrl(); setApiUrl(url); + // eslint-disable-next-line no-console + console.log("API URL set to:", url); } catch { toast({ title: "Error", @@ -61,23 +68,62 @@ const SignUp = () => { fetchApiUrl(); }, [toast]); + // eslint-disable-next-line prettier/prettier + + const form = useForm({ resolver: zodResolver(formSchema), }); + const [open, setOpen] = useState(false); - const handleFormSubmit = () => { - form.handleSubmit(() => { - if (form.formState.isValid) { - setOpen(true); + const handleSubmit = async (data: FormData) => { + try { + if (!apiUrl) { + throw new Error("API URL is not available"); } - })(); - }; - const handleSubmit = async () => { - form.handleSubmit(handleFormSubmit)(); + const nameParts = data.fullname.trim().split(/\s+/); + const first_name = nameParts[0]; + const last_name = nameParts.slice(1).join(""); + + const apiData = { + first_name, + last_name, + email: data.email, + password: data.password, + }; + + const response = await axios.post(`${apiUrl}`, apiData); + if (response.status === 200 || response.status === 201) { + toast({ + title: "Success", + description: "Account created successfully!", + }); + // Redirect to login page or dashboard + router.push("/login"); + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + toast({ + title: "Error", + description: + error.response?.data?.message || + "Failed to create account. Please try again.", + variant: "destructive", + }); + } else { + toast({ + title: "Error", + description: "An unexpected error occurred. Please try again.", + variant: "destructive", + }); + } + } }; + const onSubmit: SubmitHandler = (data) => handleSubmit(data); + return (
@@ -161,13 +207,7 @@ const SignUp = () => {
- { - event.preventDefault(); - handleSubmit(); - }} - > + { )} /> - + Create Account diff --git a/src/app/(landing-routes)/career/[slug]/page.tsx b/src/app/(landing-routes)/career/[slug]/page.tsx index e7ff22f45..f2389d49b 100644 --- a/src/app/(landing-routes)/career/[slug]/page.tsx +++ b/src/app/(landing-routes)/career/[slug]/page.tsx @@ -1,6 +1,7 @@ "use client"; import { Plus } from "lucide-react"; +import Link from "next/link"; import { useEffect, useState } from "react"; import { Breadcrumb } from "~/components/common/breadcrumb"; @@ -43,7 +44,7 @@ const JobDetails = () => {
-
+

@@ -209,17 +210,19 @@ const JobDetails = () => {

-
- } - isLeftIconVisible={isSmallScreen} - isDisabled={false} - className={`h-[50px] w-[250px]`} - > - Apply Now - +
+ + } + isLeftIconVisible={isSmallScreen} + isDisabled={false} + className={`h-[50px] w-[250px]`} + > + Apply Now + +
); diff --git a/src/app/(landing-routes)/career/page.tsx b/src/app/(landing-routes)/career/page.tsx index 386382c5f..b5f799622 100644 --- a/src/app/(landing-routes)/career/page.tsx +++ b/src/app/(landing-routes)/career/page.tsx @@ -1,36 +1,104 @@ +"use client"; + +// Career.tsx +import React, { useEffect, useState } from "react"; + import CareerCardParent from "~/components/common/CareerCard"; import Pagination from "~/components/layouts/pagination/Pagination"; -// +const ITEMS_PER_PAGE = 6; +const TOTAL_ITEMS = 200; + +interface UsePaginationReturn { + currentPage: number; + changePage: (page: number) => void; +} + +function usePagination(initialPage: number = 1): UsePaginationReturn { + const [currentPage, setCurrentPage] = useState(initialPage); + + const changePage = (page: number) => { + const pageNumber = Math.max( + 1, + Math.min(page, Math.ceil(TOTAL_ITEMS / ITEMS_PER_PAGE)), + ); + setCurrentPage(pageNumber); + }; + + return { + currentPage, + changePage, + }; +} + +interface PaginationWrapperProperties { + currentPage: number; + onPageChange: (page: number) => void; +} -export default function Career() { +const PaginationWrapper: React.FC = ({ + currentPage, + onPageChange, +}) => { return ( -
+ + ); +}; + +const Career: React.FC = () => { + const { currentPage, changePage } = usePagination(); + const [visibleJobs, setVisibleJobs] = useState([]); + + useEffect(() => { + const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; + const newVisibleJobs = Array.from( + { length: ITEMS_PER_PAGE }, + (_, index) => startIndex + index, + ); + setVisibleJobs(newVisibleJobs); + }, [currentPage]); + + return ( +
-

+

Career

- -

+

Available Jobs in Our company -

-

+ +

Explore job opportunities across various fields that fit for your skills and career aspirations.

- {Array.from({ length: 6 }).map((_, index) => ( - + {visibleJobs.map((jobIndex) => ( +
+ +
))}
-
Showing 50 of 500
+
+ Showing {Math.min(currentPage * ITEMS_PER_PAGE, TOTAL_ITEMS)} of{" "} + {TOTAL_ITEMS} +
- +
); -} +}; + +export default Career; diff --git a/src/app/(landing-routes)/help-center/page.tsx b/src/app/(landing-routes)/help-center/page.tsx index 514d7e4b8..f7bc14467 100644 --- a/src/app/(landing-routes)/help-center/page.tsx +++ b/src/app/(landing-routes)/help-center/page.tsx @@ -15,39 +15,39 @@ const HelpCenter = () => { return (
-
+
Help Center -
+

- How can we help You? + How can we help You?

Find advice and answers from our support team

-
- - -
+
+
+ +
@@ -55,12 +55,12 @@ const HelpCenter = () => {
Browse by topics @@ -68,11 +68,11 @@ const HelpCenter = () => {
-
-
+
+

@@ -85,7 +85,7 @@ const HelpCenter = () => { Contact us @@ -96,12 +96,12 @@ const HelpCenter = () => {

-
+
-

+

Didn’t find an answer?

-

+

Contact us for more inquiries and information about our services.

diff --git a/src/components/layouts/pagination/Pagination.tsx b/src/components/layouts/pagination/Pagination.tsx index 518f63d46..ca1f15118 100644 --- a/src/components/layouts/pagination/Pagination.tsx +++ b/src/components/layouts/pagination/Pagination.tsx @@ -16,6 +16,8 @@ export interface PaginationProperties { activeVariant?: "default" | "outline"; navigationVariant?: "semibold" | "medium"; onChange?: (page: number) => void; + previousText?: string; + nextText?: string; } const Pagination = ({ @@ -68,19 +70,34 @@ const Pagination = ({ - currentPage > 1 && handleChange(currentPage - 1)} - className={`${ + className={`flex items-center ${ navigationVariant === "semibold" ? "text-xl font-semibold leading-normal" - : "text-base font-medium leading-6" + : "pr-1 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" + : "cursor-not-allowed text-stroke-colors-stroke" }`} - href={currentPage > 1 ? `?page=${currentPage - 1}` : "#"} - /> + > + {" "} + currentPage > 1 && handleChange(currentPage - 1)} + className={`${ + navigationVariant === "semibold" + ? "text-xl font-semibold leading-normal" + : "border-none text-base font-medium leading-6" + } ${ + currentPage > 1 + ? "cursor-pointer bg-background" + : "cursor-not-allowed bg-background text-stroke-colors-stroke hover:bg-transparent hover:text-stroke-colors-stroke" + }`} + href={currentPage > 1 ? `?page=${currentPage - 1}` : "#"} + > + Previous +
{getPageNumbers().map((page, index) => typeof page === "number" ? ( @@ -92,7 +109,7 @@ const Pagination = ({ className={ currentPage === page && activeVariant === "default" ? "bg-primary" - : "" + : "bg-inherit text-neutral-800" } > {page} @@ -109,8 +126,11 @@ const Pagination = ({ currentPage < totalPages && handleChange(currentPage + 1) } > - + currentPage < totalPages && handleChange(currentPage + 1) + } + className={`flex items-center ${ navigationVariant === "semibold" ? "text-xl font-semibold leading-normal" : "text-base font-medium leading-6" @@ -119,8 +139,22 @@ const Pagination = ({ ? "cursor-pointer" : "cursor-not-allowed text-stroke-colors-stroke hover:bg-transparent hover:text-stroke-colors-stroke" }`} - href={currentPage < totalPages ? `?page=${currentPage + 1}` : "#"} - /> + > + {" "} + Next + +
diff --git a/src/components/layouts/pagination/PaginationHook.tsx b/src/components/layouts/pagination/PaginationHook.tsx new file mode 100644 index 000000000..7bab4506d --- /dev/null +++ b/src/components/layouts/pagination/PaginationHook.tsx @@ -0,0 +1,38 @@ +// usePagination.ts +import { useEffect, useState } from "react"; + +interface UsePaginationProperties { + totalItems: number; + itemsPerPage: number; + initialPage?: number; +} + +interface UsePaginationReturn { + currentPage: number; + pageCount: number; + changePage: (page: number) => void; +} + +export function usePagination({ + totalItems, + itemsPerPage, + initialPage = 1, +}: UsePaginationProperties): UsePaginationReturn { + const [currentPage, setCurrentPage] = useState(initialPage); + const [pageCount, setPageCount] = useState(0); + + useEffect(() => { + setPageCount(Math.ceil(totalItems / itemsPerPage)); + }, [totalItems, itemsPerPage]); + + const changePage = (page: number) => { + const pageNumber = Math.max(1, Math.min(page, pageCount)); + setCurrentPage(pageNumber); + }; + + return { + currentPage, + pageCount, + changePage, + }; +}