import React, { useEffect, useState } from 'react'; import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { motion } from 'framer-motion'; import { Plus, Search, Menu, ChevronDown, LogOut, User as UserIcon, Users } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Image } from '@/components/ui/image'; import { base44 } from '@/api/base44Client'; import { KROW_LOGO_URL } from '@/assets/brand'; import { logActivity } from '@/lib/userTracking'; import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@/components/ui/dropdown-menu'; import { AssistantPanel, AssistantPanelProvider } from '@/components/ai-assistant'; import OwliverChatBubble from '@/components/krow/OwliverChatBubble'; import { HoverCard, HoverCardTrigger, HoverCardContent } from '@/components/ui/hover-card'; import KrowIdPreview from '@/components/krow/KrowIdPreview'; const ROLE_NAV = { employer: [ { label: 'Overview', path: '/' }, { label: 'Positions', path: '/positions' }, { label: 'Candidates', path: '/candidates' }, { label: 'Hired History', path: '/hired' }, { label: 'Talent Pool', path: '/talent-pool' }, { label: 'Analytics', path: '/analytics' }, ], admin: [ { label: 'Control Center', path: '/admin' }, { label: 'Positions', path: '/positions' }, { label: 'Candidates', path: '/candidates' }, { label: 'Hired History', path: '/hired' }, { label: 'Talent Pool', path: '/talent-pool' }, { label: 'KROW Forge', path: '/university' }, { label: 'Analytics', path: '/analytics' }, { label: 'Activity', path: '/tracking' }, ], talent: [ { label: 'My Portal', path: '/employee' }, { label: 'KROW Forge', path: '/university' }, { label: 'Opportunities', path: '/apply' }, ], }; const ROLE_HOME = { employer: '/', admin: '/admin', talent: '/employee' }; const ROLE_LABEL = { employer: 'Employer', admin: 'Admin', talent: 'Talent' }; const STATUS_OPTIONS = ['All Statuses', 'Active', 'Draft', 'Paused', 'Closed']; export default function Layout() { const location = useLocation(); const navigate = useNavigate(); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('All Statuses'); const [menuOpen, setMenuOpen] = useState(false); const [user, setUser] = useState(null); const [activeRole, setActiveRole] = useState(() => localStorage.getItem('activeRole') || 'employer'); // Owliver's own open state. Owliver is a separate product with its own UI and // voice workflow; it is not routed through the Krow AI panel. const [owliverOpen, setOwliverOpen] = useState(false); useEffect(() => { base44.auth.me().then(setUser).catch(() => {}); }, []); const handleLogout = () => { base44.auth.logout('/'); }; useEffect(() => { const pendingRole = localStorage.getItem('pending_account_type'); if (pendingRole) { localStorage.removeItem('pending_account_type'); (async () => { try { await base44.auth.updateMe({ account_type: pendingRole }); await logActivity('signup', { account_type: pendingRole }); } catch {} })(); } }, []); // Adopt saved/default role from the user's account type on first load. useEffect(() => { if (user && !localStorage.getItem('activeRole')) { const r = ['employer', 'admin', 'talent'].includes(user.account_type) ? user.account_type : 'employer'; setActiveRole(r); localStorage.setItem('activeRole', r); } }, [user]); const handleRoleChange = (role) => { setActiveRole(role); localStorage.setItem('activeRole', role); navigate(ROLE_HOME[role]); }; // Anything in the talent portal can open Owliver through this event — the // portal hero's own CTA uses it. useEffect(() => { const openOwliver = () => setOwliverOpen(true); window.addEventListener('krow:open-owliver', openOwliver); return () => window.removeEventListener('krow:open-owliver', openOwliver); }, []); const navItems = ROLE_NAV[activeRole]; const isTalent = activeRole === 'talent'; const isActive = (path) => path === '/' ? location.pathname === '/' : location.pathname.startsWith(path); const handleSearch = (e) => { e.preventDefault(); navigate(`/positions${searchQuery ? `?search=${encodeURIComponent(searchQuery)}` : ''}`); }; const handleStatusChange = (value) => { setStatusFilter(value); const param = value === 'All Statuses' ? '' : `?status=${value.toLowerCase()}`; navigate(`/positions${param}`); }; return (
{/* Header */}
{/* Brand */} {/* Nav pill (desktop) */}
{/* Role switcher */} {Object.keys(ROLE_LABEL).map((r) => ( handleRoleChange(r)} className={cn('cursor-pointer', r === activeRole && 'text-[#0A39DF] font-semibold')} > {ROLE_LABEL[r]} ))} {/* User menu */}

{user?.full_name || 'User'}

{user?.email}

(isTalent ? window.dispatchEvent(new CustomEvent('krow:open-owliver')) : navigate('/identity'))} className="cursor-pointer" > {isTalent ? 'Build Profile with Owliver' : 'Create KROW Identity'} {isTalent && location.pathname !== '/me' ? ( navigate('/me')} className="cursor-pointer"> My KROW ID ) : ( navigate('/me')} className="cursor-pointer"> My KROW ID )} navigate('/profile')} className="cursor-pointer"> Profile Log out
{/* Mobile menu button */}
{/* Control bar (Employer / Admin only) */} {!isTalent && (
{/* Below `sm` this wraps to two rows: search gets a full-width row of its own, the filter + action share the next. On one row the search field was being crushed to ~90px by the status select's intrinsic width, clipping its own placeholder. From `sm` up it is the original single 56px-tall row. */}
setSearchQuery(e.target.value)} placeholder="Search positions..." type="text" className="w-full pl-9 pr-4 h-9 text-[13px] rounded-full border border-white/60 bg-white/70 backdrop-blur-sm focus:bg-white focus:border-[#0A39DF] focus:outline-none transition-all" />
)} {/* Mobile menu Sheet */} Menu {/* Page shell. The dashboard is the primary experience: `main` keeps the same measure it has always had and stays centred, so with the assistant closed this layout is identical to the original. Opening the assistant animates a track open beside it; `main` is a flex child, so it reflows smoothly rather than jumping, and closing restores the original layout exactly. */}
{/* Krow AI — the dashboard assistant. Employer and Admin only; see ai-assistant/placement.js. Sticky below the sticky chrome so it stays put while the dashboard scrolls. */}
{/* Owliver — the independent AI product, with its own branding, voice experience and workflow. Talent only. Deliberately untouched by the Krow AI work: different product, different UI, no shared components. */} {isTalent && }
); }