/* ============================================================ ZENITH — App root: routing, roles, language, tweaks ============================================================ */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "lang": "en", "accent": "#1456C7", "zStyle": "gauge", "examStrict": true, "density": "regular" }/*EDITMODE-END*/; const NAV = { student: [ { id: "dashboard", en: "Dashboard", si: "උපකරණ පුවරුව", icon: "squares-four" }, { id: "exams", en: "My exams", si: "මගේ විභාග", icon: "exam" }, { id: "results", en: "Results & Z-score", si: "ප්රතිඵල සහ Z", icon: "chart-line-up" }, { id: "billing", en: "Billing", si: "ගෙවීම්", icon: "credit-card" }, ], teacher: [ { id: "queue", en: "Marking queue", si: "ලකුණු පෝලිම", icon: "stack" }, ], admin: [ { id: "overview", en: "Overview", si: "දළ විශ්ලේෂණය", icon: "gauge" }, { id: "streams", en: "Streams", si: "විෂය ධාරා", icon: "graduation-cap" }, { id: "teachers", en: "Teachers", si: "ගුරුවරු", icon: "chalkboard-teacher" }, { id: "backlog", en: "Marking list", si: "ලකුණු ලැයිස්තුව", icon: "list-checks" }, { id: "physical", en: "Physical scripts", si: "භෞතික පත්ර", icon: "printer" }, { id: "batch", en: "Batch & Release", si: "කාණ්ඩ හා නිකුතුව", icon: "upload-simple" }, { id: "payments", en: "Payments", si: "ගෙවීම්", icon: "currency-circle-dollar" }, ], }; const HOME = { student: "dashboard", teacher: "queue", admin: "overview" }; const TITLES = { dashboard: { en: "Dashboard", si: "උපකරණ පුවරුව" }, exams: { en: "My exams", si: "මගේ විභාග" }, results: { en: "Results & predicted Z-score", si: "ප්රතිඵල සහ පුරෝකථන Z" }, queue: { en: "Marking queue", si: "ලකුණු කිරීමේ පෝලිම" }, overview: { en: "Institute overview", si: "ආයතන දළ විශ්ලේෂණය" }, streams: { en: "Streams & subjects", si: "විෂය ධාරා හා විෂයයන්" }, teachers: { en: "Teachers & evaluators", si: "ගුරුවරු හා ඇගයීම්කරුවන්" }, backlog: { en: "Marking list", si: "ලකුණු කිරීමේ ලැයිස්තුව" }, physical: { en: "Physical script tracking", si: "භෞතික පත්ර ලුහුබැඳීම" }, batch: { en: "Batch upload & scheduled release", si: "කාණ්ඩ උඩුගත හා නිකුතුව" }, billing: { en: "Billing & payments", si: "ගෙවීම් හා ගිණුම්" }, payments: { en: "Payments & revenue", si: "ගෙවීම් හා ආදායම" }, }; const ROLE_SUB = { student: { en: "Combined Mathematics · A/L 2025", si: "සංයුක්ත ගණිතය · උ.පෙ 2025" }, teacher: { en: "Mr. R. Wijesinghe · Combined Maths", si: "ආර්. විජේසිංහ මයා · සංයුක්ත ගණිතය" }, admin: { en: "Open Ai Pvt Ltd · Colombo", si: "Open Ai Pvt Ltd · කොළඹ" }, }; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const lang = t.lang; const T = (en, si) => tx(lang, en, si); const [auth, setAuth] = useState(false); // false = logged out const [authMode, setAuthMode] = useState(() => (typeof location !== "undefined" && location.hash === "#admin") ? "admin" : "main"); const [role, setRoleRaw] = useState("student"); const [view, setView] = useState("dashboard"); const [exam, setExam] = useState(null); // {examId, paper, stage, result} const [script, setScript] = useState(null); // teacher: open script const [paidExams, setPaidExams] = useState(() => new Set((window.ZDATA.billing && window.ZDATA.billing.paidExams) || [])); const [authCfg, setAuthCfg] = useState(null); // {mode, googleClientId} — null until loaded const [needsOnboarding, setNeedsOnboarding] = useState(false); const [googleErr, setGoogleErr] = useState(null); const [googleBusy, setGoogleBusy] = useState(false); const [me, setMe] = useState(null); // signed-in user {name, picture} (google mode) const isGoogle = authCfg && authCfg.mode === "google"; // keep #admin path in sync useEffect(() => { const onHash = () => setAuthMode(location.hash === "#admin" ? "admin" : "main"); window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); // load the auth mode; in google mode, resume an existing session on refresh useEffect(() => { (async () => { let cfg = { mode: "dev", googleClientId: null }; try { cfg = await window.ZAPI.authConfig(); } catch (e) { /* backend down → dev fallback */ } if (cfg.mode === "google") { try { const s = await window.ZAPI.authSession(); if (s.authenticated) { setMe({ name: s.name, picture: s.picture }); if (s.needsOnboarding) { setRoleRaw("student"); setNeedsOnboarding(true); } else { await bootAuthed(s.role); } } } catch (e) { /* not signed in */ } } setAuthCfg(cfg); })(); }, []); const goAdmin = () => { location.hash = "#admin"; setAuthMode("admin"); }; const goMain = () => { if (location.hash) location.hash = ""; setAuthMode("main"); }; const goSignup = () => setAuthMode("signup"); // pick the dev identity for the backend, then (re)load that role's data // `who` overrides the default actor (e.g. { systemId: "SX-…" } after a real signup, // or { systemId: "", email } so the backend resolves a self-signed-up student by email) const reloadData = async (r, who) => { window.ZAPI.setActor(r, who); await window.ZDATA_load(r); setPaidExams(new Set((window.ZDATA.billing && window.ZDATA.billing.paidExams) || [])); }; const login = async (r, who) => { await reloadData(r, who); setRoleRaw(r); setView(HOME[r]); setScript(null); setExam(null); setAuth(true); }; // google mode: the session cookie identifies the actor, so just load + enter the app for that role const bootAuthed = async (r) => { await window.ZDATA_load(r); setPaidExams(new Set((window.ZDATA.billing && window.ZDATA.billing.paidExams) || [])); setRoleRaw(r); setView(HOME[r]); setScript(null); setExam(null); setNeedsOnboarding(false); setAuth(true); }; const onGoogleCredential = async (credential) => { setGoogleErr(null); setGoogleBusy(true); try { const res = await window.ZAPI.googleSignIn(credential); setMe({ name: res.name, picture: res.picture }); if (res.needsOnboarding) { setRoleRaw("student"); setNeedsOnboarding(true); } else { await bootAuthed(res.role); } } catch (e) { console.error("google sign-in failed", e); setGoogleErr(T("Sign-in failed — please try again.", "පිවිසීම අසාර්ථකයි — නැවත උත්සාහ කරන්න.")); } finally { setGoogleBusy(false); } }; const signOut = async () => { if (isGoogle) { try { await window.ZAPI.logout(); } catch (e) { /* ignore */ } } setAuth(false); setExam(null); setScript(null); setNeedsOnboarding(false); setMe(null); }; // apply accent + density to document useEffect(() => { document.documentElement.style.setProperty("--blue", t.accent); const dark = shade(t.accent, -0.28); document.documentElement.style.setProperty("--blue-dark", dark); }, [t.accent]); // ---- still resolving which auth mode the backend uses ---- if (authCfg === null) { return (