Skip to content

Commit 57f804c

Browse files
committed
Job details page client component.
1 parent 086c1cb commit 57f804c

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"use client"
2+
3+
import React, { useEffect, useMemo, useState } from 'react'
4+
import Link from 'next/link'
5+
import { useSearchParams } from 'next/navigation'
6+
import JobDetails from '@/components/jobs/JobDetails'
7+
import type { ApiResponse, JobWithRelations } from '@/lib/jobs/types'
8+
9+
type LoadState = 'idle' | 'loading' | 'error' | 'success'
10+
11+
export default function JobDetailsClient() {
12+
const searchParams = useSearchParams()
13+
const [job, setJob] = useState<JobWithRelations | null>(null)
14+
const [state, setState] = useState<LoadState>('idle')
15+
const [errorMessage, setErrorMessage] = useState<string>('')
16+
17+
const jobId = useMemo(() => {
18+
const raw = searchParams.get('id')
19+
if (!raw) return null
20+
21+
const parsed = parseInt(raw, 10)
22+
return Number.isNaN(parsed) ? null : parsed
23+
}, [searchParams])
24+
25+
useEffect(() => {
26+
if (jobId === null) {
27+
setState('error')
28+
setErrorMessage('Missing or invalid job id in the URL.')
29+
setJob(null)
30+
return
31+
}
32+
33+
const controller = new AbortController()
34+
35+
const loadJob = async () => {
36+
try {
37+
setState('loading')
38+
setErrorMessage('')
39+
40+
const res = await fetch(`https://api.codebuilder.org/jobs/${jobId}`, {
41+
signal: controller.signal,
42+
})
43+
44+
if (!res.ok) {
45+
throw new Error('Job not found.')
46+
}
47+
48+
const json: ApiResponse<JobWithRelations> | JobWithRelations = await res.json()
49+
const data = (json as any)?.success === true ? (json as any).data : json
50+
51+
if (!data) {
52+
throw new Error('Job payload is empty.')
53+
}
54+
55+
setJob(data)
56+
setState('success')
57+
} catch (error: any) {
58+
if (controller.signal.aborted) {
59+
return
60+
}
61+
62+
setJob(null)
63+
setState('error')
64+
setErrorMessage(error?.message || 'Unable to load job details.')
65+
}
66+
}
67+
68+
loadJob()
69+
70+
return () => controller.abort()
71+
}, [jobId])
72+
73+
return (
74+
<div className="flex flex-col inset-0 z-50 bg-primary transition-transform">
75+
<section className="bg-gray-100 py-4 md:py-6">
76+
<div className="container mx-auto py-16 px-8 md:px-20 lg:px-32">
77+
<nav className="mb-6">
78+
<Link
79+
href="/jobs"
80+
className="text-blue-600 hover:text-blue-800 transition-colors duration-200"
81+
>
82+
← Back to Jobs
83+
</Link>
84+
</nav>
85+
86+
{state === 'loading' && (
87+
<div className="rounded-lg bg-white p-6 shadow-md text-gray-700">Loading job details...</div>
88+
)}
89+
90+
{state === 'error' && (
91+
<div className="rounded-lg bg-white p-6 shadow-md">
92+
<h1 className="mb-2 text-2xl font-semibold text-gray-800">Job Not Found</h1>
93+
<p className="text-gray-600">{errorMessage || 'This job does not exist or is unavailable.'}</p>
94+
</div>
95+
)}
96+
97+
{state === 'success' && job && <JobDetails job={job} />}
98+
</div>
99+
</section>
100+
</div>
101+
)
102+
}

0 commit comments

Comments
 (0)