From 0129ac48e7ae4ac97ede8a9320c467b5598abf6c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 15:01:09 +0000 Subject: [PATCH] =?UTF-8?q?Kundenbereich:=20=E2=80=9EWartung=E2=80=9C=20he?= =?UTF-8?q?i=C3=9Ft=20jetzt=20=E2=80=9EVertr=C3=A4ge=E2=80=9C,=20auch=20am?= =?UTF-8?q?=20Desktop=20in=20der=20Navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- public/app.js | 56 +++++++++++++++++++--------------------- public/dashboard.html | 41 +++++++++++++++-------------- public/kundenbereich.css | 27 ++++++++++--------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/public/app.js b/public/app.js index afd2a9e..2906a2d 100644 --- a/public/app.js +++ b/public/app.js @@ -816,7 +816,7 @@ function renderAboSideOffer(card, data, { ended }) { : '50 €pro Monat'}

${offer?.validUntil ? `

Angebot gültig bis ${fmtDateShort(offer.validUntil)}

` : ''} - +

${data.available ? '12 Monate Mindestlaufzeit. Gemäß § 19 UStG ohne Umsatzsteuer.' : 'Die Online-Bestellung wird gerade eingerichtet – schreiben Sie uns gern im Chat.'}

` @@ -837,7 +837,7 @@ function renderAboSideActive(card, data) { card.innerHTML = `

Ihre Wartung

${status} -

${dateLine}

@@ -971,7 +971,7 @@ function renderAboAccountView(view, data) {

Wartungs-Abo

Ihr Stundenkontingent, der Verlauf und Ihr Vertrag.

- ${sub.paymentProblem ? '
Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart über „Zahlungsart aktualisieren“ in der Seitenleisteunter Wartung, damit Ihre Website online bleibt.
' : ''} + ${sub.paymentProblem ? '
Die letzte Abbuchung ist fehlgeschlagen. Bitte aktualisieren Sie Ihre Zahlungsart über „Zahlungsart aktualisieren“ in der Seitenleisteunter Verträge, damit Ihre Website online bleibt.
' : ''} ${sub.serviceStartsAt ? `
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 @@
- -
@@ -50,7 +50,7 @@
@@ -70,9 +70,10 @@

Verträge

@@ -90,8 +91,8 @@
- + @@ -184,18 +184,21 @@
- -
+ +
-

Wartung

-

Betrieb, Pflege und Support für Ihre Website.

+

Verträge

+

Ihre Verträge mit WEBklar: Hosting, Wartung und Zusatzspeicher.

Lade…

- -
+ +
+
+
+
diff --git a/public/kundenbereich.css b/public/kundenbereich.css index 79bff7b..c2c11d5 100644 --- a/public/kundenbereich.css +++ b/public/kundenbereich.css @@ -533,7 +533,7 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } } /* Elemente, die es nur in der App-Ansicht gibt */ -.kb-back, .kb-logo-mark, .kb-app-title, .kb-tab-ico, .kb-nav-item.kb-tab-wartung, .kb-app-only, .kb-install { display: none; } +.kb-back, .kb-logo-mark, .kb-app-title, .kb-tab-ico, .kb-app-only, .kb-install { display: none; } @keyframes kb-pop-in { from { opacity: 0; transform: translateY(-6px) scale(0.98); } } @keyframes kb-sheet-up { from { transform: translateY(100%); } } @@ -552,7 +552,7 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } /* ----------------------------------------------------- App-Ansicht (Handy) --- */ /* Unter 880px (und Handys quer): Kopfzeile als App-Leiste (Titel erscheint beim Scrollen), - Navigation als Tab-Leiste unten, Karten der Seitenleiste im Konto-Sheet bzw. Tab "Wartung". + Navigation als Tab-Leiste unten, Karten der Seitenleiste im Konto-Sheet bzw. Tab "Verträge". Dieselbe Bedingung steht in app.js (appLayout). */ @media (max-width: 880px), (pointer: coarse) and (max-height: 500px) { .kb-page { @@ -625,7 +625,6 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } padding: 4px 0 2px; border: none; border-radius: 12px; background: none; color: rgba(255, 255, 255, 0.55); font-size: 10.5px; font-weight: 600; letter-spacing: 0; text-align: center; touch-action: manipulation; } - .kb-nav-item.kb-tab-wartung { display: flex; } .kb-nav-item::before { position: absolute; top: 3px; left: 50%; width: 56px; height: 30px; margin-left: -28px; border-radius: 999px; background: transparent; box-shadow: none; transition: background-color 0.2s; @@ -726,18 +725,18 @@ button.kb-avatar:focus-visible { outline: 2px solid #fff; outline-offset: 2px; } /* Eingabe des Chat-Composers als Pille, passend zu dessen runden Knoepfen (Senden-Knopf: chat-composer.css) */ .chat-input-row textarea { box-sizing: border-box; min-height: 46px; padding: 11px 16px; border-radius: 23px; font-size: 16px; line-height: 1.35; } - /* Tab "Wartung": Abo-Karte gross; darunter Unterseiten Stundenkonto/Bestellung und Gutschrift */ + /* Tab "Verträge": Abo-Karte gross; darunter Unterseiten Stundenkonto/Bestellung und Gutschrift */ #kb-abo-slot:has(> .kb-abo:not(.hidden)) > .kb-empty { display: none; } - #view-wartung .kb-abo { padding: 20px 18px 18px; border-radius: 20px; } - #view-wartung .kb-abo h3 { font-size: 22px; } - #view-wartung .kb-price strong, #view-wartung .kb-hours strong { font-size: 32px; } - #view-wartung .kb-price span, #view-wartung .kb-hours span, #view-wartung .kb-sub, #view-wartung .kb-offer-until { font-size: 14px; } - #view-wartung .kb-checks { gap: 9px; font-size: 14.5px; } - #view-wartung .kb-fine { font-size: 12.5px; } - #view-wartung .kb-side-alert { font-size: 13.5px; } - #view-wartung .kb-side-btn { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 14px 16px; border-radius: 14px; font-size: 15px; } - #view-wartung .kb-side-btn .kb-badge { float: none; flex: none; margin: 0 0 0 auto; } - #view-wartung .kb-btn-primary { padding: 15px 22px; font-size: 15px; } + #view-vertraege .kb-abo { padding: 20px 18px 18px; border-radius: 20px; } + #view-vertraege .kb-abo h3 { font-size: 22px; } + #view-vertraege .kb-price strong, #view-vertraege .kb-hours strong { font-size: 32px; } + #view-vertraege .kb-price span, #view-vertraege .kb-hours span, #view-vertraege .kb-sub, #view-vertraege .kb-offer-until { font-size: 14px; } + #view-vertraege .kb-checks { gap: 9px; font-size: 14.5px; } + #view-vertraege .kb-fine { font-size: 12.5px; } + #view-vertraege .kb-side-alert { font-size: 13.5px; } + #view-vertraege .kb-side-btn { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 14px 16px; border-radius: 14px; font-size: 15px; } + #view-vertraege .kb-side-btn .kb-badge { float: none; flex: none; margin: 0 0 0 auto; } + #view-vertraege .kb-btn-primary { padding: 15px 22px; font-size: 15px; } .kb-stats { gap: 10px; } .kb-stat strong { font-size: 24px; } .abo-summary, .abo-summary tbody, .abo-summary tr, .abo-summary td { display: block; width: auto !important; }