Files
Webklar-Kundenbereich/public/neu/bestell-bausteine.js
T
ClaudeandClaude Sonnet 5.5 d4050f870a Kundenansicht neu E-Fix F1: Kerninfo und Knopf der Bestellseiten passen in den nutzbaren Bereich (390x844, 375x667)
Die Messung im Werkzeug nahm innerHeight; nutzbar ist am Handy nur der Bereich zwischen Kopf und fester Leiste (735 px bei 844, 558 px bei 667).
Gemessen vor der Korrektur (Blockanfang bis Knopfunterkante, 375x667): Abo bestellen privat 799 px, Wechsel privat 888 px, Zusatzspeicher 567 px.

- Block knapp: Leistung als eine Zeile mit Link "Leistungen ansehen" auf die Leistungsbeschreibung, Preis, Fällig, Beginn, Laufzeit und Zahlung in je ein bis zwei Zeilen.
- Die ausführlichen Sätze stehen unter dem Knopf und der Kundenart-Zeile (baueEinzelheiten), ohne Klappe.
- Handy: Beschriftung läuft in der Zeile mit, kleinere Überschrift, knappere Abstände.
- Die Wortlaute des Blocks sind gekürzt und gehören in die Rechtsrunde.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 21:25:48 +00:00

116 lines
5.7 KiB
JavaScript

