Recht (Web): Vertraege-hier-kuendigen-Button + Bestaetigungsseite im Hub

Kuendigungsbutton (Par. 312k BGB) im Hub-Footer unter dem
Widerrufsbutton, staendig verfuegbar; fuehrt unmittelbar auf die
Bestaetigungsseite mit dem Formular und der gesetzeskonformen
Bestaetigungsschaltflaeche 'jetzt kuendigen'. Eigener Error-Bag,
damit das Kontakt-Modal im Footer nicht mit aufspringt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-03 11:32:53 +00:00
parent 1c3a52ae3c
commit 52bc534e7d
2 changed files with 179 additions and 5 deletions

View file

@ -66,11 +66,19 @@
<li><a href="{{ route('datenschutz', ['edition' => 'de']) }}" class="hover:text-white transition-colors">Datenschutz</a></li>
<li><a href="{{ route('cookies', ['edition' => 'de']) }}" class="hover:text-white transition-colors">Cookies</a></li>
</ul>
{{-- Gesetzlicher Widerrufsbutton: gut sichtbare Schaltfläche mit klarer Aufschrift. --}}
<button type="button" x-data @click="$dispatch('open-withdrawal-modal')"
class="mt-4 inline-flex items-center justify-center px-4 py-2 text-[12.5px] font-semibold text-white border border-white/40 hover:border-white hover:bg-white/10 rounded-[4px] transition-colors cursor-pointer">
Vertrag widerrufen
</button>
{{-- Gesetzliche Buttons: Widerruf (Modal) + Kündigungsbutton § 312k BGB
(„Verträge hier kündigen", ständig verfügbar, führt unmittelbar
auf die Bestätigungsseite). --}}
<div class="mt-4 flex flex-col items-start gap-2.5">
<button type="button" x-data @click="$dispatch('open-withdrawal-modal')"
class="inline-flex items-center justify-center px-4 py-2 text-[12.5px] font-semibold text-white border border-white/40 hover:border-white hover:bg-white/10 rounded-[4px] transition-colors cursor-pointer">
Vertrag widerrufen
</button>
<a href="{{ route('kuendigung', ['edition' => 'de']) }}"
class="inline-flex items-center justify-center px-4 py-2 text-[12.5px] font-semibold text-white border border-white/40 hover:border-white hover:bg-white/10 rounded-[4px] transition-colors">
Verträge hier kündigen
</a>
</div>
</div>
</div>

View file

