Website: Abo-Seiten an die Vertragsbedingungen angeglichen (Wechsel, Zusatzspeicher, Sicherheit), AGB-Verweise, Tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-27 20:10:07 +00:00
1 parent dc27e508dd
commit 2c1a4c150c
5 files changed
+131 -66

No files matched your search

+12 -1
View File
@@ -2,7 +2,8 @@ import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner"; import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { useEffect } from "react";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
import { ThemeProvider } from "@/components/ThemeProvider"; import { ThemeProvider } from "@/components/ThemeProvider";
import { AnalyticsProvider } from "@/components/AnalyticsProvider"; import { AnalyticsProvider } from "@/components/AnalyticsProvider";
import Index from "./pages/Index"; import Index from "./pages/Index";
@@ -17,6 +18,15 @@ import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
/** Scrollt bei einem Seitenwechsel nach oben; Anker-Links (URL mit #) bleiben unberührt. */
const ScrollToTop = () => {
const { pathname, hash } = useLocation();
useEffect(() => {
if (!hash) window.scrollTo(0, 0);
}, [pathname, hash]);
return null;
};
const App = () => ( const App = () => (
<ThemeProvider> <ThemeProvider>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
@@ -24,6 +34,7 @@ const App = () => (
<Toaster /> <Toaster />
<Sonner /> <Sonner />
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}> <BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<ScrollToTop />
<AnalyticsProvider> <AnalyticsProvider>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
+5 -5
View File
@@ -124,10 +124,10 @@ const AGB = () => {
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2"> <ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li> <li>
Hosting bietet WEBklar als Hosting-Abo an. Für das Hosting-Abo und das Wartungs-Abo gelten die{" "} Hosting bietet WEBklar als Hosting-Abo an. Für das Hosting-Abo und das Wartungs-Abo gelten die{" "}
<a href="https://project.webklar.com/abo-bedingungen.html" className="text-foreground underline hover:no-underline">Vertragsbedingungen der WEBklar-Abos</a> <a href="https://project.webklar.com/abo-bedingungen.html" className="text-foreground underline hover:no-underline">Vertragsbedingungen für die WEBklar-Abos</a>
{" "}mit Leistungsbeschreibung; sie gehen diesen AGB vor. {" "}mit Leistungsbeschreibungen; sie gehen diesen AGB vor.
</li> </li>
<li>Bestehende Hostingverträge mit jährlicher Laufzeit behalten die bei ihrem Abschluss geltende Fassung dieses Moduls.</li> <li>Bestehende Hosting-Verträge mit jährlicher Laufzeit behalten die bei ihrem Abschluss vereinbarten Bedingungen.</li>
<li>WEBklar ist berechtigt, externe Anbieter (z. B. Rechenzentren) einzusetzen.</li> <li>WEBklar ist berechtigt, externe Anbieter (z. B. Rechenzentren) einzusetzen.</li>
<li>WEBklar übernimmt keine Haftung für Ausfälle externer Anbieter.</li> <li>WEBklar übernimmt keine Haftung für Ausfälle externer Anbieter.</li>
</ol> </ol>
@@ -194,7 +194,7 @@ const AGB = () => {
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2"> <ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Die Vergütung ergibt sich aus dem Angebot.</li> <li>Die Vergütung ergibt sich aus dem Angebot.</li>
<li>Projektleistungen sind nach Vereinbarung fällig.</li> <li>Projektleistungen sind nach Vereinbarung fällig.</li>
<li>Für das Hosting-Abo und das Wartungs-Abo gelten die Zahlungsbedingungen der Vertragsbedingungen der WEBklar-Abos (monatlich im Voraus).</li> <li>Für die Abos gelten die Vertragsbedingungen für die WEBklar-Abos.</li>
</ol> </ol>
</section> </section>
{contractDivider} {contractDivider}
@@ -243,7 +243,7 @@ const AGB = () => {
12. Kündigung 12. Kündigung
</h2> </h2>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2"> <ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>Laufzeitverträge (z. B. Hosting, Wartung) können zum Ende der jeweiligen Laufzeit gekündigt werden. Laufzeit und Kündigung des Hosting-Abos und des Wartungs-Abos richten sich nach den Vertragsbedingungen der WEBklar-Abos (Mindestlaufzeit 12 Monate, danach jederzeit zum Ende des Abrechnungsmonats kündbar).</li> <li>Laufzeitverträge können zum Ende der jeweiligen Laufzeit gekündigt werden. Laufzeit und Kündigung der Abos richten sich nach den Vertragsbedingungen für die WEBklar-Abos.</li>
<li>Das Recht zur außerordentlichen Kündigung bleibt unberührt.</li> <li>Das Recht zur außerordentlichen Kündigung bleibt unberührt.</li>
</ol> </ol>
</section> </section>
+30 -29
View File
@@ -19,7 +19,7 @@ const leistungen = [
}, },
{ {
titel: "1 E-Mail-Postfach", titel: "1 E-Mail-Postfach",
text: `Mit Ihrer eigenen Domain, 5 GB Speicher, Webmail und Zugriff per Outlook oder Smartphone. Mehr Platz gibt es über den E-Mail-Zusatzspeicher: je 5 GB für 1,00 € pro Monat (${STEUER}).`, text: `Unter Ihrer eigenen Domain, 5 GB Speicher, Webmail und Zugriff per Outlook oder Smartphone. Mehr Platz gibt es über den E-Mail-Zusatzspeicher: je 5 GB für 1,00 € pro Monat (${STEUER}).`,
}, },
{ {
titel: "Störungen inklusive", titel: "Störungen inklusive",
@@ -36,12 +36,12 @@ const HostingAbo = () => {
<Link to="/" className="flex items-center gap-2 group"> <Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span> <span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span>
</Link> </Link>
<Button asChild variant="ghost" className="text-muted-foreground hover:text-foreground">
<Link to="/"> <Link to="/">
<Button variant="ghost" className="text-muted-foreground hover:text-foreground">
<ArrowLeft className="w-4 h-4 mr-2" /> <ArrowLeft className="w-4 h-4 mr-2" />
Zurück Zurück
</Button>
</Link> </Link>
</Button>
</div> </div>
</div> </div>
</header> </header>
@@ -92,8 +92,8 @@ const HostingAbo = () => {
</p> </p>
<p> <p>
Updates und Pflege der Website selbst sind nicht enthalten; bei statischen Websites sind sie für den sicheren Updates und Pflege der Website selbst sind nicht enthalten; bei statischen Websites sind sie für den sicheren
Betrieb nicht nötig. Braucht Ihre Website dynamische Teile oder wünschen Sie laufende Pflege und eine Stunde Betrieb in der Regel nicht nötig. Braucht Ihre Website dynamische Teile oder wünschen Sie laufende Pflege und
Arbeitszeit pro Monat, ist unser{" "} eine Stunde Arbeitszeit pro Monat, ist unser{" "}
<Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link> das Richtige. <Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link> das Richtige.
</p> </p>
</div> </div>
@@ -115,9 +115,9 @@ const HostingAbo = () => {
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">E-Mail-Zusatzspeicher</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">E-Mail-Zusatzspeicher</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Brauchen Sie mehr Platz für E-Mails, buchen Sie im Kundenportal unter „Verträge“ Zusatzspeicher: Pakete von je Brauchen Sie mehr Platz für E-Mails, buchen Sie im Kundenportal unter „Verträge“ Zusatzspeicher: Pakete von je
5 GB für 1,00 € pro Monat ({STEUER}), bis zu 10 Pakete. Die Zahl der Pakete können Sie dort jederzeit erhöhen 5 GB für 1,00 € pro Monat ({STEUER}), bis zu 10 Pakete. Bestellen und verringern können Sie die Pakete dort,
oder verringern, solange Ihr Abo nicht gekündigt ist. Eine Verringerung wirkt zum Ende des Abrechnungsmonats. sobald Ihr Abo begonnen hat, solange es nicht gekündigt ist und keine Zahlung offen ist. Eine Verringerung wirkt
Einzelheiten:{" "} zum Ende des Abrechnungsmonats. Einzelheiten:{" "}
<a href={`${PORTAL}/abo-bedingungen.html#zusatzspeicher`} className={linkClass}>§ 10 der Vertragsbedingungen</a>. <a href={`${PORTAL}/abo-bedingungen.html#zusatzspeicher`} className={linkClass}>§ 10 der Vertragsbedingungen</a>.
</p> </p>
</section> </section>
@@ -127,10 +127,10 @@ const HostingAbo = () => {
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Wechsel ins Wartungs-Abo</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Wechsel ins Wartungs-Abo</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Braucht Ihre Website später dynamische Teile oder möchten Sie Pflege und Arbeitszeit dazubuchen, wechseln Sie Braucht Ihre Website später dynamische Teile oder möchten Sie Pflege und Arbeitszeit dazubuchen, wechseln Sie
im Kundenportal unter „Verträge“ jederzeit ins{" "} im Kundenportal unter „Verträge“ ins{" "}
<Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link>, auch während der Mindestlaufzeit, solange <Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link>, auch während der Mindestlaufzeit: sobald
Ihr Hosting-Abo nicht gekündigt ist. Mit dem Wechsel beginnt eine neue Mindestlaufzeit von 12 Monaten. Ihr Hosting-Abo begonnen hat, solange es nicht gekündigt ist und keine Zahlung offen ist. Mit dem Wechsel
Einzelheiten:{" "} beginnt eine neue Mindestlaufzeit von 12 Monaten. Einzelheiten:{" "}
<a href={`${PORTAL}/abo-bedingungen.html#wechsel`} className={linkClass}>§ 9 der Vertragsbedingungen</a>. <a href={`${PORTAL}/abo-bedingungen.html#wechsel`} className={linkClass}>§ 9 der Vertragsbedingungen</a>.
</p> </p>
</section> </section>
@@ -140,8 +140,8 @@ const HostingAbo = () => {
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">So schließen Sie das Abo ab</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">So schließen Sie das Abo ab</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Das Hosting-Abo schließen Sie in unserem Kundenportal ab. Den Zugang richten wir Ihnen ein, sprechen Sie uns Das Hosting-Abo schließen Sie in unserem Kundenportal ab. Den Zugang richten wir Ihnen ein, sprechen Sie uns
einfach an. Nach der Anmeldung finden Sie das Angebot im Bereich „Verträge“. Dort sehen Sie danach jederzeit einfach an. Nach der Anmeldung finden Sie das Angebot im Bereich „Verträge“. Im Kundenportal sehen Sie danach
Ihre Verträge und Rechnungen, buchen Zusatzspeicher und können uns Aufträge schicken. jederzeit Ihre Verträge und Rechnungen, buchen Zusatzspeicher und schicken uns Aufträge über den Chat.
</p> </p>
</section> </section>
{divider} {divider}
@@ -149,18 +149,19 @@ const HostingAbo = () => {
<section> <section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Widerruf und Kündigung</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Widerruf und Kündigung</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Verbraucher können den Vertrag innerhalb von 14 Tagen widerrufen; das gilt für jede Bestellung, also für das Verbraucher können jede Bestellung innerhalb von 14 Tagen ab Vertragsschluss widerrufen: den Abschluss des
Abo, den Wechsel ins Wartungs-Abo und den Zusatzspeicher. Bereits gezahlte Beträge erstatten wir dann nach der{" "} Abos, den Wechsel ins Wartungs-Abo und die Bestellung von Zusatzspeicher. Bereits gezahlte Beträge erstatten
wir dann nach der{" "}
<a href={`${PORTAL}/abo-bedingungen.html#widerruf`} className={linkClass}>Widerrufsbelehrung</a>. <a href={`${PORTAL}/abo-bedingungen.html#widerruf`} className={linkClass}>Widerrufsbelehrung</a>.
Kündigen können Sie online ohne Anmeldung oder per E-Mail. Kündigen können Sie online ohne Anmeldung oder per E-Mail.
</p> </p>
<div className="mt-4 flex flex-wrap gap-3"> <div className="mt-4 flex flex-wrap gap-3">
<a href={`${PORTAL}/kuendigen.html`}> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Verträge hier kündigen</Button> <a href={`${PORTAL}/kuendigen.html`}>Verträge hier kündigen</a>
</a> </Button>
<a href={`${PORTAL}/widerrufen.html`}> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Vertrag widerrufen</Button> <a href={`${PORTAL}/widerrufen.html`}>Vertrag widerrufen</a>
</a> </Button>
</div> </div>
</section> </section>
{divider} {divider}
@@ -168,7 +169,7 @@ const HostingAbo = () => {
<section> <section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
<a href={`${PORTAL}/abo-bedingungen.html`} className={linkClass}>Vertragsbedingungen der WEBklar-Abos</a> <a href={`${PORTAL}/abo-bedingungen.html`} className={linkClass}>Vertragsbedingungen für die WEBklar-Abos</a>
{" · "} {" · "}
<a href={`${PORTAL}/abo-bedingungen.html#hosting`} className={linkClass}>Leistungsbeschreibung A (Hosting-Abo)</a> <a href={`${PORTAL}/abo-bedingungen.html#hosting`} className={linkClass}>Leistungsbeschreibung A (Hosting-Abo)</a>
{" · "} {" · "}
@@ -182,12 +183,12 @@ const HostingAbo = () => {
</article> </article>
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4"> <div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<a href={`${PORTAL}/login.html`}> <Button asChild className="btn-minimal rounded-full">
<Button className="btn-minimal rounded-full">Zum Kundenportal</Button> <a href={`${PORTAL}/login.html`}>Zum Kundenportal</a>
</a> </Button>
<Link to="/kontakt"> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Kontakt aufnehmen</Button> <Link to="/kontakt">Kontakt aufnehmen</Link>
</Link> </Button>
</div> </div>
</div> </div>
</div> </div>
+31 -26
View File
@@ -11,7 +11,7 @@ const linkClass = "text-foreground underline hover:no-underline";
const leistungen = [ const leistungen = [
{ {
titel: "Betrieb & Pflege Ihrer Website", titel: "Betrieb & Pflege Ihrer Website",
text: "Hosting auf unserem Server in Deutschland, Sicherheitsupdates für Server und Website, SSL-Zertifikat, tägliche Datensicherung und Überwachung der Erreichbarkeit. Für alle Websites und Web-Anwendungen, die wir erstellt oder zur Betreuung übernommen haben, auch mit Content-Management-System, Login oder Datenbank.", text: "Hosting auf unserem Server in Deutschland, Sicherheitsupdates für den Server und für die Software-Komponenten Ihrer Website, die wir einsetzen, SSL-Zertifikat, tägliche Datensicherung und Überwachung der Erreichbarkeit. Für alle Websites und Web-Anwendungen, die wir erstellt oder zur Betreuung übernommen haben, auch mit Content-Management-System, Login oder Datenbank.",
}, },
{ {
titel: "1 Domain inklusive", titel: "1 Domain inklusive",
@@ -36,12 +36,12 @@ const WartungsAbo = () => {
<Link to="/" className="flex items-center gap-2 group"> <Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span> <span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span>
</Link> </Link>
<Button asChild variant="ghost" className="text-muted-foreground hover:text-foreground">
<Link to="/"> <Link to="/">
<Button variant="ghost" className="text-muted-foreground hover:text-foreground">
<ArrowLeft className="w-4 h-4 mr-2" /> <ArrowLeft className="w-4 h-4 mr-2" />
Zurück Zurück
</Button>
</Link> </Link>
</Button>
</div> </div>
</div> </div>
</header> </header>
@@ -92,10 +92,11 @@ const WartungsAbo = () => {
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">E-Mail-Postfächer und Zusatzspeicher</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">E-Mail-Postfächer und Zusatzspeicher</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Im Wartungs-Abo richten wir Ihnen beliebig viele Postfächer unter Ihrer Domain ein, mit zusammen 5 GB Im Wartungs-Abo richten wir Ihnen beliebig viele Postfächer unter Ihrer Domain ein, mit zusammen 5 GB
Speicher. Wie der Speicher auf die Postfächer verteilt wird, bestimmen Sie. Brauchen Sie mehr Speicher. Wie der Speicher auf die Postfächer verteilt wird, bestimmen Sie. Brauchen Sie mehr Platz, buchen Sie
Platz, buchen Sie im Kundenportal unter „Verträge“ Zusatzspeicher: Pakete von je 5 GB für 1,00 € pro Monat im Kundenportal unter „Verträge“ Zusatzspeicher: Pakete von je 5 GB für 1,00 € pro Monat ({STEUER}), bis zu
({STEUER}), bis zu 10 Pakete. Die Zahl der Pakete können Sie dort jederzeit erhöhen oder verringern, solange 10 Pakete. Bestellen und verringern können Sie die Pakete dort, sobald Ihr Abo begonnen hat, solange es nicht
Ihr Abo nicht gekündigt ist. Eine Verringerung wirkt zum Ende des Abrechnungsmonats. Einzelheiten:{" "} gekündigt ist und keine Zahlung offen ist. Eine Verringerung wirkt zum Ende des Abrechnungsmonats.
Einzelheiten:{" "}
<a href={`${PORTAL}/abo-bedingungen.html#zusatzspeicher`} className={linkClass}>§ 10 der Vertragsbedingungen</a>. <a href={`${PORTAL}/abo-bedingungen.html#zusatzspeicher`} className={linkClass}>§ 10 der Vertragsbedingungen</a>.
</p> </p>
</section> </section>
@@ -106,8 +107,11 @@ const WartungsAbo = () => {
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Sie haben eine statische Website und brauchen weder Pflege noch Arbeitszeit? Dann genügt vielleicht unser{" "} Sie haben eine statische Website und brauchen weder Pflege noch Arbeitszeit? Dann genügt vielleicht unser{" "}
<Link to="/hosting-abo" className={linkClass}>Hosting-Abo</Link> für 15,00 € pro Monat ({STEUER}). Vom <Link to="/hosting-abo" className={linkClass}>Hosting-Abo</Link> für 15,00 € pro Monat ({STEUER}). Vom
Hosting- ins Wartungs-Abo können Sie jederzeit wechseln; ein Wechsel vom Wartungs- ins Hosting-Abo ist auf Hosting- ins Wartungs-Abo wechseln Sie im Kundenportal, sobald das Hosting-Abo begonnen hat, solange es nicht
Anfrage nach Ablauf der Mindestlaufzeit möglich. gekündigt ist und keine Zahlung offen ist; mit dem Wechsel beginnt eine neue Mindestlaufzeit von 12 Monaten.
Ein Wechsel vom Wartungs- ins Hosting-Abo ist auf Anfrage nach Ablauf der Mindestlaufzeit zum Ende eines
Abrechnungsmonats möglich, wenn Ihre Website statisch ist. Einzelheiten in{" "}
<a href={`${PORTAL}/abo-bedingungen.html#wechsel`} className={linkClass}>§ 9 der Vertragsbedingungen</a>.
</p> </p>
</section> </section>
{divider} {divider}
@@ -116,8 +120,8 @@ const WartungsAbo = () => {
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">So schließen Sie das Abo ab</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">So schließen Sie das Abo ab</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Das Wartungs-Abo schließen Sie in unserem Kundenportal ab. Den Zugang richten wir Ihnen ein, sprechen Sie uns Das Wartungs-Abo schließen Sie in unserem Kundenportal ab. Den Zugang richten wir Ihnen ein, sprechen Sie uns
einfach an. Nach der Anmeldung finden Sie das Angebot im Bereich „Verträge“. Dort sehen Sie danach jederzeit einfach an. Nach der Anmeldung finden Sie das Angebot im Bereich „Verträge“. Im Kundenportal sehen Sie danach
Ihr Stundenkontingent, Ihre Rechnungen und können uns Aufträge schicken. jederzeit Ihr Stundenkontingent und Ihre Rechnungen und schicken uns Aufträge über den Chat.
</p> </p>
</section> </section>
{divider} {divider}
@@ -125,18 +129,19 @@ const WartungsAbo = () => {
<section> <section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Widerruf und Kündigung</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Widerruf und Kündigung</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
Verbraucher können den Vertrag innerhalb von 14 Tagen widerrufen; das gilt für jede Bestellung, also für das Verbraucher können jede Bestellung innerhalb von 14 Tagen ab Vertragsschluss widerrufen: den Abschluss des
Abo und den Zusatzspeicher. Bereits gezahlte Beträge erstatten wir dann nach der{" "} Abos, den Wechsel ins Wartungs-Abo und die Bestellung von Zusatzspeicher. Bereits gezahlte Beträge erstatten
wir dann nach der{" "}
<a href={`${PORTAL}/abo-bedingungen.html#widerruf`} className={linkClass}>Widerrufsbelehrung</a>. <a href={`${PORTAL}/abo-bedingungen.html#widerruf`} className={linkClass}>Widerrufsbelehrung</a>.
Kündigen können Sie online ohne Anmeldung oder per E-Mail. Kündigen können Sie online ohne Anmeldung oder per E-Mail.
</p> </p>
<div className="mt-4 flex flex-wrap gap-3"> <div className="mt-4 flex flex-wrap gap-3">
<a href={`${PORTAL}/kuendigen.html`}> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Verträge hier kündigen</Button> <a href={`${PORTAL}/kuendigen.html`}>Verträge hier kündigen</a>
</a> </Button>
<a href={`${PORTAL}/widerrufen.html`}> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Vertrag widerrufen</Button> <a href={`${PORTAL}/widerrufen.html`}>Vertrag widerrufen</a>
</a> </Button>
</div> </div>
</section> </section>
{divider} {divider}
@@ -144,7 +149,7 @@ const WartungsAbo = () => {
<section> <section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2> <h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
<a href={`${PORTAL}/abo-bedingungen.html`} className={linkClass}>Vertragsbedingungen der WEBklar-Abos</a> <a href={`${PORTAL}/abo-bedingungen.html`} className={linkClass}>Vertragsbedingungen für die WEBklar-Abos</a>
{" · "} {" · "}
<a href={`${PORTAL}/abo-bedingungen.html#wartung`} className={linkClass}>Leistungsbeschreibung B (Wartungs-Abo)</a> <a href={`${PORTAL}/abo-bedingungen.html#wartung`} className={linkClass}>Leistungsbeschreibung B (Wartungs-Abo)</a>
{" · "} {" · "}
@@ -158,12 +163,12 @@ const WartungsAbo = () => {
</article> </article>
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4"> <div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<a href={`${PORTAL}/login.html`}> <Button asChild className="btn-minimal rounded-full">
<Button className="btn-minimal rounded-full">Zum Kundenportal</Button> <a href={`${PORTAL}/login.html`}>Zum Kundenportal</a>
</a> </Button>
<Link to="/kontakt"> <Button asChild variant="outline" className="rounded-full">
<Button variant="outline" className="rounded-full">Kontakt aufnehmen</Button> <Link to="/kontakt">Kontakt aufnehmen</Link>
</Link> </Button>
</div> </div>
</div> </div>
</div> </div>
+53 -5
View File
@@ -1,17 +1,27 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react"; import { render, screen, within } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import HostingAbo from "@/pages/HostingAbo"; import HostingAbo from "@/pages/HostingAbo";
import WartungsAbo from "@/pages/WartungsAbo"; import WartungsAbo from "@/pages/WartungsAbo";
import AGB from "@/pages/AGB"; import AGB from "@/pages/AGB";
// Dieselben Future-Flags wie der BrowserRouter in App.tsx, sonst warnt React Router in der Testausgabe.
const routerFuture = { v7_startTransition: true, v7_relativeSplatPath: true };
// Texte, die auf einer Seite mehrfach vorkommen können, werden mit getAllByText // Texte, die auf einer Seite mehrfach vorkommen können, werden mit getAllByText
// geprüft (getByText wirft bei mehreren Treffern); Abwesenheit mit queryAllByText. // geprüft (getByText wirft bei mehreren Treffern); Abwesenheit mit queryAllByText.
/** Liefert den <section>-Abschnitt zu einer Überschrift. */
const sectionOf = (name: string | RegExp): HTMLElement => {
const section = screen.getByRole("heading", { name }).closest("section");
if (!section) throw new Error(`Abschnitt zur Überschrift ${String(name)} fehlt`);
return section;
};
describe("Abo-Seiten", () => { describe("Abo-Seiten", () => {
it("Hosting-Abo-Seite: Preis, Postfach, Links", () => { it("Hosting-Abo-Seite: Preis, Postfach, Links", () => {
render( render(
<MemoryRouter> <MemoryRouter future={routerFuture}>
<HostingAbo /> <HostingAbo />
</MemoryRouter>, </MemoryRouter>,
); );
@@ -27,25 +37,63 @@ describe("Abo-Seiten", () => {
); );
}); });
it("Hosting-Abo-Seite: Wechsel und Zusatzspeicher nur unter den Bedingungen des Portals", () => {
render(
<MemoryRouter future={routerFuture}>
<HostingAbo />
</MemoryRouter>,
);
// § 9 Abs. 1: Wechsel nur, wenn das Hosting-Abo begonnen hat, nicht gekündigt ist und keine Zahlung offen ist;
// mit dem Wechsel beginnt eine neue Mindestlaufzeit.
const wechsel = within(sectionOf("Wechsel ins Wartungs-Abo"));
expect(wechsel.getAllByText(/begonnen hat/).length).toBeGreaterThan(0);
expect(wechsel.getAllByText(/nicht gekündigt/).length).toBeGreaterThan(0);
expect(wechsel.getAllByText(/keine Zahlung offen/).length).toBeGreaterThan(0);
expect(wechsel.getAllByText(/neue Mindestlaufzeit von 12 Monaten/).length).toBeGreaterThan(0);
// § 10 Abs. 1: dieselben Bedingungen für Bestellung und Verringerung des Zusatzspeichers.
const speicher = within(sectionOf("E-Mail-Zusatzspeicher"));
expect(speicher.getAllByText(/begonnen hat/).length).toBeGreaterThan(0);
expect(speicher.getAllByText(/nicht gekündigt/).length).toBeGreaterThan(0);
expect(speicher.getAllByText(/keine Zahlung offen/).length).toBeGreaterThan(0);
});
it("Wartungs-Abo-Seite: beliebig viele Postfächer, Zusatzspeicher", () => { it("Wartungs-Abo-Seite: beliebig viele Postfächer, Zusatzspeicher", () => {
render( render(
<MemoryRouter> <MemoryRouter future={routerFuture}>
<WartungsAbo /> <WartungsAbo />
</MemoryRouter>, </MemoryRouter>,
); );
expect(screen.getAllByText("Beliebig viele Postfächer").length).toBeGreaterThan(0);
expect(screen.getAllByText(/beliebig viele Postfächer/).length).toBeGreaterThan(0); expect(screen.getAllByText(/beliebig viele Postfächer/).length).toBeGreaterThan(0);
expect(screen.queryAllByText("1 E-Mail-Postfach").length).toBe(0); expect(screen.queryAllByText("1 E-Mail-Postfach").length).toBe(0);
expect(screen.queryAllByText(/1 E-Mail-Postfach/).length).toBe(0); expect(screen.queryAllByText(/1 E-Mail-Postfach/).length).toBe(0);
expect(screen.getAllByText(/1,00 €/).length).toBeGreaterThan(0); expect(screen.getAllByText(/1,00 €/).length).toBeGreaterThan(0);
// Wechsel und Zusatzspeicher auch hier nur unter den Bedingungen des Portals.
const speicher = within(sectionOf("E-Mail-Postfächer und Zusatzspeicher"));
expect(speicher.getAllByText(/nicht gekündigt/).length).toBeGreaterThan(0);
expect(speicher.getAllByText(/keine Zahlung offen/).length).toBeGreaterThan(0);
const hosting = within(sectionOf("Hosting-Abo für statische Websites"));
expect(hosting.getAllByText(/nicht gekündigt/).length).toBeGreaterThan(0);
expect(hosting.getAllByText(/keine Zahlung offen/).length).toBeGreaterThan(0);
expect(hosting.getAllByText(/neue Mindestlaufzeit von 12 Monaten/).length).toBeGreaterThan(0);
expect(hosting.getByRole("link", { name: /§ 9 der Vertragsbedingungen/ })).toHaveAttribute(
"href",
"https://project.webklar.com/abo-bedingungen.html#wechsel",
);
}); });
it("AGB Modul C verweist auf das Hosting-Abo", () => { it("AGB Modul C verweist auf das Hosting-Abo", () => {
render( render(
<MemoryRouter> <MemoryRouter future={routerFuture}>
<AGB /> <AGB />
</MemoryRouter>, </MemoryRouter>,
); );
expect(screen.getAllByText(/Hosting-Abo/).length).toBeGreaterThan(0); const modulC = within(sectionOf(/Leistungsmodul C/));
expect(modulC.getAllByText(/Hosting-Abo/).length).toBeGreaterThan(0);
expect(modulC.getByRole("link", { name: "Vertragsbedingungen für die WEBklar-Abos" })).toHaveAttribute(
"href",
"https://project.webklar.com/abo-bedingungen.html",
);
expect(screen.queryAllByText(/verlängern sich automatisch/).length).toBe(0); expect(screen.queryAllByText(/verlängern sich automatisch/).length).toBe(0);
}); });
}); });