mirror of
https://github.com/OwethuManagedServices/oms-website-nextjs.git
synced 2025-12-17 19:08:09 +00:00
350 lines
13 KiB
TypeScript
350 lines
13 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
useState /* useRef, useEffect - Remove these if no longer needed */,
|
|
} from "react"; // Updated imports
|
|
import Link from "next/link";
|
|
import { Vacancy } from "@/types";
|
|
import VacancyApplicationForm from "@/components/VacancyApplicationForm";
|
|
|
|
import {
|
|
FaMapMarkerAlt,
|
|
FaBriefcase,
|
|
FaClock,
|
|
FaCalendarAlt,
|
|
FaDollarSign,
|
|
FaGraduationCap,
|
|
FaShareAlt,
|
|
FaCheckCircle,
|
|
FaBuilding,
|
|
FaLink,
|
|
FaListUl,
|
|
FaInfoCircle,
|
|
FaStar,
|
|
FaGift,
|
|
FaTools,
|
|
} from "react-icons/fa";
|
|
import { formatDate } from "@/lib/helpers";
|
|
import { COLORS } from "@/constants";
|
|
import Image from "next/image";
|
|
import { MetadataItem } from "./MetadataItem";
|
|
import { Badge } from "./Badge";
|
|
import { ListSection } from "./ListSection";
|
|
import Button from "@/components/ui/Button"; // Assuming you have a Button component
|
|
import Modal from "@/components/ui/Modal";
|
|
|
|
interface VacancyClientContentProps {
|
|
vacancy: Vacancy & {
|
|
company?: { name: string; logoUrl?: string; websiteUrl?: string };
|
|
skills?: string[];
|
|
};
|
|
shareUrl: string;
|
|
shareTitle: string;
|
|
}
|
|
|
|
export default function VacancyClientContent({
|
|
vacancy,
|
|
shareUrl,
|
|
shareTitle,
|
|
}: VacancyClientContentProps) {
|
|
// State to control modal visibility
|
|
const [isApplyModalOpen, setIsApplyModalOpen] = useState(false);
|
|
// const applyFormRef = useRef<HTMLDivElement>(null); // Remove this ref
|
|
// Remove the useEffect for scrolling
|
|
|
|
const handleOpenApplyModal = () => {
|
|
setIsApplyModalOpen(true);
|
|
};
|
|
|
|
const handleCloseApplyModal = () => {
|
|
setIsApplyModalOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{" "}
|
|
{/* Adjusted padding */}
|
|
{/* --- Company Header --- */}
|
|
{vacancy.company && (
|
|
<div className="mb-10 flex flex-col sm:flex-row items-center justify-between gap-6 p-6 bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg border border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center gap-4 flex-grow min-w-0">
|
|
{" "}
|
|
{/* Added flex-grow and min-w-0 for wrapping */}
|
|
{vacancy.company.logoUrl ? (
|
|
<Image
|
|
src={vacancy.company.logoUrl}
|
|
alt={`${vacancy.company.name} Logo`}
|
|
width={64} // Add width/height for better layout shift prevention
|
|
height={64}
|
|
className="h-16 w-16 object-contain rounded-md flex-shrink-0"
|
|
/>
|
|
) : (
|
|
<div className="h-16 w-16 bg-gray-200 dark:bg-gray-700 rounded-md flex items-center justify-center flex-shrink-0">
|
|
<FaBuilding
|
|
className="h-8 w-8 text-primary"
|
|
style={{ color: COLORS.primary }}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="min-w-0">
|
|
{" "}
|
|
{/* Added min-w-0 here too */}
|
|
<h1 className="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white truncate">
|
|
{" "}
|
|
{/* Consider truncate */}
|
|
{vacancy.title}
|
|
</h1>
|
|
<p className="text-lg text-gray-700 dark:text-gray-300">
|
|
at {vacancy.company.name}
|
|
</p>
|
|
{vacancy.company.websiteUrl && (
|
|
<Link
|
|
href={vacancy.company.websiteUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 inline-flex items-center gap-1 group mt-1"
|
|
>
|
|
Visit website{" "}
|
|
<FaLink className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-1" />
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{/* Apply Button in Header */}
|
|
<Button
|
|
variant="primary" // Use your Button component if available
|
|
onClick={handleOpenApplyModal}
|
|
className="flex-shrink-0 whitespace-nowrap" // Prevent shrinking/wrapping
|
|
>
|
|
Apply Now
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{/* --- End Company Header --- */}
|
|
{/* --- Main Grid Layout --- */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 lg:gap-10">
|
|
{/* --- Main Content Area (Left Column) --- */}
|
|
<div className="lg:col-span-2 bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6 md:p-8 border border-gray-200 dark:border-gray-700">
|
|
{/* Title if no company header */}
|
|
{!vacancy.company && (
|
|
<div className="flex justify-between items-start mb-6">
|
|
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white">
|
|
{vacancy.title}
|
|
</h1>
|
|
{/* Apply Button if no header */}
|
|
<Button
|
|
variant="primary"
|
|
onClick={handleOpenApplyModal}
|
|
className="ml-4 flex-shrink-0 whitespace-nowrap"
|
|
>
|
|
Apply Now
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Job Description */}
|
|
<div className="mb-8">
|
|
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
|
<FaInfoCircle
|
|
className="h-5 w-5 text-primary"
|
|
style={{ color: COLORS.primary }}
|
|
/>{" "}
|
|
Job Description
|
|
</h3>
|
|
<div
|
|
className="prose prose-sm sm:prose-base max-w-none text-gray-700 dark:text-gray-300 font-poppins leading-relaxed prose-headings:text-gray-900 prose-headings:dark:text-white prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-strong:text-gray-800 dark:prose-strong:text-gray-200"
|
|
dangerouslySetInnerHTML={{ __html: vacancy.description || "" }} // Use dangerouslySetInnerHTML if description is HTML
|
|
>
|
|
{/* Or render as text if plain: <p>{vacancy.description}</p> */}
|
|
</div>
|
|
</div>
|
|
|
|
{/* --- List Sections (Responsibilities, Qualifications, Skills, Benefits) --- */}
|
|
{/* Assuming ListSection renders conditionally if items are empty */}
|
|
<ListSection
|
|
title="Responsibilities"
|
|
items={vacancy.responsibilities}
|
|
icon={FaListUl}
|
|
/>
|
|
<ListSection
|
|
title="Required Qualifications"
|
|
items={vacancy.requiredQualifications}
|
|
icon={FaStar}
|
|
/>
|
|
<ListSection
|
|
title="Preferred Qualifications"
|
|
items={vacancy.preferredQualifications}
|
|
icon={FaStar}
|
|
/>
|
|
|
|
{/* Skills Section */}
|
|
{vacancy.skills && vacancy.skills.length > 0 && (
|
|
<div className="mb-8">
|
|
<h3 className="flex items-center gap-2 text-xl font-semibold mb-4 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
|
<FaTools
|
|
className="h-5 w-5 text-primary"
|
|
style={{ color: COLORS.primary }}
|
|
/>{" "}
|
|
Skills
|
|
</h3>
|
|
<div className="flex flex-wrap gap-2 mt-3">
|
|
{vacancy.skills.map((skill, index) => (
|
|
<Badge key={index}>{skill}</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Benefits Section */}
|
|
{vacancy.benefits && vacancy.benefits.length > 0 && (
|
|
<div className="mb-8">
|
|
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2">
|
|
<FaGift
|
|
className="h-5 w-5 text-primary"
|
|
style={{ color: COLORS.primary }}
|
|
/>{" "}
|
|
Benefits
|
|
</h3>
|
|
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-2 text-gray-700 dark:text-gray-300 mt-3 text-sm">
|
|
{vacancy.benefits.map((item, index) => (
|
|
<li
|
|
key={index}
|
|
className="flex items-center space-x-2 font-poppins"
|
|
>
|
|
<FaCheckCircle className="h-4 w-4 text-green-600 flex-shrink-0" />
|
|
<span>{item}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
{/* --- End List Sections --- */}
|
|
|
|
{/* Apply button below main content (redundant if header button exists, keep or remove as needed) */}
|
|
<div className="mt-10 text-center lg:text-left">
|
|
<Button variant="primary" onClick={handleOpenApplyModal}>
|
|
Apply for this Position
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
{/* --- End Main Content Area --- */}
|
|
|
|
{/* --- Sidebar (Right Column) --- */}
|
|
<div className="lg:col-span-1 space-y-8">
|
|
{/* Metadata Card */}
|
|
<div className="bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg p-6 border-l-4 border-primary dark:border-gold-500">
|
|
<h3 className="text-xl font-semibold mb-5 text-gray-900 dark:text-white font-poppins">
|
|
Job Overview
|
|
</h3>
|
|
<div className="space-y-4">
|
|
<MetadataItem
|
|
icon={FaBuilding}
|
|
label="Department"
|
|
value={vacancy.department}
|
|
/>
|
|
<MetadataItem
|
|
icon={FaMapMarkerAlt}
|
|
label="Location"
|
|
value={
|
|
<>
|
|
{vacancy.location.city}, {vacancy.location.country}{" "}
|
|
{vacancy.location.remote && <Badge>Remote Possible</Badge>}
|
|
</>
|
|
}
|
|
/>
|
|
<MetadataItem
|
|
icon={FaBriefcase}
|
|
label="Type"
|
|
value={<Badge>{vacancy.employmentType}</Badge>}
|
|
/>
|
|
<MetadataItem
|
|
icon={FaGraduationCap}
|
|
label="Level"
|
|
value={vacancy.experienceLevel}
|
|
/>
|
|
{vacancy.salary && (
|
|
<MetadataItem
|
|
icon={FaDollarSign}
|
|
label="Salary"
|
|
value={
|
|
<span className="font-semibold text-gray-800 dark:text-gray-200">
|
|
{vacancy.salary.min.toLocaleString()} -{" "}
|
|
{vacancy.salary.max.toLocaleString()}{" "}
|
|
{vacancy.salary.currency} {vacancy.salary.period}
|
|
</span>
|
|
}
|
|
/>
|
|
)}
|
|
<MetadataItem
|
|
icon={FaCalendarAlt}
|
|
label="Posted"
|
|
value={formatDate(vacancy.postedDate)}
|
|
/>
|
|
{vacancy.applicationDeadline && (
|
|
<MetadataItem
|
|
icon={FaClock}
|
|
label="Apply by"
|
|
value={formatDate(vacancy.applicationDeadline)}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Share Card */}
|
|
<div className="bg-gray-50 dark:bg-gray-800 shadow-md rounded-lg p-6 border-l-4 border-primary dark:border-gold-500">
|
|
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2 font-poppins">
|
|
<FaShareAlt
|
|
className="h-5 w-5 text-primary"
|
|
style={{ color: COLORS.primary }}
|
|
/>{" "}
|
|
Share this opening
|
|
</h3>
|
|
<div className="flex space-x-3 text-sm font-poppins">
|
|
<a
|
|
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
|
|
shareUrl
|
|
)}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
LinkedIn
|
|
</a>
|
|
<a
|
|
href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(
|
|
shareUrl
|
|
)}&text=${shareTitle}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
|
|
>
|
|
Twitter
|
|
</a>
|
|
<a
|
|
href={`mailto:?subject=${shareTitle}&body=Check out this job opening: ${encodeURIComponent(
|
|
shareUrl
|
|
)}`}
|
|
className="text-gray-600 hover:underline dark:text-gray-400 dark:hover:text-gray-300"
|
|
>
|
|
Email
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Modal
|
|
isOpen={isApplyModalOpen}
|
|
onClose={handleCloseApplyModal}
|
|
title={`Apply for: ${vacancy.title}`}
|
|
size="4xl"
|
|
>
|
|
<VacancyApplicationForm
|
|
vacancyId={vacancy.id}
|
|
vacancyTitle={vacancy.title}
|
|
onClose={handleCloseApplyModal}
|
|
/>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|