Funktionen/News: Schlussdurchgang (Webhook-Retry, Checkout-Sitzung, Rechnungslink, Widerruf Funktion, UI- und Rechtstext-Korrekturen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-09-30 17:38:15 +00:00
1 parent 0dc57fe5e8
commit 4b7f73abba
22 files changed
+400 -57

No files matched your search

+2 -2
View File
@@ -137,7 +137,7 @@ function projectState(project) {
return ['kb-state-off', 'Noch keine Website']
}
const FEATURE_LABELS = { preview: 'Vorschau', chat: 'Chat', invoices: 'Rechnungen' }
const FEATURE_LABELS = { preview: 'Vorschau', chat: 'Chat', invoices: 'Rechnungen', news: 'News-Pinnwand' }
function renderProjectCard(project, features) {
const li = document.createElement('li')
@@ -146,7 +146,7 @@ function renderProjectCard(project, features) {
const url = project.previewUrl ? escapeAttr(project.previewUrl) : ''
const [stateClass, stateText] = projectState(project)
const extraChips = featuresForProject(features, project.id)
.map((f) => FEATURE_LABELS[f.featureKey] || f.featureKey)
.map((f) => FEATURE_LABELS[f.featureKey]) // unbekannte Schluessel nie roh anzeigen
.filter((label) => label && !['Vorschau', 'Chat', 'Rechnungen'].includes(label))
li.innerHTML = `
+1 -1
View File
@@ -168,7 +168,7 @@
</section>
<!-- News-Pinnwand: Buchung, Einrichtungsstatus oder Editor (funktionen.js, initNews) -->
<section class="kb-view" id="view-news" aria-live="polite">
<section class="kb-view" id="view-news">
<header class="kb-head">
<h1>News-Pinnwand</h1>
<p>Kurze Neuigkeiten, die auf deiner Website hängen.</p>
+2 -2
View File
@@ -86,9 +86,9 @@
<!-- ENTWURF, Freigabe Nutzer ausstehend -->
<h2 id="news">6a. News-Beiträge und Fotos</h2>
<p>Mit der Funktion „News-Pinnwand“ können Sie im Kundenportal kurze Beiträge mit Titel, Text und Foto schreiben, die auf Ihrer eigenen Website erscheinen. Dafür verarbeiten wir den Inhalt Ihrer Beiträge, hochgeladene Fotos, Zettelfarbe und Anzeigedauer sowie die Zeitpunkte von Anlage, Änderung und Abnahme. Beiträge und Fotos speichern wir auf unserem Server bei Hetzner im Rechenzentrum Falkenstein (Deutschland), wie die übrigen Portal-Daten (Abschnitt 3).</p>
<p>Jedes Foto wird beim Hochladen neu kodiert und verkleinert. Dabei entfernen wir alle Metadaten, auch Angaben zum Aufnahmeort (GPS) und zur Kamera. Nicht verwendete Uploads löschen wir spätestens nach 24 Stunden.</p>
<p>Jedes Foto wird beim Hochladen neu kodiert und verkleinert. Dabei entfernen wir alle Metadaten, auch Angaben zum Aufnahmeort (GPS) und zur Kamera. Nicht verwendete Uploads löschen wir nach etwa 24 Stunden.</p>
<p>Veröffentlichte Beiträge sind auf Ihrer Website öffentlich sichtbar, bis die von Ihnen gewählte Anzeigedauer endet oder Sie den Beitrag abnehmen. Die Besucher Ihrer Website sehen dabei nur den Inhalt der Beiträge, nicht Ihre Kundendaten. Sie können Beiträge und Fotos jederzeit im Kundenportal löschen. Was beim Aufruf der Pinnwand durch einen Besucher übertragen wird, steht in der Datenschutzerklärung Ihrer Website.</p>
<p>Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertrag). Nach dem Ende der Geschäftsbeziehung löschen wir Ihre Beiträge und Fotos zusammen mit den übrigen Portal-Daten nach den Fristen in Abschnitt 6 und 7 und schalten die Funktion ab.</p>
<p>Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO (Vertrag). Auf Ihren Wunsch oder wenn die Zusammenarbeit endet und wir die Funktion abschalten, ist die Pinnwand auf Ihrer Website nicht mehr erreichbar. Beiträge und Fotos löschen wir, wenn Sie sie selbst löschen, andernfalls nach den Fristen in Abschnitt 7.</p>
<h2>7. Abos und Zahlung über Stripe</h2>
<p>Schließen Sie eines unserer Abos ab, verarbeiten wir Name, E-Mail-Adresse, Rechnungsanschrift, das gewählte Abo und den gebuchten Zusatzspeicher, Abo- und Zahlungsstatus sowie Ihre Rechnungen. Zahlungsdaten wie Kartennummer oder IBAN geben Sie direkt bei unserem Zahlungsdienstleister ein. Wir erhalten davon nur eingeschränkte Angaben, etwa die Art des Zahlungsmittels und die letzten vier Ziffern.</p>
+2 -2
View File
@@ -34,7 +34,7 @@
'.wkn-polaroid figcaption{display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-top:12px}',
'.wkn-polaroid figcaption b{font:600 17px/1.25 var(--wkn-font-display);color:var(--wkn-ink);overflow-wrap:break-word;hyphens:auto}',
'.wkn-combo{container:wkn-card/inline-size}',
'.wkn-combo-in{display:grid;grid-template-columns:1.25fr 1fr;align-items:center}',
'.wkn-combo-in{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);align-items:center}',
'.wkn-combo .wkn-polaroid{transform:rotate(-2deg)}',
'.wkn-combo .wkn-note{margin-left:-28px;transform:rotate(2.5deg);z-index:1}',
'.wkn-combo::before{left:28%}',
@@ -57,7 +57,7 @@
'.wkn-more{margin-top:24px;padding-inline:20px}.wkn-more .wkn-btn{width:100%}',
'.wkn-swipehint{display:flex}',
'}',
'@container wkn-card (max-width:340px){.wkn-combo-in{grid-template-columns:1fr}.wkn-combo .wkn-note{margin:-26px 12px 0 26px}}',
'@container wkn-card (max-width:340px){.wkn-combo-in{grid-template-columns:minmax(0,1fr)}.wkn-combo .wkn-note{margin:-26px 12px 0 26px}}',
'@media (prefers-reduced-motion:reduce){.wkn-pin,.wkn-btn,.wkn-dots i{transition:none}}'
].join('\n')
+11 -4
View File
@@ -64,7 +64,7 @@ const isSetup = (state) => state === 'ordered' || state === 'setup'
// Vorauswahl: zuerst ein aktives Projekt, dann eins in Einrichtung, sonst das erste
function pickProject(projects) {
return projects.find((p) => newsOf(p).state === 'active') || projects.find((p) => isSetup(newsOf(p).state)) || projects[0]
return projects.find((p) => newsOf(p).state === 'active') || projects.find((p) => isSetup(newsOf(p).state)) || projects.find((p) => newsOf(p).state === 'off') || projects[0]
}
/* ------------------------------------------------------------ Kacheln --- */
@@ -80,7 +80,7 @@ function newsTile(projects) {
const active = states.filter((s) => s === 'active').length
let pill = ['Aus', 'off']
let cta = `Einrichten lassen · ${PRICE}`
if (active) { pill = ['Aktiv', 'on']; cta = 'Beiträge schreiben' } else if (states.some(isSetup)) { pill = ['In Einrichtung', 'setup']; cta = 'Status ansehen' }
if (active) { pill = ['Aktiv', 'on']; cta = 'Beiträge schreiben' } else if (states.some(isSetup)) { pill = ['In Einrichtung', 'setup']; cta = 'Status ansehen' } else if (states.length && states.every((s) => s === 'disabled')) { pill = ['Abgeschaltet', 'off']; cta = 'Status ansehen' }
const target = pickProject(projects)
const count = projects.length > 1 && active ? `<p class="fn-count">aktiv für ${active} von ${projects.length} Websites</p>` : ''
return `
@@ -126,7 +126,14 @@ export async function initNews(root, { projectId } = {}) {
const project = projects.find((p) => p.projectId === selection.projectId) || pickProject(projects)
selection.projectId = project.projectId
const feature = newsOf(project)
const ordered = /[?&]bestellt=1\b/.test(window.location.hash)
// Rueckkehr von Stripe (#news?bestellt=1): Flag nur einmal auswerten und aus der Adresse nehmen
const orderedFlag = /[?&]bestellt=1\b/.test(window.location.hash)
if (orderedFlag) window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#news`)
const ordered = orderedFlag && isSetup(feature.state)
// Editor nicht neu aufbauen, solange Projekt und Zustand gleich sind: Entwurf und hochgeladenes Foto bleiben
const renderKey = `${project.projectId}:${feature.state}`
if (feature.state === 'active' && root.dataset.newsKey === renderKey && root.querySelector('.nw')) return
root.dataset.newsKey = feature.state === 'active' ? renderKey : ''
root.innerHTML = `
${projects.length > 1 ? `
@@ -140,7 +147,7 @@ export async function initNews(root, { projectId } = {}) {
initNews(root).catch((err) => { root.innerHTML = `<p class="muted">${esc(err.message)}</p>` })
})
const stage = root.querySelector('#news-stage')
const thanks = ordered && feature.state !== 'active'
const thanks = ordered
? '<p class="fn-thanks" role="status">Danke! Sobald die Zahlung da ist, legen wir los.</p>'
: ''
if (feature.state === 'active') renderEditor(stage, project)
@@ -60,10 +60,10 @@
<p>Der Einbau kostet 30,00 € einmalig, inklusive Umsatzsteuer. Die Nutzung der Pinnwand danach kostet nichts; es entstehen keine laufenden Kosten und kein Abo. Sie bezahlen bei der Bestellung im Kundenportal über Stripe und erhalten eine Rechnung.</p>
<h2 id="einbau">3. Einbau und Freischaltung</h2>
<p>Mit Ihrer Bestellung legen wir einen Auftrag für den Einbau an. Der Einbau dauert erfahrungsgemäß meist innerhalb von 1–2 Wochen. Das ist eine Einschätzung und keine verbindliche Zusage eines Termins. Nach Abschluss des Einbaus schalten wir die Funktion für Sie frei; ab dann erscheinen Ihre Beiträge auf der Website.</p>
<p>Mit Ihrer Bestellung legen wir einen Auftrag für den Einbau an. Der Einbau ist erfahrungsgemäß meist innerhalb von 1–2 Wochen abgeschlossen. Das ist eine Einschätzung und keine verbindliche Zusage eines Termins. Nach Abschluss des Einbaus schalten wir die Funktion für Sie frei; ab dann erscheinen Ihre Beiträge auf der Website.</p>
<h2 id="ende">4. Ende der Zusammenarbeit</h2>
<p>Endet die Zusammenarbeit mit uns, schalten wir die Pinnwand ab. Ihre Beiträge und Fotos löschen wir nach den Fristen der Datenschutzerklärung (<a href="/datenschutz.html#news">Abschnitt 6a</a>).</p>
<p>Auf Ihren Wunsch schalten wir die Pinnwand ab; das tun wir auch, wenn die Zusammenarbeit endet und wir die Funktion bei Ihnen abschalten. Ihre Beiträge und Fotos werden gelöscht, wenn Sie sie selbst löschen, andernfalls nach den Fristen der Datenschutzerklärung (<a href="/datenschutz.html#news">Abschnitt 6a</a>, dort Abschnitt 7).</p>
<h2 id="inhalte">5. Ihre Inhalte</h2>
<p>Sie sind für die Inhalte Ihrer Beiträge und Fotos verantwortlich. Sie versichern, dass Sie die nötigen Rechte an Texten und Fotos haben und dass die Inhalte keine Rechte Dritter verletzen, etwa Urheberrechte, Persönlichkeitsrechte oder das Recht am eigenen Bild abgebildeter Personen.</p>
+1
View File
@@ -20,6 +20,7 @@
<label class="radio"><input type="radio" name="order" value="abschluss" data-text="Abschluss meines Abos" checked> Abschluss meines Abos</label>
<label class="radio"><input type="radio" name="order" value="wechsel" data-text="Wechsel ins Wartungs-Abo"> Wechsel ins Wartungs-Abo</label>
<label class="radio"><input type="radio" name="order" value="zusatzspeicher" data-text="E-Mail-Zusatzspeicher"> E-Mail-Zusatzspeicher</label>
<label class="radio"><input type="radio" name="order" value="funktion" data-text="Funktion (z. B. News-Pinnwand)"> Funktion (z. B. News-Pinnwand)</label>
</fieldset>
<label for="name" data-label-for="name" data-label="Name">Name</label>