Wizard: User erst beim Abschliessen, Reihenfolge Sprache/Design/Modus, Reset im Einzelmodus
Image bauen und in die Registry schieben / build (push) Successful in 6s
Image bauen und in die Registry schieben / build (push) Successful in 6s
- Benutzer wird erst beim 'Einrichtung abschliessen' angelegt (POST /api/register im wizardFinish); bis dahin frei umentscheidbar, im Modus- Schritt Hinweis + Live-Validierung (Finish deaktiviert bis ausgefuellt) - Reihenfolge: Sprache -> Darstellung (Theme/OLED/Akzent) -> Modus (Einzel/Mehrbenutzer); Onboarding bleibt Sprache -> Darstellung - Sprach-Schritt: Repo-Link + Kurztext zum Starten (nutzt den Raum) - Wizard-Modal: min-height statt fixer Hoehe -> Modus-Schritt mit Formular wird nicht mehr abgeschnitten - Server-Reset auch im Einzelmodus moeglich (nur Bestaetigungstext, kein Passwort noetig); Reset-Sektion erscheint dann entsprechend - Tests: 167 Checks gruen (39+18+52+32+26)
This commit is contained in:
1 parent
655316278a
commit
135425c781
6 files changed
+141
-108
No files matched your search
@@ -158,6 +158,8 @@ const LANG = {
|
||||
prev_week: "Vorige Woche", next_week: "Nächste Woche", week_of: "Woche vom",
|
||||
wizard_done: "Alles erledigt - die App ist bereit.",
|
||||
wizard_user_created: "Benutzer '{u}' angelegt.",
|
||||
wizard_lang_hint: "Bereit loszulegen? Anleitung, Demo-Daten und der Übungsplan stehen im Repo:",
|
||||
wizard_user_later: "Der Benutzer wird beim Abschließen angelegt - du kannst dich im Wizard bis dahin umentscheiden.",
|
||||
logged_in: "Angemeldet.", user_created_toast: "Benutzer angelegt.",
|
||||
app_load_fail: "App konnte nicht geladen werden: {m}",
|
||||
},
|
||||
@@ -273,6 +275,8 @@ const LANG = {
|
||||
prev_week: "Previous week", next_week: "Next week", week_of: "Week of",
|
||||
wizard_done: "All done - the app is ready.",
|
||||
wizard_user_created: "User '{u}' created.",
|
||||
wizard_lang_hint: "Ready to get started? The guide, demo data and the exercise plan are in the repo:",
|
||||
wizard_user_later: "The user will be created when you finish - you can still change your mind until then.",
|
||||
logged_in: "Logged in.", user_created_toast: "User created.",
|
||||
app_load_fail: "Could not load the app: {m}",
|
||||
},
|
||||
@@ -1649,17 +1653,19 @@ function switchView(name) {
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
const wizard = { mode: "setup", step: 0, multi: null, lang: null,
|
||||
theme: null, oled: false, accent: null, userCreated: false };
|
||||
theme: null, oled: false, accent: null,
|
||||
regUser: null, regPass: null };
|
||||
|
||||
function renderSetupWizard(mode) {
|
||||
wizard.mode = mode;
|
||||
wizard.step = 0; // Sprache zuerst
|
||||
wizard.step = 0; // Reihenfolge: Sprache -> Darstellung -> Modus
|
||||
wizard.multi = null;
|
||||
wizard.lang = (app.settings && app.settings.language) || "de";
|
||||
wizard.theme = (app.settings && app.settings.theme) || "dark";
|
||||
wizard.oled = !!(app.settings && app.settings.oled);
|
||||
wizard.accent = (app.settings && app.settings.accent) || "white";
|
||||
wizard.userCreated = false;
|
||||
wizard.regUser = null;
|
||||
wizard.regPass = null;
|
||||
document.body.classList.add("no-auth");
|
||||
$("#setup-gate").hidden = false;
|
||||
renderWizardStep();
|
||||
@@ -1698,11 +1704,11 @@ function previewWizardAppearance() {
|
||||
|
||||
function wizardStepValid() {
|
||||
if (wizard.step === 0) return !!wizard.lang;
|
||||
if (wizard.mode === "setup" && wizard.step === 1) {
|
||||
if (wizard.multi === null) return false;
|
||||
return wizard.multi === false || wizard.userCreated;
|
||||
}
|
||||
return !!wizard.theme && !!wizard.accent;
|
||||
if (wizard.step === 1) return !!wizard.theme && !!wizard.accent;
|
||||
// Schritt 2 (nur Setup): Modus
|
||||
if (wizard.multi === null) return false;
|
||||
if (wizard.multi === false) return true;
|
||||
return !!(wizard.regUser && wizard.regPass);
|
||||
}
|
||||
|
||||
function wizardMaxStep() {
|
||||
@@ -1731,7 +1737,7 @@ function renderWizardStep() {
|
||||
const body = el("div", "wizard-body");
|
||||
|
||||
if (wizard.step === 0) {
|
||||
// Schritt 0: Sprache (zuerst)
|
||||
// Schritt 0: Sprache
|
||||
body.appendChild(el("h3", "wizard-title", t("lang_step")));
|
||||
if (wizard.mode === "setup") {
|
||||
body.appendChild(el("p", "auth-sub", t("lang_server")));
|
||||
@@ -1748,48 +1754,14 @@ function renderWizardStep() {
|
||||
row.appendChild(b);
|
||||
}
|
||||
body.appendChild(row);
|
||||
} else if (wizard.step === 1 && wizard.mode === "setup") {
|
||||
// Schritt 1: Einzel- oder Mehrbenutzermodus
|
||||
body.appendChild(el("p", "auth-sub", t("welcome")));
|
||||
const grid = el("div", "setup-options");
|
||||
const multi = setupOption(t("multi_mode"), t("multi_desc"), wizard.multi === true);
|
||||
multi.addEventListener("click", () => {
|
||||
wizard.multi = true;
|
||||
renderWizardStep();
|
||||
});
|
||||
grid.appendChild(multi);
|
||||
const solo = setupOption(t("solo_mode"), t("solo_desc"), wizard.multi === false);
|
||||
if (wizard.userCreated) solo.disabled = true;
|
||||
solo.addEventListener("click", () => {
|
||||
wizard.multi = false;
|
||||
renderWizardStep();
|
||||
});
|
||||
grid.appendChild(solo);
|
||||
body.appendChild(grid);
|
||||
if (wizard.multi === true) {
|
||||
if (wizard.userCreated && app.me && app.me.username) {
|
||||
const ok = el("div", "auth-register",
|
||||
t("wizard_user_created", { u: app.me.username }));
|
||||
body.appendChild(ok);
|
||||
} else {
|
||||
const form = el("form", "auth-form");
|
||||
form.appendChild(authField(t("username"), "setup-user"));
|
||||
form.appendChild(authField(t("password"), "setup-pass"));
|
||||
const err = el("div", "auth-error", "");
|
||||
err.hidden = true;
|
||||
const submit = el("button", "btn btn-primary btn-block", t("first_user"));
|
||||
submit.type = "submit";
|
||||
form.appendChild(submit);
|
||||
form.appendChild(err);
|
||||
form.addEventListener("submit", ev => {
|
||||
ev.preventDefault();
|
||||
wizardRegister($("#setup-user").value, $("#setup-pass").value, err);
|
||||
});
|
||||
body.appendChild(form);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Letzter Schritt: Darstellung (Theme + OLED + Akzent)
|
||||
body.appendChild(el("p", "auth-sub", t("wizard_lang_hint")));
|
||||
const repo = el("a", "btn", "git.somantis.net/J4stMarkus/trainingsplan");
|
||||
repo.href = "https://git.somantis.net/J4stMarkus/trainingsplan";
|
||||
repo.target = "_blank";
|
||||
repo.rel = "noopener";
|
||||
body.appendChild(repo);
|
||||
} else if (wizard.step === 1) {
|
||||
// Schritt 1: Darstellung (Theme + OLED + Akzent)
|
||||
body.appendChild(el("h3", "wizard-title", t("appearance")));
|
||||
body.appendChild(el("p", "auth-sub", t("appearance_desc")));
|
||||
body.appendChild(el("div", "picker-label", t("theme_label")));
|
||||
@@ -1822,6 +1794,44 @@ function renderWizardStep() {
|
||||
accentRow.appendChild(sw);
|
||||
}
|
||||
body.appendChild(accentRow);
|
||||
} else {
|
||||
// Schritt 2 (nur Setup): Einzel- oder Mehrbenutzermodus
|
||||
body.appendChild(el("p", "auth-sub", t("welcome")));
|
||||
const grid = el("div", "setup-options");
|
||||
const multi = setupOption(t("multi_mode"), t("multi_desc"), wizard.multi === true);
|
||||
multi.addEventListener("click", () => {
|
||||
wizard.multi = true;
|
||||
renderWizardStep();
|
||||
});
|
||||
grid.appendChild(multi);
|
||||
const solo = setupOption(t("solo_mode"), t("solo_desc"), wizard.multi === false);
|
||||
solo.addEventListener("click", () => {
|
||||
wizard.multi = false;
|
||||
renderWizardStep();
|
||||
});
|
||||
grid.appendChild(solo);
|
||||
body.appendChild(grid);
|
||||
if (wizard.multi === true) {
|
||||
const form = el("form", "auth-form");
|
||||
form.appendChild(authField(t("username"), "setup-user"));
|
||||
form.appendChild(authField(t("password"), "setup-pass"));
|
||||
form.appendChild(el("div", "auth-register", t("wizard_user_later")));
|
||||
form.addEventListener("submit", ev => ev.preventDefault());
|
||||
body.appendChild(form);
|
||||
// Felder in wizard-Zustand schreiben; Finish live aktivieren
|
||||
const uInput = form.querySelector("#setup-user");
|
||||
const pInput = form.querySelector("#setup-pass");
|
||||
uInput.value = wizard.regUser || "";
|
||||
pInput.value = wizard.regPass || "";
|
||||
const sync = () => {
|
||||
wizard.regUser = uInput.value.trim();
|
||||
wizard.regPass = pInput.value.trim();
|
||||
const fin = box.querySelector(".wizard-nav .btn-primary");
|
||||
if (fin) fin.disabled = !wizardStepValid();
|
||||
};
|
||||
uInput.addEventListener("input", sync);
|
||||
pInput.addEventListener("input", sync);
|
||||
}
|
||||
}
|
||||
|
||||
box.appendChild(body);
|
||||
@@ -1863,8 +1873,27 @@ function renderWizardStep() {
|
||||
async function wizardFinish() {
|
||||
const patch = {
|
||||
theme: wizard.theme, oled: wizard.oled, accent: wizard.accent,
|
||||
language: wizard.lang, setupDone: true,
|
||||
language: wizard.lang,
|
||||
};
|
||||
if (wizard.mode === "setup") {
|
||||
patch.setupDone = true;
|
||||
if (wizard.multi) {
|
||||
try {
|
||||
const r = await api("/api/register", jsonOpts("POST", {
|
||||
username: wizard.regUser, password: wizard.regPass,
|
||||
}));
|
||||
localStorage.setItem("tp_token", r.token);
|
||||
app.me = { username: r.username, is_owner: true };
|
||||
} catch (e) {
|
||||
if (e.status === 403) {
|
||||
setTimeout(() => afterAuth(), 600);
|
||||
} else {
|
||||
flashError(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (app.me && app.me.username) patch.onboarded = true;
|
||||
try {
|
||||
app.settings = await api("/api/settings", jsonOpts("PUT", patch));
|
||||
@@ -1875,29 +1904,6 @@ async function wizardFinish() {
|
||||
await afterAuth();
|
||||
}
|
||||
|
||||
async function wizardRegister(username, password, errEl) {
|
||||
try {
|
||||
const r = await api("/api/register", jsonOpts("POST", {
|
||||
username, password,
|
||||
}));
|
||||
localStorage.setItem("tp_token", r.token);
|
||||
app.me = { username: r.username, is_owner: true };
|
||||
wizard.userCreated = true;
|
||||
renderWizardStep();
|
||||
} catch (e) {
|
||||
if (e.status === 403) {
|
||||
// Ein Benutzer existiert inzwischen (z.B. zweiter Tab): neu laden
|
||||
if (errEl) errEl.textContent = eMsg(e);
|
||||
setTimeout(() => afterAuth(), 600);
|
||||
} else if (errEl) {
|
||||
errEl.hidden = false;
|
||||
errEl.textContent = eMsg(e);
|
||||
} else {
|
||||
flashError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
Anmeldung (Auth-Gate)
|
||||
------------------------------------------------------------------------- */
|
||||
@@ -2113,7 +2119,7 @@ function renderSettingsView() {
|
||||
if (app.me && (!app.me.username || app.me.is_owner)) {
|
||||
view.appendChild(renderUsersSection());
|
||||
}
|
||||
if (app.me && app.me.is_owner) {
|
||||
if (app.me && (!app.me.username || app.me.is_owner)) {
|
||||
view.appendChild(renderResetSection());
|
||||
}
|
||||
}
|
||||
@@ -2312,9 +2318,12 @@ function renderResetSection() {
|
||||
const body = el("div", "data-body");
|
||||
body.appendChild(el("p", "", t("reset_server_desc")));
|
||||
const row = el("div", "form-row");
|
||||
const pw = el("input", "file-input");
|
||||
pw.type = "password";
|
||||
pw.placeholder = t("current_pw");
|
||||
let pw = null;
|
||||
if (app.me.username) {
|
||||
pw = el("input", "file-input");
|
||||
pw.type = "password";
|
||||
pw.placeholder = t("current_pw");
|
||||
}
|
||||
const conf = el("input", "file-input");
|
||||
conf.type = "text";
|
||||
conf.placeholder = t("reset_confirm_hint");
|
||||
@@ -2322,14 +2331,15 @@ function renderResetSection() {
|
||||
btn.type = "button";
|
||||
btn.addEventListener("click", async () => {
|
||||
try {
|
||||
const body_pw = pw ? pw.value : "";
|
||||
await api("/api/reset", jsonOpts("POST", {
|
||||
password: pw.value, confirm: conf.value.trim(),
|
||||
password: body_pw, confirm: conf.value.trim(),
|
||||
}));
|
||||
toast(t("reset_done"), "accent");
|
||||
await afterAuth();
|
||||
} catch (e) { flashError(e); }
|
||||
});
|
||||
row.appendChild(pw);
|
||||
if (pw) row.appendChild(pw);
|
||||
row.appendChild(conf);
|
||||
row.appendChild(btn);
|
||||
body.appendChild(row);
|
||||
|
||||
Reference in new issue
Block a user