From 2f4d012d590eca9f422244e42fda415340c76892 Mon Sep 17 00:00:00 2001 From: Chneemann Date: Tue, 8 Sep 2026 07:33:22 +0200 Subject: [PATCH] feat(legal): add legal notice and privacy pages with footer links --- app/(app)/dm/[conversationId]/page.tsx | 3 + app/(app)/page.tsx | 10 +- .../[serverId]/channels/[channelId]/page.tsx | 3 + app/(legal)/imprint/page.tsx | 112 ++++++++++++++ app/(legal)/privacy/page.tsx | 138 ++++++++++++++++++ components/layout/AppFooter.tsx | 48 ++++++ lib/legal.ts | 22 +++ 7 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 app/(legal)/imprint/page.tsx create mode 100644 app/(legal)/privacy/page.tsx create mode 100644 components/layout/AppFooter.tsx create mode 100644 lib/legal.ts diff --git a/app/(app)/dm/[conversationId]/page.tsx b/app/(app)/dm/[conversationId]/page.tsx index d7d2cfd..283f1d3 100644 --- a/app/(app)/dm/[conversationId]/page.tsx +++ b/app/(app)/dm/[conversationId]/page.tsx @@ -14,6 +14,7 @@ import { ChatMessages } from "@/components/chat/ChatMessages"; import type { MessageWithMember } from "@/components/chat/ChatItem"; import { isValidUuid } from "@/lib/utils"; import { getUserFriendships } from "@/lib/services/friends.service"; +import AppFooter from "@/components/layout/AppFooter"; /** Renders the direct message conversation page with header, message history, and input field. */ export default async function DirectMessagePage({ @@ -98,6 +99,8 @@ export default async function DirectMessagePage({ conversationId={conversationId} placeholderName={partner.username} /> + + ); } diff --git a/app/(app)/page.tsx b/app/(app)/page.tsx index f61018e..8b93532 100644 --- a/app/(app)/page.tsx +++ b/app/(app)/page.tsx @@ -9,14 +9,9 @@ import { friendships } from "@/db/schema"; import { or, eq } from "drizzle-orm"; import { redirect } from "next/navigation"; import { FriendsView, type Friendship } from "@/components/friends/FriendsView"; +import AppFooter from "@/components/layout/AppFooter"; -/** - * Asynchronously renders the main application page for authenticated users. - * - * @async - * @function AppPage - * @returns {Promise} The rendered application page container with the FriendsView component. - */ +/** Asynchronously renders the main application page for authenticated users. */ export default async function AppPage() { const session = await auth(); const currentUserId = session?.user?.id; @@ -58,6 +53,7 @@ export default async function AppPage() { currentUserId={currentUserId} initialFriendships={rawFriendships as Friendship[]} /> + ); } diff --git a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx index aa10cf5..1979a23 100644 --- a/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx +++ b/app/(app)/servers/[serverId]/channels/[channelId]/page.tsx @@ -13,6 +13,7 @@ import { getChannelMessages } from "@/lib/services/message.service"; import { getServerById } from "@/lib/services/server.service"; import { isValidUuid } from "@/lib/utils"; import { getUserFriendships } from "@/lib/services/friends.service"; +import AppFooter from "@/components/layout/AppFooter"; /** Renders the channel chat view by validating parameters, checking user session, fetching channel data, friendships, and displaying headers, messages, and input controls. */ export default async function ChannelPage({ @@ -66,6 +67,8 @@ export default async function ChannelPage({ channelId={channel.id} placeholderName={channel.name} /> + + ); } diff --git a/app/(legal)/imprint/page.tsx b/app/(legal)/imprint/page.tsx new file mode 100644 index 0000000..f16c84a --- /dev/null +++ b/app/(legal)/imprint/page.tsx @@ -0,0 +1,112 @@ +/** + * @file app/(legal)/imprint/page.tsx + * @description Server component rendering the imprint/legal notice page tailored to the app layout design. + */ + +export default async function ImprintPage() { + return ( +
+
+

+ Legal Notice. +

+ +
+ {/* Information pursuant to § 5 DDG */} +
+

+ // Information Pursuant to § + 5 DDG +

+
+

André Kempf

+

Full-Stack Web Developer

+

Großschneidersweg 2a

+

76149 Karlsruhe, Germany

+
+

+ Also responsible for content pursuant to § 18 Abs. 2 MStV. +

+
+ + {/* Contact */} +
+

+ // Contact +

+
+

+ Email:{" "} + + dev@andre-kempf.com + +

+
+
+ + {/* Disclaimer */} +
+

+ // Disclaimer & Legal Notes +

+
+

+ Liability for Content +

+

+ 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. +

+
+
+

+ Liability for Links +

+

+ 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. +

+
+
+

Copyright

+

+ 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. +

+
+
+ + {/* Dispute Resolution */} +
+

+ // Dispute Resolution +

+

+ The European Commission provides a platform for online dispute + resolution (OS):{" "} + + https://ec.europa.eu/consumers/odr/ + + .
I am neither willing nor obligated to participate in + dispute resolution proceedings before a consumer arbitration + board. +

+
+
+
+
+ ); +} diff --git a/app/(legal)/privacy/page.tsx b/app/(legal)/privacy/page.tsx new file mode 100644 index 0000000..ca9fa7d --- /dev/null +++ b/app/(legal)/privacy/page.tsx @@ -0,0 +1,138 @@ +/** + * @file app/(legal)/privacy/page.tsx + * @description Server component rendering the privacy policy page tailored to the app layout design. + */ + +export default async function PrivacyPage() { + return ( +
+
+

+ Privacy Policy. +

+ +
+ {/* Overview */} +
+

+ // Data Protection at a + Glance +

+
+

+ General Information +

+

+ 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. +

+
+
+

+ Data Collection on This Website +

+

+ 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. +

+
+
+ + {/* Responsible Party */} +
+

+ // Responsible Party + (Controller) +

+

+ The controller responsible for data processing on this website is: +

+
+

André Kempf

+

Großschneidersweg 2a

+

76149 Karlsruhe, Germany

+

+ Email:{" "} + + dev@andre-kempf.com + +

+
+
+ + {/* Hosting & Server Log Files */} +
+

+ // Hosting & Server + Infrastructure +

+

+ This website is hosted externally on web servers operated by + Netcup GmbH. Personal data processed on this website is stored on + the host's secure servers. +

+

+ The hosting provider automatically processes technical access data + in server environment variables necessary to establish a stable + connection and deliver page assets securely. +

+
+ + {/* Server Analytics */} +
+

+ // Server Analytics & + Privacy-First Tracking +

+

+ 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). +

+
+

+ // Key privacy guarantees: +

+
    +
  • + No Cookies: We do not + store cookies or local storage identifiers on your device. +
  • +
  • + Anonymized IPs: IP + addresses are instantly hashed with a daily salt and never + stored in plain text. +
  • +
  • + Zero Third Parties:{" "} + Analytics data is processed locally on our own server and + never shared with external tracking services. +
  • +
+
+

+ The legal basis for this processing is our legitimate interest in + maintaining and optimizing our online portfolio (Art. 6(1)(f) + GDPR). +

+
+ + {/* Your Rights */} +
+

+ // Your Rights +

+

+ 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. +

+
+
+
+
+ ); +} diff --git a/components/layout/AppFooter.tsx b/components/layout/AppFooter.tsx new file mode 100644 index 0000000..449599d --- /dev/null +++ b/components/layout/AppFooter.tsx @@ -0,0 +1,48 @@ +/** + * @file app/components/layout/AppFooter.tsx + * @description Site footer component rendering legal compliance links and dynamic copyright notice. + */ + +"use client"; + +import Link from "next/link"; +import { LEGAL_LINKS, COPYRIGHT_TEXT, ICON_MAP } from "@/lib/legal"; + +/** Renders the site footer containing the dynamic copyright notice and legal compliance links with icons. */ +export default function AppFooter() { + const currentYear = new Date().getFullYear(); + + return ( + + ); +} diff --git a/lib/legal.ts b/lib/legal.ts new file mode 100644 index 0000000..6e03d39 --- /dev/null +++ b/lib/legal.ts @@ -0,0 +1,22 @@ +/** + * @file app/lib/legal.ts + * @description Defines legal navigation configurations, icon mappings, and dynamic copyright text generators. + */ + +import { FileText, ShieldCheck } from "lucide-react"; + +/** List of legal navigation links with their corresponding route paths and icon identifiers. */ +export const LEGAL_LINKS = [ + { name: "Imprint", href: "/imprint", iconName: "FileText" }, + { name: "Privacy Policy", href: "/privacy", iconName: "ShieldCheck" }, +] as const; + +/** Mapping object linking string identifiers to their respective Lucide icon components. */ +export const ICON_MAP = { + FileText: FileText, + ShieldCheck: ShieldCheck, +}; + +/** Generates the standardized copyright notice string with the given year. */ +export const COPYRIGHT_TEXT = (year: number) => + `© ${year} André Kempf. All rights reserved.`;