Kündigung und Widerruf: Vertrag bzw. Bestellung auswählbar (§ 312k, § 356a BGB)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
f66a713531
commit
7ffe95f24a
10 files changed
+992
-87
No files matched your search
+15
-6
@@ -14,6 +14,18 @@
|
||||
<h1>Verträge hier kündigen</h1>
|
||||
|
||||
<form id="decl-form" class="card" novalidate>
|
||||
<!-- § 312k Abs. 3 BGB: eindeutige Bezeichnung des Vertrags - das Abo (mit Zusatzspeicher) oder nur der Zusatzspeicher -->
|
||||
<fieldset>
|
||||
<legend data-label-for="contract" data-label="Vertrag">Vertrag</legend>
|
||||
<label class="radio"><input type="radio" name="contract" value="abo" data-text="Mein Abo (Hosting- oder Wartungs-Abo, mit Zusatzspeicher)" checked> Mein Abo (Hosting- oder Wartungs-Abo, mit Zusatzspeicher)</label>
|
||||
<label class="radio"><input type="radio" name="contract" value="zusatzspeicher" data-text="Nur den E-Mail-Zusatzspeicher"> Nur den E-Mail-Zusatzspeicher</label>
|
||||
</fieldset>
|
||||
<div id="packages-wrap" class="hidden">
|
||||
<label for="packages" data-label-for="packages" data-label="Pakete">Zahl der Pakete, die Sie kündigen <small>(je 5 GB; leer = alle gebuchten Pakete)</small></label>
|
||||
<input id="packages" name="packages" type="number" inputmode="numeric" min="1" max="10" step="1" placeholder="alle">
|
||||
<p class="muted">Die Kündigung des Zusatzspeichers wirkt zum Ende des laufenden Abrechnungsmonats. Das geht auch, wenn Ihr Abo schon gekündigt ist.</p>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-label-for="kind" data-label="Art der Kündigung">Art der Kündigung</legend>
|
||||
<label class="radio"><input type="radio" name="kind" value="ordentlich" data-text="Ordentliche Kündigung" checked> Ordentliche Kündigung</label>
|
||||
@@ -34,10 +46,7 @@
|
||||
<label for="customerNumber" data-label-for="customerNumber" data-label="Kundennummer">Kundennummer <small>(optional)</small></label>
|
||||
<input id="customerNumber" name="customerNumber" maxlength="50">
|
||||
|
||||
<label>Vertrag</label>
|
||||
<input value="WEBklar Wartungs-Abo" disabled>
|
||||
|
||||
<fieldset>
|
||||
<fieldset id="when-wrap">
|
||||
<legend>Zeitpunkt</legend>
|
||||
<label class="radio"><input type="radio" name="when" value="naechstmoeglich" checked> zum nächstmöglichen Zeitpunkt</label>
|
||||
<label class="radio"><input type="radio" name="when" value="datum"> zu einem bestimmten Datum</label>
|
||||
@@ -52,13 +61,13 @@
|
||||
<h2 style="margin-top:0">Ihre Kündigung ist eingegangen</h2>
|
||||
<p>Eingang: <strong id="done-at"></strong></p>
|
||||
<dl id="done-list"></dl>
|
||||
<p>Die Bestätigung mit dem Zeitpunkt, zu dem Ihr Vertrag endet, haben wir an Ihre E-Mail-Adresse geschickt.</p>
|
||||
<p>Eine Eingangsbestätigung mit diesen Angaben haben wir an Ihre E-Mail-Adresse geschickt. Den Zeitpunkt, zu dem die Kündigung wirkt, bestätigen wir Ihnen per E-Mail.</p>
|
||||
<button type="button" class="print-btn" id="print-btn">Diese Bestätigung drucken oder als PDF speichern</button>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/legal-form.js"></script>
|
||||
<script>
|
||||
initLegalForm({ endpoint: '/api/abo/public/cancel', fields: ['kind', 'reason', 'name', 'email', 'customerNumber', 'effective'] })
|
||||
initLegalForm({ endpoint: '/api/abo/public/cancel', fields: ['contract', 'packages', 'kind', 'reason', 'name', 'email', 'customerNumber', 'effective'] })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+31
-10
@@ -1,6 +1,8 @@
|
||||
// Oeffentliche Erklaerungsseiten: Kuendigung (§ 312k BGB) und Widerruf (§ 356a BGB).
|
||||
// Ohne Login erreichbar, Formular sofort sichtbar (keine Zwischenschritte).
|
||||
// Nach dem Absenden: Bestaetigung mit Datum/Uhrzeit, speicher- und druckbar.
|
||||
// Der Kunde waehlt den Vertrag (Kuendigung: Abo oder nur der Zusatzspeicher, ganz oder mit Paketzahl) bzw. die
|
||||
// Bestellung (Widerruf: Abschluss, Wechsel, Zusatzspeicher); die Auswahl geht in den Request und steht in der
|
||||
// Bestaetigung. Nach dem Absenden: Bestaetigung mit Datum/Uhrzeit, speicher- und druckbar.
|
||||
function initLegalForm({ endpoint, fields }) {
|
||||
const form = document.getElementById('decl-form')
|
||||
const done = document.getElementById('done')
|
||||
@@ -21,23 +23,34 @@ function initLegalForm({ endpoint, fields }) {
|
||||
|
||||
const reasonWrap = document.getElementById('reason-wrap')
|
||||
form.querySelectorAll('[name="kind"]').forEach((r) =>
|
||||
r.addEventListener('change', () => reasonWrap?.classList.toggle('hidden', form.kind.value !== 'ausserordentlich'))
|
||||
r.addEventListener('change', () => reasonWrap?.classList.toggle('hidden', form.elements.kind.value !== 'ausserordentlich'))
|
||||
)
|
||||
const dateInput = document.getElementById('effective-date')
|
||||
form.querySelectorAll('[name="when"]').forEach((r) =>
|
||||
r.addEventListener('change', () => {
|
||||
const onDate = form.when.value === 'datum'
|
||||
const onDate = form.elements.when.value === 'datum'
|
||||
dateInput?.classList.toggle('hidden', !onDate)
|
||||
if (onDate) dateInput?.focus()
|
||||
})
|
||||
)
|
||||
// Nur der Zusatzspeicher: Paketzahl abfragen; der Zeitpunkt entfaellt (Ende des Abrechnungsmonats)
|
||||
const storageOnly = () => form.elements.contract?.value === 'zusatzspeicher'
|
||||
const packagesWrap = document.getElementById('packages-wrap')
|
||||
const whenWrap = document.getElementById('when-wrap')
|
||||
form.querySelectorAll('[name="contract"]').forEach((r) =>
|
||||
r.addEventListener('change', () => {
|
||||
packagesWrap?.classList.toggle('hidden', !storageOnly())
|
||||
whenWrap?.classList.toggle('hidden', storageOnly())
|
||||
})
|
||||
)
|
||||
|
||||
form.addEventListener('submit', async (ev) => {
|
||||
ev.preventDefault()
|
||||
errEl.classList.add('hidden')
|
||||
const payload = {}
|
||||
for (const f of fields) payload[f] = String(form.elements[f]?.value ?? '').trim()
|
||||
if (form.when?.value === 'datum') payload.effective = dateInput?.value || ''
|
||||
if (form.elements.when?.value === 'datum' && !storageOnly()) payload.effective = dateInput?.value || ''
|
||||
if (form.elements.contract && !storageOnly()) delete payload.packages
|
||||
const btn = form.querySelector('button[type="submit"]')
|
||||
btn.disabled = true
|
||||
try {
|
||||
@@ -50,17 +63,25 @@ function initLegalForm({ endpoint, fields }) {
|
||||
if (!res.ok) throw new Error(data.error || `Fehler ${res.status}`)
|
||||
const at = new Date(data.receivedAt).toLocaleString('de-DE', { dateStyle: 'full', timeStyle: 'medium' })
|
||||
document.getElementById('done-at').textContent = at
|
||||
const rows = [['Vertrag', 'WEBklar Wartungs-Abo']]
|
||||
// Zeile "Vertrag": die gewaehlte Beschriftung (data-text des Radioknopfs) statt eines festen Namens
|
||||
const rows = []
|
||||
for (const f of fields) {
|
||||
if (!payload[f] || f === 'effective') continue
|
||||
if (f === 'effective') continue
|
||||
if (f === 'packages') {
|
||||
if (storageOnly()) rows.push(['Pakete', payload.packages ? `${payload.packages} Paket(e) zu je 5 GB` : 'alle gebuchten Pakete'])
|
||||
continue
|
||||
}
|
||||
if (!payload[f]) continue
|
||||
const label = form.querySelector(`[data-label-for="${f}"]`)?.dataset.label || f
|
||||
const value = form.querySelector(`[name="${f}"]:checked`)?.dataset.text || payload[f]
|
||||
rows.push([label, value])
|
||||
}
|
||||
if (form.when) {
|
||||
rows.push(['Zeitpunkt', payload.effective
|
||||
? `zum ${new Date(payload.effective + 'T12:00:00').toLocaleDateString('de-DE')} (Ende des dann laufenden Abrechnungsmonats)`
|
||||
: 'zum nächstmöglichen Zeitpunkt'])
|
||||
if (form.elements.when) {
|
||||
rows.push(['Zeitpunkt', storageOnly()
|
||||
? 'zum Ende des laufenden Abrechnungsmonats'
|
||||
: payload.effective
|
||||
? `zum ${new Date(payload.effective + 'T12:00:00').toLocaleDateString('de-DE')} (Ende des dann laufenden Abrechnungsmonats)`
|
||||
: 'zum nächstmöglichen Zeitpunkt'])
|
||||
}
|
||||
document.getElementById('done-list').innerHTML = rows.map(([k, v]) => `<dt>${esc(k)}</dt><dd>${esc(v)}</dd>`).join('')
|
||||
form.classList.add('hidden')
|
||||
|
||||
@@ -14,12 +14,17 @@
|
||||
<h1>Vertrag widerrufen</h1>
|
||||
|
||||
<form id="decl-form" class="card" novalidate>
|
||||
<!-- § 356a BGB: welche Bestellung widerrufen wird (jede hat ihre eigene Widerrufsfrist) -->
|
||||
<fieldset>
|
||||
<legend data-label-for="order" data-label="Vertrag">Vertrag</legend>
|
||||
<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>
|
||||
</fieldset>
|
||||
|
||||
<label for="name" data-label-for="name" data-label="Name">Name</label>
|
||||
<input id="name" name="name" autocomplete="name" required maxlength="200">
|
||||
|
||||
<label>Vertrag</label>
|
||||
<input value="WEBklar Wartungs-Abo" disabled>
|
||||
|
||||
<label for="customerNumber" data-label-for="customerNumber" data-label="Kundennummer">Kundennummer <small>(optional)</small></label>
|
||||
<input id="customerNumber" name="customerNumber" maxlength="50">
|
||||
|
||||
@@ -40,7 +45,7 @@
|
||||
</main>
|
||||
<script src="/legal-form.js"></script>
|
||||
<script>
|
||||
initLegalForm({ endpoint: '/api/abo/public/withdraw', fields: ['name', 'customerNumber', 'email'] })
|
||||
initLegalForm({ endpoint: '/api/abo/public/withdraw', fields: ['order', 'name', 'customerNumber', 'email'] })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user