// ============== recruiter dashboard ============== const APP_STATUSES = ["submitted", "reviewed", "shortlisted", "interviewing", "offered", "placed", "rejected", "withdrawn"]; const ROLE_STATUSES = ["draft", "open", "paused", "closed"]; function RecruiterDashboard({ go }) { const { user, logout } = useAuth(); const [tab, setTab] = React.useState("overview"); const [inviteOpen, setInviteOpen] = React.useState(false); return (
Recruiter · {user.email}

Control room.

{inviteOpen && setInviteOpen(false)} />}
{tab === "overview" && } {tab === "roles" && } {tab === "applications" && } {tab === "candidates" && } {tab === "companies" && }
); } function Overview() { const [stats, setStats] = React.useState(null); React.useEffect(() => { window.W3J_API.apiFetch("/admin/dashboard").then(setStats).catch(() => setStats({})); }, []); if (!stats) return

Loading…

; const cards = [ { num: stats.open_roles ?? 0, label: "Open roles" }, { num: stats.total_roles ?? 0, label: "Total roles" }, { num: stats.candidates ?? 0, label: "Candidates" }, { num: stats.applications_total ?? 0, label: "Applications" }, ]; return (
{cards.map((c) => (
{c.num}
{c.label}
))}

Applications by status

{Object.entries(stats.applications_by_status || {}).map(([k, v]) => (
{k.replace(/_/g, " ")}
{v}
))}
); } // ---------- roles ---------- function AdminRoles() { const [roles, setRoles] = React.useState([]); const [companies, setCompanies] = React.useState([]); const [editing, setEditing] = React.useState(null); const [loading, setLoading] = React.useState(true); const reload = React.useCallback(async () => { const [r, c] = await Promise.all([ window.W3J_API.apiFetch("/admin/roles"), window.W3J_API.apiFetch("/admin/companies"), ]); setRoles(r); setCompanies(c); setLoading(false); }, []); React.useEffect(() => { reload(); }, [reload]); if (loading) return

Loading…

; return (

{roles.length} roles

{roles.map((r) => (
setEditing(r)}> {r.public_id}
{r.title}
{r.company_display} · {r.remote}
{r.chain && {r.chain}} {r.status}
${r.min_comp_k}–{r.max_comp_k}k
))}
{editing && setEditing(null)} onSaved={reload} />}
); } const ROLE_CATEGORIES = ["Protocol", "DeFi", "Trading", "Infrastructure", "Security", "DAO", "Gaming/NFT"]; const CHAINS = ["Solana", "EVM", "Bitcoin", "Multi-chain"]; const LEVELS = ["Mid", "Senior", "Lead", "Staff", "Director"]; function RoleEditor({ role, companies, onClose, onSaved }) { const isNew = !role.id; const [form, setForm] = React.useState({ public_id: role.public_id || "", company_id: role.company_id || (companies[0]?.id || ""), title: role.title || "", category: role.category || ROLE_CATEGORIES[0], chain: role.chain || "", stack: (role.stack || []).join(", "), type: role.type || "Full-time", level: role.level || LEVELS[1], remote: role.remote || "Remote · Global", min_comp_k: role.min_comp_k ?? 0, max_comp_k: role.max_comp_k ?? 0, description: role.description || "", requirements: (role.requirements || []).join("\n"), offer: (role.offer || []).join("\n"), status: role.status || "draft", }); const [questions, setQuestions] = React.useState(role.questions || []); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(""); const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value })); const submit = async (e) => { e.preventDefault(); setBusy(true); setErr(""); try { const body = { ...form, chain: form.chain || null, min_comp_k: Number(form.min_comp_k), max_comp_k: Number(form.max_comp_k), stack: form.stack.split(",").map((s) => s.trim()).filter(Boolean), requirements: form.requirements.split("\n").map((s) => s.trim()).filter(Boolean), offer: form.offer.split("\n").map((s) => s.trim()).filter(Boolean), questions: questions.filter((q) => q.label?.trim()), }; if (isNew) { await window.W3J_API.apiFetch("/admin/roles", { method: "POST", body }); } else { const { public_id, company_id, ...patch } = body; await window.W3J_API.apiFetch(`/admin/roles/${role.id}`, { method: "PATCH", body: patch }); } onSaved(); onClose(); } catch (ex) { setErr(ex.message || "Save failed"); } finally { setBusy(false); } }; const remove = async () => { if (!confirm("Delete this role? Applications will be deleted too.")) return; await window.W3J_API.apiFetch(`/admin/roles/${role.id}`, { method: "DELETE" }); onSaved(); onClose(); }; return (
e.stopPropagation()}>

{isNew ? "New role" : `Edit ${role.public_id}`}