Files
einkaufsapp/web/html/js/views/settings.js
2026-08-09 18:44:21 +02:00

237 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Einstellungen des angemeldeten Kontos: Anzeigename, Passwort,
// Benachrichtigungen.
"use strict";
import { del, patch, post } from "../api.js";
import { el, mount } from "../dom.js";
import * as push from "../push.js";
import { set, state } from "../store.js";
import { appBar } from "../appbar.js";
function formatDate(iso) {
return new Date(iso).toLocaleDateString("de-DE", {
day: "2-digit", month: "2-digit", year: "numeric",
});
}
export async function settingsView(root, { back, admin }) {
let banner = null;
let pushStatus = null;
let devices = [];
async function reload() {
pushStatus = await push.status();
devices = pushStatus.serverEnabled
? await push.listDevices().catch(() => [])
: [];
}
function say(message, kind = "notice") {
banner = { message, kind };
render();
setTimeout(() => { banner = null; render(); }, 8000);
}
async function guarded(fn) {
try {
await fn();
await reload();
render();
} catch (err) {
say(err.message, "error");
}
}
// -------------------------------------------------------------------
// Benachrichtigungen
// -------------------------------------------------------------------
function pushSection() {
if (!pushStatus.serverEnabled) {
return el("section.card", {},
el("h2", {}, "Benachrichtigungen"),
el("p.lead", {},
"Auf diesem Server sind Push-Benachrichtigungen nicht eingerichtet. " +
"Dafür wird ein VAPID-Schlüsselpaar in der Konfiguration benötigt."));
}
// Auf iOS gibt es Push nur in der installierten App - das ist eine
// Einschränkung von Apple, kein Fehler der Anwendung.
if (pushStatus.needsInstall) {
return el("section.card", {},
el("h2", {}, "Benachrichtigungen"),
el("p.lead.warn", {},
"Auf iPhone und iPad funktionieren Benachrichtigungen nur, wenn die " +
"App über „Teilen → Zum Home-Bildschirm“ installiert wurde. " +
"Öffne sie danach über das Symbol auf dem Startbildschirm und " +
"komm hierher zurück."));
}
if (!pushStatus.supported) {
return el("section.card", {},
el("h2", {}, "Benachrichtigungen"),
el("p.lead", {},
"Dieser Browser unterstützt keine Push-Benachrichtigungen. " +
"Über eine unverschlüsselte Verbindung sind sie ebenfalls nicht " +
"möglich dann hilft der Zugriff über HTTPS."));
}
const blocked = pushStatus.permission === "denied";
return el("section.card", {},
el("h2", {}, "Benachrichtigungen"),
el("p.lead", {},
"Wenn jemand eine geteilte Liste ändert, bekommst du eine Meldung ",
el("strong", {}, `höchstens eine je Liste alle ${pushStatus.throttleHours} Stunden`),
". Wer im Laden steht und abhakt, erzeugt sonst im Minutentakt " +
"Benachrichtigungen."),
blocked
? el("p.lead.warn", {},
"Benachrichtigungen wurden für diese Seite abgelehnt. Das lässt " +
"sich nur in den Einstellungen des Browsers wieder ändern " +
"meist über das Schloss- oder Info-Symbol in der Adresszeile.")
: null,
pushStatus.subscribed
? el("div.menu-actions", {},
el("button", {
type: "button",
onclick: () => guarded(async () => {
await push.sendTest();
say("Testnachricht unterwegs. Kommt sie nicht an, steht der " +
"Grund im Log des api-Containers.");
}),
}, "Testnachricht senden"),
el("button.danger", {
type: "button",
onclick: () => guarded(async () => {
await push.unsubscribe();
say("Dieses Gerät bekommt keine Benachrichtigungen mehr.");
}),
}, "Auf diesem Gerät abschalten"))
: el("button.primary", {
type: "button",
disabled: blocked,
onclick: () => guarded(async () => {
const result = await push.subscribe();
if (!result.ok) {
say(result.reason, "error");
return;
}
say("Benachrichtigungen für dieses Gerät eingeschaltet.");
}),
}, "Auf diesem Gerät einschalten"),
devices.length
? el("div", {},
el("h3.device-heading", {}, "Angemeldete Geräte"),
el("ul.share-list", {}, devices.map((device) =>
el("li", {},
el("div.who-block", {},
el("span.name", {}, device.label || "Unbenanntes Gerät"),
el("span.sub", {},
`seit ${formatDate(device.created_at)}`,
device.last_success_at
? ` · zuletzt erreicht ${formatDate(device.last_success_at)}`
: " · noch nichts zugestellt")),
el("div.member-actions", {},
el("button.danger", {
type: "button",
onclick: () => guarded(() =>
del(`/api/push/subscriptions/${device.id}`)),
}, "Entfernen"))))))
: null
);
}
// -------------------------------------------------------------------
// Profil und Passwort
// -------------------------------------------------------------------
function profileSection() {
const nameField = el("input", {
type: "text",
value: state.user.display_name || "",
maxLength: 80,
placeholder: "wird anderen Mitgliedern angezeigt",
});
return el("section.card", {},
el("h2", {}, "Profil"),
el("p.lead", {}, state.user.email),
el("label", {}, "Anzeigename ", el("span.hint", {}, "(freiwillig)")),
el("div.row", {}, nameField,
el("button.primary", {
type: "button",
onclick: () => guarded(async () => {
const user = await patch("/api/auth/me", {
display_name: nameField.value.trim() || null,
});
set({ user });
say("Gespeichert.");
}),
}, "Speichern"))
);
}
function passwordSection() {
const current = el("input", { type: "password", autocomplete: "current-password" });
const next = el("input", { type: "password", autocomplete: "new-password" });
const repeat = el("input", { type: "password", autocomplete: "new-password" });
return el("section.card", {},
el("h2", {}, "Passwort ändern"),
el("p.lead", {},
"Nach der Änderung werden alle anderen Geräte abgemeldet."),
el("label", {}, "Bisheriges Passwort"), current,
el("label", {}, "Neues Passwort ", el("span.hint", {}, "(mindestens 12 Zeichen)")), next,
el("label", {}, "Wiederholen"), repeat,
el("button.primary", {
type: "button",
onclick: () => guarded(async () => {
if (next.value !== repeat.value) {
throw new Error("Die beiden Eingaben stimmen nicht überein.");
}
const result = await post("/api/auth/password/change", {
current_password: current.value,
new_password: next.value,
});
current.value = next.value = repeat.value = "";
say(result.detail);
}),
}, "Passwort ändern")
);
}
function render() {
mount(root,
appBar({
back: { label: "Listen", onClick: back },
title: "Einstellungen",
actions: state.user.is_admin
? [{ label: "Benutzerverwaltung", onClick: admin,
hint: "Konten, Einladungen, Bereinigung" }]
: [],
}),
banner ? el(banner.kind === "error" ? "p.error" : "p.notice", {}, banner.message) : null,
// Die Benutzerverwaltung steht im Menü der Kopfleiste - nur für
// Administratoren, und der Endpunkt prüft zusätzlich.
el("section.card", {},
el("h1", {}, state.user.display_name || state.user.email),
el("p.lead", {},
state.user.email,
state.user.is_admin ? " · Administrator" : "")),
pushSection(),
profileSection(),
passwordSection()
);
}
await reload();
render();
return render;
}