Spaces:
Sleeping
Sleeping
File size: 9,644 Bytes
ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 59f9574 b66d14f 59f9574 b66d14f 59f9574 b66d14f 59f9574 ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 59f9574 ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f 59f9574 ea9ca44 59f9574 ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 4b3a33f ea9ca44 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { supabase } from '../supabaseClient';
import { SearchIcon } from './Icons';
import JobDetail from './JobDetail';
import ApplyModel from './ApplyModel';
import JobCard from './JobCard';
import VerificationModal from './VerificationModal'; // β
Import the new modal
export default function JobListings({ searchQuery, setSearchQuery, isSearching, filteredJobListings }) {
const [selectedJob, setSelectedJob] = useState(null);
const [appliedJobIds, setAppliedJobIds] = useState(new Set());
const [applying, setApplying] = useState(null);
// State for the Apply Modal
const [jobToApply, setJobToApply] = useState(null);
// β
State for Verification Modal
const [showVerificationModal, setShowVerificationModal] = useState(false);
// 1. Check Existing Applications on Load
useEffect(() => {
const fetchApplications = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (user) {
const { data } = await supabase
.from('applications')
.select('job_id')
.eq('user_id', user.id);
if (data) {
setAppliedJobIds(new Set(data.map(app => app.job_id)));
}
}
};
fetchApplications();
}, []);
// 2. Open Apply Modal
const initiateApply = (jobId) => {
const job = filteredJobListings.find(j => j.id === jobId);
if (job) {
setJobToApply(job);
}
};
// β
Helper: Send welcome message to applicant
const sendApplicationConfirmationMessage = async (userId, jobTitle) => {
try {
const message = `Hello, Thank you for applying for the **${jobTitle}** position. We have received your application and our team is currently reviewing your profile. If your qualifications match our requirements, we will contact you shortly regarding the next steps in the selection process. We appreciate your interest in this opportunity.`;
// Insert message using applicant's ID for both sender and receiver
// (RLS prevents applicants from inserting messages on behalf of an Admin)
const { error } = await supabase.from('messages').insert([{
sender_id: userId,
receiver_id: userId,
content: message,
is_read: false
}]);
if (error) console.error('Error sending confirmation message:', error);
} catch (err) {
console.error('Failed to send confirmation message:', err);
}
};
// 3. Submit Application (With Verification Gatekeeper)
const handleFinalSubmit = async (formData) => {
if (!jobToApply) return;
setApplying(jobToApply.id);
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
alert("Please log in to apply.");
return;
}
// --- π GATEKEEPER CHECK: Verify Phone Status ---
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('is_phone_verified, experience_years')
.eq('id', user.id)
.single();
if (profileError) throw profileError;
// If NOT verified, stop the application and show modal
/** if (!profile.is_phone_verified) {
setApplying(null); // Stop loading spinner
setJobToApply(null); // Close application form
setShowVerificationModal(true); // Open Verification Modal
return; // π Stop execution here
} **/
// --- β
IF VERIFIED: Proceed with Application ---
const { error } = await supabase
.from('applications')
.insert([{
job_id: jobToApply.id,
user_id: user.id,
status: 'Pending',
resume_url: formData.resume_url,
cover_letter: formData.cover_letter,
experience: profile.experience // Include experience from profile
}]);
if (error) throw error;
// β
Send confirmation message to applicant
await sendApplicationConfirmationMessage(user.id, jobToApply.title);
setAppliedJobIds(prev => new Set(prev).add(jobToApply.id));
alert("Application submitted successfully!");
setJobToApply(null);
} catch (error) {
console.error("Error applying:", error.message);
alert(`Could not apply: ${error.message}`);
} finally {
// Only stop spinner if we didn't switch to verification modal
if (!showVerificationModal) setApplying(null);
}
};
// 4. Withdraw Application
const handleWithdraw = async (jobId) => {
if (!confirm("Are you sure you want to withdraw this application?")) {
return;
}
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { error } = await supabase
.from('applications')
.delete()
.eq('job_id', jobId)
.eq('user_id', user.id);
if (error) throw error;
setAppliedJobIds(prev => {
const newSet = new Set(prev);
newSet.delete(jobId);
return newSet;
});
} catch (error) {
console.error("Error withdrawing:", error.message);
alert("Failed to withdraw application.");
}
};
return (
<>
{/* Search Bar */}
<div style={{ backgroundColor: 'rgba(251, 191, 36, 0.1)', border: '1px solid rgba(251, 191, 36, 0.3)', borderRadius: '1rem', padding: '1.5rem', marginBottom: '2rem' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Open Positions</h2>
<p style={{ color: '#d1d5db', marginBottom: '1rem' }}>Browse through our current job openings</p>
<div style={{ position: 'relative' }}>
<motion.div animate={{ scale: isSearching ? 1.1 : 1, rotate: isSearching ? 5 : 0 }} transition={{ type: 'spring', stiffness: 400, damping: 15 }} style={{ position: 'absolute', left: '0.75rem', top: '0', bottom: '0', display: 'grid', placeItems: 'center' }}><SearchIcon /></motion.div>
<input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search by job title..." style={{ width: '100%', padding: '0.75rem 1rem 0.75rem 2.5rem', borderRadius: '0.5rem', border: '1px solid rgba(251, 191, 36, 0.3)', backgroundColor: 'rgba(255,255,255,0.1)', color: 'white' }} />
</div>
</div>
{/* Job Grid */}
<motion.main layout style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '2rem' }}>
<AnimatePresence>
{filteredJobListings.length > 0 ? (
filteredJobListings.map((job) => (
<motion.div key={job.id} layout initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.8 }} transition={{ duration: 0.2 }}>
<JobCard
{...job}
onViewDetails={() => setSelectedJob(job)}
onApply={() => initiateApply(job.id)}
onWithdraw={() => handleWithdraw(job.id)}
isApplied={appliedJobIds.has(job.id)}
isApplying={applying === job.id}
/>
</motion.div>
))
) : (
<motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }} style={{ color: '#d1d5db' }}>No jobs found.</motion.p>
)}
</AnimatePresence>
</motion.main>
{/* Job Detail Modal */}
{selectedJob && (
<JobDetail
job={selectedJob}
onClose={() => setSelectedJob(null)}
onApply={() => initiateApply(selectedJob.id)}
isApplied={appliedJobIds.has(selectedJob.id)}
isApplying={applying === selectedJob.id}
/>
)}
{/* Apply Form Modal */}
{jobToApply && (
<ApplyModel
job={jobToApply}
isSubmitting={applying === jobToApply.id}
onClose={() => setJobToApply(null)}
onSubmit={handleFinalSubmit}
/>
)}
{/* β
OTP Verification Modal */}
{showVerificationModal && (
<VerificationModal
onClose={() => setShowVerificationModal(false)}
onVerified={() => {
setShowVerificationModal(false);
alert("Phone verified successfully! Please click Apply again.");
}}
/>
)}
</>
);
}; |