Refactor footer component and integrate into landing page
- Remove unnecessary elements and simplify the footer layout - Update copyright notice and styling for consistency - Add Footer27 component to the landing page - Enhance tests to verify footer rendering and legal links
This commit is contained in:
55
backlog/tasks/task-4 - Add-legal-footer-and-pages.md
Normal file
55
backlog/tasks/task-4 - Add-legal-footer-and-pages.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
id: TASK-4
|
||||||
|
title: Add legal footer and pages
|
||||||
|
status: In Progress
|
||||||
|
assignee: []
|
||||||
|
created_date: '2026-05-06 19:43'
|
||||||
|
updated_date: '2026-05-06 19:51'
|
||||||
|
labels: []
|
||||||
|
dependencies: []
|
||||||
|
modified_files:
|
||||||
|
- src/pages/index.astro
|
||||||
|
- src/components/footer27.tsx
|
||||||
|
- src/pages/impressum.astro
|
||||||
|
- src/pages/datenschutz.astro
|
||||||
|
- tests/landing-content.test.mjs
|
||||||
|
priority: high
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Bind the existing footer into the landing page and add real Impressum and Datenschutz routes so the legal links resolve correctly.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [x] #1 Landing page renders the footer component below the main landing content
|
||||||
|
- [x] #2 Footer links point to /impressum and /datenschutz
|
||||||
|
- [x] #3 /impressum contains the provided business and VAT ID information
|
||||||
|
- [x] #4 /datenschutz states that no cookies are used and documents the current Rybbit analytics usage
|
||||||
|
- [x] #5 Automated content tests and Astro build pass
|
||||||
|
<!-- AC:END -->
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
<!-- SECTION:PLAN:BEGIN -->
|
||||||
|
1. Add failing content tests for footer rendering and legal pages
|
||||||
|
2. Implement footer wiring and legal Astro pages
|
||||||
|
3. Run automated tests and build
|
||||||
|
4. Check off verified acceptance criteria and leave task In Progress for user confirmation
|
||||||
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
Implemented footer rendering on the landing page and added /impressum plus /datenschutz Astro routes.
|
||||||
|
|
||||||
|
Verified with node --test tests/*.mjs and env CI=true pnpm build. The first sandboxed build attempt failed because pnpm needed network access to recreate node_modules; the escalated build completed successfully.
|
||||||
|
|
||||||
|
Task remains In Progress pending explicit user confirmation before moving to Done.
|
||||||
|
|
||||||
|
Adjusted footer per visual feedback: removed the CTA/contact block and kept only the compact copyright, analytics note, Impressum, and Datenschutz row. Footer now uses the same horizontal page padding as the landing sections instead of max-w-6xl centering.
|
||||||
|
|
||||||
|
Re-verified after the footer adjustment with node --test tests/*.mjs and env CI=true pnpm build.
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
import { ArrowRight, Mail, Phone } from "lucide-react";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface Footer27Props {
|
interface Footer27Props {
|
||||||
@@ -9,48 +6,11 @@ interface Footer27Props {
|
|||||||
|
|
||||||
const Footer27 = ({ className }: Footer27Props) => {
|
const Footer27 = ({ className }: Footer27Props) => {
|
||||||
return (
|
return (
|
||||||
<footer className={cn("px-4 pb-10 sm:px-6 lg:px-8", className)}>
|
<footer className={cn("px-5 pb-10 sm:px-8 lg:px-12", className)}>
|
||||||
<div className="mx-auto max-w-6xl border-t border-border/80 pt-8">
|
<div className="border-t border-border/80 pt-6">
|
||||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-start">
|
<div className="flex flex-col gap-4 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div>
|
|
||||||
<h2 className="max-w-2xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl">
|
|
||||||
Bereit für eine Website, die Ihr Unternehmen klarer erklärt?
|
|
||||||
</h2>
|
|
||||||
<p className="mt-4 max-w-xl text-base leading-7 text-muted-foreground">
|
|
||||||
Eine kurze Nachricht reicht. Ich prüfe, welcher Weg sinnvoll ist,
|
|
||||||
und melde mich mit einer ehrlichen Einschätzung.
|
|
||||||
</p>
|
|
||||||
<Button asChild size="lg" className="mt-6 h-11 rounded-md px-5">
|
|
||||||
<a href="#kontakt">
|
|
||||||
Kostenloses Angebot anfordern
|
|
||||||
<ArrowRight className="shrink-0" aria-hidden />
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<address className="not-italic">
|
|
||||||
<div className="space-y-3 text-sm text-muted-foreground">
|
|
||||||
<a
|
|
||||||
href="mailto:info@matthias-meister-webdesign.de"
|
|
||||||
className="flex items-center gap-2 transition-colors hover:text-foreground"
|
|
||||||
>
|
|
||||||
<Mail className="size-4" aria-hidden />
|
|
||||||
info@matthias-meister-webdesign.de
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="tel:037627984400"
|
|
||||||
className="flex items-center gap-2 transition-colors hover:text-foreground"
|
|
||||||
>
|
|
||||||
<Phone className="size-4" aria-hidden />
|
|
||||||
03762 798 4400
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</address>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-10 flex flex-col gap-4 border-t border-border/80 pt-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p>© 2026 Matthias Meister Webdesign — Crimmitschau</p>
|
<p>© 2026 Matthias Meister Webdesign</p>
|
||||||
<p className="text-xs text-muted-foreground/90">
|
<p className="text-xs text-muted-foreground/90">
|
||||||
Diese Website misst Nutzung anonym und cookiefrei (Rybbit).
|
Diese Website misst Nutzung anonym und cookiefrei (Rybbit).
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
132
src/pages/datenschutz.astro
Normal file
132
src/pages/datenschutz.astro
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
import "@/styles/global.css";
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>Datenschutz | Matthias Meister Softwareentwicklung</title>
|
||||||
|
<script
|
||||||
|
src="https://rybbit.matthias.lol/api/script.js"
|
||||||
|
data-site-id="60abc81e438a"
|
||||||
|
defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="min-h-screen bg-background px-5 py-8 text-foreground sm:px-8 lg:px-12">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="text-sm font-semibold uppercase tracking-[0.24em] text-muted-foreground transition hover:text-foreground"
|
||||||
|
>
|
||||||
|
Matthias Meister
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<section class="mx-auto grid max-w-5xl gap-12 py-16 lg:grid-cols-[0.42fr_0.58fr] lg:py-24">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Datenschutz
|
||||||
|
</p>
|
||||||
|
<h1 class="mt-6 max-w-[9ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Ihre Daten
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-10 text-lg leading-8 text-foreground/85">
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Verantwortlicher
|
||||||
|
</h2>
|
||||||
|
<div class="mt-4 space-y-1 text-muted-foreground">
|
||||||
|
<p>Matthias Meister Softwareentwicklung</p>
|
||||||
|
<p>Inhaber: Matthias Meister</p>
|
||||||
|
<p>Karl-Marx-Str. 22</p>
|
||||||
|
<p>08451 Crimmitschau</p>
|
||||||
|
<p>
|
||||||
|
E-Mail:
|
||||||
|
<a
|
||||||
|
class="underline underline-offset-4 transition hover:text-foreground"
|
||||||
|
href="mailto:hallo@matthias-meister.com"
|
||||||
|
>
|
||||||
|
hallo@matthias-meister.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Keine Cookies
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Diese Website setzt keine Cookies. Es gibt keine Nutzerkonten,
|
||||||
|
keinen Newsletter, keine Zahlungsabwicklung und keine eingebetteten
|
||||||
|
Drittanbieter-Medien.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Kontakt per E-Mail
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Wenn Sie über einen mailto-Link Kontakt aufnehmen, werden die von
|
||||||
|
Ihnen per E-Mail übermittelten Angaben zur Bearbeitung Ihrer
|
||||||
|
Anfrage verarbeitet. Auf dieser Website wird dafür kein
|
||||||
|
Kontaktformular gespeichert.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Reichweitenmessung mit Rybbit
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Diese Website nutzt Rybbit Analytics über
|
||||||
|
https://rybbit.matthias.lol/api/script.js, um anonymisierte und
|
||||||
|
aggregierte Statistiken zur Nutzung der Website zu erhalten.
|
||||||
|
Rybbit arbeitet cookielos und verwendet nach Anbieterangaben keine
|
||||||
|
Cookies oder local storage für das Tracking.
|
||||||
|
</p>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Dabei können technische Besuchsdaten wie aufgerufene Seiten,
|
||||||
|
Referrer, Browser- und Geräteinformationen sowie aus der
|
||||||
|
IP-Adresse abgeleitete ungefähre Standortdaten verarbeitet werden.
|
||||||
|
Die IP-Adresse wird dabei nur vorübergehend zur Verarbeitung
|
||||||
|
genutzt und nicht als Klartext in der Analysedatenbank gespeichert.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Ihre Rechte
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Sie haben im Rahmen der gesetzlichen Vorgaben insbesondere Rechte
|
||||||
|
auf Auskunft, Berichtigung, Löschung, Einschränkung der
|
||||||
|
Verarbeitung und Widerspruch gegen die Verarbeitung Ihrer
|
||||||
|
personenbezogenen Daten. Bitte wenden Sie sich dafür an die oben
|
||||||
|
genannte Kontaktadresse.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<p class="border-t border-border pt-8 text-sm text-muted-foreground">
|
||||||
|
Hinweis: Dieser Text beschreibt die technische Umsetzung dieser
|
||||||
|
Website und ersetzt keine anwaltliche Prüfung.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<nav class="flex flex-wrap gap-5 text-sm text-muted-foreground">
|
||||||
|
<a class="underline underline-offset-4 transition hover:text-foreground" href="/">
|
||||||
|
Startseite
|
||||||
|
</a>
|
||||||
|
<a class="underline underline-offset-4 transition hover:text-foreground" href="/impressum">
|
||||||
|
Impressum
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
src/pages/impressum.astro
Normal file
86
src/pages/impressum.astro
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
import "@/styles/global.css";
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>Impressum | Matthias Meister Softwareentwicklung</title>
|
||||||
|
<script
|
||||||
|
src="https://rybbit.matthias.lol/api/script.js"
|
||||||
|
data-site-id="60abc81e438a"
|
||||||
|
defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="min-h-screen bg-background px-5 py-8 text-foreground sm:px-8 lg:px-12">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="text-sm font-semibold uppercase tracking-[0.24em] text-muted-foreground transition hover:text-foreground"
|
||||||
|
>
|
||||||
|
Matthias Meister
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<section class="mx-auto grid max-w-5xl gap-12 py-16 lg:grid-cols-[0.42fr_0.58fr] lg:py-24">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm uppercase tracking-[0.3em] text-primary">
|
||||||
|
Rechtliches
|
||||||
|
</p>
|
||||||
|
<h1 class="mt-6 max-w-[8ch] text-5xl font-black uppercase leading-[0.86] sm:text-7xl">
|
||||||
|
Impressum
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-10 text-lg leading-8 text-foreground/85">
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Angaben gemäß § 5 TMG
|
||||||
|
</h2>
|
||||||
|
<div class="mt-4 space-y-1 text-muted-foreground">
|
||||||
|
<p>Matthias Meister Softwareentwicklung</p>
|
||||||
|
<p>Inhaber: Matthias Meister</p>
|
||||||
|
<p>Karl-Marx-Str. 22</p>
|
||||||
|
<p>08451 Crimmitschau</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Umsatzsteuer
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
Umsatzsteuer-Identifikationsnummer: DE460155697
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 class="text-sm font-black uppercase tracking-[0.22em] text-primary">
|
||||||
|
Kontakt
|
||||||
|
</h2>
|
||||||
|
<p class="mt-4 text-muted-foreground">
|
||||||
|
E-Mail:
|
||||||
|
<a
|
||||||
|
class="underline underline-offset-4 transition hover:text-foreground"
|
||||||
|
href="mailto:hallo@matthias-meister.com"
|
||||||
|
>
|
||||||
|
hallo@matthias-meister.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="flex flex-wrap gap-5 border-t border-border pt-8 text-sm text-muted-foreground">
|
||||||
|
<a class="underline underline-offset-4 transition hover:text-foreground" href="/">
|
||||||
|
Startseite
|
||||||
|
</a>
|
||||||
|
<a class="underline underline-offset-4 transition hover:text-foreground" href="/datenschutz">
|
||||||
|
Datenschutz
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { LandingHeroSection } from "@/components/landing-hero-section";
|
import { LandingHeroSection } from "@/components/landing-hero-section";
|
||||||
import { LandingRest } from "@/components/landing";
|
import { LandingRest } from "@/components/landing";
|
||||||
|
import { Footer27 } from "@/components/footer27";
|
||||||
import "@/styles/global.css";
|
import "@/styles/global.css";
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ import "@/styles/global.css";
|
|||||||
<main class="min-h-screen overflow-hidden bg-background text-foreground">
|
<main class="min-h-screen overflow-hidden bg-background text-foreground">
|
||||||
<LandingHeroSection client:media="(min-width: 1024px)" />
|
<LandingHeroSection client:media="(min-width: 1024px)" />
|
||||||
<LandingRest />
|
<LandingRest />
|
||||||
|
<Footer27 />
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
import { initCookieInfoBanner } from "@/lib/cookie-banner-info";
|
import { initCookieInfoBanner } from "@/lib/cookie-banner-info";
|
||||||
|
|||||||
@@ -7,12 +7,17 @@ const sourcePaths = [
|
|||||||
new URL("../src/components/landing-hero-section.tsx", import.meta.url),
|
new URL("../src/components/landing-hero-section.tsx", import.meta.url),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const footerPath = new URL("../src/components/footer27.tsx", import.meta.url);
|
||||||
|
const indexPath = new URL("../src/pages/index.astro", import.meta.url);
|
||||||
|
const impressumPath = new URL("../src/pages/impressum.astro", import.meta.url);
|
||||||
|
const datenschutzPath = new URL("../src/pages/datenschutz.astro", import.meta.url);
|
||||||
|
|
||||||
test("Landing component contains the core brief anchors", async () => {
|
test("Landing component contains the core brief anchors", async () => {
|
||||||
const source = (
|
const source = (
|
||||||
await Promise.all(sourcePaths.map((p) => readFile(p, "utf8")))
|
await Promise.all(sourcePaths.map((p) => readFile(p, "utf8")))
|
||||||
).join("\n");
|
).join("\n");
|
||||||
|
|
||||||
for (const phrase of ["Projektbrief", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) {
|
for (const phrase of ["Online Fertig Passt", "01", "Website", "Kontakt", "für", "müssen", "Änderungen"]) {
|
||||||
assert.match(source, new RegExp(phrase));
|
assert.match(source, new RegExp(phrase));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -37,3 +42,44 @@ test("Landing component uses real German umlauts in visible copy", async () => {
|
|||||||
assert.doesNotMatch(source, new RegExp(asciiFallback));
|
assert.doesNotMatch(source, new RegExp(asciiFallback));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Landing page renders the footer with legal links", async () => {
|
||||||
|
const [indexSource, footerSource] = await Promise.all([
|
||||||
|
readFile(indexPath, "utf8"),
|
||||||
|
readFile(footerPath, "utf8"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.match(indexSource, /import\s+\{\s*Footer27\s*\}/);
|
||||||
|
assert.match(indexSource, /<Footer27\s*\/>/);
|
||||||
|
assert.match(footerSource, /href="\/impressum"/);
|
||||||
|
assert.match(footerSource, /href="\/datenschutz"/);
|
||||||
|
assert.match(footerSource, /© 2026 Matthias Meister Webdesign/);
|
||||||
|
assert.doesNotMatch(footerSource, /Bereit für eine Website/);
|
||||||
|
assert.doesNotMatch(footerSource, /Kostenloses Angebot anfordern/);
|
||||||
|
assert.doesNotMatch(footerSource, /max-w-6xl/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Legal pages contain required business and privacy information", async () => {
|
||||||
|
const [impressumSource, datenschutzSource] = await Promise.all([
|
||||||
|
readFile(impressumPath, "utf8"),
|
||||||
|
readFile(datenschutzPath, "utf8"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const phrase of [
|
||||||
|
"Matthias Meister Softwareentwicklung",
|
||||||
|
"Karl-Marx-Str. 22",
|
||||||
|
"08451 Crimmitschau",
|
||||||
|
"DE460155697",
|
||||||
|
]) {
|
||||||
|
assert.match(impressumSource, new RegExp(phrase));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const phrase of [
|
||||||
|
"keine Cookies",
|
||||||
|
"mailto",
|
||||||
|
"Rybbit",
|
||||||
|
"rybbit.matthias.lol",
|
||||||
|
]) {
|
||||||
|
assert.match(datenschutzSource, new RegExp(phrase));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user