diff --git a/package.json b/package.json index e47c678e..f0957ca6 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", "@t3-oss/env-nextjs": "^0.11.1", + "@tanstack/react-query": "^5.59.17", "@types/lodash": "^4.17.7", "@types/uuid": "^10.0.0", "@uidotdev/usehooks": "^2.4.1", diff --git a/src/actions/job.action.ts b/src/actions/job.action.ts index b3b15ade..c98ae38c 100644 --- a/src/actions/job.action.ts +++ b/src/actions/job.action.ts @@ -188,9 +188,22 @@ export const getRecommendedJobs = withServerActionAsyncCatcher< ServerActionReturnType >(async (data) => { const result = RecommendedJobSchema.parse(data); - const { id, category } = result; + const { id } = result; + + const job = await prisma.job.findUnique({ + where: { id }, + select: { + id: true, + category: true, + }, + }); + + if (!job) { + throw new Error('Job not found'); + } + + const { category } = job; - // fettching the latest three jobs excluding the current job and in the same category const jobs = await prisma.job.findMany({ where: { category: category, @@ -236,7 +249,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher< orderBy: { postedAt: 'desc', }, - take: 3, // Fallback to showing latest 3 jobs from other categories + take: 3, select: { id: true, type: true, diff --git a/src/app/jobs/[id]/page.tsx b/src/app/jobs/[id]/page.tsx index e34810b3..bef48bce 100644 --- a/src/app/jobs/[id]/page.tsx +++ b/src/app/jobs/[id]/page.tsx @@ -1,62 +1,56 @@ import { getJobById, getRecommendedJobs } from '@/actions/job.action'; import { Job } from '@/components/job'; -import JobCard from '@/components/job-card-rec'; import { JobByIdSchemaType } from '@/lib/validators/jobs.validator'; +import { HydrationBoundary, dehydrate } from '@tanstack/react-query'; import { ArrowLeft } from 'lucide-react'; import Link from 'next/link'; -import { redirect } from 'next/navigation'; +import getQueryClient from '../../../providers/queryClient'; +import { RecommendJobs } from '@/components/RecommendJobs'; const page = async ({ params }: { params: JobByIdSchemaType }) => { - const job = await getJobById(params); - if (!job.status) { - return; - } + const queryClient = getQueryClient(); - const jobDetail = job.additional?.job; - if (!jobDetail) { - return redirect('/jobs'); - } - - const curatedJobs = await getRecommendedJobs({ - id: jobDetail.id, - category: jobDetail.category, + await queryClient.prefetchQuery({ + queryKey: ['jobs', params], + queryFn: () => getJobById({ id: params.id }), + staleTime: 1000 * 60 * 5, }); - if (!curatedJobs.status) { - return; - } - - const recommendedJobs = curatedJobs.additional?.jobs; + await queryClient.prefetchQuery({ + queryKey: ['RecommendedJobs', params], + queryFn: () => getRecommendedJobs({ id: params.id }), + staleTime: 1000 * 60 * 5, + }); return ( -
-
- - -

Back to All Jobs

- -
-
- {/* the particular job details */} - - - {/* job recommendations */} - -
-
+ +
+
+ + +

Back to All Jobs

