()
| 416 | } |
| 417 | |
| 418 | export default function Layout() { |
| 419 | const { t, i18n } = useTranslation(); |
| 420 | const toast = useToast(); |
| 421 | const navigate = useNavigate(); |
| 422 | const location = useLocation(); |
| 423 | const { user, logout, setAuth } = useAuthStore(); |
| 424 | const queryClient = useQueryClient(); |
| 425 | const isChinese = i18n.language?.startsWith('zh'); |
| 426 | // Detect chat page: needs fixed-height main-content for inner scroll to work |
| 427 | const isChatPage = !!useMatch('/agents/:id/chat'); |
| 428 | const isAgentSettingsPage = !!useMatch('/agents/:id/settings'); |
| 429 | const activeAgentNestedMatch = useMatch('/agents/:id/*'); |
| 430 | const activeAgentRootMatch = useMatch('/agents/:id'); |
| 431 | const activeAgentId = activeAgentNestedMatch?.params.id || activeAgentRootMatch?.params.id; |
| 432 | const canAccessPlatformSettings = user?.role === 'platform_admin' || !!(user as any)?.is_platform_admin; |
| 433 | const canAccessCompanySettings = user?.role === 'platform_admin' || user?.role === 'org_admin' || !!(user as any)?.is_platform_admin; |
| 434 | const routeParams = new URLSearchParams(location.search); |
| 435 | const showCompanyTour = routeParams.get('tour') === 'company'; |
| 436 | const tourAssistantId = routeParams.get('assistantId') || ''; |
| 437 | |
| 438 | const [showAccountSettings, setShowAccountSettings] = useState(false); |
| 439 | const [showAccountMenu, setShowAccountMenu] = useState(false); |
| 440 | const [showLanguageSubmenu, setShowLanguageSubmenu] = useState(false); |
| 441 | const [langSubmenuPos, setLangSubmenuPos] = useState({ top: 0, left: 0 }); |
| 442 | const accountMenuRef = useRef<HTMLDivElement>(null); |
| 443 | const accountDropdownRef = useRef<HTMLDivElement>(null); |
| 444 | const langSubmenuPortalRef = useRef<HTMLDivElement>(null); |
| 445 | const langHoverCloseTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 446 | const [showNotifications, setShowNotifications] = useState(false); |
| 447 | const [showTalentMarket, setShowTalentMarket] = useState(false); |
| 448 | const [notifCategory, setNotifCategory] = useState<string>('all'); |
| 449 | const [selectedNotification, setSelectedNotification] = useState<any | null>(null); |
| 450 | const [showTenantMenu, setShowTenantMenu] = useState(false); |
| 451 | const [showTenantSetupModal, setShowTenantSetupModal] = useState(false); |
| 452 | const [tenantSearch, setTenantSearch] = useState(''); |
| 453 | const [joinInviteCode, setJoinInviteCode] = useState(''); |
| 454 | const [createCompanyName, setCreateCompanyName] = useState(''); |
| 455 | const [tenantFormLoading, setTenantFormLoading] = useState(false); |
| 456 | const [tenantFormError, setTenantFormError] = useState(''); |
| 457 | const [allowSelfCreate, setAllowSelfCreate] = useState(true); |
| 458 | const tenantSwitcherRef = useRef<HTMLDivElement>(null); |
| 459 | const tenantMenuPortalRef = useRef<HTMLDivElement>(null); |
| 460 | const [tenantMenuPos, setTenantMenuPos] = useState({ top: 0, left: 0, maxHeight: 520 }); |
| 461 | |
| 462 | // Notification polling |
| 463 | const { data: unreadCount = 0 } = useQuery({ |
| 464 | queryKey: ['notifications-unread'], |
| 465 | queryFn: async () => { |
| 466 | const res = await fetchJson<{ unread_count: number }>('/notifications/unread-count'); |
| 467 | return (res as any)?.unread_count || 0; |
| 468 | }, |
| 469 | refetchInterval: 30000, |
| 470 | enabled: !!user, |
| 471 | }); |
| 472 | const { data: notifications = [] } = useQuery({ |
| 473 | queryKey: ['notifications', notifCategory], |
| 474 | queryFn: () => fetchJson<any[]>(`/notifications?limit=50${notifCategory !== 'all' ? `&category=${notifCategory}` : ''}`), |
| 475 | enabled: !!user && showNotifications, |
nothing calls this directly
no test coverage detected