Muhammed Sameer commited on
Commit
6e5bfbf
·
1 Parent(s): ff85727

Initial commit for hosting

Browse files
backend/src/services/ats_service.py CHANGED
@@ -145,6 +145,7 @@ def calculate_ats_score(resume_data: dict, job_data: dict) -> dict:
145
  "matches": matches,
146
  "summary": summary,
147
  "recommendations": recommendations,
 
148
  "debug_resume_skills": list(resume_skills), # Helpful for debugging
149
  "debug_job_skills": list(job_skills)
150
  }
 
145
  "matches": matches,
146
  "summary": summary,
147
  "recommendations": recommendations,
148
+ "resume_data": resume_data, # Added to pre-fill the Resume Builder
149
  "debug_resume_skills": list(resume_skills), # Helpful for debugging
150
  "debug_job_skills": list(job_skills)
151
  }
src/components/ApplicantLayout.jsx CHANGED
@@ -4,7 +4,7 @@ import { supabase } from '../supabaseClient';
4
  import {
5
  LogoutIcon, BriefcaseIcon, UserCircleIcon, ChatIcon,
6
  CalendarIcon, AtsCheckerIcon
7
- } from './Icons';
8
 
9
  const BellIcon = () => (
10
  <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -19,12 +19,23 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
19
 
20
  const [notifications, setNotifications] = useState([]);
21
  const [showNotifications, setShowNotifications] = useState(false);
 
22
  const notifRef = useRef(null);
23
 
24
- // ⭐ NEW: unread message badge state
25
  const [unreadMessages, setUnreadMessages] = useState(0);
26
  const [unreadMessagesList, setUnreadMessagesList] = useState([]);
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  useEffect(() => {
29
  function handleClickOutside(event) {
30
  if (notifRef.current && !notifRef.current.contains(event.target)) {
@@ -45,7 +56,7 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
45
  .select('full_name')
46
  .eq('id', user.id)
47
  .maybeSingle();
48
-
49
  if (profile && profile.full_name) {
50
  const firstName = profile.full_name.split(' ')[0];
51
  setUserName(firstName);
@@ -89,6 +100,30 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
89
 
90
  fetchUnreadMessages();
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }, []);
93
 
94
  // ⭐ NEW: realtime update for new messages
@@ -100,6 +135,8 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
100
  "postgres_changes",
101
  { event: "INSERT", schema: "public", table: "messages" },
102
  async (payload) => {
 
 
103
  // Fetch the sender's name and add to the list
104
  const { data: senderProfile } = await supabase
105
  .from("profiles")
@@ -116,12 +153,129 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
116
 
117
  setUnreadMessagesList(prev => [newMsg, ...prev]);
118
  setUnreadMessages(prev => prev + 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
  )
121
  .subscribe();
122
 
123
  return () => {
124
  supabase.removeChannel(channel);
 
 
125
  };
126
 
127
  }, []);
@@ -159,6 +313,91 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
159
  fetchNotifications();
160
  }, [activePage]);
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  // ⭐ NEW: mark messages read when user opens messages page
163
  useEffect(() => {
164
 
@@ -180,6 +419,10 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
180
 
181
  markMessagesRead();
182
 
 
 
 
 
183
  }, [activePage]);
184
 
185
  const handleLogout = async () => {
@@ -200,20 +443,23 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
200
 
201
  return (
202
  <div style={{ height: '100vh', width: '100%', backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif", padding: '2rem', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', position: 'relative' }}>
203
-
204
  <header style={{ position: 'relative', zIndex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', flexShrink: 0 }}>
205
  <h1 style={{ fontSize: '1.875rem', fontWeight: 'bold' }}>
206
  {userName ? `Hi, ${userName} 👋` : 'Welcome 👋'}
207
  </h1>
208
 
209
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
210
-
211
  {activePage === 'applicant-profile' && (
212
  <div style={{ position: 'relative' }} ref={notifRef}>
213
- <motion.button
214
  whileHover={{ scale: 1.05 }}
215
  whileTap={{ scale: 0.95 }}
216
- onClick={() => setShowNotifications(!showNotifications)}
 
 
 
217
  style={{
218
  background: 'rgba(255,255,255,0.05)',
219
  border: '1px solid rgba(255,255,255,0.1)',
@@ -225,7 +471,7 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
225
  }}
226
  >
227
  <BellIcon />
228
- {(notifications.length + unreadMessages) > 0 && (
229
  <span style={{
230
  position: 'absolute',
231
  top: '-5px',
@@ -242,7 +488,7 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
242
  fontSize: '0.75rem',
243
  fontWeight: 'bold'
244
  }}>
245
- {notifications.length + unreadMessages}
246
  </span>
247
  )}
248
  </motion.button>
@@ -267,10 +513,10 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
267
  }}
268
  >
269
  <div style={{ padding: '1rem', fontWeight: 'bold', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
270
- Recent Notification ({notifications.length + unreadMessages})
271
  </div>
272
  <div style={{ padding: '0.75rem' }}>
273
- {notifications.length + unreadMessages === 0 ? (
274
  <div style={{ padding: '1rem', textAlign: 'center', color: '#94a3b8', fontSize: '0.85rem' }}>
275
  No new notifications
276
  </div>
@@ -305,6 +551,22 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
305
  </div>
306
  </div>
307
  ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  </>
309
  )}
310
  </div>
@@ -314,10 +576,10 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
314
  </div>
315
  )}
316
 
317
- <motion.button
318
- onClick={handleLogout}
319
- whileHover={{ scale: 1.03 }}
320
- whileTap={{ scale: 0.98 }}
321
  style={{ backgroundColor: '#FBBF24', color: '#1a202c', display: 'flex', alignItems: 'center', padding: '0.75rem', borderRadius: '0.5rem', fontWeight: 'bold', cursor: 'pointer', border: 'none' }}
322
  >
323
  <LogoutIcon />
@@ -331,9 +593,9 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
331
  {navItems.map(({ key, icon, label }) => {
332
  const active = isActive(key);
333
  return (
334
- <div
335
- key={key}
336
- onClick={() => onNavigate(key)}
337
  style={{ position: 'relative', padding: '0.75rem 1.5rem', borderRadius: '0.5rem', cursor: 'pointer', display: 'flex', alignItems: 'center', color: active ? '#FCD34D' : '#d1d5db', fontWeight: active ? 'bold' : 'normal', zIndex: 1 }}
338
  >
339
  {icon}
@@ -357,9 +619,44 @@ export default function ApplicantLayout({ children, activePage, onNavigate }) {
357
  </nav>
358
  </div>
359
 
360
- <main style={{ position: 'relative', zIndex: 1, flex: 1, overflowY: 'auto' }}>
361
  {children}
362
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  </div>
364
  );
365
  }
 
4
  import {
5
  LogoutIcon, BriefcaseIcon, UserCircleIcon, ChatIcon,
6
  CalendarIcon, AtsCheckerIcon
7
+ } from './Icons';
8
 
9
  const BellIcon = () => (
10
  <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
 
19
 
20
  const [notifications, setNotifications] = useState([]);
21
  const [showNotifications, setShowNotifications] = useState(false);
22
+ const [hasSeenNotifs, setHasSeenNotifs] = useState(false);
23
  const notifRef = useRef(null);
24
 
 
25
  const [unreadMessages, setUnreadMessages] = useState(0);
26
  const [unreadMessagesList, setUnreadMessagesList] = useState([]);
27
 
28
+ // ⭐ NEW: recent jobs notifications
29
+ const [newJobs, setNewJobs] = useState([]);
30
+
31
+ // ⭐ NEW: toast notification state
32
+ const [toastNotif, setToastNotif] = useState(null);
33
+
34
+ const triggerToast = (title, message, icon) => {
35
+ setToastNotif({ id: Date.now(), title, message, icon });
36
+ setTimeout(() => setToastNotif(null), 5000); // Hide after 5 seconds
37
+ };
38
+
39
  useEffect(() => {
40
  function handleClickOutside(event) {
41
  if (notifRef.current && !notifRef.current.contains(event.target)) {
 
56
  .select('full_name')
57
  .eq('id', user.id)
58
  .maybeSingle();
59
+
60
  if (profile && profile.full_name) {
61
  const firstName = profile.full_name.split(' ')[0];
62
  setUserName(firstName);
 
100
 
101
  fetchUnreadMessages();
102
 
103
+ const fetchRecentJobs = async () => {
104
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
105
+ const { data: recentJobs } = await supabase
106
+ .from('jobs')
107
+ .select('id, title, company_id, created_at, companies ( name )')
108
+ .eq('status', 'Active')
109
+ .gte('created_at', oneDayAgo)
110
+ .order('created_at', { ascending: false })
111
+ .limit(3);
112
+
113
+ if (recentJobs) {
114
+ const jobNotifs = recentJobs.map(job => ({
115
+ id: `job-${job.id}`,
116
+ title: 'New Job Posted',
117
+ text: `${job.companies?.name || 'A company'} is hiring: ${job.title}`,
118
+ time: new Date(job.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
119
+ color: '#8b5cf6' // Purple color for jobs
120
+ }));
121
+ setNewJobs(jobNotifs);
122
+ }
123
+ };
124
+
125
+ fetchRecentJobs();
126
+
127
  }, []);
128
 
129
  // ⭐ NEW: realtime update for new messages
 
135
  "postgres_changes",
136
  { event: "INSERT", schema: "public", table: "messages" },
137
  async (payload) => {
138
+ console.log("=== MESSAGES PAYLOAD ===", payload);
139
+
140
  // Fetch the sender's name and add to the list
141
  const { data: senderProfile } = await supabase
142
  .from("profiles")
 
153
 
154
  setUnreadMessagesList(prev => [newMsg, ...prev]);
155
  setUnreadMessages(prev => prev + 1);
156
+
157
+ // Trigger realtime toast popup
158
+ triggerToast(
159
+ 'New Message Received',
160
+ `${senderProfile?.full_name || 'Someone'} sent you a message`,
161
+ '📨'
162
+ );
163
+ }
164
+ )
165
+ .subscribe();
166
+
167
+ const jobsChannel = supabase
168
+ .channel("jobs-badge")
169
+ .on(
170
+ "postgres_changes",
171
+ { event: "INSERT", schema: "public", table: "jobs", filter: "status=eq.Active" },
172
+ async (payload) => {
173
+ console.log("=== JOBS PAYLOAD ===", payload);
174
+
175
+ let companyName = 'A company';
176
+ if (payload.new.company_id) {
177
+ const { data: comp } = await supabase
178
+ .from("companies")
179
+ .select("name")
180
+ .eq("id", payload.new.company_id)
181
+ .maybeSingle();
182
+ if (comp) companyName = comp.name;
183
+ }
184
+
185
+ const newJobNotif = {
186
+ id: `job-${payload.new.id}`,
187
+ title: 'New Job Posted',
188
+ text: `${companyName} is hiring: ${payload.new.title}`,
189
+ time: new Date(payload.new.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
190
+ color: '#8b5cf6'
191
+ };
192
+
193
+ setNewJobs(prev => [newJobNotif, ...prev]);
194
+
195
+ // Trigger realtime toast popup
196
+ triggerToast(
197
+ 'New Job Posted',
198
+ `${companyName} is hiring: ${payload.new.title}`,
199
+ '💼'
200
+ );
201
+ }
202
+ )
203
+ .subscribe();
204
+
205
+ const appsChannel = supabase
206
+ .channel("interviews-badge-applicant") // unique channel name
207
+ .on(
208
+ "postgres_changes",
209
+ { event: "*", schema: "public", table: "interviews" },
210
+ async (payload) => {
211
+ console.log("=== INTERVIEWS PAYLOAD ===", payload);
212
+
213
+ const { data: { user } } = await supabase.auth.getUser();
214
+ if (!user || !payload.new) return;
215
+
216
+ // Interviews don't have user_id on them directly. They have application_id.
217
+ // We must fetch the application to verify if it belongs to this user.
218
+ const { data: appData } = await supabase
219
+ .from('applications')
220
+ .select('user_id, job_id')
221
+ .eq('id', payload.new.application_id)
222
+ .maybeSingle();
223
+
224
+ if (!appData || appData.user_id !== user.id) {
225
+ return; // Not this user's interview
226
+ }
227
+
228
+ // For an insert OR an update where status is Scheduled
229
+ if (payload.new.status === 'Scheduled') {
230
+
231
+ // If it's an UPDATE, optionally ensure it actually CHANGED to Scheduled
232
+ if (payload.eventType === 'UPDATE') {
233
+ if (payload.old && payload.old.status === 'Scheduled') {
234
+ return;
235
+ }
236
+ }
237
+
238
+ console.log(">>> TRIGGERING INTERVIEW NOTIFICATION! <<<");
239
+
240
+ const { data: job } = await supabase
241
+ .from('jobs')
242
+ .select('title')
243
+ .eq('id', appData.job_id)
244
+ .maybeSingle();
245
+
246
+ const jobTitle = job?.title || 'a job';
247
+
248
+ let timeLabel = '';
249
+ if (payload.new.scheduled_time) {
250
+ const dateObj = new Date(payload.new.scheduled_time);
251
+ timeLabel = ` on ${dateObj.toLocaleDateString()} at ${dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
252
+ }
253
+
254
+ triggerToast(
255
+ 'Interview Scheduled! 📅',
256
+ `You have been invited to interview for ${jobTitle}${timeLabel}`,
257
+ '🎉'
258
+ );
259
+
260
+ setHasSeenNotifs(false);
261
+
262
+ const newNotif = {
263
+ id: Date.now(),
264
+ title: `Interview Scheduled`,
265
+ text: `You have an upcoming interview for ${jobTitle}.`,
266
+ time: new Date().toISOString(),
267
+ color: '#3B82F6'
268
+ };
269
+ setNotifications(prev => [newNotif, ...prev]);
270
+ }
271
  }
272
  )
273
  .subscribe();
274
 
275
  return () => {
276
  supabase.removeChannel(channel);
277
+ supabase.removeChannel(jobsChannel);
278
+ supabase.removeChannel(appsChannel);
279
  };
280
 
281
  }, []);
 
313
  fetchNotifications();
314
  }, [activePage]);
315
 
316
+ // ⭐ NEW: specific timing interview notifications
317
+ useEffect(() => {
318
+ let interviewInterval;
319
+
320
+ const checkInterviews = async () => {
321
+ try {
322
+ const { data: { user } } = await supabase.auth.getUser();
323
+ if (!user) return;
324
+
325
+ const { data: ints } = await supabase
326
+ .from('interviews')
327
+ .select('id, scheduled_time, created_at, applications!inner( jobs(title) )')
328
+ .eq('applications.user_id', user.id)
329
+ .eq('status', 'Scheduled');
330
+
331
+ if (ints && ints.length > 0) {
332
+ const now = new Date();
333
+
334
+ ints.forEach(intObj => {
335
+ if (!intObj.scheduled_time) return;
336
+
337
+ const interviewDate = new Date(intObj.scheduled_time);
338
+ if (isNaN(interviewDate.getTime())) return;
339
+
340
+ const diffMs = interviewDate - now;
341
+ const diffHours = diffMs / (1000 * 60 * 60);
342
+
343
+ const ONE_WEEK = 7 * 24;
344
+ const TWO_DAYS = 2 * 24;
345
+ const TWENTY_FOUR_HOURS = 24;
346
+ const FIVE_HOURS = 5;
347
+
348
+ const windowHours = 0.5;
349
+
350
+ let triggered = false;
351
+ let timeLabel = '';
352
+
353
+ if (diffHours > 0) {
354
+ if (Math.abs(diffHours - ONE_WEEK) <= windowHours) {
355
+ triggered = true;
356
+ timeLabel = 'in 1 week';
357
+ } else if (Math.abs(diffHours - TWO_DAYS) <= windowHours) {
358
+ triggered = true;
359
+ timeLabel = 'in 2 days';
360
+ } else if (Math.abs(diffHours - TWENTY_FOUR_HOURS) <= windowHours) {
361
+ triggered = true;
362
+ timeLabel = 'in 24 hours';
363
+ } else if (Math.abs(diffHours - FIVE_HOURS) <= windowHours) {
364
+ triggered = true;
365
+ timeLabel = 'in 5 hours';
366
+ }
367
+ }
368
+
369
+ // Just scheduled within last 30 minutes
370
+ const updatedDate = new Date(intObj.created_at);
371
+ const updatedDiffHours = (now - updatedDate) / (1000 * 60 * 60);
372
+ if (updatedDiffHours <= windowHours) {
373
+ triggered = true;
374
+ timeLabel = 'recently scheduled';
375
+ }
376
+
377
+ if (triggered) {
378
+ const jobTitle = intObj.applications?.jobs?.title || 'a job';
379
+ triggerToast(
380
+ 'Interview Reminder! 📅',
381
+ `You have an interview ${timeLabel} for: ${jobTitle}`,
382
+ '⏰'
383
+ );
384
+ }
385
+ });
386
+ }
387
+ } catch (error) {
388
+ console.error("Error checking interviews:", error);
389
+ }
390
+ };
391
+
392
+ // Check immediately on load
393
+ checkInterviews();
394
+
395
+ // Check every 30 minutes (30 * 60 * 1000 ms)
396
+ interviewInterval = setInterval(checkInterviews, 1800000);
397
+
398
+ return () => clearInterval(interviewInterval);
399
+ }, []);
400
+
401
  // ⭐ NEW: mark messages read when user opens messages page
402
  useEffect(() => {
403
 
 
419
 
420
  markMessagesRead();
421
 
422
+ if (activePage === "applicant-jobs") {
423
+ setNewJobs([]); // Clear job notifications when jobs page is opened
424
+ }
425
+
426
  }, [activePage]);
427
 
428
  const handleLogout = async () => {
 
443
 
444
  return (
445
  <div style={{ height: '100vh', width: '100%', backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif", padding: '2rem', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', position: 'relative' }}>
446
+
447
  <header style={{ position: 'relative', zIndex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', flexShrink: 0 }}>
448
  <h1 style={{ fontSize: '1.875rem', fontWeight: 'bold' }}>
449
  {userName ? `Hi, ${userName} 👋` : 'Welcome 👋'}
450
  </h1>
451
 
452
  <div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
453
+
454
  {activePage === 'applicant-profile' && (
455
  <div style={{ position: 'relative' }} ref={notifRef}>
456
+ <motion.button
457
  whileHover={{ scale: 1.05 }}
458
  whileTap={{ scale: 0.95 }}
459
+ onClick={() => {
460
+ setShowNotifications(!showNotifications);
461
+ if (!showNotifications) setHasSeenNotifs(true);
462
+ }}
463
  style={{
464
  background: 'rgba(255,255,255,0.05)',
465
  border: '1px solid rgba(255,255,255,0.1)',
 
471
  }}
472
  >
473
  <BellIcon />
474
+ {!hasSeenNotifs && (notifications.length + unreadMessages + newJobs.length) > 0 && (
475
  <span style={{
476
  position: 'absolute',
477
  top: '-5px',
 
488
  fontSize: '0.75rem',
489
  fontWeight: 'bold'
490
  }}>
491
+ {notifications.length + unreadMessages + newJobs.length}
492
  </span>
493
  )}
494
  </motion.button>
 
513
  }}
514
  >
515
  <div style={{ padding: '1rem', fontWeight: 'bold', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
516
+ Recent Notification ({notifications.length + unreadMessages + newJobs.length})
517
  </div>
518
  <div style={{ padding: '0.75rem' }}>
519
+ {notifications.length + unreadMessages + newJobs.length === 0 ? (
520
  <div style={{ padding: '1rem', textAlign: 'center', color: '#94a3b8', fontSize: '0.85rem' }}>
521
  No new notifications
522
  </div>
 
551
  </div>
552
  </div>
553
  ))}
554
+ {newJobs.map(jobNotif => (
555
+ <div key={jobNotif.id} style={{ padding: '0.75rem', borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', gap: '0.5rem', alignItems: 'flex-start' }}>
556
+ <span style={{ color: jobNotif.color, fontSize: '1rem' }}>💼</span>
557
+ <div style={{ flex: 1 }}>
558
+ <p style={{ margin: 0, fontSize: '0.85rem', color: '#e2e8f0', fontWeight: 500 }}>
559
+ {jobNotif.title}
560
+ </p>
561
+ <p style={{ margin: '0.25rem 0 0 0', fontSize: '0.75rem', color: '#cbd5e1' }}>
562
+ {jobNotif.text}
563
+ </p>
564
+ <p style={{ margin: '0.25rem 0 0 0', fontSize: '0.7rem', color: '#64748b' }}>
565
+ {jobNotif.time}
566
+ </p>
567
+ </div>
568
+ </div>
569
+ ))}
570
  </>
571
  )}
572
  </div>
 
576
  </div>
577
  )}
578
 
579
+ <motion.button
580
+ onClick={handleLogout}
581
+ whileHover={{ scale: 1.03 }}
582
+ whileTap={{ scale: 0.98 }}
583
  style={{ backgroundColor: '#FBBF24', color: '#1a202c', display: 'flex', alignItems: 'center', padding: '0.75rem', borderRadius: '0.5rem', fontWeight: 'bold', cursor: 'pointer', border: 'none' }}
584
  >
585
  <LogoutIcon />
 
593
  {navItems.map(({ key, icon, label }) => {
594
  const active = isActive(key);
595
  return (
596
+ <div
597
+ key={key}
598
+ onClick={() => onNavigate(key)}
599
  style={{ position: 'relative', padding: '0.75rem 1.5rem', borderRadius: '0.5rem', cursor: 'pointer', display: 'flex', alignItems: 'center', color: active ? '#FCD34D' : '#d1d5db', fontWeight: active ? 'bold' : 'normal', zIndex: 1 }}
600
  >
601
  {icon}
 
619
  </nav>
620
  </div>
621
 
622
+ <main style={{ position: 'relative', zIndex: 1, flex: 1, overflowY: 'auto' }} className="hide-scrollbar">
623
  {children}
624
  </main>
625
+
626
+ {/* ⭐ NEW: Realtime Toast Notification Popup */}
627
+ <AnimatePresence>
628
+ {toastNotif && (
629
+ <motion.div
630
+ key={toastNotif.id}
631
+ initial={{ opacity: 0, y: -20, scale: 0.9 }}
632
+ animate={{ opacity: 1, y: 0, scale: 1 }}
633
+ exit={{ opacity: 0, y: -20, scale: 0.9 }}
634
+ style={{
635
+ position: 'absolute',
636
+ top: '60px', // Below the header
637
+ right: '2rem', // Aligned near the bell icon
638
+ backgroundColor: '#1e293b',
639
+ border: '1px solid rgba(255,255,255,0.1)',
640
+ borderLeft: '4px solid #FBBF24',
641
+ borderRadius: '12px',
642
+ padding: '1rem 1.5rem',
643
+ display: 'flex',
644
+ alignItems: 'flex-start',
645
+ gap: '1rem',
646
+ boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3)',
647
+ zIndex: 9999,
648
+ width: '320px',
649
+ maxWidth: '90vw'
650
+ }}
651
+ >
652
+ <span style={{ fontSize: '1.8rem' }}>{toastNotif.icon}</span>
653
+ <div>
654
+ <h4 style={{ margin: 0, color: 'white', fontWeight: 'bold', fontSize: '1rem' }}>{toastNotif.title}</h4>
655
+ <p style={{ margin: '0.25rem 0 0 0', color: '#cbd5e1', fontSize: '0.9rem' }}>{toastNotif.message}</p>
656
+ </div>
657
+ </motion.div>
658
+ )}
659
+ </AnimatePresence>
660
  </div>
661
  );
662
  }
src/components/Icons.jsx CHANGED
@@ -20,6 +20,11 @@ export const ChevronRightIcon = () => (<svg style={{ width: '16px', height: '16p
20
  export const CheckSquareIcon = () => (<svg style={{ width: '18px', height: '18px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>);
21
  export const MailIcon = () => (<svg style={{ width: '18px', height: '18px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>);
22
  export const LoaderIcon = () => (<svg style={{ width: '24px', height: '24px', animation: 'spin 1s linear infinite' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>);
 
 
 
 
 
23
  // --- Add this to src/components/Icons.js ---
24
 
25
  export const RoundCheckbox = ({ checked, onChange, style }) => (
 
20
  export const CheckSquareIcon = () => (<svg style={{ width: '18px', height: '18px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>);
21
  export const MailIcon = () => (<svg style={{ width: '18px', height: '18px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>);
22
  export const LoaderIcon = () => (<svg style={{ width: '24px', height: '24px', animation: 'spin 1s linear infinite' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>);
23
+
24
+ export const ArrowLeftIcon = () => (<svg style={{ width: '16px', height: '16px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>);
25
+ export const PlusIcon = ({ size = 16 }) => (<svg style={{ width: `${size}px`, height: `${size}px` }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>);
26
+ export const TrashIcon = ({ size = 16 }) => (<svg style={{ width: `${size}px`, height: `${size}px` }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>);
27
+ export const DownloadIcon = () => (<svg style={{ width: '16px', height: '16px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>);
28
  // --- Add this to src/components/Icons.js ---
29
 
30
  export const RoundCheckbox = ({ checked, onChange, style }) => (
src/components/JobCard.jsx CHANGED
@@ -56,6 +56,16 @@ const JobCard = ({
56
  isApplying,
57
  matchPercentage
58
  }) => {
 
 
 
 
 
 
 
 
 
 
59
  return (
60
  <div
61
  style={{
@@ -163,7 +173,7 @@ const JobCard = ({
163
  >
164
  {type}
165
  </span>
166
- <span style={{ fontSize: '0.75rem', color: '#9ca3af', fontWeight: '500' }}>
167
  Deadline: {deadline}
168
  </span>
169
  </div>
@@ -222,19 +232,19 @@ const JobCard = ({
222
  ) : (
223
  <button
224
  onClick={() => onApply(id)}
225
- disabled={isApplying}
226
  style={{
227
  flex: 1,
228
  padding: '0.5rem',
229
- backgroundColor: isApplying ? '#4b5563' : '#FBBF24',
230
  border: 'none',
231
- color: isApplying ? '#d1d5db' : '#1a202c',
232
  borderRadius: '0.5rem',
233
  fontWeight: 'bold',
234
- cursor: isApplying ? 'not-allowed' : 'pointer'
235
  }}
236
  >
237
- {isApplying ? '...' : 'Apply'}
238
  </button>
239
  )}
240
  </div>
 
56
  isApplying,
57
  matchPercentage
58
  }) => {
59
+ // Determine if the job is expired based on the deadline string
60
+ const isExpired = React.useMemo(() => {
61
+ if (!deadline || deadline === 'Open') return false;
62
+ const deadlineDate = new Date(deadline);
63
+ // Normalize today to start of day for fair comparison
64
+ const today = new Date();
65
+ today.setHours(0, 0, 0, 0);
66
+ return deadlineDate < today;
67
+ }, [deadline]);
68
+
69
  return (
70
  <div
71
  style={{
 
173
  >
174
  {type}
175
  </span>
176
+ <span style={{ fontSize: '0.75rem', color: isExpired ? '#EF4444' : '#9ca3af', fontWeight: '500' }}>
177
  Deadline: {deadline}
178
  </span>
179
  </div>
 
232
  ) : (
233
  <button
234
  onClick={() => onApply(id)}
235
+ disabled={isApplying || isExpired}
236
  style={{
237
  flex: 1,
238
  padding: '0.5rem',
239
+ backgroundColor: (isApplying || isExpired) ? '#4b5563' : '#FBBF24',
240
  border: 'none',
241
+ color: (isApplying || isExpired) ? '#d1d5db' : '#1a202c',
242
  borderRadius: '0.5rem',
243
  fontWeight: 'bold',
244
+ cursor: (isApplying || isExpired) ? 'not-allowed' : 'pointer'
245
  }}
246
  >
247
+ {isApplying ? '...' : (isExpired ? 'Expired' : 'Apply')}
248
  </button>
249
  )}
250
  </div>
src/components/JobDetail.jsx CHANGED
@@ -10,6 +10,15 @@ const CloseIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" width="24" heig
10
  const CheckIcon = () => (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>);
11
 
12
  const JobDetail = ({ job, onClose, onApply, isApplied, isApplying }) => {
 
 
 
 
 
 
 
 
 
13
  // Portal renders this outside the main app flow
14
  return ReactDOM.createPortal(
15
  <AnimatePresence>
@@ -74,7 +83,7 @@ const JobDetail = ({ job, onClose, onApply, isApplied, isApplying }) => {
74
  )}
75
 
76
  <div style={{ fontSize: '0.9rem', color: '#6b7280', marginTop: '1rem', borderTop: '1px solid rgba(255,255,255,0.05)', paddingTop: '1rem' }}>
77
- Posted: {job.postedAt} • Deadline: <span style={{ color: '#ef4444' }}>{job.deadline}</span>
78
  </div>
79
  </div>
80
 
@@ -90,15 +99,15 @@ const JobDetail = ({ job, onClose, onApply, isApplied, isApplying }) => {
90
  ) : (
91
  <button
92
  onClick={() => onApply && onApply(job.id)}
93
- disabled={isApplying}
94
  style={{
95
  padding: '0.75rem 2rem',
96
- backgroundColor: isApplying ? '#4b5563' : '#FBBF24',
97
- color: isApplying ? '#d1d5db' : '#1a202c',
98
- border: 'none', borderRadius: '0.5rem', fontWeight: 'bold', cursor: isApplying ? 'not-allowed' : 'pointer'
99
  }}
100
  >
101
- {isApplying ? 'Applying...' : 'Apply Now'}
102
  </button>
103
  )}
104
  </div>
 
10
  const CheckIcon = () => (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>);
11
 
12
  const JobDetail = ({ job, onClose, onApply, isApplied, isApplying }) => {
13
+ // Determine if the job is expired based on the deadline string
14
+ const isExpired = React.useMemo(() => {
15
+ if (!job || !job.deadline || job.deadline === 'Open') return false;
16
+ const deadlineDate = new Date(job.deadline);
17
+ const today = new Date();
18
+ today.setHours(0, 0, 0, 0);
19
+ return deadlineDate < today;
20
+ }, [job]);
21
+
22
  // Portal renders this outside the main app flow
23
  return ReactDOM.createPortal(
24
  <AnimatePresence>
 
83
  )}
84
 
85
  <div style={{ fontSize: '0.9rem', color: '#6b7280', marginTop: '1rem', borderTop: '1px solid rgba(255,255,255,0.05)', paddingTop: '1rem' }}>
86
+ Posted: {job.postedAt} • Deadline: <span style={{ color: isExpired ? '#ef4444' : '#d1d5db' }}>{job.deadline}</span>
87
  </div>
88
  </div>
89
 
 
99
  ) : (
100
  <button
101
  onClick={() => onApply && onApply(job.id)}
102
+ disabled={isApplying || isExpired}
103
  style={{
104
  padding: '0.75rem 2rem',
105
+ backgroundColor: (isApplying || isExpired) ? '#4b5563' : '#FBBF24',
106
+ color: (isApplying || isExpired) ? '#d1d5db' : '#1a202c',
107
+ border: 'none', borderRadius: '0.5rem', fontWeight: 'bold', cursor: (isApplying || isExpired) ? 'not-allowed' : 'pointer'
108
  }}
109
  >
110
+ {isApplying ? 'Applying...' : (isExpired ? 'Expired' : 'Apply Now')}
111
  </button>
112
  )}
113
  </div>
src/components/ResumeBuilder.jsx ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { DownloadIcon, ArrowLeftIcon, PlusIcon, TrashIcon } from './Icons';
4
+
5
+ export default function ResumeBuilder({ analysisResult, onBack }) {
6
+ const printRef = useRef(null);
7
+ const missingKeywords = analysisResult?.matches?.filter(m => !m.found) || [];
8
+
9
+ const resumeData = analysisResult?.resume_data || {};
10
+
11
+ // Form State - Auto-fill from resume_data if available
12
+ const [personalInfo, setPersonalInfo] = useState({
13
+ fullName: resumeData.name || '',
14
+ email: resumeData.email || '',
15
+ phone: resumeData.phone || '',
16
+ location: resumeData.location || '',
17
+ linkedin: resumeData.linkedin || '',
18
+ portfolio: resumeData.portfolio || ''
19
+ });
20
+
21
+ const [summary, setSummary] = useState(resumeData.summary || '');
22
+
23
+ const [experience, setExperience] = useState(
24
+ resumeData.work_experience?.length > 0
25
+ ? resumeData.work_experience.map((exp, idx) => ({
26
+ id: Date.now() + idx,
27
+ company: exp.company || '',
28
+ role: exp.role || '',
29
+ startDate: exp.start_date || '',
30
+ endDate: exp.end_date || '',
31
+ description: Array.isArray(exp.responsibilities) ? exp.responsibilities.map(r => `• ${r}`).join('\n') : (exp.responsibilities || '')
32
+ }))
33
+ : [{ id: 1, company: '', role: '', startDate: '', endDate: '', description: '' }]
34
+ );
35
+
36
+ const [education, setEducation] = useState(
37
+ resumeData.education?.length > 0
38
+ ? resumeData.education.map((edu, idx) => ({
39
+ id: Date.now() + idx,
40
+ institution: edu.institution || '',
41
+ degree: edu.degree || '',
42
+ startDate: edu.start_date || '',
43
+ endDate: edu.end_date || ''
44
+ }))
45
+ : [{ id: 1, institution: '', degree: '', startDate: '', endDate: '' }]
46
+ );
47
+
48
+ const initialSkills = [];
49
+ if (resumeData.technical_skills) initialSkills.push(...resumeData.technical_skills);
50
+ if (resumeData.skills) initialSkills.push(...resumeData.skills);
51
+
52
+ const [skills, setSkills] = useState(initialSkills.join(', '));
53
+
54
+ // Drag and Drop Handlers
55
+ const handleDragStart = (e, keyword) => {
56
+ e.dataTransfer.setData("text/plain", keyword);
57
+ };
58
+
59
+ const handleDrop = (e, setter, currentValue) => {
60
+ e.preventDefault();
61
+ const draggedText = e.dataTransfer.getData("text/plain");
62
+ if (draggedText) {
63
+ // Append the dropped text
64
+ // Determine if we need a comma or space based on context (hacky but functional for this scope)
65
+ const separator = currentValue ? (setter === setSkills ? ', ' : ' ') : '';
66
+ setter(`${currentValue}${separator}${draggedText}`);
67
+ }
68
+ };
69
+
70
+ const handleExperienceDrop = (e, id, currentValue) => {
71
+ e.preventDefault();
72
+ const draggedText = e.dataTransfer.getData("text/plain");
73
+ if (draggedText) {
74
+ const separator = currentValue ? ' ' : '';
75
+ updateExperience(id, 'description', `${currentValue}${separator}${draggedText}`);
76
+ }
77
+ };
78
+
79
+ const handleDragOver = (e) => {
80
+ e.preventDefault(); // Necessary to allow dropping
81
+ };
82
+
83
+ // Handlers
84
+ const handlePersonalInfoChange = (e) => {
85
+ const { name, value } = e.target;
86
+ setPersonalInfo(prev => ({ ...prev, [name]: value }));
87
+ };
88
+
89
+ const addExperience = () => {
90
+ setExperience(prev => [...prev, { id: Date.now(), company: '', role: '', startDate: '', endDate: '', description: '' }]);
91
+ };
92
+
93
+ const updateExperience = (id, field, value) => {
94
+ setExperience(prev => prev.map(exp => exp.id === id ? { ...exp, [field]: value } : exp));
95
+ };
96
+
97
+ const removeExperience = (id) => {
98
+ setExperience(prev => prev.filter(exp => exp.id !== id));
99
+ };
100
+
101
+ const addEducation = () => {
102
+ setEducation(prev => [...prev, { id: Date.now(), institution: '', degree: '', startDate: '', endDate: '' }]);
103
+ };
104
+
105
+ const updateEducation = (id, field, value) => {
106
+ setEducation(prev => prev.map(edu => edu.id === id ? { ...edu, [field]: value } : edu));
107
+ };
108
+
109
+ const removeEducation = (id) => {
110
+ setEducation(prev => prev.filter(edu => edu.id !== id));
111
+ };
112
+
113
+ const handlePrint = () => {
114
+ window.print();
115
+ };
116
+
117
+ const inputStyle = {
118
+ width: '100%',
119
+ padding: '0.75rem',
120
+ borderRadius: '0.5rem',
121
+ border: '1px solid rgba(236, 183, 26, 0.46)',
122
+ backgroundColor: 'rgba(255,255,255,0.05)',
123
+ color: 'white',
124
+ marginBottom: '1rem',
125
+ fontFamily: 'inherit',
126
+ };
127
+
128
+ const labelStyle = {
129
+ display: 'block',
130
+ marginBottom: '0.5rem',
131
+ color: '#d1d5db',
132
+ fontSize: '0.875rem'
133
+ };
134
+
135
+ const sectionHeaderStyle = {
136
+ fontSize: '1.25rem',
137
+ fontWeight: 'bold',
138
+ color: '#FBBF24',
139
+ marginBottom: '1rem',
140
+ borderBottom: '1px solid rgba(236, 183, 26, 0.3)',
141
+ paddingBottom: '0.5rem'
142
+ };
143
+
144
+ return (
145
+ <div style={{ display: 'grid', gridTemplateColumns: 'minmax(300px, 1fr) 1.5fr', gap: '2rem', height: 'calc(100vh - 150px)' }}>
146
+
147
+ {/* Left Panel: Form & Gaps */}
148
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem', overflowY: 'auto', paddingRight: '1rem' }} className="no-print hide-scrollbar">
149
+
150
+ {/* Header Actions */}
151
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
152
+ <button
153
+ onClick={onBack}
154
+ style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', background: 'none', border: 'none', color: '#d1d5db', cursor: 'pointer', padding: '0.5rem 0' }}
155
+ >
156
+ <ArrowLeftIcon /> Back to Analysis
157
+ </button>
158
+ <button
159
+ onClick={handlePrint}
160
+ style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', backgroundColor: '#FBBF24', color: 'black', border: 'none', padding: '0.5rem 1rem', borderRadius: '0.5rem', fontWeight: 'bold', cursor: 'pointer' }}
161
+ >
162
+ <DownloadIcon /> Export PDF
163
+ </button>
164
+ </div>
165
+
166
+ {/* Fix Your Gaps Panel */}
167
+ {missingKeywords.length > 0 && (
168
+ <div style={{
169
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
170
+ border: '1px solid rgba(239, 68, 68, 0.3)',
171
+ borderRadius: '1rem',
172
+ padding: '1.5rem',
173
+ position: 'sticky',
174
+ top: 0,
175
+ zIndex: 10,
176
+ backdropFilter: 'blur(8px)', // Optional: makes it look better when scrolling over text
177
+ boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.5)'
178
+ }}>
179
+ <h3 style={{ fontSize: '1.125rem', fontWeight: 'bold', color: '#EF4444', marginBottom: '1rem' }}>Fix Your ATS Gaps</h3>
180
+ <p style={{ color: '#d1d5db', fontSize: '0.875rem', marginBottom: '1rem' }}>Drag and drop these keywords into your text fields below.</p>
181
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
182
+ {missingKeywords.map((match, idx) => (
183
+ <span
184
+ key={idx}
185
+ draggable
186
+ onDragStart={(e) => handleDragStart(e, match.keyword)}
187
+ style={{
188
+ backgroundColor: 'rgba(239, 68, 68, 0.2)',
189
+ color: '#FCA5A5',
190
+ padding: '0.25rem 0.5rem',
191
+ borderRadius: '0.25rem',
192
+ fontSize: '0.75rem',
193
+ border: '1px solid rgba(239, 68, 68, 0.4)',
194
+ cursor: 'grab'
195
+ }}
196
+ >
197
+ {match.keyword}
198
+ </span>
199
+ ))}
200
+ </div>
201
+ </div>
202
+ )}
203
+
204
+ {/* Builder Form */}
205
+ <div style={{ backgroundColor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '1rem', padding: '1.5rem' }}>
206
+
207
+ {/* Personal Info */}
208
+ <div style={{ marginBottom: '2rem' }}>
209
+ <h3 style={sectionHeaderStyle}>Personal Information</h3>
210
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
211
+ <div><label style={labelStyle}>Full Name</label><input style={inputStyle} name="fullName" value={personalInfo.fullName} onChange={handlePersonalInfoChange} placeholder="John Doe" /></div>
212
+ <div><label style={labelStyle}>Email</label><input style={inputStyle} name="email" value={personalInfo.email} onChange={handlePersonalInfoChange} placeholder="john@example.com" /></div>
213
+ <div><label style={labelStyle}>Phone</label><input style={inputStyle} name="phone" value={personalInfo.phone} onChange={handlePersonalInfoChange} placeholder="(555) 123-4567" /></div>
214
+ <div><label style={labelStyle}>Location</label><input style={inputStyle} name="location" value={personalInfo.location} onChange={handlePersonalInfoChange} placeholder="City, State" /></div>
215
+ <div><label style={labelStyle}>LinkedIn (Optional)</label><input style={inputStyle} name="linkedin" value={personalInfo.linkedin} onChange={handlePersonalInfoChange} placeholder="linkedin.com/in/johndoe" /></div>
216
+ <div><label style={labelStyle}>Portfolio (Optional)</label><input style={inputStyle} name="portfolio" value={personalInfo.portfolio} onChange={handlePersonalInfoChange} placeholder="johndoe.com" /></div>
217
+ </div>
218
+ </div>
219
+
220
+ {/* Summary */}
221
+ <div style={{ marginBottom: '2rem' }}>
222
+ <h3 style={sectionHeaderStyle}>Professional Summary</h3>
223
+ <textarea
224
+ style={{ ...inputStyle, minHeight: '100px', resize: 'vertical' }}
225
+ value={summary}
226
+ onChange={e => setSummary(e.target.value)}
227
+ onDrop={(e) => handleDrop(e, setSummary, summary)}
228
+ onDragOver={handleDragOver}
229
+ placeholder="Brief overview of your professional background and goals..."
230
+ />
231
+ </div>
232
+
233
+ {/* Experience */}
234
+ <div style={{ marginBottom: '2rem' }}>
235
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
236
+ <h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Experience</h3>
237
+ <button onClick={addExperience} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
238
+ <PlusIcon size={14} /> Add Role
239
+ </button>
240
+ </div>
241
+ {experience.map((exp, index) => (
242
+ <div key={exp.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
243
+ {index > 0 && (
244
+ <button onClick={() => removeExperience(exp.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Role">
245
+ <TrashIcon size={16} />
246
+ </button>
247
+ )}
248
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
249
+ <div><label style={labelStyle}>Company</label><input style={inputStyle} value={exp.company} onChange={e => updateExperience(exp.id, 'company', e.target.value)} placeholder="Acme Corp" /></div>
250
+ <div><label style={labelStyle}>Job Title</label><input style={inputStyle} value={exp.role} onChange={e => updateExperience(exp.id, 'role', e.target.value)} placeholder="Software Engineer" /></div>
251
+ <div><label style={labelStyle}>Start Date</label><input style={inputStyle} value={exp.startDate} onChange={e => updateExperience(exp.id, 'startDate', e.target.value)} placeholder="MMM YYYY" /></div>
252
+ <div><label style={labelStyle}>End Date</label><input style={inputStyle} value={exp.endDate} onChange={e => updateExperience(exp.id, 'endDate', e.target.value)} placeholder="MMM YYYY or Present" /></div>
253
+ </div>
254
+ <label style={labelStyle}>Description (Bullet points recommended)</label>
255
+ <textarea
256
+ style={{ ...inputStyle, minHeight: '80px', resize: 'vertical', marginBottom: 0 }}
257
+ value={exp.description}
258
+ onChange={e => updateExperience(exp.id, 'description', e.target.value)}
259
+ onDrop={(e) => handleExperienceDrop(e, exp.id, exp.description)}
260
+ onDragOver={handleDragOver}
261
+ placeholder="• Achieved X by doing Y..."
262
+ />
263
+ </div>
264
+ ))}
265
+ </div>
266
+
267
+ {/* Education */}
268
+ <div style={{ marginBottom: '2rem' }}>
269
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
270
+ <h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Education</h3>
271
+ <button onClick={addEducation} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
272
+ <PlusIcon size={14} /> Add Degree
273
+ </button>
274
+ </div>
275
+ {education.map((edu, index) => (
276
+ <div key={edu.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
277
+ {index > 0 && (
278
+ <button onClick={() => removeEducation(edu.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Degree">
279
+ <TrashIcon size={16} />
280
+ </button>
281
+ )}
282
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
283
+ <div><label style={labelStyle}>Institution</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.institution} onChange={e => updateEducation(edu.id, 'institution', e.target.value)} placeholder="University of State" /></div>
284
+ <div><label style={labelStyle}>Degree / Major</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.degree} onChange={e => updateEducation(edu.id, 'degree', e.target.value)} placeholder="B.S. Computer Science" /></div>
285
+ <div><label style={labelStyle}>Start Date</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.startDate} onChange={e => updateEducation(edu.id, 'startDate', e.target.value)} placeholder="YYYY" /></div>
286
+ <div><label style={labelStyle}>End Date</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.endDate} onChange={e => updateEducation(edu.id, 'endDate', e.target.value)} placeholder="YYYY" /></div>
287
+ </div>
288
+ </div>
289
+ ))}
290
+ </div>
291
+
292
+ {/* Skills */}
293
+ <div>
294
+ <h3 style={sectionHeaderStyle}>Skills</h3>
295
+ <label style={labelStyle}>Comma separated list of skills</label>
296
+ <textarea
297
+ style={{ ...inputStyle, minHeight: '80px', resize: 'vertical' }}
298
+ value={skills}
299
+ onChange={e => setSkills(e.target.value)}
300
+ onDrop={(e) => handleDrop(e, setSkills, skills)}
301
+ onDragOver={handleDragOver}
302
+ placeholder="React, Python, SQL, Project Management..."
303
+ />
304
+ </div>
305
+
306
+ </div>
307
+ </div>
308
+
309
+ {/* Right Panel: Live Preview (Printable Area) */}
310
+ <div
311
+ style={{
312
+ backgroundColor: '#52525B', // Darker backdrop for contrast
313
+ padding: '2rem',
314
+ overflowY: 'auto',
315
+ borderRadius: '1rem',
316
+ display: 'flex',
317
+ justifyContent: 'center'
318
+ }}
319
+ className="print-container no-print hide-scrollbar" // Wrapper won't print, only content below
320
+ >
321
+ {/* Actual Paper Element */}
322
+ <div
323
+ ref={printRef}
324
+ className="resume-print-area"
325
+ style={{
326
+ backgroundColor: 'white',
327
+ color: '#333333',
328
+ width: '100%',
329
+ maxWidth: '800px',
330
+ minHeight: '1056px', // ~8.5x11 aspect ratio
331
+ padding: '40px 50px',
332
+ boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.5)',
333
+ fontFamily: "'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif",
334
+ lineHeight: '1.5',
335
+ boxSizing: 'border-box'
336
+ }}
337
+ >
338
+ {/* Header */}
339
+ <div style={{ textAlign: 'center', marginBottom: '20px' }}>
340
+ <h1 style={{ fontSize: '36px', fontWeight: '800', margin: '0 0 5px 0', color: '#1e3a8a', letterSpacing: '-0.5px' }}>
341
+ {personalInfo.fullName || 'Your Name'}
342
+ </h1>
343
+ <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '10px', fontSize: '14px', color: '#555555' }}>
344
+ {personalInfo.location && <span>{personalInfo.location}</span>}
345
+ {personalInfo.location && (personalInfo.phone || personalInfo.email) && <span style={{ color: '#ccc' }}>•</span>}
346
+ {personalInfo.phone && <span>{personalInfo.phone}</span>}
347
+ {personalInfo.phone && personalInfo.email && <span style={{ color: '#ccc' }}>•</span>}
348
+ {personalInfo.email && <span style={{ color: '#1e3a8a' }}>{personalInfo.email}</span>}
349
+ {personalInfo.linkedin && <><span style={{ color: '#ccc' }}>•</span><span style={{ color: '#1e3a8a' }}>{personalInfo.linkedin}</span></>}
350
+ {personalInfo.portfolio && <><span style={{ color: '#ccc' }}>•</span><span style={{ color: '#1e3a8a' }}>{personalInfo.portfolio}</span></>}
351
+ </div>
352
+ </div>
353
+
354
+ {/* Summary Preview */}
355
+ {summary && (
356
+ <div style={{ marginBottom: '20px' }}>
357
+ <p style={{ fontSize: '15px', color: '#444444', margin: 0, textAlign: 'justify' }}>
358
+ {summary}
359
+ </p>
360
+ </div>
361
+ )}
362
+
363
+ {/* Skills Preview */}
364
+ {skills && (
365
+ <div style={{ marginBottom: '20px' }}>
366
+ <h2 style={{ fontSize: '16px', fontWeight: '700', color: '#1e3a8a', textTransform: 'uppercase', borderBottom: '2px solid #1e3a8a', paddingBottom: '4px', marginBottom: '12px', letterSpacing: '0.5px' }}>
367
+ Technical Skills
368
+ </h2>
369
+ <p style={{ fontSize: '15px', color: '#333333', margin: 0, lineHeight: '1.6' }}>
370
+ {skills}
371
+ </p>
372
+ </div>
373
+ )}
374
+
375
+ {/* Experience Preview */}
376
+ {experience.some(exp => exp.company || exp.role) && (
377
+ <div style={{ marginBottom: '20px' }}>
378
+ <h2 style={{ fontSize: '16px', fontWeight: '700', color: '#1e3a8a', textTransform: 'uppercase', borderBottom: '2px solid #1e3a8a', paddingBottom: '4px', marginBottom: '12px', letterSpacing: '0.5px' }}>
379
+ Professional Experience
380
+ </h2>
381
+ {experience.map(exp => (
382
+ (exp.company || exp.role) ? (
383
+ <div key={exp.id} style={{ marginBottom: '16px' }}>
384
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '2px' }}>
385
+ <div style={{ fontWeight: '700', fontSize: '16px', color: '#222222' }}>{exp.role || 'Role'}</div>
386
+ <div style={{ fontSize: '14px', color: '#666666', fontStyle: 'italic', whiteSpace: 'nowrap' }}>
387
+ {exp.startDate} {exp.startDate && exp.endDate && '–'} {exp.endDate}
388
+ </div>
389
+ </div>
390
+ <div style={{ fontSize: '15px', color: '#1e3a8a', fontWeight: '600', marginBottom: '8px' }}>{exp.company}</div>
391
+ {exp.description && (
392
+ <div style={{ fontSize: '14px', color: '#444444' }}>
393
+ {exp.description.split('\n').map((line, i) => (
394
+ <div style={{ display: 'flex', marginBottom: '4px' }} key={i}>
395
+ {line.trim().startsWith('•') || line.trim().startsWith('-') ? (
396
+ <span style={{ marginRight: '8px', color: '#1e3a8a' }}>•</span>
397
+ ) : line.trim() ? (
398
+ <span style={{ marginRight: '8px', color: '#1e3a8a' }}>•</span>
399
+ ) : null}
400
+ <span style={{ flex: 1 }}>{line.replace(/^[•-]\s*/, '')}</span>
401
+ </div>
402
+ ))}
403
+ </div>
404
+ )}
405
+ </div>
406
+ ) : null
407
+ ))}
408
+ </div>
409
+ )}
410
+
411
+ {/* Education Preview */}
412
+ {education.some(edu => edu.institution || edu.degree) && (
413
+ <div style={{ marginBottom: '20px' }}>
414
+ <h2 style={{ fontSize: '16px', fontWeight: '700', color: '#1e3a8a', textTransform: 'uppercase', borderBottom: '2px solid #1e3a8a', paddingBottom: '4px', marginBottom: '12px', letterSpacing: '0.5px' }}>
415
+ Education
416
+ </h2>
417
+ {education.map(edu => (
418
+ (edu.institution || edu.degree) ? (
419
+ <div key={edu.id} style={{ marginBottom: '12px' }}>
420
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '2px' }}>
421
+ <div style={{ fontWeight: '700', fontSize: '15px', color: '#222222' }}>{edu.institution || 'Institution'}</div>
422
+ <div style={{ fontSize: '14px', color: '#666666', fontStyle: 'italic', whiteSpace: 'nowrap' }}>
423
+ {edu.startDate} {edu.startDate && edu.endDate && '–'} {edu.endDate}
424
+ </div>
425
+ </div>
426
+ <div style={{ fontSize: '15px', color: '#444444' }}>{edu.degree}</div>
427
+ </div>
428
+ ) : null
429
+ ))}
430
+ </div>
431
+ )}
432
+ </div>
433
+ </div>
434
+
435
+ </div>
436
+ );
437
+ }
src/index.css CHANGED
@@ -25,4 +25,54 @@ body {
25
  .dark-select option {
26
  background-color: #111827;
27
  color: rgb(4, 42, 31);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
 
25
  .dark-select option {
26
  background-color: #111827;
27
  color: rgb(4, 42, 31);
28
+ }
29
+
30
+ /* ===============================
31
+ Scrollbar Utilities
32
+ =============================== */
33
+ .hide-scrollbar::-webkit-scrollbar {
34
+ display: none;
35
+ }
36
+
37
+ .hide-scrollbar {
38
+ -ms-overflow-style: none; /* IE and Edge */
39
+ scrollbar-width: none; /* Firefox */
40
+ }
41
+
42
+ /* ===============================
43
+ Print Styles for Resume Builder
44
+ =============================== */
45
+ @media print {
46
+ body * {
47
+ visibility: hidden;
48
+ }
49
+
50
+ .print-container, .print-container * {
51
+ visibility: visible;
52
+ }
53
+
54
+ .print-container {
55
+ position: absolute;
56
+ left: 0;
57
+ top: 0;
58
+ width: 100vw;
59
+ margin: 0;
60
+ padding: 0;
61
+ background: white !important;
62
+ }
63
+
64
+ .no-print {
65
+ display: none !important;
66
+ }
67
+
68
+ .resume-print-area {
69
+ width: 100% !important;
70
+ max-width: none !important;
71
+ padding: 0 !important;
72
+ box-shadow: none !important;
73
+ }
74
+
75
+ @page {
76
+ margin: 1cm;
77
+ }
78
  }
src/pages/ApplicantATS.jsx CHANGED
@@ -1,5 +1,6 @@
1
  import React, { useRef, useState } from 'react';
2
  import ApplicantLayout from '../components/ApplicantLayout';
 
3
  import { motion } from 'framer-motion';
4
  import {
5
  UploadIcon,
@@ -13,6 +14,7 @@ export default function ApplicantATS({ onNavigate }) {
13
  const [jobDescription, setJobDescription] = useState('');
14
  const [isAnalyzing, setIsAnalyzing] = useState(false);
15
  const [analysisResult, setAnalysisResult] = useState(null);
 
16
 
17
  // -------------------------
18
  // Handle Resume Upload
@@ -82,6 +84,14 @@ export default function ApplicantATS({ onNavigate }) {
82
 
83
  const isDisabled = !resumeFile || !jobDescription.trim() || isAnalyzing;
84
 
 
 
 
 
 
 
 
 
85
  return (
86
  <ApplicantLayout activePage="applicant-ats" onNavigate={onNavigate}>
87
  <div>
@@ -142,7 +152,24 @@ export default function ApplicantATS({ onNavigate }) {
142
  {analysisResult.score}/100
143
  </div>
144
  <p style={{ fontSize: '1.25rem', color: '#d1d5db' }}>ATS Match Score</p>
145
- <p style={{ color: '#d1d5db', marginTop: '0.5rem' }}>{analysisResult.summary}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  </div>
147
 
148
  {/* Keyword Matches */}
@@ -257,6 +284,7 @@ export default function ApplicantATS({ onNavigate }) {
257
  resize: 'vertical',
258
  fontFamily: 'inherit',
259
  }}
 
260
  />
261
  </div>
262
 
 
1
  import React, { useRef, useState } from 'react';
2
  import ApplicantLayout from '../components/ApplicantLayout';
3
+ import ResumeBuilder from '../components/ResumeBuilder';
4
  import { motion } from 'framer-motion';
5
  import {
6
  UploadIcon,
 
14
  const [jobDescription, setJobDescription] = useState('');
15
  const [isAnalyzing, setIsAnalyzing] = useState(false);
16
  const [analysisResult, setAnalysisResult] = useState(null);
17
+ const [showBuilder, setShowBuilder] = useState(false);
18
 
19
  // -------------------------
20
  // Handle Resume Upload
 
84
 
85
  const isDisabled = !resumeFile || !jobDescription.trim() || isAnalyzing;
86
 
87
+ if (showBuilder) {
88
+ return (
89
+ <ApplicantLayout activePage="applicant-ats" onNavigate={onNavigate}>
90
+ <ResumeBuilder analysisResult={analysisResult} onBack={() => setShowBuilder(false)} />
91
+ </ApplicantLayout>
92
+ );
93
+ }
94
+
95
  return (
96
  <ApplicantLayout activePage="applicant-ats" onNavigate={onNavigate}>
97
  <div>
 
152
  {analysisResult.score}/100
153
  </div>
154
  <p style={{ fontSize: '1.25rem', color: '#d1d5db' }}>ATS Match Score</p>
155
+ <p style={{ color: '#d1d5db', marginTop: '0.5rem', marginBottom: '1.5rem' }}>{analysisResult.summary}</p>
156
+
157
+ <button
158
+ onClick={() => setShowBuilder(true)}
159
+ style={{
160
+ backgroundColor: '#FBBF24',
161
+ color: '#111827',
162
+ padding: '0.75rem 1.5rem',
163
+ borderRadius: '0.5rem',
164
+ fontWeight: 'bold',
165
+ border: 'none',
166
+ cursor: 'pointer',
167
+ fontSize: '1rem',
168
+ boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
169
+ }}
170
+ >
171
+ Create Optimized Resume
172
+ </button>
173
  </div>
174
 
175
  {/* Keyword Matches */}
 
284
  resize: 'vertical',
285
  fontFamily: 'inherit',
286
  }}
287
+ className="hide-scrollbar"
288
  />
289
  </div>
290
 
src/pages/ApplicantJobPage.jsx CHANGED
@@ -157,12 +157,12 @@ export default function ApplicantJobPage({ onNavigate }) {
157
  job.company.toLowerCase().includes(lowerQuery)
158
  );
159
  }
160
-
161
  // ✅ Limit to 4 jobs unless "See All" is clicked
162
  if (!showAllJobs && !searchQuery) {
163
  result = result.slice(0, 4);
164
  }
165
-
166
  setFilteredJobs(result);
167
  }, [searchQuery, activeTab, allJobs, userProfile, showAllJobs]);
168
 
@@ -203,18 +203,18 @@ export default function ApplicantJobPage({ onNavigate }) {
203
  ) : (
204
  <>
205
  <JobListings searchQuery={searchQuery} setSearchQuery={setSearchQuery} isSearching={searchQuery.length > 0} filteredJobListings={filteredJobs} />
206
-
207
  {/* ✅ See All Button */}
208
  {!showAllJobs && !searchQuery && (
209
  <div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
210
- <button
211
- onClick={() => setShowAllJobs(true)}
212
- style={{
213
- padding: '0.75rem 2rem',
214
- backgroundColor: '#FBBF24',
215
- color: '#1a202c',
216
- border: 'none',
217
- borderRadius: '0.5rem',
218
  cursor: 'pointer',
219
  fontWeight: 'bold',
220
  fontSize: '1rem',
 
157
  job.company.toLowerCase().includes(lowerQuery)
158
  );
159
  }
160
+
161
  // ✅ Limit to 4 jobs unless "See All" is clicked
162
  if (!showAllJobs && !searchQuery) {
163
  result = result.slice(0, 4);
164
  }
165
+
166
  setFilteredJobs(result);
167
  }, [searchQuery, activeTab, allJobs, userProfile, showAllJobs]);
168
 
 
203
  ) : (
204
  <>
205
  <JobListings searchQuery={searchQuery} setSearchQuery={setSearchQuery} isSearching={searchQuery.length > 0} filteredJobListings={filteredJobs} />
206
+
207
  {/* ✅ See All Button */}
208
  {!showAllJobs && !searchQuery && (
209
  <div style={{ display: 'flex', justifyContent: 'center', marginTop: '2rem' }}>
210
+ <button
211
+ onClick={() => setShowAllJobs(true)}
212
+ style={{
213
+ padding: '0.75rem 2rem',
214
+ backgroundColor: '#FBBF24',
215
+ color: '#1a202c',
216
+ border: 'none',
217
+ borderRadius: '0.5rem',
218
  cursor: 'pointer',
219
  fontWeight: 'bold',
220
  fontSize: '1rem',