(async function () { const root = document.getElementById("app"); const user = await bootstrapAuth({ requireAuth: true, forbidIfMustChange: true }); if (!user) return; const state = { competitions: [], users: [] }; async function loadCompetitions() { state.competitions = await API.listCompetitions(); } async function loadUsers() { if (user.is_system_admin) state.users = await API.listUsers(); } function openCompetitionModal() { const backdrop = el("div", { class: "modal-backdrop", onclick: (e) => { if (e.target === backdrop) backdrop.remove(); } }); const nameInput = el("input", { type: "text" }); const allowInput = el("input", { type: "checkbox" }); backdrop.appendChild(el("div", { class: "modal" }, el("h3", null, t("new_competition")), el("div", { class: "field" }, el("label", null, t("competition_name")), nameInput), el("label", { class: "row" }, allowInput, " " + t("allow_any_scorer_edit")), el("div", { class: "row", style: { justifyContent: "flex-end", marginTop: "1rem" } }, el("button", { onclick: () => backdrop.remove() }, t("cancel")), el("button", { class: "primary", onclick: async () => { if (!nameInput.value.trim()) return; try { await API.createCompetition({ name: nameInput.value.trim(), allow_any_scorer_edit: allowInput.checked }); backdrop.remove(); await loadCompetitions(); render(); } catch (e) { alert(e.message); } } }, t("create")), ), )); document.body.appendChild(backdrop); } function openUserModal() { const backdrop = el("div", { class: "modal-backdrop", onclick: (e) => { if (e.target === backdrop) backdrop.remove(); } }); const username = el("input", { type: "text", oninput: (e) => { e.target.value = e.target.value.toLowerCase(); } }); const password = el("input", { type: "password" }); const displayName = el("input", { type: "text" }); const langSelect = el("select", null, ...I18N_AVAILABLE.map((l) => el("option", { value: l }, I18N_NAMES[l])) ); const isAdmin = el("input", { type: "checkbox" }); backdrop.appendChild(el("div", { class: "modal" }, el("h3", null, t("add_user")), el("div", { class: "field" }, el("label", null, t("username")), username), el("div", { class: "field" }, el("label", null, t("password")), password), el("div", { class: "field" }, el("label", null, t("display_name")), displayName), el("div", { class: "field" }, el("label", null, t("language")), langSelect), el("label", { class: "row" }, isAdmin, " " + t("is_admin")), el("div", { class: "row", style: { justifyContent: "flex-end", marginTop: "1rem" } }, el("button", { onclick: () => backdrop.remove() }, t("cancel")), el("button", { class: "primary", onclick: async () => { if (!username.value.trim() || !password.value) return; try { await API.createUser({ username: username.value.trim().toLowerCase(), password: password.value, display_name: displayName.value, language: langSelect.value, is_system_admin: isAdmin.checked, }); backdrop.remove(); await loadUsers(); render(); } catch (err) { alert(err.message); } } }, t("create")), ), )); document.body.appendChild(backdrop); } function renderUsersAdmin() { const card = el("div", { class: "card" }); card.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.5rem" } }, el("h2", { style: { margin: 0 } }, t("users")), el("button", { onclick: openUserModal }, t("add_user")), )); if (state.users.length === 0) { card.appendChild(el("div", { class: "muted" }, "—")); return card; } const table = el("table"); table.appendChild(el("thead", null, el("tr", null, el("th", null, t("username")), el("th", null, t("display_name")), el("th", null, t("language")), el("th", null, t("is_admin")), el("th", null, t("must_change_password")), el("th", null, t("actions")), ) )); const tbody = el("tbody"); for (const u of state.users) { const row = el("tr", null, el("td", null, u.username), el("td", null, u.display_name), el("td", null, I18N_NAMES[u.language] || u.language), el("td", null, u.is_system_admin ? t("yes") : t("no")), el("td", null, u.must_change_password ? el("span", { class: "badge warn" }, t("yes")) : t("no")), el("td", null, el("button", { class: "action-btn", onclick: () => openEditUserModal(u) }, t("edit")), !u.must_change_password && el("button", { class: "action-btn", onclick: async () => { if (!confirm(t("confirm_force_password"))) return; await API.updateUser(u.id, { must_change_password: true }); await loadUsers(); render(); } }, t("force_password_change")), u.id !== user.id && el("button", { class: "action-btn danger", onclick: async () => { if (!confirm(t("confirm_delete"))) return; await API.deleteUser(u.id); await loadUsers(); render(); } }, t("delete")), ), ); tbody.appendChild(row); } table.appendChild(tbody); card.appendChild(el("div", { class: "table-wrap" }, table)); return card; } function openEditUserModal(u) { const backdrop = el("div", { class: "modal-backdrop", onclick: (e) => { if (e.target === backdrop) backdrop.remove(); } }); const username = el("input", { type: "text", value: u.username, oninput: (e) => { e.target.value = e.target.value.toLowerCase(); } }); const displayName = el("input", { type: "text", value: u.display_name || "" }); const password = el("input", { type: "password", placeholder: t("leave_blank_keep") }); const isAdmin = el("input", { type: "checkbox", checked: !!u.is_system_admin }); backdrop.appendChild(el("div", { class: "modal" }, el("h3", null, t("edit") + ": " + u.username), el("div", { class: "field" }, el("label", null, t("username")), username), el("div", { class: "field" }, el("label", null, t("display_name")), displayName), el("div", { class: "field" }, el("label", null, t("new_password")), password), u.id !== user.id && el("label", { class: "row" }, isAdmin, " " + t("is_admin")), el("div", { class: "row", style: { justifyContent: "flex-end", marginTop: "1rem" } }, el("button", { onclick: () => backdrop.remove() }, t("cancel")), el("button", { class: "primary", onclick: async () => { const body = {}; const newUsername = username.value.trim().toLowerCase(); if (newUsername && newUsername !== u.username) body.username = newUsername; if (displayName.value !== (u.display_name || "")) body.display_name = displayName.value; if (password.value) body.password = password.value; if (u.id !== user.id && isAdmin.checked !== !!u.is_system_admin) body.is_system_admin = isAdmin.checked; if (Object.keys(body).length === 0) { backdrop.remove(); return; } try { await API.updateUser(u.id, body); backdrop.remove(); await loadUsers(); render(); } catch (e) { alert((e.data && e.data.error) || e.message); } } }, t("save")), ), )); document.body.appendChild(backdrop); } function render() { clearNode(root); root.appendChild(renderTopbar(user)); const container = el("div", { class: "container" }); container.appendChild(el("div", { class: "row", style: { justifyContent: "space-between", marginBottom: "0.75rem" } }, el("h2", { style: { margin: 0 } }, t("competitions")), user.is_system_admin && el("button", { class: "primary", onclick: openCompetitionModal }, t("new_competition")), )); if (state.competitions.length === 0) { container.appendChild(el("div", { class: "muted" }, t("no_competitions"))); } else { const grid = el("div", { class: "grid" }); for (const c of state.competitions) { grid.appendChild(el("div", { class: "card" }, el("h2", null, c.name), el("div", { class: "muted", style: { marginBottom: "0.5rem" } }, el("span", { class: "badge accent" }, t(c.role)), ), el("button", { class: "primary", onclick: () => navigate("competition", { id: c.id }) }, t("open")), )); } container.appendChild(grid); } if (user.is_system_admin) { container.appendChild(renderUsersAdmin()); } root.appendChild(container); } await loadCompetitions(); await loadUsers(); render(); })();