iris_backend / src /pages /ApplicantJobPage.jsx
Muhammed Sameer
Initial commit for hosting
6e5bfbf
import React, { useState, useEffect } from 'react';
import { supabase } from '../supabaseClient';
import JobListings from '../components/JobListings';
import ApplicantLayout from '../components/ApplicantLayout';
// Simple "Ghost" Card Component for loading state
const SkeletonLoader = () => (
<div style={{
backgroundColor: 'rgba(255, 255, 255, 0.03)',
borderRadius: '1rem',
padding: '1.5rem',
marginBottom: '1rem',
border: '1px solid rgba(255, 255, 255, 0.05)',
display: 'flex',
flexDirection: 'column',
gap: '1rem'
}}>
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<div style={{ width: '48px', height: '48px', borderRadius: '10px', backgroundColor: 'rgba(255,255,255,0.1)' }}></div>
<div style={{ flex: 1 }}>
<div style={{ width: '40%', height: '20px', borderRadius: '4px', backgroundColor: 'rgba(255,255,255,0.1)', marginBottom: '8px' }}></div>
<div style={{ width: '25%', height: '16px', borderRadius: '4px', backgroundColor: 'rgba(255,255,255,0.05)' }}></div>
</div>
</div>
<div style={{ width: '100%', height: '1px', backgroundColor: 'rgba(255,255,255,0.05)' }}></div>
</div>
);
export default function ApplicantJobPage({ onNavigate }) {
const [allJobs, setAllJobs] = useState([]);
const [filteredJobs, setFilteredJobs] = useState([]);
const [userProfile, setUserProfile] = useState(null);
const [searchQuery, setSearchQuery] = useState('');
const [activeTab, setActiveTab] = useState('all');
const [loading, setLoading] = useState(true);
const [showAllJobs, setShowAllJobs] = useState(false); // ✅ Track if showing all jobs
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// 1️⃣ Get User
const { data: { user } } = await supabase.auth.getUser();
if (user) {
const { data: profile } = await supabase
.from('profiles')
.select('skills, job_title')
.eq('id', user.id)
.maybeSingle();
if (profile) {
console.log("LOG: User Profile Found:", profile);
setUserProfile(profile);
}
}
// 🔹 Today (for DATE column comparison)
const today = new Date().toISOString().split('T')[0];
// 2️⃣ Fetch ACTIVE jobs first
const { data: jobsData, error } = await supabase
.from('jobs')
.select(`
*,
companies ( name, logo_url )
`)
.eq('status', 'Active');
if (error) throw error;
// 3️⃣ Find expired jobs
const expiredJobIds = (jobsData || [])
.filter(job => job.deadline && job.deadline < today)
.map(job => job.id);
// 4️⃣ Mark expired jobs as INACTIVE
if (expiredJobIds.length > 0) {
await supabase
.from('jobs')
.update({ status: 'Inactive' })
.in('id', expiredJobIds);
}
// 5️⃣ Fetch ONLY ACTIVE jobs (final list)
const { data: activeJobs, error: activeError } = await supabase
.from('jobs')
.select(`
*,
companies ( name, logo_url )
`)
.eq('status', 'Active')
.order('created_at', { ascending: false });
if (activeError) throw activeError;
// 6️⃣ Format jobs for UI
const formattedJobs = (activeJobs || []).map(job => ({
id: job.id,
title: job.title,
type: job.job_type,
company: job.companies?.name || 'Unknown Company',
logo: job.companies?.logo_url || '',
location: job.location || 'Remote',
salary: job.salary_range || job.salary || 'Not disclosed',
deadline: job.deadline
? new Date(job.deadline).toLocaleDateString()
: 'Open',
postedAt: new Date(job.created_at).toLocaleDateString(),
skills: job.skills_required || [],
description: job.description
}));
setAllJobs(formattedJobs);
setFilteredJobs(formattedJobs);
} catch (error) {
console.error("Error fetching data:", error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
// --- SMART RECOMMENDATION ENGINE ---
const getRecommendedJobs = () => {
if (!userProfile) return [];
return allJobs.filter(job => {
const userSkills = userProfile.skills || [];
const jobSkills = job.skills || [];
const skillMatch = jobSkills.some(skill =>
userSkills.some(userSkill => userSkill.toLowerCase() === skill.toLowerCase())
);
let titleMatch = false;
if (userProfile.job_title) {
const userTitle = userProfile.job_title.toLowerCase();
const jobTitle = job.title.toLowerCase();
titleMatch = jobTitle.includes(userTitle) || userTitle.includes(jobTitle);
}
return skillMatch || titleMatch;
});
};
useEffect(() => {
let result = activeTab === 'recommended' ? getRecommendedJobs() : allJobs;
if (searchQuery) {
const lowerQuery = searchQuery.toLowerCase();
result = result.filter(job =>
job.title.toLowerCase().includes(lowerQuery) ||
job.company.toLowerCase().includes(lowerQuery)
);
}
// ✅ Limit to 4 jobs unless "See All" is clicked
if (!showAllJobs && !searchQuery) {
result = result.slice(0, 4);
}
setFilteredJobs(result);
}, [searchQuery, activeTab, allJobs, userProfile, showAllJobs]);
return (
<ApplicantLayout activePage="applicant-jobs" onNavigate={onNavigate}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>
{activeTab === 'recommended' ? 'Jobs For You' : 'All Opportunities'}
</h2>
<div style={{ backgroundColor: 'rgba(255,255,255,0.1)', padding: '0.25rem', borderRadius: '0.5rem', display: 'flex', gap: '0.5rem' }}>
<button onClick={() => { setActiveTab('all'); setShowAllJobs(false); }} style={{ padding: '0.5rem 1rem', borderRadius: '0.3rem', border: 'none', cursor: 'pointer', fontWeight: 'bold', backgroundColor: activeTab === 'all' ? '#FBBF24' : 'transparent', color: activeTab === 'all' ? '#1a202c' : '#9ca3af' }}>All Jobs</button>
<button onClick={() => { setActiveTab('recommended'); setShowAllJobs(false); }} style={{ padding: '0.5rem 1rem', borderRadius: '0.3rem', border: 'none', cursor: 'pointer', fontWeight: 'bold', backgroundColor: activeTab === 'recommended' ? '#FBBF24' : 'transparent', color: activeTab === 'recommended' ? '#1a202c' : '#9ca3af' }}>Recommended ✨</button>
</div>
</div>
{loading ? (
<div>
<SkeletonLoader />
<SkeletonLoader />
<SkeletonLoader />
</div>
) : (
<>
{activeTab === 'recommended' && filteredJobs.length === 0 ? (
<div style={{ textAlign: 'center', padding: '3rem', color: '#9ca3af', border: '1px dashed #374151', borderRadius: '1rem' }}>
<p style={{ fontSize: '1.2rem', marginBottom: '1rem' }}>No direct matches found yet.</p>
<p>We look for matches based on your <strong>Job Title</strong> or <strong>Skills</strong>.</p>
{userProfile && !userProfile.job_title && !userProfile.skills && (
<p style={{ color: '#F87171', marginTop: '10px', fontSize: '0.9rem' }}>
⚠️ Your profile has no Job Title or Skills saved.
</p>
)}
<button onClick={() => onNavigate('applicant-profile')} style={{ marginTop: '1rem', padding: '0.5rem 1rem', backgroundColor: '#374151', color: 'white', border: 'none', borderRadius: '0.5rem', cursor: 'pointer' }}>Update Profile</button>
</div>
) : (
<>
<JobListings searchQuery={searchQuery} setSearchQuery={setSearchQuery} isSearching={searchQuery.length > 0} filteredJobListings={filteredJobs} />
{/* ✅ See All Button */}
{!showAllJobs && !searchQuery && (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
<button
onClick={() => setShowAllJobs(true)}
style={{
padding: '0.75rem 2rem',
backgroundColor: '#FBBF24',
color: '#1a202c',
border: 'none',
borderRadius: '0.5rem',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '1rem',
transition: 'all 0.2s'
}}
>
See All Jobs
</button>
</div>
)}
</>
)}
</>
)}
</ApplicantLayout>
);
}