Spaces:
Sleeping
Sleeping
File size: 3,684 Bytes
c8cd296 | 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 | import React, { useState } from 'react';
import { Briefcase, GraduationCap, ArrowRight } from 'lucide-react';
import './SearchPathGate.css';
export const SEARCH_PATH_OPTIONS = [
{
value: 'Internship search',
title: 'Internship',
description: 'I am looking for an internship (typical for juniors and earlier).',
icon: GraduationCap,
},
{
value: 'Full-time / entry-level',
title: 'Full-time job',
description: 'I am looking for a full-time / entry-level role (typical for seniors and grads).',
icon: Briefcase,
},
];
/**
* First-run gate: internship vs full-time changes advisor trajectory.
*/
const SearchPathGate = ({ authToken, onComplete }) => {
const [selected, setSelected] = useState(null);
const [saving, setSaving] = useState(false);
const [error, setError] = useState('');
const handleContinue = async () => {
if (!selected) {
setError('Pick internship or full-time to continue.');
return;
}
setSaving(true);
setError('');
try {
const resp = await fetch(`${process.env.REACT_APP_API_URL}/api/users/me/profile`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ cyber_role: selected }),
});
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data.detail || 'Could not save your choice');
}
const profile = await resp.json();
localStorage.setItem('launchpadSearchPath', selected);
onComplete?.(profile, selected);
} catch (e) {
setError(e.message || 'Could not save your choice');
} finally {
setSaving(false);
}
};
return (
<div className="search-path-backdrop" role="dialog" aria-modal="true" aria-labelledby="search-path-title">
<div className="search-path-card">
<h2 id="search-path-title">What are you aiming for right now?</h2>
<p className="search-path-sub">
This steers every advisor — search strategy, resume tone, interview prep, and weekly cadence.
</p>
<div className="search-path-options">
{SEARCH_PATH_OPTIONS.map((opt) => {
const Icon = opt.icon;
const active = selected === opt.value;
return (
<button
key={opt.value}
type="button"
className={`search-path-option ${active ? 'active' : ''}`}
onClick={() => { setSelected(opt.value); setError(''); }}
disabled={saving}
>
<Icon className="search-path-option-icon" size={22} />
<span className="search-path-option-title">{opt.title}</span>
<span className="search-path-option-desc">{opt.description}</span>
</button>
);
})}
</div>
{error && <div className="search-path-error">{error}</div>}
<button
type="button"
className="search-path-continue"
onClick={handleContinue}
disabled={saving || !selected}
>
{saving ? 'Saving…' : 'Continue to advisors'}
{!saving && <ArrowRight size={16} />}
</button>
</div>
</div>
);
};
export function needsSearchPath(profile) {
const role = profile?.cyber_role;
if (!role) return true;
const normalized = String(role).toLowerCase();
return !(
normalized.includes('internship')
|| normalized.includes('full-time')
|| normalized.includes('full time')
|| normalized.includes('both')
);
}
export default SearchPathGate;
|