feat(legal): add imprint and privacy policy pages with responsive layout and fixed footer integration

This commit is contained in:
Chneemann 2026-08-15 10:13:16 +02:00
parent 76d239df7b
commit 949dde7aba
No known key found for this signature in database
7 changed files with 368 additions and 33 deletions

View file

@ -1,6 +1,6 @@
/**
* @file layout.tsx
* @description Server component layout protecting authenticated routes, verifying user session status, and rendering the responsive application shell structure with global toast notifications.
* @file (app)/layout.tsx
* @description Server component layout protecting authenticated routes by verifying user sessions and arranging the main dashboard application structure with responsive sidebars, headers, navigation bars, error toasts, and footers.
*/
import { auth } from "@/auth";
@ -9,15 +9,16 @@ import Sidebar from "@/app/components/layout/Sidebar";
import Header from "@/app/components/layout/Header";
import Navbar from "@/app/components/layout/Navbar";
import ErrorToast from "../components/ErrorToast";
import Footer from "@/app/components/layout/Footer";
/**
* Renders the protected application layout wrapper.
* Checks for an active user session and redirects to the login page if unauthenticated.
* Renders the main application layout for authenticated views, checking user sessions
* and assembling the responsive page shell.
*
* @async
* @param {Object} props - The component props.
* @param {React.ReactNode} props.children - The child components or pages to render within the layout.
* @returns {Promise<JSX.Element>} The rendered application layout structure.
* @param {React.ReactNode} props.children - The inner child components/pages to render inside the authenticated layout.
* @returns {Promise<JSX.Element>} The rendered application layout component.
*/
export default async function AppLayout({
children,
@ -38,9 +39,13 @@ export default async function AppLayout({
</div>
<div className="flex-1 flex flex-col min-w-0 overflow-hidden pb-16 md:pb-0">
<Header />
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
{children}
</main>
<div className="flex-1 overflow-y-auto flex flex-col">
<main className="flex-1 p-4 md:p-6 lg:p-8">{children}</main>
</div>
<Footer />
<div className="md:hidden flex">
<Navbar />
</div>

View file

@ -1,12 +1,14 @@
/**
* @file AuthLayout.tsx
* @description Layout component wrapping authentication views with a centered container structure.
* @file (auth)/layout.tsx
* @description Layout component wrapping authentication views with a centered container structure and fixed bottom footer.
*/
import React from "react";
import Footer from "../components/layout/Footer";
/**
* Renders a centered layout wrapper optimized for authentication pages (login, registration, etc.).
* Renders a centered layout wrapper optimized for authentication pages (login, registration, etc.),
* including a fixed footer at the bottom of the viewport.
*
* @param {Object} props - The component props.
* @param {React.ReactNode} props.children - The inner child components/pages to render inside the layout.
@ -20,6 +22,10 @@ export default function AuthLayout({
return (
<div className="flex h-dvh w-full items-center justify-center p-4">
<div className="w-full max-w-md">{children}</div>
<div className="absolute bottom-0 left-0 right-0">
<Footer />
</div>
</div>
);
}

View file

@ -0,0 +1,45 @@
/**
* @file app/components/layout/Footer.tsx
* @description Client component rendering the site footer with a copyright notice and links to legal pages like Imprint and Privacy Policy.
*/
"use client";
import Link from "next/link";
/**
* Renders the application footer containing dynamic copyright information and legal navigation links.
*
* @returns {JSX.Element} The rendered footer component.
*/
export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="w-full bg-background text-xs px-3 py-3 border-t border-border">
<div className="max-w-5xl mx-auto flex flex-col md:flex-row justify-between items-center gap-2">
{/* Left: Copyright Notice */}
<p className="order-1 md:order-1">
&copy; {currentYear} André Kempf. All rights reserved.
</p>
{/* Right: Legal & Compliance Links */}
<div className="flex gap-4 order-2 md:order-3">
<Link
href="/imprint"
className="hover:text-primary transition-colors"
>
Imprint
</Link>
<span>|</span>
<Link
href="/privacy"
className="hover:text-primary transition-colors"
>
Privacy Policy
</Link>
</div>
</div>
</footer>
);
}

119
app/imprint/page.tsx Normal file
View file

@ -0,0 +1,119 @@
/**
* @file app/imprint/page.tsx
* @description Server component rendering the legal notice (imprint) page with a title header and back button navigation.
*/
/**
* Renders the imprint/legal notice page containing the main title and layout container.
*
* @async
* @returns {Promise<JSX.Element>} The rendered imprint page component.
*/
export default async function ImprintPage() {
return (
<section className="flex-1 overflow-y-auto w-full">
<div className="relative p-6 max-w-5xl mx-auto">
<h1 className="text-2xl font-bold text-foreground mb-1 tracking-tight">
Legal Notice<span className="text-primary">.</span>
</h1>
<div className="space-y-6 text-sm mt-3 text-foreground-muted leading-relaxed">
{/* Information in accordance with § 5 DDG & § 18 MStV */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Information Pursuant to §
5 DDG
</h2>
<div className="font-mono text-xs bg-background/60 p-4 rounded-xl border border-border text-foreground space-y-1">
<p className="font-semibold">André Kempf</p>
<p className="text-primary-hover">Full-Stack Web Developer</p>
<p>Großschneidersweg 2a</p>
<p>76149 Karlsruhe, Germany</p>
</div>
<p className="text-xs text-foreground-muted">
Also responsible for content pursuant to § 18 Abs. 2 MStV.
</p>
</div>
{/* Contact */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Contact
</h2>
<div className="p-4 rounded-xl bg-background/60 border border-border font-mono text-xs">
<p className="text-foreground-muted">
<span className="text-foreground">Email:</span>{" "}
<span className="text-primary hover:underline cursor-pointer">
dev@andre-kempf.com
</span>
</p>
</div>
</div>
{/* Disclaimer */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-4">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Disclaimer & Legal Notes
</h2>
<div className="space-y-1">
<h3 className="font-semibold text-foreground text-sm">
Liability for Content
</h3>
<p>
As a service provider, I am responsible for my own content on
these pages according to general laws pursuant to § 7 Abs. 1
DDG. However, according to §§ 8 to 10 DDG, I am not obligated to
monitor transmitted or stored third-party information or to
investigate circumstances that indicate illegal activity.
</p>
</div>
<div className="space-y-1 pt-3 border-t border-border">
<h3 className="font-semibold text-foreground text-sm">
Liability for Links
</h3>
<p>
My website contains links to external third-party websites over
whose content I have no control. Therefore, I cannot accept any
liability for these external contents. The respective provider
or operator of the pages is always responsible for the content
of the linked pages.
</p>
</div>
<div className="space-y-1 pt-3 border-t border-border">
<h3 className="font-semibold text-foreground text-sm">
Copyright
</h3>
<p>
The content and works created on these pages are subject to
German copyright law. Duplication, processing, distribution, or
any form of commercialization beyond the scope of copyright law
require the prior written consent of the author or creator.
</p>
</div>
</div>
{/* Dispute Resolution */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Dispute Resolution
</h2>
<p>
The European Commission provides a platform for online dispute
resolution (OS):{" "}
<a
href="https://ec.europa.eu/consumers/odr/"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline break-all"
>
https://ec.europa.eu/consumers/odr/
</a>
.<br />I am neither willing nor obligated to participate in
dispute resolution proceedings before a consumer arbitration
board.
</p>
</div>
</div>
</div>
</section>
);
}

View file

@ -6,13 +6,14 @@
import { auth } from "@/auth";
import { redirect } from "next/navigation";
import Link from "next/link";
import Footer from "@/app/components/layout/Footer";
/**
* Renders the welcome landing page. Checks for an active session and redirects
* if logged in; otherwise, provides navigation links to sign in or register.
* Renders the welcome page containing branding elements, authentication status checks,
* and navigation links for signing in or registering.
*
* @async
* @returns {Promise<JSX.Element>} The rendered welcome page component.
* @returns {Promise<JSX.Element>} The rendered welcome landing page component.
*/
export default async function WelcomePage() {
const session = typeof auth === "function" ? await auth() : null;
@ -21,27 +22,29 @@ export default async function WelcomePage() {
}
return (
<div className="flex h-dvh w-full flex-col items-center justify-center p-6 text-center">
<div className="max-w-md space-y-6">
<h1 className="text-4xl font-extrabold tracking-tight">Flowstate</h1>
<p className="text-foreground-muted">Welcome Page</p>
<div className="flex items-center justify-center gap-4">
<Link
href="/login/"
className="px-4 py-2 text-sm font-medium rounded-lg bg-foreground text-background hover:opacity-70 transition-opacity"
>
Sign In
</Link>
<div className="flex flex-col h-dvh w-full justify-between">
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
<div className="max-w-md space-y-6">
<h1 className="text-4xl font-extrabold tracking-tight">Flowstate</h1>
<p className="text-foreground-muted">Welcome Page</p>
<div className="flex items-center justify-center gap-4">
<Link
href="/login/"
className="px-4 py-2 text-sm font-medium rounded-lg bg-foreground text-background hover:opacity-70 transition-opacity"
>
Sign In
</Link>
{/* TODO */}
<Link
href="/register/"
className="px-4 py-2 text-sm font-medium rounded-lg border border-border hover:opacity-70 transition-opacity"
>
Register
</Link>
<Link
href="/register/"
className="px-4 py-2 text-sm font-medium rounded-lg border border-border hover:opacity-70 transition-opacity"
>
Register
</Link>
</div>
</div>
</div>
<Footer />
</div>
);
}

145
app/privacy/page.tsx Normal file
View file

@ -0,0 +1,145 @@
/**
* @file app/privacy/page.tsx
* @description Server component rendering the privacy policy page with a title header and back button navigation.
*/
/**
* Renders the privacy policy page containing the main title and layout container alongside a back navigation button.
*
* @async
* @returns {Promise<JSX.Element>} The rendered privacy policy page component.
*/
export default async function PrivacyPage() {
return (
<section className="flex-1 overflow-y-auto w-full">
<div className="relative p-6 max-w-5xl mx-auto">
<h1 className="text-2xl font-bold text-foreground mb-1 tracking-tight">
Privacy Policy<span className="text-primary">.</span>
</h1>
<div className="space-y-6 text-sm mt-3 text-foreground-muted leading-relaxed">
{/* Overview */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-4">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Data Protection at a
Glance
</h2>
<div className="space-y-2">
<h3 className="font-semibold text-foreground text-sm">
General Information
</h3>
<p>
The following notes provide a simple overview of what happens to
your personal data when you visit this website. Personal data is
any data that can be used to personally identify you.
</p>
</div>
<div className="space-y-2">
<h3 className="font-semibold text-foreground text-sm">
Data Collection on This Website
</h3>
<p>
Data processing on this website is carried out by the website
operator. Your data is collected when you provide it to us, or
automatically by our IT systems when you visit the site.
</p>
</div>
</div>
{/* Responsible Party */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Responsible Party
(Controller)
</h2>
<p>
The controller responsible for data processing on this website is:
</p>
<div className="font-mono text-xs bg-background/60 p-4 rounded-xl border border-border text-foreground space-y-1">
<p className="font-semibold">André Kempf</p>
<p>Großschneidersweg 2a</p>
<p>76149 Karlsruhe, Germany</p>
<p className="pt-2">
Email:{" "}
<span className="text-primary hover:underline cursor-pointer">
dev@andre-kempf.com
</span>
</p>
</div>
</div>
{/* Hosting & Server Log Files */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Hosting & Server
Infrastructure
</h2>
<p>
This website is hosted externally on web servers operated by
Netcup GmbH. Personal data processed on this website is stored on
the host&apos;s secure servers.
</p>
<p>
The hosting provider automatically processes technical access data
in server environment variables necessary to establish a stable
connection and deliver page assets securely.
</p>
</div>
{/* Server Analytics */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Server Analytics &
Privacy-First Tracking
</h2>
<p>
To evaluate website reach and optimize user experience, this
website processes minimal access metrics (e.g., total daily views,
coarse device category, and daily visitor counts).
</p>
<div className="p-4 rounded-xl bg-background/60 border border-border space-y-2 text-xs font-mono">
<p className="text-emerald-400 font-semibold">
// Key privacy guarantees:
</p>
<ul className="list-disc list-inside space-y-1 text-foreground-muted">
<li>
<strong className="text-foreground">No Cookies:</strong> We do
not store cookies or local storage identifiers on your device.
</li>
<li>
<strong className="text-foreground">Anonymized IPs:</strong>{" "}
IP addresses are instantly hashed with a daily salt and never
stored in plain text.
</li>
<li>
<strong className="text-foreground">
Zero Third Parties:
</strong>{" "}
Analytics data is processed locally on our own server and
never shared with external tracking services.
</li>
</ul>
</div>
<p className="text-xs text-foreground-muted">
The legal basis for this processing is our legitimate interest in
maintaining and optimizing our online portfolio (Art. 6(1)(f)
GDPR).
</p>
</div>
{/* Your Rights */}
<div className="rounded-2xl border border-border bg-card p-6 shadow-xl space-y-3">
<h2 className="text-base font-semibold text-foreground flex items-center gap-2 font-mono">
<span className="text-primary">//</span> Your Rights
</h2>
<p>
You have the right at any time to receive information free of
charge about the origin, recipient, and purpose of your stored
personal data. You also have a right to request the correction or
deletion of this data.
</p>
</div>
</div>
</div>
</section>
);
}

View file

@ -6,4 +6,16 @@
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://flowstate.andre-kempf.com/imprint</loc>
<lastmod>2026-08-14</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<url>
<loc>https://flowstate.andre-kempf.com/privacy</loc>
<lastmod>2026-08-14</lastmod>
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
</urlset>