115 lines
5.6 KiB
JavaScript
115 lines
5.6 KiB
JavaScript
/* Kundenansicht neu (P2): Bereich Verträge mit den Reitern Rechnungen, Abo, Stunden und E-Mail. Der Rahmen lädt Abo und Rechnungen
|
|
parallel (jede Quelle mit eigenem Fehlerweg), wählt den Reiter allein aus route.reiter und gibt die schon geladenen Antworten an
|
|
die Reiter-Inhalte weiter. Bestellen, Wechsel und Gutschrift sind Etagen, die die Hülle direkt ruft. */
|
|
|
|
import { api } from './api.js'
|
|
import { h, leeren } from './dom.js'
|
|
import { baueReiter } from './reiter.js'
|
|
import { aktuelleRoute } from './router.js'
|
|
import { K, ersetze, ladeAbo, wechselFunktionAus, navigiere, nimmHinweis, ABO_NICHT_GELADEN } from './ansicht-hilfen.js'
|
|
import { offeneRechnungen } from './rechnung-offen.js'
|
|
import { zeigeRechnungen } from './vertraege-rechnungen.js'
|
|
import { zeigeAbo } from './vertraege-abo.js'
|
|
import { zeigeStunden } from './ansicht-stunden.js'
|
|
import { zeigeSpeicher } from './ansicht-speicher.js'
|
|
|
|
/** Nach ?abo=success: so oft und in diesem Abstand wird auf ein laufendes Abo gewartet (Tests verkürzen es). */
|
|
export const AUF_ABO_WARTEN = { versuche: 10, pauseMs: 2000 }
|
|
|
|
const LABEL = { rechnungen: 'Rechnungen', abo: 'Abo', stunden: 'Stunden', email: 'E-Mail' }
|
|
// Reiterklicks navigieren mit den ausdrücklichen Hashes (P1 schreibt #wartung und #abo kanonisch um)
|
|
const HASH = { rechnungen: '#rechnungen', abo: '#abo', stunden: '#stunden', email: '#speicher' }
|
|
const laeufe = new WeakMap() // Wurzel → Nummer des jüngsten Aufrufs
|
|
let reiterWechsel = null // Reiterklick mit laufendem Router: der nächste Aufruf nimmt die schon geladenen Antworten
|
|
|
|
/** Sichtbare Reiter: ohne laufendes Abo Rechnungen und Abo; Hosting dazu E-Mail; Wartung dazu Stunden und E-Mail. */
|
|
export function sichtbareReiter(daten) {
|
|
const sub = daten?.subscription
|
|
const reiter = ['rechnungen', 'abo']
|
|
if (!sub?.active) return reiter
|
|
if (sub.productKey === 'hosting') return [...reiter, 'email']
|
|
return [...reiter, 'stunden', 'email']
|
|
}
|
|
|
|
/** Reiterwahl allein aus route.reiter (routeReiter) und den Daten, nie aus der Adressleiste. Nur der Bereichs-Einstieg (ohne Reiter) springt auf Rechnungen. */
|
|
export function waehleReiter({ routeReiter, sichtbar, offeneRechnungen: offen }) {
|
|
if (routeReiter) return sichtbar.includes(routeReiter) ? routeReiter : 'abo'
|
|
return offen > 0 ? 'rechnungen' : 'abo'
|
|
}
|
|
|
|
const pause = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
const laden = (api_) => api_('/api/invoices').catch(() => null)
|
|
|
|
export async function zeigeVertraege(wurzel, { route, me, nav } = {}) {
|
|
const nr = (laeufe.get(wurzel) ?? 0) + 1
|
|
laeufe.set(wurzel, nr)
|
|
const aktuell = () => laeufe.get(wurzel) === nr
|
|
const vorgabe = reiterWechsel && reiterWechsel.wurzel === wurzel && reiterWechsel.bis > Date.now() ? reiterWechsel : null
|
|
reiterWechsel = null
|
|
const suche = new URLSearchParams(globalThis.window?.location?.search ?? '')
|
|
const aboParam = suche.get('abo')
|
|
const nachStripe = aboParam === 'success' || aboParam === 'cancel'
|
|
|
|
let daten
|
|
let rechnungen
|
|
if (vorgabe) {
|
|
({ daten, rechnungen } = vorgabe)
|
|
} else {
|
|
leeren(wurzel).append(h('p', { class: K.leer }, 'Wird geladen …'))
|
|
;[daten, rechnungen] = await Promise.all([ladeAbo(), laden(api)])
|
|
if (!aktuell()) return
|
|
}
|
|
|
|
// Rückkehr von Stripe: auf das laufende Abo warten (der Webhook kann noch unterwegs sein)
|
|
if (aboParam === 'success') {
|
|
for (let i = 0; i < AUF_ABO_WARTEN.versuche && !daten?.subscription?.active; i += 1) {
|
|
ersetze(wurzel, h('p', { class: K.meldung }, 'Vielen Dank! Dein Abo wird aktiviert …'), h('p', { class: 'kn-hinweis' }, 'Das dauert nur einen Moment.'))
|
|
await pause(AUF_ABO_WARTEN.pauseMs)
|
|
if (!aktuell()) return
|
|
daten = (await ladeAbo()) ?? daten
|
|
if (!aktuell()) return
|
|
}
|
|
}
|
|
if (nachStripe) window.history.replaceState(window.history.state, '', window.location.pathname + window.location.hash)
|
|
if (aboParam === 'success' && wechselFunktionAus(daten?.subscription)) {
|
|
navigiere('#funktionen', { ersetzen: true })
|
|
return
|
|
}
|
|
|
|
const sichtbar = sichtbareReiter(daten)
|
|
const offen = offeneRechnungen(rechnungen).length
|
|
const gewaehlt = waehleReiter({ routeReiter: route?.reiter, sichtbar, offeneRechnungen: offen })
|
|
const stundenOhneWartung = route?.reiter === 'stunden' && !sichtbar.includes('stunden')
|
|
const hinweis = nimmHinweis()
|
|
|
|
const inhalt = h('div', { class: 'kn-reiter-inhalt' })
|
|
const zeichneReiter = (key) => {
|
|
leeren(inhalt)
|
|
if (key === 'rechnungen') return zeigeRechnungen(inhalt, { me, antwort: rechnungen ?? undefined })
|
|
if (!daten) return Promise.resolve()
|
|
if (key === 'abo') {
|
|
if (stundenOhneWartung) inhalt.append(h('p', { class: 'kn-hinweis' }, 'Ein Stundenkonto gibt es im Wartungs-Abo.'))
|
|
const platz = h('div', { class: 'kn-reiter-abo' })
|
|
inhalt.append(platz)
|
|
return zeigeAbo(platz, { daten, me })
|
|
}
|
|
if (key === 'stunden') return zeigeStunden(inhalt, { route, me, nav, daten })
|
|
return zeigeSpeicher(inhalt, { route, me, nav, daten })
|
|
}
|
|
|
|
const bei = (key) => {
|
|
if (aktuelleRoute() !== null) reiterWechsel = { wurzel, daten, rechnungen, bis: Date.now() + 5000 } // der Router ruft uns gleich neu
|
|
else zeichneReiter(key)
|
|
navigiere(HASH[key], { ersetzen: true })
|
|
}
|
|
const reiterZeile = baueReiter({ eintraege: sichtbar.map((key) => ({ key, label: LABEL[key] })), aktiv: gewaehlt, art: 'unterstrich', ariaLabel: 'Verträge', bei })
|
|
|
|
ersetze(wurzel,
|
|
hinweis ? h('p', { class: `${K.meldung} kn-meldung-ok`, role: 'status' }, hinweis) : null,
|
|
daten ? null : h('p', { class: K.fehler }, ABO_NICHT_GELADEN),
|
|
reiterZeile,
|
|
inhalt,
|
|
h('p', { class: 'kn-kuendigen' }, h('a', { href: '/kuendigen.html' }, 'Verträge hier kündigen')))
|
|
await zeichneReiter(gewaehlt)
|
|
}
|