/* ============================================================ KODILA — App shell + routing + tema ============================================================ */ const NAV = [ { route: 'dashboard', label: 'Nadzorna plošča', icon: 'grid' }, { route: 'sifranti', label: 'Šifranti', icon: 'book', permission: 'sifranti' }, { route: 'dokumenti', label: 'Dokumenti', icon: 'file', permission: 'dokumenti' }, { route: 'admin', label: 'Admin panel', icon: 'settings', permission: 'admin' }, ]; function csrfToken() { const meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.content : ''; } function setCsrfToken(token) { if (!token) return; const meta = document.querySelector('meta[name="csrf-token"]'); if (meta) meta.content = token; } async function api(path, options) { const opts = options || {}; const headers = Object.assign({ 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken() }, opts.headers || {}); const res = await fetch(path, Object.assign({}, opts, { headers, credentials: 'same-origin' })); const data = await res.json().catch(() => ({})); return { ok: res.ok, status: res.status, data }; } function useTheme() { const [theme, setTheme] = useState(() => localStorage.getItem('kodila_theme') || 'light'); useEffect(() => { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('kodila_theme', theme); }, [theme]); return [theme, () => setTheme(t => t === 'dark' ? 'light' : 'dark')]; } function visibleApps(user) { return APPS.filter(a => !a.roles || a.roles.includes(user.role)); } function visibleNav(user, permissions) { return NAV.filter(n => !n.permission || permissions[n.permission]); } function TopBar({ route, onNav, user, permissions, theme, toggleTheme, onLogout }) { const toast = useToast(); const moreApps = visibleApps(user).filter(a => !['sifranti', 'dokumenti'].includes(a.id)); return (
onNav('dashboard')}>
K
KODILA
Intranet
{}} /> ⌘K
( )}>
{user.email}
); } function App() { const [theme, toggleTheme] = useTheme(); const [user, setUser] = useState(null); const [booting, setBooting] = useState(true); const [route, setRoute] = useState(() => localStorage.getItem('kodila_route') || 'dashboard'); const [permissions, setPermissions] = useState({}); const checkSession = React.useCallback(async () => { const { ok, data } = await api('/api/auth/me'); window.__KODILA_USER__ = ok ? data.user : null; window.__KODILA_PERMISSIONS__ = ok ? data.permissions : {}; setUser(ok ? data.user : null); setPermissions(ok ? data.permissions : {}); if (ok) setCsrfToken(data.csrfToken); return ok; }, []); useEffect(() => { checkSession().finally(() => setBooting(false)); }, [checkSession]); useEffect(() => { const onFocus = () => { checkSession(); }; const interval = setInterval(checkSession, 5 * 60 * 1000); window.addEventListener('focus', onFocus); return () => { clearInterval(interval); window.removeEventListener('focus', onFocus); }; }, [checkSession]); const nav = (r) => { setRoute(r); localStorage.setItem('kodila_route', r); window.scrollTo(0, 0); }; const login = async (u, newCsrfToken) => { window.__KODILA_USER__ = u; setUser(u); setCsrfToken(newCsrfToken); await checkSession(); nav('dashboard'); }; const logout = async () => { const { data } = await api('/api/auth/logout', { method: 'POST' }); window.__KODILA_USER__ = null; window.__KODILA_PERMISSIONS__ = {}; setUser(null); setPermissions({}); setCsrfToken(data.csrfToken); }; if (booting) { return
; } if (!user) return ; const apps = visibleApps(user); const safeRoute = (route === 'admin' && !permissions.admin) ? 'dashboard' : (route === 'sifranti' && !permissions.sifranti) ? 'dashboard' : (route === 'dokumenti' && !permissions.dokumenti) ? 'dashboard' : route; return (
{safeRoute === 'dashboard' && } {safeRoute === 'sifranti' && permissions.sifranti && } {safeRoute === 'dokumenti' && permissions.dokumenti && } {safeRoute === 'admin' && permissions.admin && }
); } ReactDOM.createRoot(document.getElementById('root')).render( );