Files
Webklar-Kundenbereich/public/neu/routen.js
T
ClaudeandClaude Sonnet 5.5 5cd49d6a4a Kundenansicht neu E-Fix F1 (Nachkorrektur): Widerrufsstreifen auf den Bestellseiten ausgeblendet
Auf Abo bestellen, Wechsel, Zusatzspeicher und Übernahmegutschrift fehlt der feste Widerrufsstreifen, damit Kerninfo und Knopf "Zahlungspflichtig bestellen"
auch bei Privatkunden in laufender Frist auf 375x667 auf einem Bildschirm stehen (Spec Abschnitt 4). Auf den Hauptseiten bleibt er (Spec 5.2),
"Vertrag widerrufen" bleibt im Konto und unter Verträge erreichbar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 22:44:20 +00:00

123 lines
6.6 KiB
JavaScript

/* Kundenansicht neu: Routentabelle. Reine Logik ohne DOM, in Node importierbar.
Alle alten Hashes bleiben gültig (VIEW_HASHES, Aliase #wartung, #abo, #fortschritt); die Query nach ? bleibt Zeichen für Zeichen erhalten.
Neue Hash-Namen nur aus [a-z] wegen der Login-Whitelist /^\/dashboard\.html(#[a-z]+)?$/. */
export const BEREICHE = ['website', 'funktionen', 'chat', 'vertraege']
export const FUNKTIONS_ETAGEN = ['news', 'termine', 'webshop', 'buchungen', 'zeiten', 'analyse']
const FELDER = ['bereich', 'reiter', 'etage', 'produkt', 'scroll']
// Name (Hash ohne #) → Ziel ohne Query. Je Ziel steht die kanonische Schreibweise zuerst (abo vor wartung).
// vertraege hat bewusst keinen Reiter: nur dann wählt die Ansicht den Standardreiter; abo und wartung sind die ausdrückliche Wahl.
export const ROUTEN = Object.freeze(Object.fromEntries(Object.entries({
'': { bereich: 'website' },
projekt: { bereich: 'website', etage: 'projekt' },
fortschritt: { bereich: 'website', scroll: 'aktivitaet' },
passwort: { bereich: 'website', etage: 'passwort' },
funktionen: { bereich: 'funktionen', reiter: 'meine' },
entdecken: { bereich: 'funktionen', reiter: 'entdecken' },
news: { bereich: 'funktionen', etage: 'news' },
termine: { bereich: 'funktionen', etage: 'termine' },
webshop: { bereich: 'funktionen', etage: 'webshop' },
buchungen: { bereich: 'funktionen', etage: 'buchungen' },
zeiten: { bereich: 'funktionen', etage: 'zeiten' },
analyse: { bereich: 'funktionen', etage: 'analyse' },
chat: { bereich: 'chat' },
vertraege: { bereich: 'vertraege' },
abo: { bereich: 'vertraege', reiter: 'abo' },
wartung: { bereich: 'vertraege', reiter: 'abo' },
rechnungen: { bereich: 'vertraege', reiter: 'rechnungen' },
stunden: { bereich: 'vertraege', reiter: 'stunden' },
speicher: { bereich: 'vertraege', reiter: 'email' },
gutschrift: { bereich: 'vertraege', etage: 'gutschrift' },
wechsel: { bereich: 'vertraege', etage: 'wechsel' },
'bestellen-hosting': { bereich: 'vertraege', etage: 'bestellen', produkt: 'hosting' },
'bestellen-wartung': { bereich: 'vertraege', etage: 'bestellen', produkt: 'wartung' },
}).map(([name, ziel]) => [name, Object.freeze(ziel)])))
// Hash eines Bereichs, wenn kein Eintrag der Tabelle genau passt
const BEREICHS_HASH = { website: '', funktionen: 'funktionen', chat: 'chat', vertraege: 'vertraege' }
const TITEL_BEREICH = { website: 'Website', funktionen: 'Funktionen', chat: 'Chat', vertraege: 'Verträge' }
const TITEL_ETAGE = {
projekt: 'Projekt', news: 'News', termine: 'Terminplaner', webshop: 'Webshop', buchungen: 'Buchungskalender', zeiten: 'Öffnungszeiten',
analyse: 'Analyse', gutschrift: 'Übernahmegutschrift', wechsel: 'Abo wechseln', bestellen: 'Abo bestellen', passwort: 'Passwort ändern',
}
// Alter data-view-Name (VIEW_HASHES, app.js:1933) → Hash-Name
const ANSICHT_ZU_HASH = {
projects: '', funktionen: 'funktionen', news: 'news', analyse: 'analyse', termine: 'termine', invoices: 'rechnungen', chat: 'chat',
vertraege: 'vertraege', stunden: 'stunden', 'bestellen-hosting': 'bestellen-hosting', 'bestellen-wartung': 'bestellen-wartung',
wechsel: 'wechsel', takeover: 'gutschrift', projekt: 'projekt', passwort: 'passwort', webshop: 'webshop', buchungen: 'buchungen', zeiten: 'zeiten',
}
const gleich = (a, b) => FELDER.every((feld) => (a[feld] ?? '') === (b[feld] ?? ''))
const mitQuery = (name, query) => `#${name}${query ? `?${query}` : ''}`
/** '#termine?bestellt=1' → { bereich: 'funktionen', etage: 'termine', query: 'bestellt=1' }. Unbekannte Namen ergeben die Website. */
export function routeAusHash(hash) {
const roh = typeof hash === 'string' ? hash.replace(/^#/, '') : ''
const frage = roh.indexOf('?')
const name = (frage === -1 ? roh : roh.slice(0, frage)).toLowerCase()
const query = frage === -1 ? '' : roh.slice(frage + 1)
if (!Object.hasOwn(ROUTEN, name)) return { ...ROUTEN[''], query: '' }
return { ...ROUTEN[name], query }
}
/** Umkehrung von routeAusHash in der kanonischen Schreibweise (#wartung wird #abo, die Website ohne Query ist '#'). */
export function hashFuerRoute(route) {
const query = typeof route?.query === 'string' ? route.query : ''
const treffer = Object.entries(ROUTEN).find(([, ziel]) => gleich(ziel, route ?? {}))
const name = treffer ? treffer[0] : (BEREICHS_HASH[route?.bereich] ?? '')
return mitQuery(name, query)
}
/** Brücke für die gemeinsamen Module: alter data-view-Name → Hash, unbekannter Name → null. */
export function hashFuerAnsicht(name) {
return Object.hasOwn(ANSICHT_ZU_HASH, name) ? `#${ANSICHT_ZU_HASH[name]}` : null
}
/** Hash beim Start der Seite. ?abo=success|cancel (Rückkehr von Stripe) öffnet den Reiter Abo; die Query bleibt in location.search. */
export function startHash({ search = '', hash = '' } = {}) {
const abo = new URLSearchParams(search).get('abo')
if (abo === 'success' || abo === 'cancel') return '#abo'
return hashFuerRoute(routeAusHash(hash))
}
/** Hashes von der Website bis zum Ziel (für den Eltern-Eintrag bei Einstieg per Link). */
export function ankerKette(route) {
const ziel = hashFuerRoute(route)
if (route.bereich === 'website') return route.etage ? ['#', ziel] : [ziel]
if (!route.etage) return ['#', ziel]
return ['#', hashFuerRoute({ ...ROUTEN[BEREICHS_HASH[route.bereich]], query: '' }), ziel]
}
/** Elternroute einer Etage: derselbe Bereich, bei passwort die Herkunft (sonst die Website). Ohne Etage null. */
export function elternVon(route, herkunft = '') {
if (!route.etage) return null
if (route.etage === 'passwort') {
const vorher = routeAusHash(herkunft)
return vorher.etage === 'passwort' ? { ...ROUTEN[''], query: '' } : vorher
}
return { ...ROUTEN[BEREICHS_HASH[route.bereich]], query: '' }
}
/** Titel für Kopfzeile und Browser: die Etage, sonst der Bereich. */
export function titelFuer(route) {
return TITEL_ETAGE[route.etage] ?? TITEL_BEREICH[route.bereich] ?? ''
}
/** Etagen, auf denen etwas bestellt wird: Abo bestellen, Wechsel und die Übernahmegutschrift. Dazu der Reiter E-Mail der Verträge (Zusatzspeicher). */
const BESTELL_ETAGEN = ['bestellen', 'wechsel', 'gutschrift']
/**
* Steht auf dieser Seite der Widerrufsstreifen? Nicht auf den Bestellseiten: Dort müssen Kerninfo und Knopf „Zahlungspflichtig bestellen“ auf einem
* Bildschirm stehen (Spec Abschnitt 4), und der feste Streifen nähme am Handy 44 px davon weg. Er gehört auf die Hauptseiten (Spec 5.2);
* „Vertrag widerrufen“ bleibt im Konto und unter Verträge erreichbar. Ohne Route: ja.
*/
export function zeigtWiderrufsstreifen(route) {
if (!route) return true
if (BESTELL_ETAGEN.includes(route.etage)) return false
return !(route.bereich === 'vertraege' && route.reiter === 'email')
}