+ +
+
+ {/* the particular job details */} + + + + {/* job recommendations */} + +
+
+
); }; diff --git a/src/app/jobs/page.tsx b/src/app/jobs/page.tsx index 5780df3a..4da712f5 100644 --- a/src/app/jobs/page.tsx +++ b/src/app/jobs/page.tsx @@ -8,51 +8,74 @@ import { } from '@/lib/validators/jobs.validator'; import { redirect } from 'next/navigation'; import { Suspense } from 'react'; +import getQueryClient from '../../providers/queryClient'; +import { GetUserBookmarksId, getAllJobs } from '@/actions/job.action'; +import { HydrationBoundary, dehydrate } from '@tanstack/react-query'; +import { getServerSession } from 'next-auth'; const page = async ({ searchParams }: { searchParams: JobQuerySchemaType }) => { + const session = await getServerSession(); + if (!session) { + redirect('/auth/signin'); + } const parsedData = JobQuerySchema.safeParse(searchParams); if (!(parsedData.success && parsedData.data)) { console.error(parsedData.error); redirect('/jobs'); } const parsedSearchParams = parsedData.data; - return ( -
-
-

Explore Jobs

-

- Explore thousands of remote and onsite jobs that match your skills and - aspirations. -

-
-
-
-
- -
+ const queryClient = getQueryClient(); + + await queryClient.prefetchQuery({ + queryKey: ['jobs', parsedSearchParams], + queryFn: () => getAllJobs(parsedSearchParams), + staleTime: 1000 * 60 * 5, + }); + await queryClient.prefetchQuery({ + queryKey: ['UserBookmarksId', session?.user?.id], + queryFn: () => GetUserBookmarksId(), + }); + + return ( + +
+
+

Explore Jobs

+

+ Explore thousands of remote and onsite jobs that match your skills + and aspirations. +

-
-
- +
+
+
+ +
-
- - -
- } - > - - +
+
+ +
+ +
+ + +
+ } + > + + +
-
+
); }; diff --git a/src/components/RecentJobs.tsx b/src/components/RecentJobs.tsx index d4a476c6..28a44374 100644 --- a/src/components/RecentJobs.tsx +++ b/src/components/RecentJobs.tsx @@ -1,13 +1,28 @@ +'use client'; import { getRecentJobs, GetUserBookmarksId } from '@/actions/job.action'; import JobCard from './Jobcard'; +import { useQuery } from '@tanstack/react-query'; +import { useSession } from 'next-auth/react'; -export default async function RecentJobs() { - const [recentJobs, getUserBookmarks] = await Promise.all([ - await getRecentJobs(), - await GetUserBookmarksId(), - ]); +export default function RecentJobs() { + const session = useSession(); - const userbookmarkArr: { jobId: string }[] | null = getUserBookmarks.data; + const { data } = useQuery({ + queryKey: ['recentJobs'], + queryFn: () => getRecentJobs(), + staleTime: 1000 * 60 * 5, + }); + const bookmarks = useQuery({ + queryKey: ['UserBookmarksId', session?.data?.user?.id], + queryFn: () => GetUserBookmarksId(), + }); + if (!data?.status || !data?.additional) { + return
Error {data?.message}
; + } + const recentJobs = data; + + const userbookmarkArr: { jobId: string }[] | null = + bookmarks.data?.data || null; if (!recentJobs.status) { return
{recentJobs.message}
; diff --git a/src/components/RecommendJobs.tsx b/src/components/RecommendJobs.tsx new file mode 100644 index 00000000..573999b1 --- /dev/null +++ b/src/components/RecommendJobs.tsx @@ -0,0 +1,34 @@ +'use client'; +import { getRecommendedJobs } from '@/actions/job.action'; +import { useQuery } from '@tanstack/react-query'; +import JobCard from './Jobcard'; +import { useParams } from 'next/navigation'; + +export const RecommendJobs = ({ jobId }: { jobId: string }) => { + const params = useParams(); + const { data } = useQuery({ + queryKey: ['RecommendedJobs', params], + queryFn: () => getRecommendedJobs({ id: jobId }), + staleTime: 1000 * 60 * 5, + }); + + if (!data?.status) { + return
Error
; + } + const recommendedJobs = data?.additional?.jobs; + + return ( +
+ {recommendedJobs && + recommendedJobs.map((job, index) => { + return ( + + ); + })} +
+ ); +}; diff --git a/src/components/all-jobs.tsx b/src/components/all-jobs.tsx index 4a5f8f2c..50e51909 100644 --- a/src/components/all-jobs.tsx +++ b/src/components/all-jobs.tsx @@ -1,4 +1,5 @@ -import { getAllJobs, GetUserBookmarksId } from '@/actions/job.action'; +'use client'; +import { GetUserBookmarksId, getAllJobs } from '@/actions/job.action'; import { DEFAULT_PAGE, JOBS_PER_PAGE } from '@/config/app.config'; import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; import { JobQuerySchemaType } from '@/lib/validators/jobs.validator'; @@ -10,31 +11,42 @@ import { Pagination, PaginationContent, PaginationItem } from './ui/pagination'; import { PaginationPages } from './ui/paginator'; import JobCard from './Jobcard'; import APP_PATHS from '@/config/path.config'; +import { useQuery } from '@tanstack/react-query'; +import { useSession } from 'next-auth/react'; type PaginatorProps = { searchParams: JobQuerySchemaType; }; -const AllJobs = async ({ searchParams }: PaginatorProps) => { - const [jobs, getUserBookmarks] = await Promise.all([ - await getAllJobs(searchParams), - await GetUserBookmarksId(), - ]); +const AllJobs = ({ searchParams }: PaginatorProps) => { + // const userbookmarkArr: { jobId: string }[] | null = userbookmarks; + const session = useSession(); - const userbookmarkArr: { jobId: string }[] | null = getUserBookmarks.data; + const userbookmark = useQuery({ + queryKey: ['UserBookmarksId', session?.data?.user?.id], + queryFn: () => GetUserBookmarksId(), + }); - if (!jobs.status || !jobs.additional) { - return
Error {jobs.message}
; + const { data } = useQuery({ + queryKey: ['jobs', searchParams], + queryFn: () => getAllJobs(searchParams), + staleTime: 1000 * 60 * 5, + }); + if (!data?.status || !data?.additional) { + return
Error {data?.message}
; } + const jobs = data; + const userbookmarkArr: { jobId: string }[] | null = + userbookmark.data?.data || null; const totalPages = Math.ceil((jobs.additional?.totalJobs || 0) / JOBS_PER_PAGE) || DEFAULT_PAGE; const currentPage = parseInt(searchParams.page?.toString()) || DEFAULT_PAGE; - + const jobsLength = jobs.additional?.jobs.length || 0; // Defaults to 0 if undefined return (
- {jobs.additional.jobs.length > 0 ? ( + {jobsLength > 0 ? ( jobs.additional?.jobs.map((job, index) => ( { @@ -13,30 +17,47 @@ export const getFirstLetterCaps = (str: string): string => { return str.charAt(0).toUpperCase(); }; -export const JobLanding = () => { +export const JobLanding = async () => { + const session = await getServerSession(); + const queryClient = getQueryClient(); + + await queryClient.prefetchQuery({ + queryKey: ['recentJobs'], + queryFn: () => getRecentJobs(), + staleTime: 1000 * 60 * 5, + }); + await queryClient.prefetchQuery({ + queryKey: ['UserBookmarksId', session?.user?.id], + queryFn: () => GetUserBookmarksId(), + }); + return ( -
-
-

Recently Added jobs

-

- Stay ahead with newly added jobs -

-
- -
- + +
+
+

+ Recently Added jobs +

+

+ Stay ahead with newly added jobs +

+
+ +
+ +
-
+ ); }; diff --git a/src/components/job.tsx b/src/components/job.tsx index 31f6153b..49d59cda 100644 --- a/src/components/job.tsx +++ b/src/components/job.tsx @@ -1,5 +1,4 @@ 'use client'; -import { JobType } from '@/types/jobs.types'; import Icon from './ui/icon'; import { formatSalary } from '@/lib/utils'; import { Button } from './ui/button'; @@ -8,11 +7,29 @@ import { Briefcase, MapPin } from 'lucide-react'; import Link from 'next/link'; import Linkify from 'linkify-react'; import { ShareJobDialog } from './ShareJobDialog'; +import { useParams } from 'next/navigation'; +import { useQuery } from '@tanstack/react-query'; +import { getJobById } from '@/actions/job.action'; const options = { defaultProtocol: 'https', target: '_blank', }; -export const Job = ({ job }: { job: JobType }) => { +export const Job = () => { + const params = useParams(); + const id: any = params.id; + const { data } = useQuery({ + queryKey: ['jobs', { id }], + queryFn: () => getJobById({ id }), + staleTime: 1000 * 60 * 5, + }); + if (!data?.status) { + return
Error
; + } + const job = data?.additional?.job; + if (!job) { + return
Error
; + } + return (