displaying vacancy completed

This commit is contained in:
libertyoms
2025-04-27 09:58:38 +02:00
parent 1be00d7a42
commit d0b0d10124
7 changed files with 456 additions and 254 deletions

View File

@ -45,7 +45,7 @@ export default async function VacancyDetailsPage({
);
return (
<div className="bg-white text-gray-800 font-poppins overflow-x-hidden min-h-screen py-12 md:py-5">
<div className="bg-white dark:bg-black text-gray-800 font-poppins overflow-x-hidden min-h-screen py-12 md:py-5">
<VacancyClientContent
vacancy={vacancy}
shareUrl={shareUrl}

View File

@ -12,7 +12,7 @@ export const ListSection = ({
if (!items || items.length === 0) return null;
return (
<div className="mb-8">
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 font-poppins border-b border-gray-200 pb-2">
<h3 className="flex items-center gap-2 text-xl font-semibold mb-3 text-gray-900 dark:text-white font-poppins border-b border-gray-200 pb-2">
{Icon && (
<Icon
className="h-5 w-5 text-primary"
@ -21,7 +21,7 @@ export const ListSection = ({
)}
{title}
</h3>
<ul className="list-disc space-y-2 pl-6 text-gray-700 font-poppins text-sm leading-relaxed">
<ul className="list-disc space-y-2 pl-6 text-gray-700 dark:text-white font-poppins text-sm leading-relaxed">
{items.map((item, index) => (
<li key={index}>{item}</li>
))}

View File

@ -9,7 +9,7 @@ export const MetadataItem = ({
label: string;
value: React.ReactNode;
}) => (
<div className="flex items-start space-x-2 text-sm text-gray-700 font-poppins">
<div className="flex items-start space-x-2 text-sm text-gray-700 dark:text-white font-poppins">
<Icon
className="h-5 w-5 mt-0.5 flex-shrink-0 text-primary"
aria-hidden="true"

View File

@ -1,6 +1,8 @@
"use client";
import { useState, useRef, useEffect } from "react";
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";
@ -28,7 +30,8 @@ import Image from "next/image";
import { MetadataItem } from "./MetadataItem";
import { Badge } from "./Badge";
import { ListSection } from "./ListSection";
import Button from "@/components/ui/Button";
import Button from "@/components/ui/Button"; // Assuming you have a Button component
import Modal from "@/components/ui/Modal";
interface VacancyClientContentProps {
vacancy: Vacancy & {
@ -44,56 +47,62 @@ export default function VacancyClientContent({
shareUrl,
shareTitle,
}: VacancyClientContentProps) {
const [showApplyForm, setShowApplyForm] = useState(false);
const applyFormRef = useRef<HTMLDivElement>(null);
// State to control modal visibility
const [isApplyModalOpen, setIsApplyModalOpen] = useState(false);
// const applyFormRef = useRef<HTMLDivElement>(null); // Remove this ref
// Remove the useEffect for scrolling
const handleApplyClick = () => {
setShowApplyForm(true);
const handleOpenApplyModal = () => {
setIsApplyModalOpen(true);
};
useEffect(() => {
if (showApplyForm && applyFormRef.current) {
setTimeout(() => {
applyFormRef.current?.scrollIntoView({
behavior: "smooth",
block: "start",
});
}, 100);
}
}, [showApplyForm]);
const handleCloseApplyModal = () => {
setIsApplyModalOpen(false);
};
return (
// Container is now within the client component
<div className="container mx-auto px-6">
{/* --- Re-include the FULL Company Header --- */}
<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 shadow-md rounded-lg border border-gray-200">
<div className="flex items-center gap-4">
<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 rounded-md flex items-center justify-center 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>
<h1 className="text-3xl md:text-4xl font-bold text-gray-900">
<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">at {vacancy.company.name}</p>
<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 inline-flex items-center gap-1 group mt-1"
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" />
@ -101,45 +110,57 @@ export default function VacancyClientContent({
)}
</div>
</div>
{/* Apply Button in Header (conditional) */}
{!showApplyForm && (
<button
onClick={handleApplyClick}
className="inline-block bg-gray-800 text-white font-bold py-3 px-8 rounded-md hover:bg-gray-900 transition-colors duration-300 whitespace-nowrap"
>
Apply Now
</button>
)}
{/* 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-10">
<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 shadow-lg rounded-lg p-6 md:p-8 border border-gray-200">
<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 && (
<h1 className="text-3xl md:text-4xl font-bold mb-6 text-gray-900">
{vacancy.title}
</h1>
<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 border-b border-gray-200 pb-2">
<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 font-poppins leading-relaxed">
<p>{vacancy.description}</p>
<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>
{/* --- All List Sections (Responsibilities, Qualifications, Skills, Benefits) --- */}
{/* --- List Sections (Responsibilities, Qualifications, Skills, Benefits) --- */}
{/* Assuming ListSection renders conditionally if items are empty */}
<ListSection
title="Responsibilities"
items={vacancy.responsibilities}
@ -156,10 +177,10 @@ export default function VacancyClientContent({
icon={FaStar}
/>
{/* Skills Section - RE-INCLUDED */}
{/* 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 border-b border-gray-200 pb-2">
<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 }}
@ -174,17 +195,17 @@ export default function VacancyClientContent({
</div>
)}
{/* Benefits Section - RE-INCLUDED */}
{/* 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 border-b border-gray-200 pb-2">
<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 mt-3 text-sm">
<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}
@ -199,22 +220,20 @@ export default function VacancyClientContent({
)}
{/* --- End List Sections --- */}
{/* Apply button below main content (conditional) */}
{!showApplyForm && (
<div className="mt-10 text-center lg:text-left">
<Button variant="primary" onClick={handleApplyClick}>
Apply for this Position
</Button>
</div>
)}
{/* 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 - Exactly as before */}
<div className="bg-gray-50 shadow-md rounded-lg p-6 border-l-4 border-primary">
<h3 className="text-xl font-semibold mb-5 text-gray-900 font-poppins">
{/* 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">
@ -248,7 +267,7 @@ export default function VacancyClientContent({
icon={FaDollarSign}
label="Salary"
value={
<span className="font-semibold text-gray-800">
<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}
@ -271,9 +290,9 @@ export default function VacancyClientContent({
</div>
</div>
{/* Share Card - Exactly as before */}
<div className="bg-gray-50 shadow-md rounded-lg p-6 border-l-4 border-primary">
<h3 className="text-xl font-semibold mb-4 text-gray-900 flex items-center gap-2 font-poppins">
{/* 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 }}
@ -287,7 +306,7 @@ export default function VacancyClientContent({
)}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
>
LinkedIn
</a>
@ -297,7 +316,7 @@ export default function VacancyClientContent({
)}&text=${shareTitle}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
className="text-blue-600 hover:underline dark:text-blue-400 dark:hover:text-blue-300"
>
Twitter
</a>
@ -305,37 +324,26 @@ export default function VacancyClientContent({
href={`mailto:?subject=${shareTitle}&body=Check out this job opening: ${encodeURIComponent(
shareUrl
)}`}
className="text-gray-600 hover:underline"
className="text-gray-600 hover:underline dark:text-gray-400 dark:hover:text-gray-300"
>
Email
</a>
</div>
</div>
{/* --- Conditionally Rendered Application Form Section --- */}
{showApplyForm && (
<div
ref={applyFormRef}
id="apply-form-section"
className="bg-white shadow-lg rounded-lg p-6 md:p-8 border border-gray-200 scroll-mt-20 lg:mt-0"
>
{" "}
{/* scroll-mt helps anchor links, lg:mt-0 aligns with other cards */}
<h2 className="text-2xl font-bold mb-6 text-center text-gray-900 font-poppins">
Apply for: {vacancy.title}
</h2>
<VacancyApplicationForm
vacancyId={vacancy.id}
vacancyTitle={vacancy.title}
// Pass a function to close the form if needed: onFormClose={() => setShowApplyForm(false)}
/>
</div>
)}
{/* --- End Form Section --- */}
</div>
{/* --- End Sidebar --- */}
</div>
{/* --- End Main Grid --- */}
</div> // End Container
<Modal
isOpen={isApplyModalOpen}
onClose={handleCloseApplyModal}
title={`Apply for: ${vacancy.title}`}
size="4xl"
>
<VacancyApplicationForm
vacancyId={vacancy.id}
vacancyTitle={vacancy.title}
onClose={handleCloseApplyModal}
/>
</Modal>
</div>
);
}