Kundenansicht neu P1: weiße Seite, Manifest und leere Ansichten für P2 und P3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7595844170
commit
142aa6e9d6
15 files changed
+204
-24
No files matched your search
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeBestellen(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Abo bestellen (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeChat(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Chat (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeFunktionen(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Funktionen (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeGutschrift(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Übernahmegutschrift (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigePasswort(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Passwort ändern (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeVertraege(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Verträge (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeWebsite(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Website (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */
|
||||
|
||||
import { h } from './dom.js'
|
||||
|
||||
export async function zeigeWechsel(wurzel, { route, me, nav }) {
|
||||
wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Abo wechseln (folgt)'))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Kundenansicht neu: Stile der Ansichten Website, Chat, Verträge und der Etagen Passwort, Gutschrift, Wechsel, Bestellen.
|
||||
Besitzer: P2 (danach hängt P7 einen Abschnitt für die Bestellseiten an). Hier steht bis dahin nichts. */
|
||||
+77
-21
@@ -2,31 +2,87 @@
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
||||
<meta name="kn-seite" content="neu">
|
||||
<title>Kundenbereich – WEBklar</title>
|
||||
<style>
|
||||
body.kn { margin: 0; background: #fff; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
|
||||
body.kn main { max-width: 640px; margin: 0 auto; padding: 48px 16px; }
|
||||
body.kn h1 { margin: 0 0 8px; font-size: 22px; font-weight: 600; }
|
||||
body.kn p { margin: 0 0 8px; color: #5e6678; }
|
||||
</style>
|
||||
<!-- Weiße Kundenansicht (Pilot): Als App auf den Homescreen, helle Statusleiste -->
|
||||
<link rel="manifest" href="/neu/manifest.webmanifest">
|
||||
<link rel="icon" href="/favicon.ico" sizes="32x32">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="WEBklar">
|
||||
|
||||
<link rel="stylesheet" href="/neu/tokens.css">
|
||||
<link rel="stylesheet" href="/neu/basis.css">
|
||||
<link rel="stylesheet" href="/neu/huelle.css">
|
||||
<link rel="stylesheet" href="/neu/ansichten.css">
|
||||
<link rel="stylesheet" href="/neu/funktionen.css">
|
||||
<link rel="stylesheet" href="/neu/module.css">
|
||||
</head>
|
||||
<body class="kn">
|
||||
<main data-kn-platzhalter>
|
||||
<h1>Kundenansicht neu</h1>
|
||||
<p>Platzhalter bis P1</p>
|
||||
<p>Ansicht: <span id="kn-platzhalter-ansicht"></span></p>
|
||||
<a class="kn-skip" href="#kn-inhalt">Zum Inhalt</a>
|
||||
<div id="kn-banner" class="kn-banner" hidden></div>
|
||||
|
||||
<div class="kn-huelle">
|
||||
<nav id="kn-menue" class="kn-menue" aria-label="Hauptnavigation"></nav>
|
||||
|
||||
<div class="kn-spalte">
|
||||
<header id="kn-kopf" class="kn-kopf">
|
||||
<p id="kn-kopf-titel" class="kn-kopf-titel"></p>
|
||||
<div id="kn-kopf-konto"></div>
|
||||
</header>
|
||||
<div id="kn-streifen" class="kn-streifen" hidden></div>
|
||||
<p id="kn-fehler" class="kn-fehler" role="alert" hidden></p>
|
||||
|
||||
<main id="kn-inhalt" class="kn-haupt" tabindex="-1">
|
||||
<section id="bereich-website" hidden>
|
||||
<h1 class="kn-titel">Website</h1>
|
||||
<div id="website-body"></div>
|
||||
</section>
|
||||
<section id="bereich-funktionen" hidden>
|
||||
<h1 class="kn-titel">Funktionen</h1>
|
||||
<div id="funktionen-body"></div>
|
||||
</section>
|
||||
<section id="bereich-chat" hidden>
|
||||
<h1 class="kn-titel">Chat</h1>
|
||||
<div id="chat-body"></div>
|
||||
</section>
|
||||
<section id="bereich-vertraege" hidden>
|
||||
<h1 class="kn-titel">Verträge</h1>
|
||||
<div id="vertraege-body"></div>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
(function () {
|
||||
var ziel = document.getElementById('kn-platzhalter-ansicht')
|
||||
function zeige(text) { ziel.textContent = text }
|
||||
fetch('/api/auth/me', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json() })
|
||||
.then(function (me) { zeige(me && me.authenticated === false ? 'abgemeldet' : (me && typeof me.ansicht === 'string' ? me.ansicht : '')) })
|
||||
.catch(function () { zeige('Fehler') })
|
||||
})()
|
||||
</script>
|
||||
|
||||
<div id="kn-blatt" class="kn-blatt" hidden>
|
||||
<nav id="kn-brot" class="kn-brot" aria-label="Brotkrume"></nav>
|
||||
<h1 id="kn-etage-titel" class="kn-titel"></h1>
|
||||
<div id="kn-reiter-slot"></div>
|
||||
<section data-etage="news" hidden><div id="news-body"></div></section>
|
||||
<section data-etage="termine" hidden><div id="termine-body"></div></section>
|
||||
<section data-etage="webshop" hidden><div id="webshop-body"></div></section>
|
||||
<section data-etage="buchungen" hidden><div id="buchungen-body"></div></section>
|
||||
<section data-etage="zeiten" hidden><div id="zeiten-body"></div></section>
|
||||
<section data-etage="analyse" hidden><div id="analyse-body"></div></section>
|
||||
<section data-etage="seite" hidden><div id="etage-body"></div></section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="kn-leiste" class="kn-leiste" aria-label="Hauptnavigation">
|
||||
<div class="kn-leiste-reel">
|
||||
<div id="kn-zeile-haupt" class="kn-rw"></div>
|
||||
<div id="kn-zeile-etage" class="kn-rw"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="kn-konto-wurzel"></div>
|
||||
<div id="kn-ansage" class="kn-nur-leser" aria-live="polite"></div>
|
||||
|
||||
<script src="/kundenansicht.js"></script>
|
||||
<script type="module" src="/neu/huelle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Kundenansicht neu: Stile der Ansicht Funktionen (Meine, Entdecken) und der Einschalt-Ansicht.
|
||||
Besitzer: P3. Hier steht bis dahin nichts. */
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"id": "/dashboard.html",
|
||||
"name": "WEBklar Kundenbereich",
|
||||
"short_name": "WEBklar",
|
||||
"description": "Website, Funktionen, Chat und Verträge – der WEBklar-Kundenbereich als App.",
|
||||
"lang": "de",
|
||||
"dir": "ltr",
|
||||
"start_url": "/dashboard.html",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#ffffff",
|
||||
"categories": ["business", "productivity"],
|
||||
"icons": [
|
||||
{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "/img/app-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "/img/app-icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
||||
],
|
||||
"shortcuts": [
|
||||
{ "name": "Chat", "short_name": "Chat", "url": "/dashboard.html#chat", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] },
|
||||
{ "name": "Rechnungen", "short_name": "Rechnungen", "url": "/dashboard.html#rechnungen", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] },
|
||||
{ "name": "Verträge", "short_name": "Verträge", "url": "/dashboard.html#vertraege", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Kundenansicht neu: Stile der gemeinsamen Funktionsmodule (Terminplaner, Webshop, Buchungskalender, Öffnungszeiten, News, Analyse) unter .kn.
|
||||
Besitzer: P4. Hier steht bis dahin nichts. */
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { GERUEST_IDS, ANSICHTEN, ETAGE_ANSICHTEN } from '../public/neu/huelle.js'
|
||||
import { lies } from './support/knHuelle.js'
|
||||
|
||||
const html = lies('dashboard.html')
|
||||
|
||||
test('dashboard.html: weiße PWA-Metas, Stile in fester Reihenfolge, klassisches Skript vor dem Modul, nichts Altes (P2 darf chat-composer ergänzen)', () => {
|
||||
assert.match(html, /<meta name="kn-seite" content="neu">/); assert.match(html, /<meta name="theme-color" content="#ffffff">/)
|
||||
assert.match(html, /apple-mobile-web-app-status-bar-style" content="default"/); assert.match(html, /<link rel="manifest" href="\/neu\/manifest\.webmanifest">/)
|
||||
assert.match(html, /viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"/)
|
||||
const css = [...html.matchAll(/<link rel="stylesheet" href="([^"]+)"/g)].map((m) => m[1]); const ORDNUNG = ['/neu/tokens.css', '/neu/basis.css', '/neu/huelle.css', '/neu/ansichten.css', '/neu/funktionen.css', '/neu/module.css']
|
||||
assert.deepEqual(css.filter((f) => f !== '/chat-composer.css'), ORDNUNG); assert.ok(css.indexOf('/chat-composer.css') === -1 || css.indexOf('/chat-composer.css') > css.indexOf('/neu/huelle.css'))
|
||||
assert.match(html, /<body class="kn">/)
|
||||
const skripte = [...html.matchAll(/<script([^>]*)>/g)].map((m) => m[1].trim())
|
||||
assert.deepEqual([skripte[0], skripte.at(-1)], ['src="/kundenansicht.js"', 'type="module" src="/neu/huelle.js"']); assert.ok(skripte.slice(1, -1).every((s) => s === 'src="/chat-composer.js"'))
|
||||
assert.doesNotMatch(html, /login\.css|kundenbereich\.css|\/app\.js|dot-background|kb-glow|class="[^"]*\bkb-/)
|
||||
})
|
||||
|
||||
test('dashboard.html: jede Gerüst-ID genau einmal, dazu die Body-IDs der Funktionsmodule und beide Navigationen', () => {
|
||||
for (const id of GERUEST_IDS) assert.equal(html.split(`id="${id}"`).length - 1, 1, id)
|
||||
for (const id of ['news-body', 'termine-body', 'webshop-body', 'buchungen-body', 'zeiten-body', 'analyse-body', 'funktionen-body']) assert.ok(GERUEST_IDS.includes(id), id)
|
||||
for (const id of ['kn-menue', 'kn-leiste']) assert.match(html, new RegExp(`<nav(?=[^>]*id="${id}")(?=[^>]*aria-label="Hauptnavigation")`))
|
||||
})
|
||||
|
||||
test('manifest.webmanifest: weiß, gleiche Identität wie die alte App, Shortcuts Chat, Rechnungen, Verträge', () => {
|
||||
const m = JSON.parse(lies('manifest.webmanifest'))
|
||||
assert.deepEqual([m.id, m.start_url, m.scope, m.display, m.background_color, m.theme_color], ['/dashboard.html', '/dashboard.html', '/', 'standalone', '#ffffff', '#ffffff'])
|
||||
assert.deepEqual(m.shortcuts.map((s) => [s.name, s.url]), [['Chat', '/dashboard.html#chat'], ['Rechnungen', '/dashboard.html#rechnungen'], ['Verträge', '/dashboard.html#vertraege']])
|
||||
assert.deepEqual(m.icons.map((i) => i.src), ['/img/app-icon-192.png', '/img/app-icon-512.png', '/img/app-icon-maskable-512.png'])
|
||||
})
|
||||
|
||||
test('Stubs: jede Ansicht der Hülle existiert unter dem Namen aus V2 (zeige + Dateiname)', async () => {
|
||||
const soll = { website: 'zeigeWebsite', funktionen: 'zeigeFunktionen', chat: 'zeigeChat', vertraege: 'zeigeVertraege', passwort: 'zeigePasswort', gutschrift: 'zeigeGutschrift', wechsel: 'zeigeWechsel', bestellen: 'zeigeBestellen' }
|
||||
for (const [name, fn] of Object.entries(soll)) {
|
||||
assert.equal(typeof (await import(`../public/neu/ansicht-${name}.js`))[fn], 'function', fn)
|
||||
assert.deepEqual(Object.values({ ...ANSICHTEN, ...ETAGE_ANSICHTEN }).find(([p]) => p === `/neu/ansicht-${name}.js`).slice(0, 2), [`/neu/ansicht-${name}.js`, fn])
|
||||
}
|
||||
})
|
||||
@@ -66,10 +66,9 @@ test('neue Seite: HTML und Cache-Control no-cache; Query bleibt erhalten', async
|
||||
const r = await rawGet('/dashboard.html?abo=success', { cookie: customerCookie('c1') })
|
||||
assert.ok(istNeu(r)); assert.equal(r.headers.get('cache-control'), 'no-cache'); assert.match(r.headers.get('content-type'), /^text\/html/)
|
||||
})
|
||||
test('Platzhalter trägt beide Marker (N5) und die Hülle des Briefs', () => {
|
||||
test('neue Seite trägt den Marker (N5) und die Hülle des Briefs', () => {
|
||||
const html = readFileSync(path.join(publicDir, 'neu', 'dashboard.html'), 'utf8')
|
||||
for (const teil of ['<meta name="kn-seite" content="neu">', '<body class="kn">', 'data-kn-platzhalter', 'id="kn-platzhalter-ansicht"', 'Kundenansicht neu', 'Platzhalter bis P1', '/api/auth/me']) assert.ok(html.includes(teil), teil)
|
||||
assert.ok(!/<script[^>]*\btype=["']module/.test(html), 'klassisches Skript, kein Modul')
|
||||
for (const teil of ['<meta name="kn-seite" content="neu">', '<body class="kn">', '<script type="module" src="/neu/huelle.js"></script>']) assert.ok(html.includes(teil), teil) // P1 ersetzt den Platzhalter aus P0 durch die weiße Hülle
|
||||
})
|
||||
test('Direktaufruf /neu/dashboard.html: Pilot ja, sonst 302 auf /dashboard.html', async () => {
|
||||
stelle('pilot', [MAIL])
|
||||
|
||||
Reference in new issue
Block a user