Verwaltung: eigens gestalteter Abmelde-Button rechts über der Hero-Kiste
Neuer Endpunkt POST /gate-logout (functions/_middleware.js) löscht alle drei Zugangs-Cookies auf einmal (normale 90-Tage-Sitzung, Rollen-Anzeige, tägliche Verwaltungs-Sitzung) — "abmelden" bedeutet also wirklich abmelden von allem, nicht nur einen Teil der Schranke offen lassen. Button im selben "besonderen" Look wie die Hero-Kiste darunter (wandernder Verlaufsrand in den drei Markenfarben + Funkeln) statt eines schlichten Standard-Buttons. Per curl (Cookie-Löschung + anschließende 302-Sperre auf /verwaltung/ und /) und per echtem Browser-Klicktest (Playwright) end-to-end verifiziert.
This commit is contained in:
@@ -44,6 +44,8 @@
|
|||||||
- API-Routen (/api/*) bekommen bei fehlender Sitzung eine 401-JSON-Antwort statt der
|
- API-Routen (/api/*) bekommen bei fehlender Sitzung eine 401-JSON-Antwort statt der
|
||||||
302-Weiterleitung zur Zugangscode-Seite — ein fetch() im Checkout/Verwaltung-Skript soll
|
302-Weiterleitung zur Zugangscode-Seite — ein fetch() im Checkout/Verwaltung-Skript soll
|
||||||
einen klaren Fehler sehen, nicht versehentlich HTML als Antwort bekommen.
|
einen klaren Fehler sehen, nicht versehentlich HTML als Antwort bekommen.
|
||||||
|
- POST /gate-logout (04.08.2026, Abmelde-Button auf /verwaltung/) löscht alle drei
|
||||||
|
Zugangs-Cookies auf einmal (normale Sitzung + Rollen-Anzeige + tägliche Verwaltungs-Sitzung).
|
||||||
|
|
||||||
Die Signatur (HMAC-SHA256 über Web Crypto, in jeder Workers/Pages-Runtime
|
Die Signatur (HMAC-SHA256 über Web Crypto, in jeder Workers/Pages-Runtime
|
||||||
eingebaut) verhindert, dass jemand sich einfach selbst ein
|
eingebaut) verhindert, dass jemand sich einfach selbst ein
|
||||||
@@ -161,6 +163,21 @@ async function handleVerwaltungAuth(request, env) {
|
|||||||
return new Response(JSON.stringify({ ok: true, next }), { status: 200, headers });
|
return new Response(JSON.stringify({ ok: true, next }), { status: 200, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Gegenstück zu handleAuth()/handleVerwaltungAuth() oben: löscht ALLE drei Zugangs-Cookies auf
|
||||||
|
einmal (normale 90-Tage-Sitzung, die Rollen-Anzeige dafür, UND die tägliche Verwaltungs-Sitzung)
|
||||||
|
— auf ausdrücklichen Wunsch (04.08.2026) als eigener Abmelde-Button auf /verwaltung/. Löscht
|
||||||
|
bewusst auch die Verwaltungs-Sitzung mit, nicht nur die normale, damit "abmelden" wirklich
|
||||||
|
"abmelden von allem" bedeutet und nicht nur einen Teil der Schranke offen lässt. */
|
||||||
|
function handleLogout() {
|
||||||
|
const headers = new Headers({ "Content-Type": "application/json" });
|
||||||
|
const loeschen = (name, httpOnly) =>
|
||||||
|
`${name}=; Path=/; Max-Age=0; ${httpOnly ? "HttpOnly; " : ""}Secure; SameSite=Lax`;
|
||||||
|
headers.append("Set-Cookie", loeschen(COOKIE_NAME, true));
|
||||||
|
headers.append("Set-Cookie", loeschen(ROLE_COOKIE_NAME, false));
|
||||||
|
headers.append("Set-Cookie", loeschen(VERWALTUNG_COOKIE_NAME, true));
|
||||||
|
return new Response(JSON.stringify({ ok: true }), { status: 200, headers });
|
||||||
|
}
|
||||||
|
|
||||||
export async function onRequest(context) {
|
export async function onRequest(context) {
|
||||||
const { request, next, env } = context;
|
const { request, next, env } = context;
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
@@ -170,6 +187,13 @@ export async function onRequest(context) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abmelden muss IMMER funktionieren — unabhängig von SITE_PUBLIC, Hostname oder ob die
|
||||||
|
// Sitzung technisch schon abgelaufen/ungültig ist. Deshalb ganz oben geprüft, vor jeder
|
||||||
|
// sonstigen Weiche unten.
|
||||||
|
if (url.pathname === "/gate-logout" && request.method === "POST") {
|
||||||
|
return handleLogout();
|
||||||
|
}
|
||||||
|
|
||||||
// Rechtstexte (Impressum, Datenschutz, AGB, Widerruf) bleiben IMMER ohne Zugangscode
|
// Rechtstexte (Impressum, Datenschutz, AGB, Widerruf) bleiben IMMER ohne Zugangscode
|
||||||
// erreichbar. Grund: PayPal prüft vor der Freischaltung von "Log in with PayPal" die
|
// erreichbar. Grund: PayPal prüft vor der Freischaltung von "Log in with PayPal" die
|
||||||
// angegebene Datenschutz- und AGB-Adresse tatsächlich nach — hinter der Zugangsschranke
|
// angegebene Datenschutz- und AGB-Adresse tatsächlich nach — hinter der Zugangsschranke
|
||||||
|
|||||||
@@ -50,6 +50,17 @@ const lang: Locale = "de";
|
|||||||
so, auch wenn der Rest der Seite mal öffentlich wird. Alles unten sind ECHTE
|
so, auch wenn der Rest der Seite mal öffentlich wird. Alles unten sind ECHTE
|
||||||
Bestellungen aus der Datenbank, live vom Checkout befüllt.
|
Bestellungen aus der Datenbank, live vom Checkout befüllt.
|
||||||
</p>
|
</p>
|
||||||
|
{/* Abmelde-Button (04.08.2026, auf ausdrücklichen Wunsch): rechts über der Hero-Kiste,
|
||||||
|
eigens "speziell" gestaltet (wandernder Verlaufsrand + Funkeln, siehe .verwaltung-logout-btn
|
||||||
|
in global.css) — passend zum Rest der Seite, statt eines schlichten Standard-Buttons.
|
||||||
|
Löscht beim Klick ALLE drei Zugangs-Cookies (normale Sitzung, Rollen-Anzeige, tägliche
|
||||||
|
Verwaltungs-Sitzung) über POST /gate-logout und springt danach zur Zugangscode-Seite. */}
|
||||||
|
<div class="verwaltung-topbar">
|
||||||
|
<button type="button" class="verwaltung-logout-btn" id="verwaltung-logout-btn">
|
||||||
|
<span class="verwaltung-logout-sparkle" aria-hidden="true">✨</span>
|
||||||
|
<span aria-hidden="true">🚪</span> Abmelden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="verwaltung-hero">
|
<div class="verwaltung-hero">
|
||||||
<span class="verwaltung-hero-sparkle verwaltung-hero-sparkle-1" aria-hidden="true">✨</span>
|
<span class="verwaltung-hero-sparkle verwaltung-hero-sparkle-1" aria-hidden="true">✨</span>
|
||||||
<span class="verwaltung-hero-sparkle verwaltung-hero-sparkle-2" aria-hidden="true">✨</span>
|
<span class="verwaltung-hero-sparkle verwaltung-hero-sparkle-2" aria-hidden="true">✨</span>
|
||||||
@@ -480,6 +491,18 @@ const lang: Locale = "de";
|
|||||||
});
|
});
|
||||||
aktualisiereToggleUI();
|
aktualisiereToggleUI();
|
||||||
|
|
||||||
|
// ── Abmelden: löscht serverseitig alle drei Zugangs-Cookies (siehe POST /gate-logout in
|
||||||
|
// functions/_middleware.js) und springt danach zur Zugangscode-Seite. Läuft bewusst OHNE
|
||||||
|
// Rückfragedialog (gleiches Verhalten wie der Logout-Button im Kundenkonto). ──
|
||||||
|
const logoutBtn = document.getElementById("verwaltung-logout-btn");
|
||||||
|
logoutBtn?.addEventListener("click", () => {
|
||||||
|
logoutBtn.disabled = true;
|
||||||
|
logoutBtn.textContent = "Wird abgemeldet …";
|
||||||
|
fetch("/gate-logout", { method: "POST" })
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => { window.location.href = "/gate"; });
|
||||||
|
});
|
||||||
|
|
||||||
// ── Meilenstein-Feier: Konfetti + Banner, sobald eine runde Bestell-Gesamtzahl erreicht wird
|
// ── Meilenstein-Feier: Konfetti + Banner, sobald eine runde Bestell-Gesamtzahl erreicht wird
|
||||||
// (10er-Schritte bis 100, 25er bis 500, danach 50er). Zählt ALLE Bestellungen (auch
|
// (10er-Schritte bis 100, 25er bis 500, danach 50er). Zählt ALLE Bestellungen (auch
|
||||||
// stornierte/noch unbezahlte) — es geht um "die wievielte Bestellung ist reingekommen",
|
// stornierte/noch unbezahlte) — es geht um "die wievielte Bestellung ist reingekommen",
|
||||||
|
|||||||
@@ -3453,6 +3453,52 @@ a:focus-visible, button:focus-visible {
|
|||||||
.verwaltung-box-chevron { transition: none; }
|
.verwaltung-box-chevron { transition: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Abmelde-Button (04.08.2026, auf ausdrücklichen Wunsch): sitzt in einer eigenen Zeile rechts
|
||||||
|
über der Hero-Kiste. Eigene Zeile statt einfach in die Hero-Kiste gepackt — damit er auch dann
|
||||||
|
sofort auffindbar bleibt, wenn die Kiste selbst mal umgebaut wird. */
|
||||||
|
.verwaltung-topbar { display: flex; justify-content: flex-end; margin-top: 0.8rem; }
|
||||||
|
|
||||||
|
/* Bewusst genauso "besonders" gestaltet wie die Hero-Kiste direkt darunter (wandernder
|
||||||
|
Verlaufsrand in denselben drei Markenfarben + Funkeln) statt eines schlichten Standard-Buttons
|
||||||
|
— auf ausdrücklichen Wunsch ("der Button soll sehr speziell sein"). */
|
||||||
|
.verwaltung-logout-btn {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.65rem 1.3rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1.5px solid transparent;
|
||||||
|
background:
|
||||||
|
linear-gradient(155deg, var(--c-anthracite-light), var(--c-anthracite)) padding-box,
|
||||||
|
linear-gradient(120deg, #5fd3ec, #9b7fd6, #e3b23c, #5fd3ec) border-box;
|
||||||
|
background-size: 100% 100%, 300% 100%;
|
||||||
|
animation: verwaltung-hero-shift 10s ease-in-out infinite;
|
||||||
|
color: var(--c-text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
|
||||||
|
}
|
||||||
|
.verwaltung-logout-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 8px 22px -8px color-mix(in srgb, #9b7fd6 55%, transparent);
|
||||||
|
}
|
||||||
|
.verwaltung-logout-btn:disabled { cursor: default; opacity: 0.75; transform: none; box-shadow: none; }
|
||||||
|
.verwaltung-logout-sparkle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.2rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: account-nav-sparkle-twinkle 2.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.verwaltung-logout-btn, .verwaltung-logout-sparkle { animation: none; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Hero-Kopf: dieselbe "besondere" Formsprache wie die anderen Premium-Karten der Seite
|
/* Hero-Kopf: dieselbe "besondere" Formsprache wie die anderen Premium-Karten der Seite
|
||||||
(wanderndes Verlaufslicht + pulsierender Rand + Funkeln), passend zu einer Seite, die "alles
|
(wanderndes Verlaufslicht + pulsierender Rand + Funkeln), passend zu einer Seite, die "alles
|
||||||
übertrifft" — nur dezenter/edler gehalten, weil es ein Arbeits-Dashboard ist, kein Verkaufs-
|
übertrifft" — nur dezenter/edler gehalten, weil es ein Arbeits-Dashboard ist, kein Verkaufs-
|
||||||
|
|||||||
Reference in New Issue
Block a user