joseph njoroge kariuki
feat: introduce dedicated Senti Institutional Login Portal with technical styling and dynamic role propagation
5c7593f
Raw
History Blame Contribute Delete
3.97 kB
import { useState, useEffect } from 'react'
import { Outlet, useLocation } from 'react-router-dom'
import InstSidebar from './InstSidebar'
import InstControlPanel from './InstControlPanel'
import { api } from '../../lib/api'
import { useAuthStore } from '../../store/authStore'
const ORG_THEMES = {
bank: { primary: '#fbbc04', glow: 'rgba(251,188,4,0.08)', container: 'rgba(251,188,4,0.15)' },
sacco: { primary: '#10b981', glow: 'rgba(16,185,129,0.08)', container: 'rgba(16,185,129,0.15)' },
fintech: { primary: '#a855f7', glow: 'rgba(168,85,247,0.08)', container: 'rgba(168,85,247,0.15)' },
mfi: { primary: '#14b8a6', glow: 'rgba(20,184,166,0.08)', container: 'rgba(20,184,166,0.15)' },
insurer: { primary: '#ef4444', glow: 'rgba(239,68,68,0.08)', container: 'rgba(239,68,68,0.15)' },
}
export default function InstAppShell() {
const { user } = useAuthStore()
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [orgId, setOrgId] = useState(user?.orgId || 'bank')
const [manifest, setManifest] = useState(null)
const [selectedEntity, setSelectedEntity] = useState(null)
const [entityContext, setEntityContext] = useState(null)
const [loadingContext, setLoadingContext] = useState(false)
const location = useLocation()
const activeModule = location.pathname.split('/')[2] || 'risk'
useEffect(() => {
api.institutional.getManifest(orgId).then(d => setManifest(d)).catch(console.error)
}, [orgId])
useEffect(() => {
if (!selectedEntity) { setEntityContext(null); return }
setLoadingContext(true)
api.institutional.getEntityContext(selectedEntity)
.then(d => { setEntityContext(d); setLoadingContext(false) })
.catch(() => setLoadingContext(false))
}, [selectedEntity])
const t = ORG_THEMES[orgId] || ORG_THEMES.bank
return (
<div className="flex flex-col h-screen w-full bg-[#060a14] text-inst-on-surface font-sans overflow-hidden">
<header className="h-10 flex-shrink-0 flex items-center justify-between px-4 border-b border-white/[0.06] bg-[#080d1a]">
<div className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full" style={{ background: t.primary, boxShadow: '0 0 6px ' + t.primary }} />
<span className="text-[11px] font-bold tracking-wide text-white/90">SENTI</span>
<span className="text-[11px] font-bold tracking-wide" style={{ color: t.primary }}>INSTITUTIONAL</span>
<span className="text-white/20 text-[10px]">|</span>
<span className="text-[10px] text-white/40 font-mono">{manifest?.organization?.name || '...'}</span>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1.5 text-[10px] font-mono text-white/30">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 alert-pulse" />
<span>LIVE</span>
</div>
<span className="text-[10px] font-mono text-white/20">{new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})} UTC</span>
<button className="p-1 text-white/30 hover:text-white/60 transition-colors duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] btn-press"><span className="material-symbols-outlined text-[16px]">notifications</span></button>
</div>
</header>
<div className="flex flex-1 overflow-hidden">
<InstSidebar collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(!sidebarCollapsed)} orgId={orgId} setOrgId={setOrgId} manifest={manifest} />
<main className="flex-1 flex flex-col overflow-hidden bg-[#0a0f1e]">
<Outlet context={{ orgId, manifest, activeModule, selectedEntity, setSelectedEntity, entityContext, setOrgId }} />
</main>
<InstControlPanel selectedEntity={selectedEntity} entityContext={entityContext} loading={loadingContext} setSelectedEntity={setSelectedEntity} manifest={manifest} activeView={activeModule} />
</div>
</div>
)
}