Verträge: Mein Vertrag, Vergleich der Abos, Wechsel-Karte, Unterlagen in der zugestimmten Fassung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0129ac48e7
commit
19af93338b
3 files changed
+215
-53
No files matched your search
+186
-52
@@ -760,16 +760,6 @@ async function pollChatBadge() {
|
||||
// Wartungs-Abo: Karte in der Seitenleiste + Ansicht (Bestellung bzw. Stundenkonto)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ABO_FEATURES = [
|
||||
['Betrieb & Pflege Ihrer Website/Anwendung', 'Server in Deutschland, Updates, Sicherheit, tägliche Datensicherung, Überwachung'],
|
||||
['1 Domain inklusive', 'Registrierung auf Ihren Namen, DNS- und Zertifikatsverwaltung'],
|
||||
['1 E-Mail-Postfach', 'mit Ihrer eigenen Domain, 5 GB Speicher'],
|
||||
['1 Stunde Arbeitszeit pro Monat', 'nicht genutzte Zeit bleibt 12 Monate verfügbar'],
|
||||
]
|
||||
|
||||
// Kurzfassung fuer die Karte in der Seitenleiste
|
||||
const ABO_SIDE_FEATURES = ['Betrieb & Pflege, Server in Deutschland', '1 Domain inklusive', '1 E-Mail-Postfach, 5 GB', '1 Std. Arbeitszeit pro Monat']
|
||||
|
||||
function fmtHours(min) {
|
||||
const n = Math.max(0, Number(min || 0))
|
||||
const h = Math.floor(n / 60)
|
||||
@@ -787,9 +777,19 @@ function fmtDayMonth(value) {
|
||||
return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString('de-DE', { timeZone: 'Europe/Berlin', day: '2-digit', month: '2-digit' })
|
||||
}
|
||||
|
||||
function aboFeaturesHtml() {
|
||||
return `<ul class="abo-features">${ABO_FEATURES.map(
|
||||
([t, d]) => `<li>${t}<small>${d}</small></li>`
|
||||
// Leistungen eines Produkts aus dem Katalog der Server-Antwort (data.products[].features = [Titel, Detail])
|
||||
function productOf(data, key) {
|
||||
return (data?.products || []).find((p) => p.key === key) || null
|
||||
}
|
||||
|
||||
function productLabelOf(data, key) {
|
||||
const product = productOf(data, key)
|
||||
return (product?.name || (key === 'hosting' ? 'Hosting-Abo' : 'Wartungs-Abo')).replace(/^WEBklar\s+/, '')
|
||||
}
|
||||
|
||||
function aboFeaturesHtml(features) {
|
||||
return `<ul class="abo-features">${(features || []).map(
|
||||
([t, d]) => `<li>${escapeHtml(t)}<small>${escapeHtml(d)}</small></li>`
|
||||
).join('')}</ul>`
|
||||
}
|
||||
|
||||
@@ -804,48 +804,57 @@ function aboTermText(sub) {
|
||||
return 'Monatlich kündbar zum Ende des Abrechnungsmonats.'
|
||||
}
|
||||
|
||||
/* Seitenleiste: Angebot (noch kein Abo) */
|
||||
/* Seitenleiste: noch kein Abo (Auswahl steht in der Ansicht "Verträge") */
|
||||
function renderAboSideOffer(card, data, { ended }) {
|
||||
const offer = data.offer
|
||||
const cheapest = Math.min(...(data.products || []).map((p) => p.amountCents).filter(Number.isFinite), 1500)
|
||||
card.classList.add('kb-abo-offer')
|
||||
card.innerHTML = `
|
||||
<p class="kb-label">${ended ? 'Ihr Abo ist beendet' : 'Rundum versorgt'}</p>
|
||||
<h3>Wartungs-Abo</h3>
|
||||
${offer ? '<span class="kb-offer-tag">Ihr persönliches Angebot</span>' : ''}
|
||||
<p class="kb-price">${offer
|
||||
? `<strong>${euroShort(offer.monthlyPriceCents)}</strong><span>${offer.months === 1 ? 'im ersten Monat' : `pro Monat in den ersten ${offer.months} Monaten`}, danach 50 €</span>`
|
||||
: '<strong>50 €</strong><span>pro Monat</span>'}</p>
|
||||
${offer?.validUntil ? `<p class="kb-offer-until">Angebot gültig bis <strong>${fmtDateShort(offer.validUntil)}</strong></p>` : ''}
|
||||
<ul class="kb-checks">${ABO_SIDE_FEATURES.map((f) => `<li>${escapeHtml(f)}</li>`).join('')}</ul>
|
||||
<button type="button" class="kb-btn-primary kb-btn-block" data-view="stunden"${data.available ? '' : ' disabled'}>Abo abschließen</button>
|
||||
<p class="kb-label">Ihr Vertrag</p>
|
||||
<h3>${ended ? 'Ihr Abo ist beendet' : 'Rundum versorgt'}</h3>
|
||||
<p class="kb-price"><strong>ab ${euroShort(cheapest)}</strong><span>pro Monat</span></p>
|
||||
<p class="kb-sub">Hosting-Abo oder Wartungs-Abo, 12 Monate Mindestlaufzeit.</p>
|
||||
<button type="button" class="kb-btn-primary kb-btn-block" data-view="vertraege">Abos ansehen</button>
|
||||
<p class="kb-fine">${data.available
|
||||
? '12 Monate Mindestlaufzeit. Gemäß § 19 UStG ohne Umsatzsteuer.'
|
||||
? 'Gemäß § 19 UStG ohne Umsatzsteuer.'
|
||||
: 'Die Online-Bestellung wird gerade eingerichtet – schreiben Sie uns gern im Chat.'}</p>`
|
||||
}
|
||||
|
||||
/* Seitenleiste: laufendes Abo */
|
||||
function renderAboSideActive(card, data) {
|
||||
const sub = data.subscription
|
||||
const l = data.ledger || { balanceMinutes: 0 }
|
||||
// Status nur bei Kündigung; Zahlungsproblem (Hinweis unten) und späterer Beginn (Datumszeile) stehen im Text
|
||||
const status = sub.cancelAtPeriodEnd ? '<span class="kb-status kb-status-off">gekündigt</span>' : ''
|
||||
const dateLine = sub.cancelAtPeriodEnd
|
||||
function aboIsCancelled(sub) {
|
||||
return Boolean(sub.cancelAtPeriodEnd || sub.cancelAt)
|
||||
}
|
||||
|
||||
function aboDateLine(sub) {
|
||||
return aboIsCancelled(sub)
|
||||
? `Leistungen bis ${fmtDateShort(sub.cancelAt || sub.currentPeriodEnd)}`
|
||||
: sub.serviceStartsAt
|
||||
? `Beginn und erste Abbuchung am ${fmtDateShort(sub.serviceStartsAt)}`
|
||||
: `nächste Abbuchung ${fmtDateShort(sub.currentPeriodEnd)}`
|
||||
}
|
||||
|
||||
/* Seitenleiste: laufendes Abo ("Ihr Vertrag") */
|
||||
function renderAboSideActive(card, data) {
|
||||
const sub = data.subscription
|
||||
const wartung = sub.productKey === 'wartung'
|
||||
const l = data.ledger || { balanceMinutes: 0 }
|
||||
// Status nur bei Kündigung; Zahlungsproblem (Hinweis unten) und späterer Beginn (Datumszeile) stehen im Text
|
||||
const status = aboIsCancelled(sub) ? '<span class="kb-status kb-status-off">gekündigt</span>' : ''
|
||||
card.classList.remove('kb-abo-offer')
|
||||
card.innerHTML = `
|
||||
<p class="kb-label">Ihre Wartung</p>
|
||||
<p class="kb-label">Ihr Vertrag</p>
|
||||
${status}
|
||||
<h3>${escapeHtml(productLabelOf(data, sub.productKey))}</h3>
|
||||
${wartung ? `
|
||||
<button type="button" class="kb-hours" data-view="stunden" title="Stundenkonto und Verlauf ansehen" aria-label="${fmtHours(l.balanceMinutes)} Arbeitszeit verfügbar – Stundenkonto und Verlauf ansehen">
|
||||
<strong>${fmtHours(l.balanceMinutes)}</strong><span>Arbeitszeit verfügbar</span><em aria-hidden="true">›</em>
|
||||
</button>
|
||||
<p class="kb-sub">${dateLine}</p>
|
||||
</button>` : `
|
||||
<p class="kb-price"><strong>${fmtEuro(sub.priceAmount / 100)}</strong><span>pro Monat, gemäß § 19 UStG ohne Umsatzsteuer</span></p>`}
|
||||
<p class="kb-sub">${aboDateLine(sub)}</p>
|
||||
${sub.switchPending ? '<p class="kb-side-alert">Ihr Wechsel wartet auf die Zahlung.</p>' : ''}
|
||||
${sub.paymentProblem ? `
|
||||
<p class="kb-side-alert">Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart, damit Ihre Website online bleibt.</p>
|
||||
<button type="button" class="kb-side-btn" id="abo-manage">Zahlungsart aktualisieren ↗</button>` : ''}
|
||||
<button type="button" class="kb-side-btn" data-view="takeover">Übernahmegutschrift beantragen <span class="kb-badge hidden" id="takeover-badge" title="Rückfrage offen"></span></button>
|
||||
${sub.withdrawalOpen ? `<a class="kb-side-btn" href="/widerrufen.html">Vertrag widerrufen <small>· bis ${fmtDayMonth(sub.withdrawalUntil)}</small></a>` : ''}
|
||||
${sub.withdrawalOpen ? `<a class="kb-side-btn kb-withdraw" href="/widerrufen.html">Vertrag widerrufen <small>· bis ${fmtDayMonth(sub.withdrawalUntil)}</small></a>` : ''}
|
||||
<div class="kb-divider"></div>
|
||||
<p class="kb-fine" style="margin-top:0">${aboTermText(sub)}</p>
|
||||
<p class="error hidden" id="abo-error"></p>`
|
||||
@@ -884,7 +893,7 @@ function renderAboOrderView(view, data) {
|
||||
<p class="abo-note">Gültig bis ${fmtDateShort(offer.validUntil)} · wird beim Abschluss automatisch berücksichtigt</p>
|
||||
</div>` : ''}
|
||||
<div class="kb-panel">
|
||||
${aboFeaturesHtml()}
|
||||
${aboFeaturesHtml(productOf(data, 'wartung')?.features)}
|
||||
<p class="abo-note" style="margin:0">Schon bei einem anderen Anbieter für Domain, Hosting oder E-Mail bezahlt? Nach dem Abschluss können Sie eine Übernahmegutschrift beantragen – wir rechnen die doppelt bezahlte Zeit anteilig an.</p>
|
||||
</div>
|
||||
${available ? `
|
||||
@@ -955,7 +964,7 @@ function renderAboOrderView(view, data) {
|
||||
}
|
||||
|
||||
/* Ansicht "Wartungs-Abo": Stundenkonto, Verlauf, Vertrag (Bedingungen § 4: jederzeit im Portal einsehbar) */
|
||||
function renderAboAccountView(view, data) {
|
||||
function renderHoursView(view, data) {
|
||||
const sub = data.subscription
|
||||
const l = data.ledger || { balanceMinutes: 0, usedMinutes: 0, credits: [], usage: [] }
|
||||
const usageRows = (l.usage || []).map((u) => `
|
||||
@@ -968,7 +977,7 @@ function renderAboAccountView(view, data) {
|
||||
|
||||
view.innerHTML = `
|
||||
<header class="kb-head">
|
||||
<h1>Wartungs-Abo</h1>
|
||||
<h1>Stundenkonto</h1>
|
||||
<p>Ihr Stundenkontingent, der Verlauf und Ihr Vertrag.</p>
|
||||
</header>
|
||||
${sub.paymentProblem ? '<div class="abo-alert">Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart über „Zahlungsart aktualisieren“ <span class="kb-desk-only">in der Seitenleiste</span><span class="kb-app-only">unter <a href="#vertraege" data-view="vertraege">Verträge</a></span>, damit Ihre Website online bleibt.</div>' : ''}
|
||||
@@ -982,7 +991,7 @@ function renderAboAccountView(view, data) {
|
||||
<p class="abo-note" style="margin-top:0">Jeden bezahlten Monat kommt 1 Stunde dazu. Angerechnet wird in 15-Minuten-Schritten, die ältesten Stunden zuerst. Aufträge schicken Sie uns einfach über den <a href="#chat" data-view="chat">Chat</a>.</p>
|
||||
${sub.offerText ? `<p class="abo-note">Ihr Angebot: ${escapeHtml(sub.offerText)}.</p>` : ''}
|
||||
<p class="abo-note">${aboTermText(sub)}</p>
|
||||
<p class="abo-note" style="margin-bottom:0"><a href="/abo-bedingungen.html" target="_blank" rel="noopener">Leistungen & Bedingungen</a></p>
|
||||
<p class="abo-note" style="margin-bottom:0"><a href="${escapeAttr(sub.termsUrl || '/abo-bedingungen.html')}" target="_blank" rel="noopener">Leistungen & Bedingungen</a></p>
|
||||
</div>
|
||||
<div class="kb-panel">
|
||||
<h3>Verlauf</h3>
|
||||
@@ -996,10 +1005,138 @@ function renderAboAccountView(view, data) {
|
||||
|
||||
const aboState = { data: null }
|
||||
|
||||
async function loadAboCard({ afterCheckout = false } = {}) {
|
||||
/* Seitenleiste bzw. Karte in der App-Ansicht: "Ihr Vertrag" */
|
||||
function renderSideCard(card, data) {
|
||||
if (data.subscription?.active) renderAboSideActive(card, data)
|
||||
else renderAboSideOffer(card, data, { ended: Boolean(data.subscription) })
|
||||
}
|
||||
|
||||
/* Zusatzspeicher: Hülle, gefüllt in Aufgabe 17 */
|
||||
function renderStoragePanel(container, data) {
|
||||
container.dataset.units = String(data?.subscription?.storage?.units ?? 0)
|
||||
}
|
||||
|
||||
/* "Postfach anfragen": öffnet den Chat mit vorbereiteter Nachricht (Eingabefeld wird nicht automatisch gesendet) */
|
||||
function requestMailbox() {
|
||||
showView('chat')
|
||||
const box = document.querySelector('#chat-composer textarea')
|
||||
if (!box) return
|
||||
box.value = 'Bitte legen Sie ein neues Postfach an: '
|
||||
box.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
box.focus()
|
||||
box.setSelectionRange(box.value.length, box.value.length)
|
||||
}
|
||||
|
||||
/* Vergleich der beiden Abos (kein laufendes Abo) */
|
||||
function renderCompareCard(data, key) {
|
||||
const product = productOf(data, key)
|
||||
if (!product) return ''
|
||||
const offer = data.offers?.[key]
|
||||
return `
|
||||
<article class="kb-panel kb-compare-card" data-product="${escapeAttr(key)}">
|
||||
<h3>${escapeHtml(productLabelOf(data, key))}</h3>
|
||||
${offer ? `<span class="kb-offer-tag">Ihr persönliches Angebot</span><p class="abo-note" style="margin:0 0 8px">${escapeHtml(offer.text)}${offer.validUntil ? ` · gültig bis ${fmtDateShort(offer.validUntil)}` : ''}</p>` : ''}
|
||||
<p class="kb-price"><strong>${fmtEuro(product.amountCents / 100)}</strong><span>pro Monat</span></p>
|
||||
<p class="abo-note">Gemäß § 19 UStG ohne Umsatzsteuer. ${product.minTermMonths} Monate Mindestlaufzeit, danach monatlich kündbar.</p>
|
||||
${aboFeaturesHtml(product.features)}
|
||||
<button type="button" class="kb-btn-primary kb-btn-block" data-view="bestellen-${escapeAttr(key)}"${data.available ? '' : ' disabled'}>Abo abschließen</button>
|
||||
</article>`
|
||||
}
|
||||
|
||||
/* Wechsel-Karte (nur Hosting) */
|
||||
function renderSwitchCard(data) {
|
||||
const sub = data.subscription
|
||||
if (sub.productKey !== 'hosting') return ''
|
||||
const wartung = productOf(data, 'wartung')
|
||||
if (sub.switchPending) {
|
||||
const url = sub.switchPending.paymentUrl
|
||||
return `
|
||||
<div class="kb-panel kb-switch-card" id="switch-card">
|
||||
<h3>Ihr Wechsel wartet auf die Zahlung</h3>
|
||||
<p class="abo-note" style="margin-top:0">Ihr Wechsel zum Wartungs-Abo ist erst abgeschlossen, wenn die Zahlung bestätigt ist.${url ? '' : ' Schreiben Sie uns gern im Chat, wenn Sie Hilfe brauchen.'}</p>
|
||||
${url ? `<a class="kb-btn-primary" href="${escapeAttr(url)}">Jetzt bezahlen</a>` : ''}
|
||||
</div>`
|
||||
}
|
||||
if (aboIsCancelled(sub)) {
|
||||
return `
|
||||
<div class="kb-panel kb-switch-card" id="switch-card">
|
||||
<h3>Wartungs-Abo</h3>
|
||||
<p class="abo-note" style="margin:0">Ihr Abo ist gekündigt. Bitte schreiben Sie uns im Chat, wenn Sie zum Wartungs-Abo wechseln möchten.</p>
|
||||
</div>`
|
||||
}
|
||||
if (!sub.canSwitch || !wartung) return ''
|
||||
return `
|
||||
<div class="kb-panel kb-switch-card" id="switch-card">
|
||||
<h3>Mehr als Hosting? Das Wartungs-Abo</h3>
|
||||
<p class="kb-price"><strong>${fmtEuro(wartung.amountCents / 100)}</strong><span>pro Monat</span></p>
|
||||
<p class="abo-note">Gemäß § 19 UStG ohne Umsatzsteuer. Zusätzlich zum Hosting: Pflege Ihrer Website, beliebig viele Postfächer und 1 Stunde Arbeitszeit pro Monat.</p>
|
||||
<button type="button" class="kb-btn-primary" data-view="wechsel">Zum Wartungs-Abo wechseln</button>
|
||||
</div>`
|
||||
}
|
||||
|
||||
/* Ansicht "Verträge": Mein Vertrag, Wechsel-Karte, Stundenkonto; ohne Abo der Vergleich */
|
||||
function renderContractsView(view, data) {
|
||||
const sub = data.subscription
|
||||
if (!sub?.active) {
|
||||
view.innerHTML = `
|
||||
${sub ? '<div class="abo-alert">Ihr bisheriges Abo ist beendet. Sie können jederzeit ein neues abschließen.</div>' : ''}
|
||||
${data.available ? '' : '<div class="abo-alert">Die Online-Bestellung wird gerade eingerichtet – schreiben Sie uns gern im Chat.</div>'}
|
||||
<div class="kb-compare">${renderCompareCard(data, 'hosting')}${renderCompareCard(data, 'wartung')}</div>
|
||||
<p class="abo-note">Alle Preise gemäß § 19 UStG ohne Umsatzsteuer. Es gelten die <a href="/abo-bedingungen.html" target="_blank" rel="noopener">Vertragsbedingungen</a>.</p>`
|
||||
return
|
||||
}
|
||||
const product = productOf(data, sub.productKey)
|
||||
const wartung = sub.productKey === 'wartung'
|
||||
const l = data.ledger || { balanceMinutes: 0 }
|
||||
const units = Number(sub.storage?.units || 0)
|
||||
const gb = (Number(data.mailStorage?.includedGb) || 0) + units * (Number(data.mailStorage?.gbPerUnit) || 0)
|
||||
const cancelled = aboIsCancelled(sub)
|
||||
const pill = cancelled
|
||||
? '<span class="kb-status kb-status-off">gekündigt</span>'
|
||||
: sub.paymentProblem ? '<span class="kb-status kb-status-warn">Zahlung offen</span>' : '<span class="kb-status kb-status-ok">aktiv</span>'
|
||||
view.innerHTML = `
|
||||
${sub.paymentProblem ? '<div class="abo-alert">Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart, damit Ihre Website online bleibt.</div>' : ''}
|
||||
${sub.serviceStartsAt ? `<div class="abo-alert">Wie gewählt beginnen wir nach Ablauf der Widerrufsfrist am ${fmtDateShort(sub.serviceStartsAt)}. Dann wird auch zum ersten Mal abgebucht.</div>` : ''}
|
||||
<div class="kb-panel" id="contract-card">
|
||||
<div class="kb-contract-head"><h2>Mein Vertrag</h2>${pill}</div>
|
||||
<h3>${escapeHtml(productLabelOf(data, sub.productKey))}</h3>
|
||||
<p class="kb-price"><strong>${fmtEuro(sub.priceAmount / 100)}</strong><span>pro Monat</span></p>
|
||||
<p class="abo-note">Gemäß § 19 UStG ohne Umsatzsteuer.</p>
|
||||
<dl class="kb-contract-facts">
|
||||
<div><dt>${cancelled ? 'Leistungen' : 'Abrechnung'}</dt><dd>${aboDateLine(sub)}</dd></div>
|
||||
<div><dt>Laufzeit</dt><dd>${aboTermText(sub)}</dd></div>
|
||||
${sub.offerText ? `<div><dt>Angebot</dt><dd>${escapeHtml(sub.offerText)}</dd></div>` : ''}
|
||||
<div><dt>E-Mail-Speicher</dt><dd>${gb} GB${units ? ` (${units} Zusatzpaket${units === 1 ? '' : 'e'})` : ''}</dd></div>
|
||||
</dl>
|
||||
${aboFeaturesHtml(product?.features)}
|
||||
<p class="abo-note"><a href="${escapeAttr(sub.termsUrl || '/abo-bedingungen.html')}" target="_blank" rel="noopener">Vertragsbedingungen</a>
|
||||
· <a href="${escapeAttr(sub.termsPdfUrl || '/WEBklar-Abo-Vertragsunterlagen.pdf')}" target="_blank" rel="noopener">als PDF</a></p>
|
||||
${sub.withdrawalOpen ? `<a class="kb-side-btn kb-withdraw" href="/widerrufen.html">Vertrag widerrufen <small>· bis ${fmtDayMonth(sub.withdrawalUntil)}</small></a>` : ''}
|
||||
<div class="kb-contract-actions">
|
||||
${sub.paymentProblem ? '<button type="button" class="kb-btn-primary" id="contract-manage">Zahlungsart aktualisieren</button>' : ''}
|
||||
<button type="button" class="kb-side-btn" id="contract-mailbox">Postfach anfragen</button>
|
||||
<a class="kb-side-btn" href="/kuendigen.html">Verträge hier kündigen</a>
|
||||
</div>
|
||||
<p class="error hidden" id="contract-error"></p>
|
||||
</div>
|
||||
${wartung ? `
|
||||
<div class="kb-panel" id="hours-card">
|
||||
<h3>Stundenkonto</h3>
|
||||
<p class="kb-price"><strong>${fmtHours(l.balanceMinutes)}</strong><span>Arbeitszeit verfügbar</span></p>
|
||||
<a href="#stunden" data-view="stunden">Stundenkonto</a> mit Verlauf ansehen
|
||||
</div>` : ''}
|
||||
${renderSwitchCard(data)}
|
||||
<div class="kb-panel" id="storage-panel"></div>`
|
||||
view.querySelector('#contract-manage')?.addEventListener('click', (ev) => openBillingPortal(ev.currentTarget, view.querySelector('#contract-error')))
|
||||
view.querySelector('#contract-mailbox')?.addEventListener('click', requestMailbox)
|
||||
renderStoragePanel(view.querySelector('#storage-panel'), data)
|
||||
}
|
||||
|
||||
async function loadContracts({ afterCheckout = false } = {}) {
|
||||
const card = document.getElementById('abo-card')
|
||||
const view = document.getElementById('view-stunden')
|
||||
if (!card || !view) return
|
||||
const contracts = document.getElementById('kb-contracts')
|
||||
if (!card || !view || !contracts) return
|
||||
try {
|
||||
let data = await api('/api/abo')
|
||||
// Direkt nach dem Checkout kann der Stripe-Webhook noch unterwegs sein
|
||||
@@ -1010,13 +1147,10 @@ async function loadAboCard({ afterCheckout = false } = {}) {
|
||||
data = await api('/api/abo')
|
||||
}
|
||||
aboState.data = data
|
||||
if (data.subscription?.active) {
|
||||
renderAboSideActive(card, data)
|
||||
renderAboAccountView(view, data)
|
||||
} else {
|
||||
renderAboSideOffer(card, data, { ended: Boolean(data.subscription) })
|
||||
renderAboOrderView(view, data)
|
||||
}
|
||||
renderSideCard(card, data)
|
||||
renderContractsView(contracts, data)
|
||||
if (data.subscription?.active && data.subscription.productKey === 'wartung') renderHoursView(view, data)
|
||||
else view.innerHTML = '<header class="kb-head"><h1>Stundenkonto</h1></header><div class="kb-panel"><p class="abo-note" style="margin:0">Ein Stundenkonto gibt es im Wartungs-Abo. Ihre Verträge finden Sie unter <a href="#vertraege" data-view="vertraege">Verträge</a>.</p></div>'
|
||||
card.classList.remove('hidden')
|
||||
document.getElementById('invoices-abo-note')?.classList.toggle('hidden', !data.subscription)
|
||||
document.getElementById('kb-sheet-abo')?.classList.toggle('hidden', !data.subscription)
|
||||
@@ -1506,7 +1640,7 @@ async function initDashboardPage() {
|
||||
|
||||
const aboParam = getQueryParam('abo')
|
||||
if (aboParam) window.history.replaceState(null, '', `/dashboard.html${window.location.hash}`)
|
||||
loadAboCard({ afterCheckout: aboParam === 'success' })
|
||||
loadContracts({ afterCheckout: aboParam === 'success' })
|
||||
loadCustomerInvoices()
|
||||
|
||||
const initialView = viewFromHash()
|
||||
@@ -1954,8 +2088,8 @@ function initAppShell() {
|
||||
renderInstallOffers()
|
||||
updateAppBar()
|
||||
|
||||
// Nach der Bestellung bei Stripe: Bestaetigung steht auf der Abo-Karte im Tab "Verträge"
|
||||
if (isAppLayout() && getQueryParam('abo') === 'success') showView('vertraege')
|
||||
// Nach der Bestellung bei Stripe: Bestaetigung steht in der Ansicht "Verträge" (Handy und Desktop)
|
||||
if (getQueryParam('abo') === 'success') showView('vertraege')
|
||||
}
|
||||
|
||||
// iOS zoomt beim Tippen in ein Feld hinein, wenn dessen Schrift effektiv unter 16 px liegt – z. B. bei
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tab "Verträge": die Abo-Karte der Seitenleiste als Übersicht (App-Ansicht);
|
||||
<!-- Tab "Verträge": Mein Vertrag, Vergleich, Wechsel, Zusatzspeicher (app.js); in der App-Ansicht steht die Abo-Karte der Seitenleiste darüber;
|
||||
Stundenkonto, Bestellung, Wechsel und Übernahmegutschrift öffnen sich als Unterseiten -->
|
||||
<section class="kb-view" id="view-vertraege">
|
||||
<header class="kb-head">
|
||||
@@ -192,6 +192,7 @@
|
||||
<p>Ihre Verträge mit WEBklar: Hosting, Wartung und Zusatzspeicher.</p>
|
||||
</header>
|
||||
<div id="kb-abo-slot"><p class="kb-empty">Lade…</p></div>
|
||||
<div id="kb-contracts"></div>
|
||||
</section>
|
||||
|
||||
<!-- Stundenkonto (Wartungs-Abo), Bestellungen und Wechsel (app.js, Aufgaben 15 bis 18) -->
|
||||
|
||||
@@ -810,3 +810,30 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
|
||||
.kb-shell.dashboard-content-reveal, .kb-view.active, .kb-sheet[open] .kb-sheet-panel, #loading:not(.hidden), .pd-status::before { animation: none !important; }
|
||||
.kb-header, .kb-nav, .kb-logo, .kb-app-title, .kb-sheet-panel, .kb-details-chev { transition: none !important; }
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ Ansicht "Verträge" (Aufgabe 15) --- */
|
||||
#kb-contracts { display: grid; gap: 16px; margin-top: 16px; }
|
||||
#kb-contracts > .kb-panel + .kb-panel, #kb-contracts .kb-compare .kb-panel + .kb-panel { margin-top: 0; }
|
||||
#kb-abo-slot:has(> .kb-abo:not(.hidden)) + #kb-contracts { margin-top: 16px; }
|
||||
.kb-contract-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; }
|
||||
.kb-contract-head h2 { margin: 0; font-size: 20px; }
|
||||
#contract-card h3 { margin: 4px 0 6px; }
|
||||
.kb-status-ok { color: #86efac; background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.4); }
|
||||
.kb-status-warn { color: #fcd34d; background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.4); }
|
||||
.kb-contract-facts { margin: 14px 0; display: grid; gap: 10px; }
|
||||
.kb-contract-facts div { display: grid; grid-template-columns: 9.5em minmax(0, 1fr); gap: 12px; font-size: 14px; }
|
||||
.kb-contract-facts dt { color: var(--kb-muted); }
|
||||
.kb-contract-facts dd { margin: 0; }
|
||||
.kb-contract-actions { display: grid; gap: 8px; margin-top: 12px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
|
||||
.kb-contract-actions .kb-side-btn { margin-top: 0; text-align: center; }
|
||||
.kb-withdraw { margin-top: 12px; border-color: rgba(251, 191, 36, 0.65); background: rgba(251, 191, 36, 0.12); }
|
||||
.kb-compare { display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.kb-compare .abo-features { grid-template-columns: 1fr; }
|
||||
.kb-switch-card .kb-btn-primary { display: inline-block; text-decoration: none; }
|
||||
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
|
||||
.kb-compare { grid-template-columns: 1fr; }
|
||||
.kb-contract-facts div { grid-template-columns: 1fr; gap: 2px; }
|
||||
#view-vertraege .kb-abo.kb-abo-offer { display: none; }
|
||||
#kb-contracts { margin-top: 16px; }
|
||||
}
|
||||
#storage-panel:empty { display: none; }
|
||||
Reference in new issue
Block a user