// Nuance ATS — shared chrome and primitives const { useState, useMemo, useEffect, useCallback, useRef } = React; // =================== Ephemeral toast ============= // Lightweight hook: renders one inline toast inside the current screen. // Usage: // const [toast, showToast, ToastHost] = useAtsToast(); // // {ToastHost} function useAtsToast() { const [toast, setToast] = useState(null); const timerRef = React.useRef(null); const show = useCallback((message, opts = {}) => { if (timerRef.current) clearTimeout(timerRef.current); setToast({ message, tone: opts.tone || "neutral", icon: opts.icon }); timerRef.current = setTimeout(() => setToast(null), opts.duration || 2800); }, []); useEffect(() => () => timerRef.current && clearTimeout(timerRef.current), []); const host = toast ? (
{toast.message}
) : null; return [toast, show, host]; } // =================== Viewer avatar menu ========= function ViewerAvatarMenu() { const [open, setOpen] = useState(false); const buttonRef = useRef(null); const menuRef = useRef(null); // Use ref to ensure portal container is stable (fixes React 18 portal cleanup issue) const portalRootRef = useRef(null); if (!portalRootRef.current) { portalRootRef.current = document.getElementById('portal-root') || document.body; } // Get current viewer from window const currentViewer = typeof window.__atsCurrentViewer !== 'undefined' ? window.__atsCurrentViewer : VIEWERS[0]; useEffect(() => { if (!open) return; const onDoc = (e) => { if (buttonRef.current && buttonRef.current.contains(e.target)) return; if (menuRef.current && !menuRef.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', onDoc); return () => document.removeEventListener('mousedown', onDoc); }, [open]); const handleViewerChange = (viewer) => { if (typeof window.__atsSetViewer === 'function') { window.__atsSetViewer(viewer); } setOpen(false); }; const handleLogout = () => { setOpen(false); if (typeof window.__atsLogout === 'function') { window.__atsLogout(); } }; const isAuthenticated = !!localStorage.getItem('ats_token'); const authUser = window.__atsAuthUser; const isRealAdmin = authUser && authUser.role === 'admin'; const viewAsRoles = [ { role: "admin", label: "Admin", desc: "Full access", accent: ROLES.admin?.accent || "#1D3140" }, { role: "interviewer", label: "Interviewer", desc: "Limited visibility", accent: ROLES.interviewer?.accent || "#7595D5" }, { role: "external", label: "External Agent", desc: "Jon Luzha's view", accent: ROLES.external?.accent || "#9C6B4F" }, ]; const switchRole = (role) => { if (!authUser || typeof window.__atsSetViewer !== 'function') return; if (role === "admin") { const nameParts = (authUser.full_name || "").split(" "); const initials = nameParts.map(p => p[0] || "").join("").slice(0, 2).toUpperCase(); window.__atsSetViewer({ id: authUser.id, name: authUser.full_name, initials, color: "#7595D5", role: "admin", title: authUser.email, avatar_url: authUser.avatar_url, }); } else if (role === "interviewer") { const nameParts = (authUser.full_name || "").split(" "); const initials = nameParts.map(p => p[0] || "").join("").slice(0, 2).toUpperCase(); window.__atsSetViewer({ id: authUser.id, name: authUser.full_name, initials, color: "#7595D5", role: "interviewer", title: authUser.email, avatar_url: authUser.avatar_url, }); } else if (role === "external") { window.__atsSetViewer({ id: "ext-jon-luzha", name: "Jon Luzha", initials: "JL", color: "#9C6B4F", role: "external", title: "Alexander Chapman", sourceTag: "Jon Luzha", }); } setOpen(false); }; // Get button position for dropdown placement const [buttonRect, setButtonRect] = useState(null); useEffect(() => { if (open && buttonRef.current) { setButtonRect(buttonRef.current.getBoundingClientRect()); } }, [open]); return ( <> {open && buttonRect && ReactDOM.createPortal(
{authUser ? authUser.full_name : currentViewer.name}
{authUser ? authUser.email : currentViewer.title}
{isRealAdmin && (
View as
{viewAsRoles.map(v => ( ))}
)}
, portalRootRef.current )} ); } Object.assign(window, { useAtsToast, ViewerAvatarMenu }); // =================== Brand mark ================= function Mark({ size = 22 }) { return ( Nuance Labs ); } // =================== Avatar ===================== function Avatar({ src, name, size = 24, ring = false }) { const initials = (name || "?") .split(" ").map(s => s[0]).slice(0, 2).join(""); const [err, setErr] = useState(false); const dim = { width: size, height: size, fontSize: Math.max(9, size * 0.42) }; if (!src || err) { return ( {initials} ); } return ( setErr(true)} /> ); } // Tiny initials disc (recruiter etc.) function InitialDisc({ initials, color, size = 18, title, avatar }) { const [imgErr, setImgErr] = useState(false); if (avatar && !imgErr) { return ( setImgErr(true)} style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: "50%" }} /> ); } return ( {initials} ); } // =================== Stage chip ================= function StageChip({ stage, dense = false }) { return ( {dense ? stage.short : stage.label} ); } // =================== Source pill ================ function SourcePill({ source }) { const s = SOURCES.find(x => x.id === source) || SOURCES[0]; return {s.label}; } // =================== Search bar ==================== function SearchBar() { const [query, setQuery] = useState(""); const [open, setOpen] = useState(false); const [selected, setSelected] = useState(0); const inputRef = useRef(null); const wrapRef = useRef(null); const portalRef = useRef(null); if (!portalRef.current) { portalRef.current = document.getElementById('portal-root') || document.body; } const results = useMemo(() => { const q = query.trim().toLowerCase(); if (q.length < 2) return []; const terms = q.split(/\s+/); const matched = []; for (const c of CANDIDATES) { const hay = c._searchHay; if (terms.every(t => hay.includes(t))) { matched.push(c); if (matched.length >= 12) break; } } return matched; }, [query]); const onChange = (e) => { setQuery(e.target.value); setOpen(true); setSelected(0); }; useEffect(() => { const handler = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); inputRef.current?.focus(); inputRef.current?.select(); setOpen(true); } }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, []); useEffect(() => { if (!open) return; const handler = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) { const dropdown = document.querySelector('.ats-search-dropdown'); if (dropdown && dropdown.contains(e.target)) return; setOpen(false); } }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [open]); const goTo = (c) => { setOpen(false); setQuery(""); if (typeof window.__atsNavigate === "function") { window.__atsNavigate("pipeline"); } setTimeout(() => { window.location.hash = "#candidate/" + c.id; window.dispatchEvent(new CustomEvent('ats-open-candidate', { detail: c.id })); }, 50); }; const onKeyDown = (e) => { if (!open || results.length === 0) { if (e.key === 'Escape') { setOpen(false); inputRef.current?.blur(); } return; } if (e.key === 'ArrowDown') { e.preventDefault(); setSelected(s => Math.min(s + 1, results.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelected(s => Math.max(s - 1, 0)); } else if (e.key === 'Enter') { e.preventDefault(); if (results[selected]) goTo(results[selected]); } else if (e.key === 'Escape') { setOpen(false); inputRef.current?.blur(); } }; const showDropdown = open && query.trim().length >= 2; const inputRect = showDropdown && wrapRef.current ? wrapRef.current.getBoundingClientRect() : null; return (
{ if (query.trim().length >= 2) setOpen(true); }} /> {!open && ⌘K} {showDropdown && inputRect && ReactDOM.createPortal(
{results.length === 0 ? (
No candidates found
) : ( results.map((c, i) => ( )) )}
, portalRef.current )}
); } // =================== Top nav ===================== function TopNav({ activeTab = "Pipeline" }) { const viewer = useViewer(); const isAdmin = viewer && viewer.role === "admin"; const tabs = isAdmin ? ["My candidates", "Pipeline", "Permissions"] : ["My candidates", "Pipeline"]; const resolvedActive = (activeTab === "Candidates" || activeTab === "All candidates") ? "Pipeline" : activeTab; const handleTabClick = (t) => { if (typeof window.__atsNavigate === "function") { if (t === "Pipeline") window.__atsNavigate("pipeline"); else if (t === "My candidates") window.__atsNavigate("my-candidates"); else if (t === "Permissions") window.__atsNavigate("settings"); } }; return (
Nuance
); } // =================== Sidebar (jobs nav) ========== function ViewIcon({ id }) { const stroke = { fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }; switch (id) { case "board": // kanban columns return ( ); case "table": // rows return ( ); case "calendar": return ( ); case "stalled": // clock return ( ); case "referrals": // sparkle / source return ( ); case "highrating": // star return ( ); default: return null; } } const PIPELINE_VIEWS = [ { id: "board", label: "Board view", color: "var(--nl-ink)" }, { id: "table", label: "Table view", color: "var(--nl-ink)" }, { id: "calendar", label: "Calendar view", color: "var(--nl-blue)" }, ]; function SavedViewRow({ saved, isActive, onSelect, onRename, onDelete }) { const [editing, setEditing] = useState(false); const [name, setName] = useState(saved.name); const [menuOpen, setMenuOpen] = useState(false); const inputRef = useRef(null); const menuRef = useRef(null); useEffect(() => { setName(saved.name); }, [saved.name]); useEffect(() => { if (editing && inputRef.current) { inputRef.current.focus(); inputRef.current.select(); } }, [editing]); useEffect(() => { if (!menuOpen) return; const onDoc = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); }; document.addEventListener("mousedown", onDoc); return () => document.removeEventListener("mousedown", onDoc); }, [menuOpen]); const commit = () => { const trimmed = name.trim(); if (!trimmed) { setName(saved.name); setEditing(false); return; } if (trimmed !== saved.name) onRename(trimmed); setEditing(false); }; return (
{editing ? ( setName(e.target.value)} onBlur={commit} onKeyDown={(e) => { if (e.key === "Enter") commit(); else if (e.key === "Escape") { setName(saved.name); setEditing(false); } }} maxLength={48} /> ) : ( )} {menuOpen && (
)}
); } function timeAgo(iso) { const s = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); if (s < 60) return "just now"; const m = Math.floor(s / 60); if (m < 60) return m + "m ago"; const h = Math.floor(m / 60); if (h < 24) return h + "h ago"; const d = Math.floor(h / 24); return d + "d ago"; } function JobSidebar({ activeJobId, onSelect, view, onViewSelect, savedViews, activeSavedViewId, onSavedViewRename, onSavedViewDelete }) { const viewer = useViewer(); const isAdmin = viewer && viewer.role === "admin"; const [jobTick, setJobTick] = useState(0); useEffect(() => { const h = () => setJobTick(t => t + 1); window.addEventListener("jobs-updated", h); return () => window.removeEventListener("jobs-updated", h); }, []); const openJobs = JOBS.filter(j => j.status === "open" || j.status === "active"); const inactiveJobs = JOBS.filter(j => j.status !== "open" && j.status !== "active"); const [showInactive, setShowInactive] = useState(false); const [newJobFlash, setNewJobFlash] = useState(false); const [dropTarget, setDropTarget] = useState(null); const [intStatus, setIntStatus] = useState(null); const [tick, setTick] = useState(0); const [retrying, setRetrying] = useState(false); const loadStatus = () => fetchIntegrationStatus().then(d => { if (d) setIntStatus(d); }); useEffect(() => { loadStatus(); const iv = setInterval(() => { loadStatus(); setTick(t => t + 1); }, 30000); return () => clearInterval(iv); }, []); // Local active state — always tracked so clicks give visual feedback // even when the parent screen doesn't (yet) re-render on selection. const [localActive, setLocalActive] = useState(activeJobId); const effectiveActive = localActive !== undefined ? localActive : activeJobId; const handleSelect = (id) => { setLocalActive(id); if (onSelect) onSelect(id); }; const filledLabel = (j) => { if (j.status === "filled") return j.filledBy; if (j.status === "paused") return j.pauseReason || "Paused"; return "Closed"; }; return ( ); } Object.assign(window, { Mark, Avatar, InitialDisc, StageChip, SourcePill, SearchBar, TopNav, JobSidebar });