// aerOS Superadmin — app root: auth gate, routing, impersonation banner const { useState: _aS, useEffect: _aE } = React; // Real platform notifications derived from org state (suspended / over-limit). // No mock feed: empty when nothing needs attention. function SaNotifications({ orgs, lang }) { const [open, setOpen] = _aS(false); const [reqs, setReqs] = _aS([]); const loadReqs = async () => { try { const r = await window.SA_API.listUpgradeRequests(); setReqs((r || []).filter(x => x.status === 'pending')); } catch (e) {} }; _aE(() => { loadReqs(); const id = setInterval(loadReqs, 30000); return () => clearInterval(id); }, []); const resolve = async (id, action) => { try { await window.SA_API.resolveUpgrade(id, action); await loadReqs(); } catch (e) {} }; const notifs = []; (orgs || []).forEach(o => { if (o.is_active === false) notifs.push({ id: 'su' + o.id, tone: 'danger', text: `${o.name} askıya alındı` }); if (o.rooms_max > 0 && o.rooms_used >= o.rooms_max) notifs.push({ id: 'rm' + o.id, tone: 'warn', text: `${o.name}: oda limiti doldu (${o.rooms_used}/${o.rooms_max})` }); if (o.products_max > 0 && o.products_used >= o.products_max) notifs.push({ id: 'pr' + o.id, tone: 'warn', text: `${o.name}: ürün limiti doldu` }); }); const total = notifs.length + reqs.length; return (
setOpen(o => !o)} /> {open && (<>
setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
{(lang && lang.notifications) || 'Bildirimler'}
{reqs.map(r => (
{Icon.trending ? '' : ''}Plan yükseltme talebi
{r.org_name}{r.plan_name}{r.note ? ' · ' + r.note : ''}
))} {total === 0 ?
{Icon.bell({ size: 26 })}{(lang && lang.noNotifications) || 'Bildirim yok'}
: notifs.map(n => (
{n.text}
))}
)}
); } function SuperadminApp() { const ui = useUI('aeros-sa-ui'); const lang = ui.lang === 'en' ? window.SA_DATA.EN : window.SA_DATA.TR; const [authed, setAuthed] = _aS(!!window.aerosApi.getToken()); const [route, setRoute] = _aS('overview'); const [orgs, setOrgs] = _aS([]); const [loading, setLoading] = _aS(true); const [createOpen, setCreateOpen] = _aS(false); const [impersonating, setImpersonating] = _aS(null); const loadAll = async () => { setLoading(true); try { const [rows, plans] = await Promise.all([ window.SA_API.listOrgs(), window.SA_API.listPlans(), ]); if (Array.isArray(plans) && plans.length) window.SA_DATA.PLANS = plans; try { const m = await window.SA_API.metrics(); if (m && m.orders_30) window.SA_DATA.ORDERS_30 = m.orders_30; } catch (e) {} setOrgs(rows || []); } catch (e) { setOrgs([]); // Expired/invalid session -> show login instead of a blank panel. if (e && (e.status === 401 || e.status === 403)) { try { window.aerosApi.setToken(null); } catch (x) {} setAuthed(false); } } finally { setLoading(false); } }; _aE(() => { if (authed) loadAll(); }, [authed]); if (!authed) return setAuthed(true)} />; const nav = [ { section: lang.nav_platform, items: [ { id: 'overview', label: lang.overview, icon: Icon.grid }, { id: 'orgs', label: lang.orgs, icon: Icon.building, badge: orgs.length }, ] }, { section: lang.nav_management, items: [ { id: 'billing', label: lang.billing, icon: Icon.creditcard }, { id: 'settings', label: lang.settings || 'Ayarlar', icon: Icon.settings || Icon.gear || Icon.creditcard }, ] }, ]; const titles = { overview: lang.overview, orgs: lang.orgsTitle, billing: lang.billingTitle, settings: lang.settings || 'Ayarlar' }; const subs = { overview: `${lang.today} · ${new Date().toLocaleDateString(ui.lang === 'en' ? 'en-GB' : 'tr-TR', { day: 'numeric', month: 'long', year: 'numeric' })}`, orgs: lang.orgsSub, billing: lang.billingSub, settings: 'Yapay zeka ve platform ayarları' }; const onImpersonate = async (org) => { try { const res = await window.SA_API.impersonate(org.id); // Open the tenant's panel on ITS subdomain (token handoff via #t=), in a // new tab so the superadmin session here stays intact. No token overwrite. const slug = res.org_slug || org.slug; const host = location.hostname, port = location.port ? ':' + location.port : ''; window.open(location.protocol + '//' + slug + '.' + host + port + '/admin.html#t=' + encodeURIComponent(res.token), '_blank'); } catch (e) { /* ignore */ } }; const banner = impersonating ? (
{Icon.shield({ size: 17 })} {lang.imperBanner} {impersonating.name} {lang.imperAs}.
) : null; const topRight = ( <>
Süper Admin
{lang.role}
); return ( { window.SA_API.logout(); setAuthed(false); }} style={{ display: 'flex', alignItems: 'center', gap: 9, width: '100%', padding: '8px 10px', borderRadius: 'var(--r-sm)', border: 'none', background: 'transparent', color: 'var(--fg-2)', fontSize: 13, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit' }}>{Icon.logout({ size: 17 })}{lang.logout}} > {route === 'overview' && } {route === 'orgs' && setCreateOpen(true)} />} {route === 'billing' && } {route === 'settings' && } setCreateOpen(false)} onCreated={async (f) => { await window.SA_API.createOrg(f); // throws on failure (caught by the wizard) await loadAll(); setRoute('orgs'); }} /> ); } ReactDOM.createRoot(document.getElementById('root')).render();