Ihr Stundenkontingent, der Verlauf und Ihr Vertrag.
- ${sub.paymentProblem ? 'Wie gewählt beginnen wir nach Ablauf der Widerrufsfrist am ${fmtDateShort(sub.serviceStartsAt)}. Dann wird auch zum ersten Mal abgebucht, und Ihre erste Stunde steht bereit.
` : ''}
${fmtHours(l.balanceMinutes)}verfügbare Zeit
@@ -998,7 +998,7 @@ const aboState = { data: null }
async function loadAboCard({ afterCheckout = false } = {}) {
const card = document.getElementById('abo-card')
- const view = document.getElementById('view-abo')
+ const view = document.getElementById('view-stunden')
if (!card || !view) return
try {
let data = await api('/api/abo')
@@ -1020,6 +1020,8 @@ async function loadAboCard({ afterCheckout = false } = {}) {
card.classList.remove('hidden')
document.getElementById('invoices-abo-note')?.classList.toggle('hidden', !data.subscription)
document.getElementById('kb-sheet-abo')?.classList.toggle('hidden', !data.subscription)
+ // Vertragsbedingungen im Konto-Menue: die Fassung, die fuer das Abo des Kunden gilt
+ document.getElementById('kb-sheet-terms')?.setAttribute('href', data.subscription?.termsUrl || '/abo-bedingungen.html')
loadTakeoverCard({ abo: data })
document.dispatchEvent(new CustomEvent('kb:abo', { detail: data }))
} catch (err) {
@@ -1242,7 +1244,7 @@ async function loadTakeoverCard({ abo, notice = '' } = {}) {
const requests = data.requests || []
updateTakeoverBadge(requests)
if (!active && !requests.length) {
- card.innerHTML = '
Die Übernahmegutschrift können Sie beantragen, sobald Ihr Abo läuft. Zum Abo
'
+ card.innerHTML = '
Die Übernahmegutschrift können Sie beantragen, sobald Ihr Abo läuft. Zum Abo
'
return
}
@@ -1392,16 +1394,18 @@ function initPasswordView() {
}
/* ---------------------------------------------------------- Navigation --- */
-// Ansichten im Inhaltsbereich; Hash fuer Direktlinks (z. B. aus Mails: #abo, #gutschrift).
-// "wartung" gibt es nur in der App-Ansicht (Tab), am Desktop zeigt sie das Wartungs-Abo.
+// Ansichten im Inhaltsbereich; Hash fuer Direktlinks (z. B. aus Mails: #vertraege, #gutschrift).
+// Die alten Hashes #wartung und #abo fuehren zu #vertraege (HASH_ALIASES).
// "projekt" (Projektdetails als eigene Seite) gibt es ebenfalls nur in der App-Ansicht.
// "passwort" oeffnet das Konto-Menue (Profilbild).
-const VIEW_HASHES = { projects: '', activity: 'fortschritt', invoices: 'rechnungen', chat: 'chat', wartung: 'wartung', abo: 'abo', takeover: 'gutschrift', projekt: 'projekt', passwort: 'passwort' }
+const VIEW_HASHES = { projects: '', activity: 'fortschritt', invoices: 'rechnungen', chat: 'chat', vertraege: 'vertraege', stunden: 'stunden', 'bestellen-hosting': 'bestellen-hosting', 'bestellen-wartung': 'bestellen-wartung', wechsel: 'wechsel', takeover: 'gutschrift', projekt: 'projekt', passwort: 'passwort' }
+const HASH_ALIASES = { wartung: 'vertraege', abo: 'vertraege' }
const viewLoaders = { activity: loadCustomerActivity, chat: initChat }
const loadedViews = new Set()
function viewFromHash() {
const hash = window.location.hash.replace(/^#/, '')
+ if (HASH_ALIASES[hash]) return HASH_ALIASES[hash]
return Object.keys(VIEW_HASHES).find((v) => VIEW_HASHES[v] && VIEW_HASHES[v] === hash) || ''
}
@@ -1506,7 +1510,8 @@ async function initDashboardPage() {
loadCustomerInvoices()
const initialView = viewFromHash()
- if (initialView) showView(initialView, { updateHash: false })
+ // Alte Hashes (#wartung, #abo) gleich auf den neuen (#vertraege) umschreiben
+ if (initialView) showView(initialView, { updateHash: Boolean(HASH_ALIASES[window.location.hash.replace(/^#/, '')]) })
const [{ projects }, { features }] = await Promise.all([
api('/api/projects'),
@@ -1551,8 +1556,8 @@ const appLayout = window.matchMedia('(max-width: 880px), (pointer: coarse) and (
const coarsePointer = window.matchMedia('(pointer: coarse)')
// Unterseite -> Tab, zu dem sie gehoert (und Text neben dem Zurueck-Pfeil).
// "passwort" setzt onAppViewShown: die Ansicht, aus der das Konto-Menue geoeffnet wurde.
-const APP_PARENT = { abo: 'wartung', takeover: 'wartung', projekt: 'projects' }
-const APP_PARENT_LABEL = { wartung: 'Wartung', projects: 'Projekte', activity: 'Fortschritt', invoices: 'Rechnungen', chat: 'Chat' }
+const APP_PARENT = { stunden: 'vertraege', 'bestellen-hosting': 'vertraege', 'bestellen-wartung': 'vertraege', wechsel: 'vertraege', takeover: 'vertraege', projekt: 'projects' }
+const APP_PARENT_LABEL = { vertraege: 'Verträge', projects: 'Projekte', activity: 'Fortschritt', invoices: 'Rechnungen', chat: 'Chat' }
const appShell = { view: 'projects', scroll: {}, fromHistory: false, sideCards: [], installPrompt: null }
const TEXT_INPUT = 'textarea, input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=date]):not([type=button]):not([type=submit]):not([type=range]):not([type=color])'
@@ -1573,7 +1578,7 @@ function isIosDevice() {
return /iphone|ipad|ipod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
}
-// Karten der Seitenleiste: App-Ansicht -> Ansprechpartner ins Konto-Sheet, Abo-Karte in den Tab "Wartung"
+// Karten der Seitenleiste: App-Ansicht -> Ansprechpartner ins Konto-Sheet, Abo-Karte in den Tab "Verträge"
function placeSideCards() {
for (const { el, slot, anchor } of appShell.sideCards) {
if (isAppLayout()) {
@@ -1613,11 +1618,6 @@ function updateAppBar() {
}
function onAppViewShown(name) {
- // Den Tab "Wartung" gibt es nur in der App-Ansicht; am Desktop direkt das Wartungs-Abo zeigen
- if (name === 'wartung' && !isAppLayout()) {
- showView('abo', { updateHash: !appShell.fromHistory })
- return
- }
// Projektdetails als Seite nur in der App-Ansicht und nur mit gewaehltem Projekt (nicht per Direktlink)
if (name === 'projekt' && (!isAppLayout() || !projectPage.project)) {
showView('projects', { updateHash: !appShell.fromHistory })
@@ -1642,7 +1642,7 @@ function onAppViewShown(name) {
})
if (isAppLayout()) {
- // Unterseite mit eigenem Verlaufseintrag: "Zurück" (auch die Android-Geste) führt zum Tab darüber (Wartung bzw. Projekte)
+ // Unterseite mit eigenem Verlaufseintrag: "Zurück" (auch die Android-Geste) führt zum Tab darüber (Verträge bzw. Projekte)
if (nav === 'push' && !appShell.fromHistory) {
window.history.replaceState(null, '', VIEW_HASHES[parent] ? `#${VIEW_HASHES[parent]}` : window.location.pathname)
window.history.pushState({ kbSub: name }, '', `#${VIEW_HASHES[name]}`)
@@ -1670,10 +1670,10 @@ function revealChatComposer() {
if (Number.isFinite(delta) && Math.abs(delta) > 1) window.scrollBy(0, delta)
}
-/* Wartungs-Tab: Hinweis bei fehlgeschlagener Zahlung oder offener Rückfrage zur Gutschrift */
-function updateWartungBadge() {
+/* Tab "Verträge": Hinweis bei fehlgeschlagener Zahlung oder offener Rückfrage zur Gutschrift */
+function updateVertraegeBadge() {
const card = document.getElementById('abo-card')
- const badge = document.getElementById('kb-tab-wartung-badge')
+ const badge = document.getElementById('kb-tab-vertraege-badge')
if (!card || !badge) return
const paymentProblem = Boolean(card.querySelector('.kb-side-alert'))
const questions = Number(card.querySelector('#takeover-badge:not(.hidden)')?.textContent || 0)
@@ -1832,17 +1832,16 @@ function initAppShell() {
document.addEventListener('kb:view', (ev) => onAppViewShown(ev.detail.name))
document.addEventListener('kb:abo', (ev) => {
- document.getElementById('kb-tab-wartung')?.classList.toggle('hidden', !ev.detail)
const slot = document.getElementById('kb-abo-slot')
slot?.querySelector(':scope > .kb-empty')?.remove()
- if (!ev.detail) slot?.insertAdjacentHTML('afterbegin', '
Die Wartung konnte gerade nicht geladen werden. Bitte später noch einmal versuchen.
')
- updateWartungBadge()
+ if (!ev.detail) slot?.insertAdjacentHTML('afterbegin', '
Ihre Verträge konnten gerade nicht geladen werden. Bitte später noch einmal versuchen.
')
+ updateVertraegeBadge()
updateAppBar()
})
const aboCard = document.getElementById('abo-card')
- if (aboCard) new MutationObserver(updateWartungBadge).observe(aboCard, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] })
+ if (aboCard) new MutationObserver(updateVertraegeBadge).observe(aboCard, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] })
- // Zurueck: ueber den Verlauf, falls die Unterseite ihn angelegt hat, sonst direkt zur Wartung
+ // Zurueck: ueber den Verlauf, falls die Unterseite ihn angelegt hat, sonst direkt zu den Verträgen
document.getElementById('kb-back')?.addEventListener('click', () => {
if (window.history.state?.kbSub) window.history.back()
else showView(APP_PARENT[appShell.view] || 'projects')
@@ -1946,7 +1945,6 @@ function initAppShell() {
onMediaChange(appLayout, () => {
placeSideCards()
- if (!isAppLayout() && appShell.view === 'wartung') showView('abo')
if (!isAppLayout() && appShell.view === 'projekt') showView('projects')
updateAppBar()
renderInstallOffers()
@@ -1956,8 +1954,8 @@ function initAppShell() {
renderInstallOffers()
updateAppBar()
- // Nach der Bestellung bei Stripe: Bestaetigung steht auf der Abo-Karte im Tab "Wartung"
- if (isAppLayout() && getQueryParam('abo') === 'success') showView('wartung')
+ // Nach der Bestellung bei Stripe: Bestaetigung steht auf der Abo-Karte im Tab "Verträge"
+ if (isAppLayout() && getQueryParam('abo') === 'success') showView('vertraege')
}
// iOS zoomt beim Tippen in ein Feld hinein, wenn dessen Schrift effektiv unter 16 px liegt – z. B. bei
diff --git a/public/dashboard.html b/public/dashboard.html
index e823884..1da14ea 100644
--- a/public/dashboard.html
+++ b/public/dashboard.html
@@ -30,10 +30,10 @@