/* Kundenansicht neu (P7): gemeinsame Bausteine der vier Bestellseiten (Abo bestellen, Wechsel, Zusatzspeicher, Gutschrift-Antrag).
Die Sätze kommen vom Server (GET /api/abo → bestelltexte); hier werden sie nur gezeichnet. Link-Texte stehen in der Vorlage in
eckigen Klammern, ihre Adressen daneben in urls. Jede Adresse läuft durch sicherLink; Kundentext wird nie als HTML gelesen.
Keine h1: den Titel meldet die Ansicht mit meldeTitel. */
import { h } from './dom.js'
import { K } from './ansicht-hilfen.js'
/** 'unternehmen' | 'privat': nur exakt 'unternehmen' (me.customer.kundenartWirksam, sonst daten.kundenart) ist Betrieb; alles andere privat (E4). */
export function kundenartVon(me, daten = null) {
return me?.customer?.kundenartWirksam === 'unternehmen' || daten?.kundenart === 'unternehmen' ? 'unternehmen' : 'privat'
}
/** Adresse nur, wenn sie ein Pfad auf derselben Herkunft ('/…', nicht '//…'), ein Anker ('#name…') oder http(s) ohne Zugangsdaten ist; sonst ''. */
export function sicherLink(url) {
if (typeof url !== 'string' || url === '') return ''
if (/[\s\u0000-\u001f\u007f\\]/.test(url)) return ''
if (/^\/(?![/\\])/.test(url)) return url
if (/^#[a-z][\w-]*(\?[\w=&.%-]*)?$/i.test(url)) return url
if (/^https?:\/\/[^/?#]+/i.test(url)) {
try {
const u = new URL(url)
return u.hostname !== '' && u.username === '' && u.password === '' ? url : ''
} catch {
return ''
}
}
return ''
}
/**
* 'Es gilt [A] und [B].' mit ['/a', '/b'] → Teile [{ text }, { text, href? }]. Die Markierungen bekommen der Reihe nach ihre Adresse;
* eine unsichere oder fehlende Adresse macht den Teil zu einem Teil ohne href. Eine offene Klammer bleibt Text.
*/
export function linkTeile(text, urls) {
const basis = typeof text === 'string' ? text : ''
const adressen = Array.isArray(urls) ? urls : []
const teile = []
let ab = 0
let nr = 0
const muster = /\[([^\]]*)\]/g
for (let m = muster.exec(basis); m; m = muster.exec(basis)) {
if (m.index > ab) teile.push({ text: basis.slice(ab, m.index) })
const href = sicherLink(adressen[nr])
nr += 1
teile.push(href ? { text: m[1], href } : { text: m[1] })
ab = m.index + m[0].length
}
if (ab < basis.length) teile.push({ text: basis.slice(ab) })
return teile
}
const ausserhalb = (href) => !href.startsWith('#')
/**
* <p id> mit den Links der Vorlage; '/…' und http(s) öffnen in neuem Tab (target _blank, rel noopener), '#…' bleibt im Fenster.
* klasse: Vorgabe 'kn-linkzeile'. neuerTab: auch '#…' öffnet in neuem Tab (Bedingungen der Gutschrift neben dem Formular).
*/
export function baueLinkzeile({ text, urls } = {}, { id, klasse = 'kn-linkzeile', neuerTab = false } = {}) {
const kinder = linkTeile(text, urls).map((t) =>
t.href ? h('a', { href: t.href, ...(neuerTab || ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text)
return h('p', { id, class: klasse }, kinder)
}
/** <p id>: „Du bestellst als Betrieb“ | „Du bestellst privat“, ' · ', dann der Hinweis zur Korrektur mit seinem Link. */
export function baueKundenartZeile(kundenart, texte, { id } = {}) {
const lage = kundenart === 'unternehmen' ? texte?.unternehmen : texte?.privat
const korrektur = linkTeile(texte?.korrektur?.text, texte?.korrektur?.urls).map((t) =>
t.href ? h('a', { href: t.href, ...(ausserhalb(t.href) ? { target: '_blank', rel: 'noopener' } : {}) }, t.text) : t.text)
return h('p', { id, class: 'kn-kundenart-zeile' }, lage, ' · ', korrektur)
}
/**
* Block „Was du bestellst“: zeilen [{ id?, label, text, klasse?, link? }] als Beschriftungsliste. Der Text ist immer Text.
* link { text, href }: hinter dem Text ' · ' und ein Link (nur mit sicherer Adresse; Adressen außerhalb der Seite öffnen in neuem Tab).
*/
export function baueBlock(zeilen, { id } = {}) {
return h('dl', { id, class: 'kn-block' }, (zeilen || []).map((z) => {
const href = z.link ? sicherLink(z.link.href) : ''
const link = href ? [' · ', h('a', { href, ...(ausserhalb(href) ? { target: '_blank', rel: 'noopener' } : {}) }, z.link.text)] : null
return h('div', { class: 'kn-block-zeile' }, h('dt', null, z.label), h('dd', { id: z.id, class: z.klasse }, z.text, link))
}))
}
/**
* Die ausführlichen Sätze UNTER dem Knopf (und der Kundenart-Zeile): Überschrift „Im Einzelnen“ und dieselbe Beschriftungsliste wie der Block.
* Der Block über dem Knopf trägt die Kerninfo knapp, damit sie mit dem Knopf auf einen Handy-Bildschirm passt; hier steht sie ausführlich.
*/
export function baueEinzelheiten(zeilen, { id } = {}) {
return h('section', { id, class: 'kn-einzelheiten' }, h('h3', null, 'Im Einzelnen'), baueBlock(zeilen))
}
/** Adresse der Leistungsbeschreibung: die Bedingungen (erste Adresse der Linkzeile) mit Anker ('wartung', 'hosting'); unsicher oder fehlend ''. */
export function leistungLink(linkzeile, anker) {
const basis = sicherLink(Array.isArray(linkzeile?.urls) ? linkzeile.urls[0] : '')
return basis && !basis.includes('#') ? `${basis}#${anker}` : ''
}
const nichtLeer = (x) => typeof x === 'string' && x !== ''
/**
* true, wenn die Antwort den Wortlaut trägt: status, knopf und die Sätze der Seite (art 'abo' | 'wechsel' | 'speicher' | 'gutschrift').
* Ohne ihn zeigt keine Seite einen Bestellknopf.
*/
export function hatWortlaut(daten, art = 'abo') {
const t = daten?.bestelltexte
if (!t || typeof t !== 'object' || !nichtLeer(t.status) || !nichtLeer(t.knopf)) return false
if (art === 'gutschrift') return nichtLeer(t.gutschrift?.satz?.text) && nichtLeer(t.gutschrift?.knopf)
if (!nichtLeer(t[art]?.linkzeile?.text)) return false
if (art === 'wechsel') return nichtLeer(t.wechsel.haken) && nichtLeer(t.wechsel.gesperrt)
if (art === 'abo') return nichtLeer(t.abo.haken)
return true
}
export { K }