/* ============================================================
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 (
{}} />
⌘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(
);