This commit is contained in:
Iliyan Angelov
2025-12-10 01:36:00 +02:00
parent 2f6dca736a
commit 6a9e823402
84 changed files with 5293 additions and 1836 deletions

View File

@@ -1,110 +1,125 @@
"use client";
import { useParams } from "next/navigation";
import { useEffect } from "react";
import Link from "next/link";
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Header from "@/components/shared/layout/header/Header";
import JobSingle from "@/components/pages/career/JobSingle";
import Footer from "@/components/shared/layout/footer/Footer";
import CareerScrollProgressButton from "@/components/pages/career/CareerScrollProgressButton";
import CareerInitAnimations from "@/components/pages/career/CareerInitAnimations";
import { useJob } from "@/lib/hooks/useCareer";
import { JobPosition } from "@/lib/api/careerService";
import { generateCareerMetadata } from "@/lib/seo/metadata";
import { API_CONFIG, getApiHeaders } from "@/lib/config/api";
const JobPage = () => {
const params = useParams();
const slug = params?.slug as string;
const { job, loading, error } = useJob(slug);
interface JobPageProps {
params: Promise<{
slug: string;
}>;
}
// Update metadata dynamically for client component
useEffect(() => {
if (job) {
const metadata = generateCareerMetadata(job);
const title = typeof metadata.title === 'string' ? metadata.title : `Career - ${job.title} | GNX Soft`;
document.title = title;
// Update meta description
let metaDescription = document.querySelector('meta[name="description"]');
if (!metaDescription) {
metaDescription = document.createElement('meta');
metaDescription.setAttribute('name', 'description');
document.head.appendChild(metaDescription);
// Generate static params for all job positions at build time (optional - for better performance)
// This pre-generates known pages, but new pages can still be generated on-demand
export async function generateStaticParams() {
try {
// Use internal API URL for server-side requests
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
const response = await fetch(
`${apiUrl}/api/career/jobs`,
{
method: 'GET',
headers: getApiHeaders(),
next: { revalidate: 60 }, // Revalidate every minute
}
const description = typeof metadata.description === 'string' ? metadata.description : `Apply for ${job.title} at GNX Soft. ${job.location || 'Remote'} position.`;
metaDescription.setAttribute('content', description);
);
// Update canonical URL
let canonical = document.querySelector('link[rel="canonical"]');
if (!canonical) {
canonical = document.createElement('link');
canonical.setAttribute('rel', 'canonical');
document.head.appendChild(canonical);
}
canonical.setAttribute('href', `${window.location.origin}/career/${job.slug}`);
if (!response.ok) {
console.error('Error fetching jobs for static params:', response.status);
return [];
}
}, [job]);
if (loading) {
const data = await response.json();
const jobs = data.results || data;
return jobs.map((job: JobPosition) => ({
slug: job.slug,
}));
} catch (error) {
console.error('Error generating static params for jobs:', error);
return [];
}
}
// Generate metadata for each job page
export async function generateMetadata({ params }: JobPageProps): Promise<Metadata> {
const { slug } = await params;
try {
// Use internal API URL for server-side requests
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
const response = await fetch(
`${apiUrl}/api/career/jobs/${slug}`,
{
method: 'GET',
headers: getApiHeaders(),
next: { revalidate: 60 }, // Revalidate every minute
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const job = await response.json();
return generateCareerMetadata({
title: job.title,
description: job.short_description || job.about_role,
slug: job.slug,
location: job.location,
department: job.department,
employment_type: job.employment_type,
});
} catch (error) {
return {
title: 'Job Not Found | GNX Soft',
description: 'The requested job position could not be found.',
};
}
}
const JobPage = async ({ params }: JobPageProps) => {
const { slug } = await params;
try {
// Use internal API URL for server-side requests
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:1086';
const response = await fetch(
`${apiUrl}/api/career/jobs/${slug}`,
{
method: 'GET',
headers: getApiHeaders(),
next: { revalidate: 60 }, // Revalidate every minute
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const job: JobPosition = await response.json();
return (
<div className="tp-app">
<Header />
<main>
<section className="pt-120 pb-120">
<div className="container">
<div className="row">
<div className="col-12 text-center">
<h2>Loading job details...</h2>
</div>
</div>
</div>
</section>
<JobSingle job={job} />
</main>
<Footer />
<CareerScrollProgressButton />
<CareerInitAnimations />
</div>
);
} catch (error) {
notFound();
}
if (error || !job) {
return (
<div className="tp-app">
<Header />
<main>
<section className="pt-120 pb-120">
<div className="container">
<div className="row">
<div className="col-12 text-center">
<h2 className="text-danger">Job Not Found</h2>
<p className="mt-24">
The job position you are looking for does not exist or is no longer available.
</p>
<Link href="/career" className="btn mt-40">
View All Positions
</Link>
</div>
</div>
</div>
</section>
</main>
<Footer />
<CareerScrollProgressButton />
<CareerInitAnimations />
</div>
);
}
return (
<div className="tp-app">
<Header />
<main>
<JobSingle job={job} />
</main>
<Footer />
<CareerScrollProgressButton />
<CareerInitAnimations />
</div>
);
};
export default JobPage;