Funktionsmodule: gemeinsame Einschalt-Seite für das Abo-Modell
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a50c5ef6c9
commit
364ded1662
2 files changed
+205
-21
No files matched your search
@@ -17,6 +17,10 @@ const RECHTLICHES = '/rechtliches/'
|
||||
const KUNDENANSICHT = 'In der Kundenansicht kann nicht bestellt werden.'
|
||||
const ZAHLUNGSSEITE = 'Die Zahlungsseite konnte nicht geöffnet werden. Bitte versuch es noch einmal oder schreib uns im Chat.'
|
||||
const DANKE = 'Danke! Sobald die Zahlung da ist, legen wir los.'
|
||||
// Abo-Modell (Texte E1, E2, E4): Einschalten statt Bestellen
|
||||
const EINSCHALTEN_TITEL = 'Prüfen und einschalten'
|
||||
const EINSCHALTEN_OFFEN = 'Du kannst einschalten, sobald die offenen Punkte erledigt sind.'
|
||||
const EINSCHALTEN_FEHLT = 'Das Einschalten ist gerade nicht verfügbar. Bitte lade die Seite neu oder schreib uns im Chat.'
|
||||
const SCHRITT_TITEL = new Map(FRAGEBOGEN_SCHRITTE)
|
||||
|
||||
/* ---------------------------------------------------------------- Links --- */
|
||||
@@ -105,6 +109,35 @@ function ladeBeiRueckkehrNeu(element) {
|
||||
window.addEventListener('pageshow', beiAnzeige)
|
||||
}
|
||||
|
||||
/* ------------------------------------------- Zusammenfassung und offene Punkte (Bestellseite, Einschalt-Seite) --- */
|
||||
/** Die Gruppen der Zusammenfassung (je Gruppe die Angaben als Text und „Ändern“, wenn es ein Ziel gibt). */
|
||||
function gruppenKarten(gruppenFn, daten, springt, gehe) {
|
||||
return (gruppenFn ? gruppenFn(daten.einstellung, daten.fragebogen, daten.branchen) : []).map((gruppe) => h('div', { class: 'tm-bestellen-gruppe', 'data-schritt': gruppe.schritt },
|
||||
h('div', { class: 'tm-bestellen-gruppe-kopf' },
|
||||
h('h4', null, gruppe.titel),
|
||||
springt
|
||||
? h('button', {
|
||||
type: 'button', class: 'kb-btn-outline tm-knopf', 'data-aktion': 'aendern', 'data-schritt': gruppe.schritt, 'aria-label': `Ändern: ${gruppe.titel}`, onclick: () => gehe(gruppe.schritt),
|
||||
}, 'Ändern')
|
||||
: null),
|
||||
gruppe.zeilen.length > 0 ? h('ul', null, gruppe.zeilen.map((zeile) => h('li', null, zeile))) : h('p', { class: 'tm-hinweis' }, 'Noch keine Angaben.')))
|
||||
}
|
||||
|
||||
/** Die offenen Punkte als Block „Das fehlt noch“ (je Punkt „Zum Schritt“ nur mit Ziel); ohne Punkte null. anleitung = Satz unter der Überschrift oder null. */
|
||||
function offenKarte(offen, springt, gehe, anleitung) {
|
||||
if (offen.length === 0) return null
|
||||
return h('div', { class: 'tm-bestellen-offen', role: 'note' },
|
||||
h('h4', null, 'Das fehlt noch'),
|
||||
anleitung ? h('p', { class: 'tm-hinweis' }, anleitung) : null,
|
||||
h('ul', null, offen.map((punkt) => h('li', null,
|
||||
h('span', null, punkt.text),
|
||||
springt && SCHRITT_TITEL.has(punkt.schritt)
|
||||
? h('button', {
|
||||
type: 'button', class: 'kb-btn-outline tm-knopf', 'data-aktion': 'zum-schritt', 'data-schritt': punkt.schritt, 'aria-label': `Zum Schritt „${SCHRITT_TITEL.get(punkt.schritt)}“`, onclick: () => gehe(punkt.schritt),
|
||||
}, 'Zum Schritt')
|
||||
: null))))
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- Bestellseite --- */
|
||||
/**
|
||||
* „Prüfen und bestellen“ in root zeichnen und die Seite zurückgeben. Reihenfolge: Zusammenfassung der Angaben (je Gruppe „Ändern“),
|
||||
@@ -147,27 +180,10 @@ function baueSeite(root, { ctx, daten, zumSchritt, key, gruppenFn }, uebernahme)
|
||||
const gehe = (schritt) => { if (springt) zumSchritt(schritt) }
|
||||
|
||||
// 1 Zusammenfassung: je Gruppe die Angaben als Text und „Ändern“
|
||||
const gruppen = (gruppenFn ? gruppenFn(daten.einstellung, daten.fragebogen, daten.branchen) : []).map((gruppe) => h('div', { class: 'tm-bestellen-gruppe', 'data-schritt': gruppe.schritt },
|
||||
h('div', { class: 'tm-bestellen-gruppe-kopf' },
|
||||
h('h4', null, gruppe.titel),
|
||||
springt
|
||||
? h('button', {
|
||||
type: 'button', class: 'kb-btn-outline tm-knopf', 'data-aktion': 'aendern', 'data-schritt': gruppe.schritt, 'aria-label': `Ändern: ${gruppe.titel}`, onclick: () => gehe(gruppe.schritt),
|
||||
}, 'Ändern')
|
||||
: null),
|
||||
gruppe.zeilen.length > 0 ? h('ul', null, gruppe.zeilen.map((zeile) => h('li', null, zeile))) : h('p', { class: 'tm-hinweis' }, 'Noch keine Angaben.')))
|
||||
const gruppen = gruppenKarten(gruppenFn, daten, springt, gehe)
|
||||
|
||||
// 2 offene Punkte: „Zum Schritt“ nur dort, wo es einen Schritt des Fragebogens gibt (der Abo-Punkt gehört zu dieser Seite)
|
||||
const offenBlock = offen.length === 0 ? null : h('div', { class: 'tm-bestellen-offen', role: 'note' },
|
||||
h('h4', null, 'Das fehlt noch'),
|
||||
h('p', { class: 'tm-hinweis' }, 'Erledige diese Punkte, dann kannst du bestellen.'),
|
||||
h('ul', null, offen.map((punkt) => h('li', null,
|
||||
h('span', null, punkt.text),
|
||||
springt && SCHRITT_TITEL.has(punkt.schritt)
|
||||
? h('button', {
|
||||
type: 'button', class: 'kb-btn-outline tm-knopf', 'data-aktion': 'zum-schritt', 'data-schritt': punkt.schritt, 'aria-label': `Zum Schritt „${SCHRITT_TITEL.get(punkt.schritt)}“`, onclick: () => gehe(punkt.schritt),
|
||||
}, 'Zum Schritt')
|
||||
: null))))
|
||||
const offenBlock = offenKarte(offen, springt, gehe, 'Erledige diese Punkte, dann kannst du bestellen.')
|
||||
|
||||
// 3 Preis mit dem Hinweis des Servers und die Einrichtungsdauer
|
||||
const preisBlock = h('div', { class: 'tm-bestellen-preis' },
|
||||
@@ -289,12 +305,65 @@ function baueSeite(root, { ctx, daten, zumSchritt, key, gruppenFn }, uebernahme)
|
||||
return seite
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Einschalt-Seite (Abo-Modell) --- */
|
||||
/**
|
||||
* Die gemeinsame Seite der Funktionsmodule im Abo-Modell. Statt Preis, Belehrung, Haken und Knopf der Bestellseite: oben (nur wenn es etwas
|
||||
* zu prüfen gibt) „Prüfen und einschalten“ mit der Zusammenfassung der Angaben (je Gruppe „Ändern“) und den offenen Punkten (je Punkt „Zum
|
||||
* Schritt“), darunter der Platz, in den der Ablauf von P3 (`einschalten`, zeigeEinschalten aus public/neu/funktion-einschalten.js)
|
||||
* Bestätigung und Knopf zeichnet. Bei offenen Punkten steht stattdessen der Hinweis und der Ablauf wird nicht gerufen.
|
||||
* Die Seite kennt weder einen Preis noch den Checkout. daten wie bei renderBestellen (einstellung, fragebogen, branchen, offen, kundenansicht);
|
||||
* einschalten(platz, { key, projectId, viewAs, onFertig }) ist eine Init-Option der Module (die Hülle gibt sie durch); fehlt sie, steht dort eine
|
||||
* Fehlerkarte. onFertig wirkt höchstens einmal und nur, solange ctx.aktuell() nicht falsch ist; es ruft nachEinschalten() (die Ansicht
|
||||
* zeichnet sich neu). Weitere Optionen wie renderBestellen: key, zusammenfassung, zumSchritt.
|
||||
*/
|
||||
export function renderEinschaltSeite(root, { ctx = {}, daten = {}, zumSchritt, key = STANDARD_KEY, zusammenfassung: gruppenFunktion, einschalten, nachEinschalten } = {}) {
|
||||
const gruppenFn = typeof gruppenFunktion === 'function' ? gruppenFunktion : key === STANDARD_KEY ? zusammenfassung : null
|
||||
const offen = offeneListe(daten.offen)
|
||||
const springt = typeof zumSchritt === 'function'
|
||||
const gehe = (schritt) => { if (springt) zumSchritt(schritt) }
|
||||
const gruppen = gruppenKarten(gruppenFn, daten, springt, gehe)
|
||||
const offenBlock = offenKarte(offen, springt, gehe, null)
|
||||
const zeigeKopf = gruppen.length > 0 || offen.length > 0
|
||||
const viewAs = daten.kundenansicht === true || ctx.kundenansicht === true
|
||||
|
||||
const platz = h('div', { class: 'tm-einschalten-platz', 'data-einschalten': key })
|
||||
const seite = h('section', { class: 'kb-panel tm-bestellen tm-einschalten', 'data-key': key },
|
||||
zeigeKopf ? h('h3', null, EINSCHALTEN_TITEL) : null,
|
||||
zeigeKopf && gruppenFn ? h('p', { class: 'tm-abschnitt-text tm-hinweis' }, springt ? 'Lies alles in Ruhe durch. Mit „Ändern“ kommst du zu dem Schritt zurück, in dem du die Angabe gemacht hast.' : 'Lies alles in Ruhe durch.') : null,
|
||||
gruppen.length > 0 ? h('div', { class: 'tm-bestellen-zusammenfassung' }, gruppen) : null,
|
||||
offenBlock,
|
||||
offen.length > 0 ? h('p', { class: 'tm-hinweis', 'data-hinweis': 'einschalten' }, EINSCHALTEN_OFFEN) : platz)
|
||||
root.replaceChildren(seite)
|
||||
if (offen.length > 0) return seite
|
||||
|
||||
// höchstens ein Abschluss: nur der erste Aufruf wirkt, und nur solange die Ansicht noch gilt
|
||||
let fertig = false
|
||||
const onFertig = () => {
|
||||
if (fertig) return
|
||||
fertig = true
|
||||
if (typeof ctx.aktuell === 'function' && ctx.aktuell() === false) return
|
||||
if (typeof nachEinschalten === 'function') Promise.resolve().then(() => nachEinschalten()).catch(() => {})
|
||||
}
|
||||
const fehlt = () => platz.replaceChildren(fehlerKarte(EINSCHALTEN_FEHLT))
|
||||
if (typeof einschalten !== 'function') {
|
||||
fehlt()
|
||||
return seite
|
||||
}
|
||||
try {
|
||||
Promise.resolve(einschalten(platz, { key, projectId: ctx.projectId, viewAs, onFertig })).catch(fehlt)
|
||||
} catch {
|
||||
fehlt()
|
||||
}
|
||||
return seite
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- Statuskarte --- */
|
||||
/**
|
||||
* Karte für eine Bestellung im Zustand ordered („Zahlung offen“) oder setup („In Einrichtung“).
|
||||
* projekt = Eintrag des Projekts aus GET /api/funktionen (projectId), bestellung = projekt.features.termine (state, orderedAt, ticketWoid,
|
||||
* invoiceUrl), einrichtung = Text der Zeiteinschätzung (Register: setupEstimate), danke = Rückkehr von Stripe, kundenansicht = Sitzung in der
|
||||
* Kundenansicht, meldung = Text, der am Knopf „Zahlung fortsetzen“ steht (z. B. der Fehler eines Versuchs vor dem Neuzeichnen).
|
||||
* modell = 'abo' (strikt): die Faktenzeile heißt „Eingeschaltet am“ statt „Bestellt am“ (Abo-Modell, Text E5).
|
||||
* key = Schlüssel der Funktion (Standard 'termine', Checkout-Pfad), name = Name der Funktion in
|
||||
* den Texten (Standard 'Terminplaner'), hinweisEinrichtung = Satz unter den Fakten im Zustand setup (Standard: Einstellungen gesperrt).
|
||||
* weiter(fall) wird gerufen, wenn „Zahlung fortsetzen“ scheitert (Ruling R110): fall = { bestellen, meldung }, bestellen ist wahr bei 409
|
||||
@@ -303,7 +372,7 @@ function baueSeite(root, { ctx, daten, zumSchritt, key, gruppenFn }, uebernahme)
|
||||
* inzwischen bezahlt ist und der Server 400 antwortet); wirft es, bleibt die Karte stehen und der Fehler steht am Knopf. Fehlt weiter,
|
||||
* steht die Meldung des Servers am Knopf.
|
||||
*/
|
||||
export function statusKarte({ projekt, bestellung, einrichtung, danke, kundenansicht, meldung, key = STANDARD_KEY, name = 'Terminplaner', hinweisEinrichtung } = {}, { weiter } = {}) {
|
||||
export function statusKarte({ projekt, bestellung, einrichtung, danke, kundenansicht, meldung, key = STANDARD_KEY, name = 'Terminplaner', hinweisEinrichtung, modell } = {}, { weiter } = {}) {
|
||||
const bestellt = bestellung?.state === 'ordered'
|
||||
const datum = datumTag(bestellung?.orderedAt)
|
||||
const ticket = bestellung?.ticketWoid
|
||||
@@ -312,7 +381,7 @@ export function statusKarte({ projekt, bestellung, einrichtung, danke, kundenans
|
||||
const zeile = (name, wert) => h('div', null, h('dt', null, name), h('dd', null, wert))
|
||||
const fakten = [
|
||||
bestellt ? null : zeile('Ticketnummer', hatTicket ? `#${ticket}` : 'Wir legen dein Ticket gerade an.'),
|
||||
datum ? zeile('Bestellt am', datum) : null,
|
||||
datum ? zeile(modell === 'abo' ? 'Eingeschaltet am' : 'Bestellt am', datum) : null,
|
||||
!bestellt && text(einrichtung) ? zeile('Zeiteinschätzung', einrichtung) : null,
|
||||
].filter(Boolean)
|
||||
|
||||
|
||||
Reference in new issue
Block a user