/* Root — auth gate, API-backed state, routing, tweaks =============== Auth + team + connection + ad data now come from the PHP/MySQL backend (see /api and apiClient.jsx). The dashboard chrome lives in shell.jsx (Workspace); the Admin Panel lives in admin.jsx. =================================================================== */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "defaultView": "dashboard", "adminDensity": "comfortable" }/*EDITMODE-END*/; function BootSplash({ brand }){ return (
connecting…
); } /* Error boundary — a render error in any view shows a recoverable card instead of a white screen. */ class ErrorBoundary extends React.Component { constructor(props){ super(props); this.state = { error: null }; } static getDerivedStateFromError(error){ return { error }; } componentDidCatch(error, info){ console.error('[relay] render error:', error, info && info.componentStack); } render(){ if (this.state.error){ return (
Something broke while rendering this view
{String(this.state.error && this.state.error.message || this.state.error)}
); } return this.props.children; } } window.ErrorBoundary = ErrorBoundary; function Root(){ const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [boot, setBoot] = useState('checking'); // checking | ready const [wsLoaded, setWsLoaded] = useState(false); // workspace data ready const [currentUser, setCurrentUser] = useState(null); const [users, setUsers] = useState([]); const [integrations, setIntegrations] = useState([]); const [audit, setAudit] = useState([]); const [accounts, setAccountsState] = useState([]); const [brand, setBrandRaw] = useState(loadBranding); const [dark, setDark] = useState(()=>loadLS(LS.theme, false)); const [view, setView] = useState('dashboard'); const setBrand = (b)=>{ setBrandRaw(b); saveBranding(b); }; const applyAccounts = (next)=>{ const arr = typeof next==='function'?next(accounts):next; setAccountsState(arr); setAccounts(arr); }; useEffect(()=>{ document.documentElement.classList.toggle('dark', dark); saveLS(LS.theme, dark); }, [dark]); useEffect(()=>{ document.title = (brand.name||'Relay') + ' — Ads Dashboard'; }, [brand.name]); /* ---- Data loaders ---- */ const refreshAudit = ()=> RelayAPI.audit.list().then(setAudit).catch(()=>{}); const loadWorkspaceData = React.useCallback(async ()=>{ const [accs, ints] = await Promise.all([ RelayAPI.accounts.list().catch(()=>[]), RelayAPI.integrations.list().catch(()=>[]), ]); setAccounts(accs); setAccountsState(accs); setIntegrations(ints); // Team + audit need admin permission; ignore failures for non-admins. RelayAPI.users.list().then(setUsers).catch(()=>setUsers([])); refreshAudit(); setWsLoaded(true); }, []); /* ---- Restore session on first load ---- */ useEffect(()=>{ RelayAPI.auth.me() .then(r => { setCurrentUser(r.user); return loadWorkspaceData(); }) .catch(()=>{}) .finally(()=> setBoot('ready')); }, []); const signIn = async (email, password, remember) => { const r = await RelayAPI.auth.login(email, password, remember); // throws on failure → shown in LoginScreen setCurrentUser(r.user); await loadWorkspaceData(); const canAdmin = can(r.user,'users.manage') || can(r.user,'integrations.manage'); setView(canAdmin && t.defaultView==='admin' ? 'admin' : 'dashboard'); }; const signOut = async () => { try { await RelayAPI.auth.logout(); } catch(e){} RelayLive.clearAll(); setWsLoaded(false); setCurrentUser(null); setUsers([]); setIntegrations([]); setAudit([]); setAccounts([]); setAccountsState([]); setView('dashboard'); }; const tweaksPanel = ( setTweak('defaultView', v)}/> setTweak('adminDensity', v)}/> ); if (boot === 'checking') return ; if (!currentUser){ return (<> {tweaksPanel} ); } if (!wsLoaded) return ; if (view==='admin'){ return (
setView('dashboard')} onSignOut={signOut} dark={dark} setDark={setDark} /> {tweaksPanel}
); } return (<> setView('admin')} onSignOut={signOut} dark={dark} setDark={setDark} pushAudit={refreshAudit} /> {tweaksPanel} ); } ReactDOM.createRoot(document.getElementById('root')).render();