File size: 5,421 Bytes
a7ffb22
66fd359
 
a7ffb22
 
66fd359
a7ffb22
66fd359
 
 
 
a7ffb22
 
66fd359
 
 
a7ffb22
 
 
 
b6f5bc4
a7ffb22
66fd359
a7ffb22
 
 
66fd359
a7ffb22
66fd359
a7ffb22
 
 
66fd359
 
 
 
 
 
 
 
 
 
 
 
 
a7ffb22
 
 
 
 
 
66fd359
a7ffb22
 
 
b6f5bc4
a7ffb22
 
 
 
 
b6f5bc4
 
a7ffb22
b6f5bc4
 
a7ffb22
 
 
b6f5bc4
a7ffb22
b6f5bc4
a7ffb22
 
b6f5bc4
a7ffb22
66fd359
 
 
 
 
 
 
a7ffb22
66fd359
 
 
 
a7ffb22
 
66fd359
 
a7ffb22
66fd359
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// App shell: the sign-in page, then the home of the role the person chose. A person with several roles gets a chip for each.
import { api, ApiError } from "./api.js";
import { esc } from "./math.js";
import { ROLE_NAMES, visibleRoles } from "./roles.js";
import { clearRoute, leaveScreen } from "./router.js";
import * as session from "./session.js";
import { owl } from "./sky.js";

const CFG = JSON.parse(document.getElementById("config").textContent);
const app = document.getElementById("app");
const nav = document.getElementById("nav");
const ROLE_KEY = "mathly2.role";
const SCREENS = { student: "./student.js", teacher: "./teacher.js", manager: "./manager.js", admin: "./admin.js", developer: "./developer.js" };

export const view = html => { app.innerHTML = html; };

const remembered = () => { try { return localStorage.getItem(ROLE_KEY) || ""; } catch { return ""; } };
const remember = role => { try { localStorage.setItem(ROLE_KEY, role); } catch { /* private window */ } };

function showSignIn(message = "", canRetry = false) {
  opened++;                                    // a role screen still loading must not draw over the sign-in page
  leaveScreen();
  nav.innerHTML = "";
  view(`<section class="signin" id="signin"><img class="logo" src="/static/logo.svg" alt="לומדים בכיף" width="489" height="225">${owl(72)}
    <p class="status ${message ? "error" : ""}" ${message ? 'role="alert"' : ""}>${esc(message || "יש להיכנס כדי לפתוח את מסלול המתמטיקה.")}</p>
    ${canRetry ? `<p><button type="button" class="primary" id="retry">נסו שוב</button></p>` : ""}
    ${CFG.devSignIn
      ? `<p class="hint">כניסת פיתוח (dev only): ללא Supabase, כל כתובת מייל מתקבלת.</p>
         <form id="dev"><label for="email">מייל</label><input id="email" type="email" required autocomplete="email" dir="ltr">
         <p><button class="pill" type="submit">כניסה</button></p></form>`
      : canRetry ? "" : `<p><button class="pill" id="google" type="button">כניסה עם Google</button></p>`}</section>`);
  document.getElementById("retry")?.addEventListener("click", () => boot());
  document.getElementById("google")?.addEventListener("click", () => session.signInWithGoogle());
  document.getElementById("dev")?.addEventListener("submit", event => {
    event.preventDefault();
    session.signInDev(document.getElementById("email").value);
    boot();
  });
}

async function signOutAndShow(message) {
  await session.signOut();
  showSignIn(message);
}

function header(me, current) {
  const roles = visibleRoles(me.roles);
  nav.innerHTML = `<span class="muted" dir="ltr">${esc(me.email)}</span>
    ${roles.length > 1 ? `<span class="roles" role="group" aria-label="תפקיד">${roles.map(role =>
      `<button type="button" data-role="${role}" aria-pressed="${role === current}">${ROLE_NAMES[role]}</button>`).join("")}</span>` : ""}
    <button type="button" id="access">ההרשאות שלי</button> <button type="button" id="out">יציאה</button>`;
  document.getElementById("out").addEventListener("click", () => signOutAndShow());
  document.getElementById("access").addEventListener("click", async () => {
    clearRoute();
    nav.querySelectorAll("[data-role]").forEach(b => b.setAttribute("aria-pressed", "false"));
    ++opened;                                    // a role screen still loading must not draw over this one
    await (await import("./access.js")).show(me, view);
  });
  nav.querySelectorAll("[data-role]").forEach(button => button.addEventListener("click", () => open(me, button.dataset.role)));
}

let opened = 0;   // which role screen is open: a screen opened earlier never draws over a later one (its answers may come late)

async function open(me, role, { keepAddress = false } = {}) {
  const mine = ++opened;
  const draw = html => { if (mine === opened) view(html); };
  remember(role);
  clearRoute({ keepAddress });
  header(me, role);
  draw(`<section class="card"><p class="muted">טוען…</p></section>`);
  try {
    await (await import(SCREENS[role])).show(me, draw);
  } catch (error) {
    if (error instanceof ApiError && error.status === 401) return signOutAndShow("ההתחברות פגה, יש להיכנס שוב");
    draw(`<section class="card"><p class="error" role="alert">${esc(error.message || "משהו השתבש")}</p></section>`);
  }
}

async function boot() {
  view(`<section class="card"><p class="muted">טוען…</p></section>`);
  try {
    await session.init(CFG);
  } catch {
    return showSignIn("לא הצלחנו להתחבר לשירות ההתחברות. בדקו את החיבור לרשת ונסו שוב.", true);
  }
  if (!await session.token()) return showSignIn();
  try {
    const me = await api("/api/me");
    const roles = visibleRoles(me.roles);
    await open(me, roles.includes(remembered()) ? remembered() : roles[0], { keepAddress: true });   // the first screen honours a link
  } catch (error) {
    if (error instanceof ApiError && error.status === 401) return signOutAndShow("ההתחברות פגה, יש להיכנס שוב");
    if (error instanceof ApiError && error.status === 503) return showSignIn("שירות ההתחברות לא זמין כרגע.", true);
    view(`<section class="card"><p class="error" role="alert">${esc(error.message || "משהו השתבש")}</p></section>`);
  }
}

boot();