Compare commits

4 Commits
7 changed files with 400 additions and 39 deletions

No files matched your search

+14 -1
View File
@@ -2,7 +2,8 @@ import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
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 { AnalyticsProvider } from "@/components/AnalyticsProvider";
import Index from "./pages/Index";
@@ -11,11 +12,21 @@ import AGBPage from "./pages/AGB";
import ImpressumPage from "./pages/Impressum";
import AboutPage from "./pages/About";
import WartungsAboPage from "./pages/WartungsAbo";
import HostingAboPage from "./pages/HostingAbo";
import DatenschutzPage from "./pages/Datenschutz";
import NotFound from "./pages/NotFound";
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 = () => (
<ThemeProvider>
<QueryClientProvider client={queryClient}>
@@ -23,6 +34,7 @@ const App = () => (
<Toaster />
<Sonner />
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<ScrollToTop />
<AnalyticsProvider>
<Routes>
<Route path="/" element={<Index />} />
@@ -31,6 +43,7 @@ const App = () => (
<Route path="/impressum" element={<ImpressumPage />} />
<Route path="/ueber-uns" element={<AboutPage />} />
<Route path="/wartungs-abo" element={<WartungsAboPage />} />
<Route path="/hosting-abo" element={<HostingAboPage />} />
<Route path="/datenschutz" element={<DatenschutzPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
+1
View File
@@ -25,6 +25,7 @@ const footerLinks: { title: string; links: FooterLink[] }[] = [
{ id: 3, title: "Leistungen", href: "/#services" },
{ id: 4, title: "Kontakt", to: "/kontakt" },
{ id: 9, title: "Wartungs-Abo", to: "/wartungs-abo" },
{ id: 13, title: "Hosting-Abo", to: "/hosting-abo" },
],
},
{
+10 -7
View File
@@ -118,14 +118,17 @@ const AGB = () => {
{/* 5. Leistungsmodul C – Hosting */}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight text-foreground mb-2">
5. Leistungsmodul C – Hosting (jährlich)
5. Leistungsmodul C – Hosting
</h2>
<p className="text-sm text-muted-foreground mb-4">(aktiv, wenn im Angebot enthalten)</p>
<ol className="list-decimal list-inside space-y-2 text-muted-foreground leading-relaxed [&>li]:pl-2">
<li>WEBklar stellt optional Hosting-Leistungen zur Verfügung.</li>
<li>Hostingverträge haben eine jährliche Laufzeit und verlängern sich automatisch, sofern nicht fristgerecht gekündigt wird.</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>
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 für die WEBklar-Abos</a>
{" "}mit Leistungsbeschreibungen; sie gehen diesen AGB vor.
</li>
<li>Bestehende Hosting-Verträge mit jährlicher Laufzeit behalten die bei ihrem Abschluss vereinbarten Bedingungen. Abweichend davon können Verbraucher solche Verträge nach einer automatischen Verlängerung jederzeit mit einer Frist von einem Monat kündigen.</li>
<li>WEBklar ist berechtigt, externe Anbieter (z. B. Rechenzentren) einzusetzen.</li>
</ol>
</section>
{contractDivider}
@@ -190,7 +193,7 @@ const AGB = () => {
<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>Projektleistungen sind nach Vereinbarung fällig.</li>
<li>Hosting-Leistungen sind jährlich im Voraus zu zahlen.</li>
<li>Für die Abos gelten die Vertragsbedingungen für die WEBklar-Abos.</li>
</ol>
</section>
{contractDivider}
@@ -239,7 +242,7 @@ const AGB = () => {
12. Kündigung
</h2>
<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.</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>
</ol>
</section>
+200
View File
@@ -0,0 +1,200 @@
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Server } from "lucide-react";
const PORTAL = "https://project.webklar.com";
const STEUER = "Endpreis, gemäß § 19 UStG ohne Umsatzsteuer";
const divider = <div className="my-8 border-t border-border" aria-hidden />;
const linkClass = "text-foreground underline hover:no-underline";
const leistungen = [
{
titel: "Bereitstellung Ihrer Website",
text: "Hosting auf unserem Server in Deutschland, SSL-Zertifikat, tägliche Datensicherung, Überwachung der Erreichbarkeit sowie Updates von Betriebssystem und Server-Software.",
},
{
titel: "1 Domain inklusive",
text: "Registrierung auf Ihren Namen, Verlängerung und DNS-Verwaltung (.de, .com, .eu, .net oder .org).",
},
{
titel: "1 E-Mail-Postfach",
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",
text: "Störungen an Server, Domain und Postfach in unserem Verantwortungsbereich beheben wir ohne Berechnung. Servicezeiten Montag bis Freitag 9 bis 17 Uhr (außer an gesetzlichen Feiertagen in Rheinland-Pfalz), Antwort spätestens bis zum Ende des nächsten Werktags.",
},
];
const HostingAbo = () => {
return (
<div className="min-h-screen bg-background">
<header className="fixed top-0 left-0 right-0 z-50 glass-nav py-4">
<div className="container mx-auto px-6">
<div className="flex items-center justify-between">
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span>
</Link>
<Button asChild variant="ghost" className="text-muted-foreground hover:text-foreground">
<Link to="/">
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Link>
</Button>
</div>
</div>
</header>
<main className="pt-32 pb-24">
<div className="container mx-auto px-6">
<div className="max-w-3xl mx-auto">
<div className="mb-12">
<div className="label-tag mb-4 flex items-center gap-2">
<Server className="w-4 h-4" />
Leistungen
</div>
<h1 className="text-4xl md:text-5xl font-display font-medium text-foreground tracking-tight uppercase mb-2">
Hosting-Abo
</h1>
<p className="text-muted-foreground text-lg">
Ihre statische Website auf unserem Server in Deutschland: 15,00 € pro Monat, 12 Monate Mindestlaufzeit.
</p>
<p className="text-muted-foreground text-sm mt-1">Alle Preise sind Endpreise, gemäß § 19 UStG ohne Umsatzsteuer.</p>
</div>
<article className="space-y-8 text-foreground">
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-4">Das ist enthalten</h2>
<ul className="grid gap-5 sm:grid-cols-2">
{leistungen.map((l) => (
<li key={l.titel} className="rounded-2xl border border-border p-5">
<p className="font-medium mb-1">{l.titel}</p>
<p className="text-muted-foreground text-sm leading-relaxed">{l.text}</p>
</li>
))}
</ul>
</section>
{divider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Nur für statische Websites</h2>
<div className="text-muted-foreground leading-relaxed space-y-3">
<p>
Das Hosting-Abo ist für statische Websites bestimmt, die wir erstellt oder zur Betreuung übernommen haben.
Statisch heißt: Der Webserver liefert die Dateien Ihrer Website (HTML, CSS, JavaScript, Bilder) unverändert
aus. Dazu gehören auch die React- und Vite-Websites, die wir erstellen.
</p>
<p>
Nicht möglich sind ein Content-Management-System, ein Login, eine Datenbank und eigener serverseitiger Code
(etwa PHP). Kontakt über E-Mail-Links auf der Website ist möglich. Formulare, die Eingaben an einen Server
übermitteln, sind nicht enthalten; eine Lösung dafür bieten wir auf Anfrage gesondert an.
</p>
<p>
Updates und Pflege der Website selbst sind nicht enthalten; bei statischen Websites sind sie für den sicheren
Betrieb in der Regel nicht nötig. Braucht Ihre Website dynamische Teile oder wünschen Sie laufende Pflege und
eine Stunde Arbeitszeit pro Monat, ist unser{" "}
<Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link> das Richtige.
</p>
</div>
</section>
{divider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Preis und Laufzeit</h2>
<ul className="text-muted-foreground leading-relaxed space-y-1 list-disc pl-5">
<li>15,00 € pro Monat, monatlich im Voraus. {STEUER}.</li>
<li>Mindestlaufzeit 12 Monate, kündbar zum Ende der Mindestlaufzeit bis einen Monat vorher; danach jederzeit zum Ende des Abrechnungsmonats. Bei Geschäftsaufgabe Sonderkündigung mit einem Monat Frist.</li>
<li>Zahlung per Kreditkarte oder SEPA-Lastschrift über unseren Zahlungsdienstleister Stripe.</li>
<li>Ein Stundenkontingent ist nicht enthalten. Änderungen an Ihrer Website und andere Aufträge führen wir nur nach Ihrer Freigabe aus: 100 € pro Stunde ({STEUER}), abgerechnet in 15-Minuten-Schritten. Vorher nennen wir Ihnen den geschätzten Aufwand.</li>
</ul>
</section>
{divider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">E-Mail-Zusatzspeicher</h2>
<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
5 GB für 1,00 € pro Monat ({STEUER}), bis zu 10 Pakete. Bestellen und verringern können Sie die Pakete dort,
sobald Ihr Abo begonnen hat, solange es nicht 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>.
</p>
</section>
{divider}
<section>
<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">
Braucht Ihre Website später dynamische Teile oder möchten Sie Pflege und Arbeitszeit dazubuchen, wechseln Sie
im Kundenportal unter „Verträge“ ins{" "}
<Link to="/wartungs-abo" className={linkClass}>Wartungs-Abo</Link>, auch während der Mindestlaufzeit: sobald
Ihr Hosting-Abo begonnen hat, solange es nicht gekündigt ist und keine Zahlung offen ist. Mit dem Wechsel
beginnt eine neue Mindestlaufzeit von 12 Monaten. Einzelheiten:{" "}
<a href={`${PORTAL}/abo-bedingungen.html#wechsel`} className={linkClass}>§ 9 der Vertragsbedingungen</a>.
</p>
</section>
{divider}
<section>
<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">
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“. Im Kundenportal sehen Sie danach
jederzeit Ihre Verträge und Rechnungen, buchen Zusatzspeicher und schicken uns Aufträge über den Chat.
</p>
</section>
{divider}
<section>
<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">
Verbraucher können jede Bestellung innerhalb von 14 Tagen ab Vertragsschluss widerrufen: den Abschluss des
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>.
Kündigen können Sie online ohne Anmeldung oder per E-Mail.
</p>
<div className="mt-4 flex flex-wrap gap-3">
<Button asChild variant="outline" className="rounded-full">
<a href={`${PORTAL}/kuendigen.html`}>Verträge hier kündigen</a>
</Button>
<Button asChild variant="outline" className="rounded-full">
<a href={`${PORTAL}/widerrufen.html`}>Vertrag widerrufen</a>
</Button>
</div>
</section>
{divider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2>
<p className="text-muted-foreground leading-relaxed">
<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}/WEBklar-Abo-Vertragsunterlagen.pdf`} className={linkClass}>Vertragsunterlagen als PDF</a>
{" · "}
<a href={`${PORTAL}/datenschutz.html`} className={linkClass}>Datenschutz</a>
<br />
WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler · Telefon 0176 23726355 · E-Mail support@webklar.com
</p>
</section>
</article>
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<Button asChild className="btn-minimal rounded-full">
<a href={`${PORTAL}/login.html`}>Zum Kundenportal</a>
</Button>
<Button asChild variant="outline" className="rounded-full">
<Link to="/kontakt">Kontakt aufnehmen</Link>
</Button>
</div>
</div>
</div>
</main>
</div>
);
};
export default HostingAbo;
+64 -30
View File
@@ -3,21 +3,23 @@ import { Button } from "@/components/ui/button";
import { ArrowLeft, ShieldCheck } from "lucide-react";
const PORTAL = "https://project.webklar.com";
const STEUER = "Endpreis, gemäß § 19 UStG ohne Umsatzsteuer";
const divider = <div className="my-8 border-t border-border" aria-hidden />;
const linkClass = "text-foreground underline hover:no-underline";
const leistungen = [
{
titel: "Betrieb & Pflege Ihrer Website",
text: "Hosting auf unserem Server in Deutschland, Sicherheitsupdates, SSL-Zertifikat, tägliche Datensicherung und Überwachung der Erreichbarkeit.",
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",
text: "Registrierung auf Ihren Namen, Verlängerung und DNS-Verwaltung (.de, .com, .eu, .net oder .org).",
},
{
titel: "1 E-Mail-Postfach",
text: "Mit Ihrer eigenen Domain, 5 GB Speicher, Webmail und Zugriff per Outlook oder Smartphone.",
titel: "Beliebig viele Postfächer",
text: `Unter Ihrer eigenen Domain, zusammen 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: "1 Stunde Arbeitszeit pro Monat",
@@ -34,12 +36,12 @@ const WartungsAbo = () => {
<Link to="/" className="flex items-center gap-2 group">
<span className="text-xl font-display font-medium text-foreground tracking-tight">Webklar</span>
</Link>
<Link to="/">
<Button variant="ghost" className="text-muted-foreground hover:text-foreground">
<Button asChild variant="ghost" className="text-muted-foreground hover:text-foreground">
<Link to="/">
<ArrowLeft className="w-4 h-4 mr-2" />
Zurück
</Button>
</Link>
</Link>
</Button>
</div>
</div>
</header>
@@ -56,8 +58,9 @@ const WartungsAbo = () => {
Wartungs-Abo
</h1>
<p className="text-muted-foreground text-lg">
Betrieb, Pflege und Betreuung Ihrer Website: 50 € pro Monat, 12 Monate Mindestlaufzeit.
Betrieb, Pflege und Betreuung Ihrer Website: 50,00 € pro Monat, 12 Monate Mindestlaufzeit.
</p>
<p className="text-muted-foreground text-sm mt-1">Alle Preise sind Endpreise, gemäß § 19 UStG ohne Umsatzsteuer.</p>
</div>
<article className="space-y-8 text-foreground">
@@ -77,20 +80,48 @@ const WartungsAbo = () => {
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Preis und Laufzeit</h2>
<ul className="text-muted-foreground leading-relaxed space-y-1 list-disc pl-5">
<li>50,00 € pro Monat, monatlich im Voraus. Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.</li>
<li>50,00 € pro Monat, monatlich im Voraus. {STEUER}.</li>
<li>Mindestlaufzeit 12 Monate, kündbar zum Ende der Mindestlaufzeit bis einen Monat vorher; danach jederzeit zum Ende des Abrechnungsmonats. Bei Geschäftsaufgabe Sonderkündigung mit einem Monat Frist.</li>
<li>Zahlung per Kreditkarte oder SEPA-Lastschrift über unseren Zahlungsdienstleister Stripe.</li>
<li>Arbeit über die enthaltene Stunde hinaus nur nach Ihrer Freigabe, 100 € pro Stunde, abgerechnet in 15-Minuten-Schritten.</li>
<li>Arbeit über die enthaltene Stunde hinaus nur nach Ihrer Freigabe, 100 € pro Stunde ({STEUER}), abgerechnet in 15-Minuten-Schritten.</li>
</ul>
</section>
{divider}
<section>
<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">
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 Platz, buchen Sie
im Kundenportal unter „Verträge“ Zusatzspeicher: Pakete von je 5 GB für 1,00 € pro Monat ({STEUER}), bis zu
10 Pakete. Bestellen und verringern können Sie die Pakete dort, sobald Ihr Abo begonnen hat, solange es nicht
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>.
</p>
</section>
{divider}
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Hosting-Abo für statische Websites</h2>
<p className="text-muted-foreground leading-relaxed">
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
Hosting- ins Wartungs-Abo wechseln Sie im Kundenportal, sobald das Hosting-Abo begonnen hat, solange es nicht
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>
</section>
{divider}
<section>
<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">
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 auf der Startseite. Dort sehen Sie danach jederzeit Ihr
Stundenkontingent, Ihre Rechnungen und können uns Aufträge schicken.
einfach an. Nach der Anmeldung finden Sie das Angebot im Bereich „Verträge“. Im Kundenportal sehen Sie danach
jederzeit Ihr Stundenkontingent und Ihre Rechnungen und schicken uns Aufträge über den Chat.
</p>
</section>
{divider}
@@ -98,18 +129,19 @@ const WartungsAbo = () => {
<section>
<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">
Verbraucher können den Vertrag innerhalb von 14 Tagen widerrufen. Bereits gezahlte Beträge erstatten wir dann
nach der{" "}
<a href={`${PORTAL}/abo-bedingungen.html#widerruf`} className="text-foreground underline hover:no-underline">Widerrufsbelehrung</a>.
Verbraucher können jede Bestellung innerhalb von 14 Tagen ab Vertragsschluss widerrufen: den Abschluss des
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>.
Kündigen können Sie online ohne Anmeldung oder per E-Mail.
</p>
<div className="mt-4 flex flex-wrap gap-3">
<a href={`${PORTAL}/kuendigen.html`}>
<Button variant="outline" className="rounded-full">Verträge hier kündigen</Button>
</a>
<a href={`${PORTAL}/widerrufen.html`}>
<Button variant="outline" className="rounded-full">Vertrag widerrufen</Button>
</a>
<Button asChild variant="outline" className="rounded-full">
<a href={`${PORTAL}/kuendigen.html`}>Verträge hier kündigen</a>
</Button>
<Button asChild variant="outline" className="rounded-full">
<a href={`${PORTAL}/widerrufen.html`}>Vertrag widerrufen</a>
</Button>
</div>
</section>
{divider}
@@ -117,11 +149,13 @@ const WartungsAbo = () => {
<section>
<h2 className="text-lg font-display font-medium uppercase tracking-tight mb-3">Unterlagen und Kontakt</h2>
<p className="text-muted-foreground leading-relaxed">
<a href={`${PORTAL}/abo-bedingungen.html`} className="text-foreground underline hover:no-underline">Vertragsbedingungen mit Leistungsbeschreibung</a>
<a href={`${PORTAL}/abo-bedingungen.html`} className={linkClass}>Vertragsbedingungen für die WEBklar-Abos</a>
{" · "}
<a href={`${PORTAL}/WEBklar-Wartungs-Abo-Vertragsunterlagen.pdf`} className="text-foreground underline hover:no-underline">Vertragsunterlagen als PDF</a>
<a href={`${PORTAL}/abo-bedingungen.html#wartung`} className={linkClass}>Leistungsbeschreibung B (Wartungs-Abo)</a>
{" · "}
<a href={`${PORTAL}/datenschutz.html`} className="text-foreground underline hover:no-underline">Datenschutz</a>
<a href={`${PORTAL}/WEBklar-Abo-Vertragsunterlagen.pdf`} className={linkClass}>Vertragsunterlagen als PDF</a>
{" · "}
<a href={`${PORTAL}/datenschutz.html`} className={linkClass}>Datenschutz</a>
<br />
WEBklar GbR, Am Schwimmbad 10, 67722 Winnweiler · Telefon 0176 23726355 · E-Mail support@webklar.com
</p>
@@ -129,12 +163,12 @@ const WartungsAbo = () => {
</article>
<div className="mt-16 pt-12 border-t border-border flex flex-wrap gap-4">
<a href={`${PORTAL}/login.html`}>
<Button className="btn-minimal rounded-full">Zum Kundenportal</Button>
</a>
<Link to="/kontakt">
<Button variant="outline" className="rounded-full">Kontakt aufnehmen</Button>
</Link>
<Button asChild className="btn-minimal rounded-full">
<a href={`${PORTAL}/login.html`}>Zum Kundenportal</a>
</Button>
<Button asChild variant="outline" className="rounded-full">
<Link to="/kontakt">Kontakt aufnehmen</Link>
</Button>
</div>
</div>
</div>
+110
View File
@@ -0,0 +1,110 @@
import { describe, it, expect } from "vitest";
import { render, screen, within } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import HostingAbo from "@/pages/HostingAbo";
import WartungsAbo from "@/pages/WartungsAbo";
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
// 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", () => {
it("Hosting-Abo-Seite: Preis, Postfach, Links", () => {
render(
<MemoryRouter future={routerFuture}>
<HostingAbo />
</MemoryRouter>,
);
expect(screen.getAllByText(/15,00 € pro Monat/).length).toBeGreaterThan(0);
expect(screen.getAllByText(/1 E-Mail-Postfach/).length).toBeGreaterThan(0);
expect(screen.getByRole("link", { name: "Verträge hier kündigen" })).toHaveAttribute(
"href",
"https://project.webklar.com/kuendigen.html",
);
expect(screen.getByRole("link", { name: /Vertragsunterlagen als PDF/ })).toHaveAttribute(
"href",
"https://project.webklar.com/WEBklar-Abo-Vertragsunterlagen.pdf",
);
});
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", () => {
render(
<MemoryRouter future={routerFuture}>
<WartungsAbo />
</MemoryRouter>,
);
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.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(/begonnen hat/).length).toBeGreaterThan(0);
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(/begonnen hat/).length).toBeGreaterThan(0);
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", () => {
render(
<MemoryRouter future={routerFuture}>
<AGB />
</MemoryRouter>,
);
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);
// Rechtsrunde: kein eigener Haftungssatz mehr in Modul C (für die Abos gilt § 13 der Abo-Bedingungen) …
expect(modulC.queryAllByText(/keine Haftung für Ausfälle externer Anbieter/).length).toBe(0);
// … und Verbraucher können – als Ausnahme von den vereinbarten Bedingungen – automatisch verlängerte
// Jahresverträge mit einer Frist von einem Monat kündigen.
expect(
modulC.getAllByText(
/Abweichend davon können Verbraucher solche Verträge nach einer automatischen Verlängerung jederzeit mit einer Frist von einem Monat kündigen\./,
).length,
).toBeGreaterThan(0);
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react-swc";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({