@ -0,0 +1,166 @@
@extends('web.layouts.web-master')
@section('title', 'Verträge hier kündigen')
@section('meta_description', 'Kündigen Sie Ihren laufenden Vertrag online – ohne Login, über die gesetzliche Bestätigungsseite nach § 312k BGB.')
{{--
Bestätigungsseite des gesetzlichen Kündigungsbuttons (§ 312k BGB).
Öffentlich ohne Login erreichbar; erfasst Art der Kündigung (Grund nur
bei außerordentlicher Kündigung), Zuordnungsdaten und den gewünschten
Beendigungszeitpunkt. Die Bestätigungsschaltfläche trägt gesetzeskonform
die Aufschrift „jetzt kündigen". Der Zugang wird sofort automatisch per
E-Mail bestätigt (TerminationController).
--}}
@section('content')
<x-web.static-page
eyebrow="Rechtliches"
title="Verträge hier kündigen"
breadcrumb="Kündigung"
lead="Hier können Sie Ihren laufenden Vertrag (z. B. Ihr Abo) online und ohne Anmeldung kündigen. Nach dem Absenden erhalten Sie sofort eine automatische Eingangsbestätigung per E-Mail mit Datum und Uhrzeit des Zugangs."
>
<div class="max-w-[680px]">
@if (session('termination-status'))
<div class="border border-bg-rule bg-bg-elev p-6" role="status">
<div class="section-eyebrow mb-3">Kündigung</div>
<h2 class="text-[19px] font-bold text-ink tracking-[-0.3px] m-0 mb-2">Kündigung eingegangen</h2>
<p class="text-[13.5px] text-ink-2 leading-[1.6] m-0">
{{ session('termination-status') }}
</p>
</div>
@else
<form method="POST" action="{{ route('kuendigung.store', ['edition' => 'de']) }}" novalidate
x-data="{
kind: @js(old('kind', 'ordentlich')),
terminateMode: @js(old('terminate_mode', 'asap')),
}">
@csrf
{{-- Art der Kündigung --}}
<fieldset class="border border-bg-rule p-5 m-0">
<legend class="text-[12px] font-semibold text-ink px-2">Art der Kündigung</legend>
<div class="space-y-3">
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="kind" value="ordentlich" x-model="kind" class="mt-0.5">
<span>
<span class="block text-[13.5px] font-semibold text-ink">Ordentliche Kündigung</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">Kündigung zum Ende der Laufzeit bzw. zum nächstmöglichen Zeitpunkt. Ein Grund ist nicht erforderlich.</span>
</span>
</label>
<label class="flex items-start gap-3 cursor-pointer">
<input type="radio" name="kind" value="ausserordentlich" x-model="kind" class="mt-0.5">
<span>
<span class="block text-[13.5px] font-semibold text-ink">Außerordentliche Kündigung</span>
<span class="block text-[12.5px] text-ink-3 leading-[1.5]">Kündigung aus wichtigem Grund mit sofortiger Wirkung.</span>
</span>
</label>
</div>
@error('kind', 'termination')
<p class="mt-2 text-[12px] text-red-600">{{ $message }}</p>
@enderror
<div x-show="kind === 'ausserordentlich'" x-cloak class="mt-4">
<label for="termination-reason" class="block text-[12px] font-semibold text-ink mb-1.5">Kündigungsgrund</label>
<textarea id="termination-reason" name="reason" rows="3"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub">{{ old('reason') }}</textarea>
@error('reason', 'termination')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
</fieldset>
{{-- Zuordnung des Vertrags --}}
<fieldset class="border border-bg-rule p-5 m-0 mt-6">
<legend class="text-[12px] font-semibold text-ink px-2">Wer kündigt?</legend>
<div class="space-y-4">
<div>
<label for="termination-name" class="block text-[12px] font-semibold text-ink mb-1.5">Ihr Name</label>
<input id="termination-name" type="text" name="name" value="{{ old('name') }}" required autocomplete="name"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('name', 'termination')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="termination-email" class="block text-[12px] font-semibold text-ink mb-1.5">E-Mail-Adresse <span class="text-ink-3 font-normal">(zur Vertragszuordnung und für die Eingangsbestätigung)</span></label>
<input id="termination-email" type="email" name="email" value="{{ old('email') }}" required autocomplete="email"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('email', 'termination')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<label for="termination-reference" class="block text-[12px] font-semibold text-ink mb-1.5">
Vertrags-/Rechnungsnummer <span class="text-ink-3 font-normal">(falls zur Hand)</span>
</label>
<input id="termination-reference" type="text" name="contract_reference" value="{{ old('contract_reference') }}"
class="w-full border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('contract_reference', 'termination')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</fieldset>
{{-- Beendigungszeitpunkt --}}
<fieldset class="border border-bg-rule p-5 m-0 mt-6">
<legend class="text-[12px] font-semibold text-ink px-2">Zu welchem Zeitpunkt?</legend>
<div class="space-y-3">
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="terminate_mode" value="asap" x-model="terminateMode">
<span class="text-[13.5px] text-ink">Zum nächstmöglichen Zeitpunkt</span>
</label>
<label class="flex items-center gap-3 cursor-pointer">
<input type="radio" name="terminate_mode" value="date" x-model="terminateMode">
<span class="text-[13.5px] text-ink">Zu einem bestimmten Datum</span>
</label>
</div>
@error('terminate_mode', 'termination')
<p class="mt-2 text-[12px] text-red-600">{{ $message }}</p>
@enderror
<div x-show="terminateMode === 'date'" x-cloak class="mt-4">
<label for="termination-date" class="block text-[12px] font-semibold text-ink mb-1.5">Gewünschtes Beendigungsdatum</label>
<input id="termination-date" type="date" name="terminate_at" value="{{ old('terminate_at') }}"
class="border border-bg-rule px-3 py-2.5 text-[13px] bg-white text-ink focus:outline-none focus:border-hub" />
@error('terminate_at', 'termination')
<p class="mt-1 text-[12px] text-red-600">{{ $message }}</p>
@enderror
</div>
</fieldset>
{{-- Bot-Schutz: versteckte Honeypot-Felder (siehe TerminationController). --}}
<div class="hidden" aria-hidden="true">
<label for="termination-website">Website</label>
<input id="termination-website" type="text" name="website" tabindex="-1" autocomplete="off" value="{{ old('website') }}" />
<label for="termination-homepage">Homepage</label>
<input id="termination-homepage" type="text" name="homepage" tabindex="-1" autocomplete="off" value="{{ old('homepage') }}" />
</div>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mt-7">
<p class="text-[11px] text-ink-3 leading-[1.5] m-0 max-w-[380px]">
Ihre Angaben werden nur zur Bearbeitung der Kündigung verarbeitet
(<a href="{{ route('datenschutz', ['edition' => 'de']) }}" class="underline hover:text-ink">Datenschutz</a>).
Mit dem Klick geht Ihre Kündigungserklärung bei uns ein.
</p>
{{-- § 312k BGB: Bestätigungsschaltfläche, Aufschrift „jetzt kündigen". --}}
<button type="submit"
class="inline-flex items-center justify-center px-6 py-3 text-[14px] font-semibold text-white bg-hub hover:bg-hub-2 rounded-[4px] whitespace-nowrap transition-colors cursor-pointer">
jetzt kündigen
</button>
</div>
</form>
<p class="text-[12px] text-ink-3 leading-[1.6] mt-8 mb-0">
Hinweis: Möchten Sie stattdessen einen kürzlich geschlossenen Vertrag innerhalb der
14-tägigen Frist widerrufen, nutzen Sie bitte den Button „Vertrag widerrufen" im Fußbereich dieser Seite.
</p>
@endif
</div>
</x-web.static-page>
@endsection