import { useEffect, useRef, useState } from 'react' import { useClerk, useOrganization, useOrganizationList, useUser } from '@clerk/react' import { useQuery } from '@tanstack/react-query' import { NavLink, Outlet, useLocation } from 'react-router-dom' import { LANGS, locale, setLang, t, useLang, type Key } from '../i18n' import { workspaceCredits } from '../api/engine' import { Brand, Icon, type IconName } from './Icon' import { Toaster } from './Toast' // Learning Studio (learning-lab/frontend); both apps share one Clerk instance, so the user stays signed in. const STUDIO_URL: string = import.meta.env.VITE_STUDIO_URL // The Production dashboard shows what Express is making; the four settings doors run it: // feed the Brain, give an instruction, watch the schedule, collect results. export const NAV_GROUPS: { label: Key; items: { to: string; label: Key; icon: IconName }[] }[] = [ { label: 'nav.dashboard', items: [{ to: '/production', label: 'nav.production', icon: 'chart' }] }, { label: 'nav.settings', items: [ { to: '/upload', label: 'nav.brain', icon: 'brain' }, { to: '/brain', label: 'nav.instruction', icon: 'spark' }, { to: '/schedule', label: 'nav.schedule', icon: 'calendar' }, { to: '/results', label: 'nav.results', icon: 'layers' }, ], }, ] export const NAV = NAV_GROUPS.flatMap(g => g.items) export function LanguageSwitch() { const lang = useLang() return (
{LANGS.map(l => ( ))}
) } // The four steps of Express, from the mockup's "how it works" guide. const HELP_STEPS: { icon: IconName; title: Key; body: Key }[] = [ { icon: 'brain', title: 'help.brainTitle', body: 'help.brainBody' }, { icon: 'spark', title: 'help.instructionTitle', body: 'help.instructionBody' }, { icon: 'calendar', title: 'help.scheduleTitle', body: 'help.scheduleBody' }, { icon: 'layers', title: 'help.resultsTitle', body: 'help.resultsBody' }, ] function HelpDialog({ onClose }: { onClose: () => void }) { const ref = useRef(null) // Same pattern as the Results preview: open once, let the dialog's own close event unmount it. useEffect(() => { if (ref.current && !ref.current.open) ref.current.showModal() }, []) return ( e.target === ref.current && ref.current.close()}>

{t('help.title')}

{t('help.subtitle')}

{HELP_STEPS.map((s, i) => (
{i + 1}

{t(s.title)}

{t(s.body)}

))}
{t('help.note')}
) } // Polled because agents and schedules also spend credits outside this tab; main.tsx refreshes it after every mutation. function CreditBalance() { const credits = useQuery({ queryKey: ['credits'], queryFn: workspaceCredits, refetchInterval: 30_000 }) if (credits.data == null) return null return ( {t('common.credits', { n: credits.data.toLocaleString(locale()) })} ) } // The Learning space card switches the active organization. AuthGate keys the pages by orgId and // clears the query cache, so Brain, schedules, results and credits all reload for the new one. function OrgSwitcher() { const [open, setOpen] = useState(false) const ref = useRef(null) const { organization } = useOrganization() const { setActive, userMemberships } = useOrganizationList({ userMemberships: { infinite: true } }) useEffect(() => { if (!open) return const onClick = (e: MouseEvent) => { if (!ref.current?.contains(e.target as Node)) setOpen(false) } const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) } document.addEventListener('mousedown', onClick) document.addEventListener('keydown', onKey) return () => { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey) } }, [open]) const choose = (id: string) => { setOpen(false) if (id !== organization?.id) void setActive?.({ organization: id }) } return (
{open && (
    {userMemberships.data?.map(({ organization: org }) => (
  • ))}
)}
) } export function AppLayout() { useLang() const [help, setHelp] = useState(false) const { user } = useUser() const { signOut } = useClerk() const { pathname } = useLocation() const current = NAV.find(n => pathname.startsWith(n.to)) // Nav groups stay closed unless they hold the current page; a click on the label overrides that until the next route. const [toggled, setToggled] = useState>>({}) useEffect(() => setToggled({}), [pathname]) const name = user?.fullName || user?.primaryEmailAddress?.emailAddress || '-' return (
{t('layout.breadcrumb')} / {current ? t(current.label) : ''}
{t('layout.engine')} {t('layout.toStudio')}
{/* Keyed by route so each page plays the fade-in from theme.css. */}
Learning Lab Express by Bawana{t('layout.footer')}
{help && setHelp(false)} />}
) }