diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 0000000..f1bb807 --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,21 @@ +import { NextIntlClientProvider } from "next-intl"; +import { getMessages } from "next-intl/server"; +import React from "react"; + +export default async function LocaleLayout({ + children, + params, +}: { + children: React.ReactNode; + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const messages = await getMessages({ locale }); + + return ( + + {children} + + ); +} + diff --git a/app/[locale]/legal-notice/page.tsx b/app/[locale]/legal-notice/page.tsx new file mode 100644 index 0000000..c4963d3 --- /dev/null +++ b/app/[locale]/legal-notice/page.tsx @@ -0,0 +1,2 @@ +export { default } from "../../legal-notice/page"; + diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx new file mode 100644 index 0000000..f93682d --- /dev/null +++ b/app/[locale]/page.tsx @@ -0,0 +1,2 @@ +export { default } from "../_ui/HomePage"; + diff --git a/app/[locale]/privacy-policy/page.tsx b/app/[locale]/privacy-policy/page.tsx new file mode 100644 index 0000000..67cb9e3 --- /dev/null +++ b/app/[locale]/privacy-policy/page.tsx @@ -0,0 +1,2 @@ +export { default } from "../../privacy-policy/page"; + diff --git a/app/[locale]/projects/[slug]/page.tsx b/app/[locale]/projects/[slug]/page.tsx new file mode 100644 index 0000000..06de5e9 --- /dev/null +++ b/app/[locale]/projects/[slug]/page.tsx @@ -0,0 +1,36 @@ +import { prisma } from "@/lib/prisma"; +import ProjectDetailClient from "@/app/_ui/ProjectDetailClient"; +import { notFound } from "next/navigation"; + +export const revalidate = 300; + +export default async function ProjectPage({ + params, +}: { + params: Promise<{ locale: string; slug: string }>; +}) { + const { locale, slug } = await params; + + const project = await prisma.project.findFirst({ + where: { slug, published: true }, + include: { + translations: { + where: { locale }, + select: { title: true, description: true }, + }, + }, + }); + + if (!project) return notFound(); + + const tr = project.translations?.[0]; + const { translations: _translations, ...rest } = project; + const localized = { + ...rest, + title: tr?.title ?? project.title, + description: tr?.description ?? project.description, + }; + + return ; +} + diff --git a/app/[locale]/projects/page.tsx b/app/[locale]/projects/page.tsx new file mode 100644 index 0000000..5e4a9cd --- /dev/null +++ b/app/[locale]/projects/page.tsx @@ -0,0 +1,36 @@ +import { prisma } from "@/lib/prisma"; +import ProjectsPageClient from "@/app/_ui/ProjectsPageClient"; + +export const revalidate = 300; + +export default async function ProjectsPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + + const projects = await prisma.project.findMany({ + where: { published: true }, + orderBy: { createdAt: "desc" }, + include: { + translations: { + where: { locale }, + select: { title: true, description: true }, + }, + }, + }); + + const localized = projects.map((p) => { + const tr = p.translations?.[0]; + const { translations: _translations, ...rest } = p; + return { + ...rest, + title: tr?.title ?? p.title, + description: tr?.description ?? p.description, + }; + }); + + return ; +} + diff --git a/app/__tests__/components/Header.test.tsx b/app/__tests__/components/Header.test.tsx index e9c1108..8c8edd9 100644 --- a/app/__tests__/components/Header.test.tsx +++ b/app/__tests__/components/Header.test.tsx @@ -21,7 +21,7 @@ describe('Header', () => { it('renders the mobile header', () => { render(
); // Check for mobile menu button (hamburger icon) - const menuButton = screen.getByRole('button'); + const menuButton = screen.getByLabelText('Open menu'); expect(menuButton).toBeInTheDocument(); }); }); \ No newline at end of file diff --git a/app/_ui/HomePage.tsx b/app/_ui/HomePage.tsx new file mode 100644 index 0000000..40ed916 --- /dev/null +++ b/app/_ui/HomePage.tsx @@ -0,0 +1,182 @@ +"use client"; + +import Header from "../components/Header"; +import Hero from "../components/Hero"; +import About from "../components/About"; +import Projects from "../components/Projects"; +import Contact from "../components/Contact"; +import Footer from "../components/Footer"; +import Script from "next/script"; +import dynamic from "next/dynamic"; +import ErrorBoundary from "@/components/ErrorBoundary"; +import { motion } from "framer-motion"; + +// Wrap ActivityFeed in error boundary to prevent crashes +const ActivityFeed = dynamic( + () => + import("../components/ActivityFeed").catch(() => ({ default: () => null })), + { + ssr: false, + loading: () => null, + }, +); + +export default function HomePage() { + return ( +
+ - Dennis Konkol's Portfolio {children} diff --git a/app/legal-notice/page.tsx b/app/legal-notice/page.tsx index 782b249..02c11ae 100644 --- a/app/legal-notice/page.tsx +++ b/app/legal-notice/page.tsx @@ -6,8 +6,34 @@ import { ArrowLeft } from 'lucide-react'; import Header from "../components/Header"; import Footer from "../components/Footer"; import Link from "next/link"; +import { useLocale, useTranslations } from "next-intl"; +import { useEffect, useState } from "react"; +import type { JSONContent } from "@tiptap/react"; +import RichTextClient from "../components/RichTextClient"; export default function LegalNotice() { + const locale = useLocale(); + const t = useTranslations("common"); + const [cmsDoc, setCmsDoc] = useState(null); + const [cmsTitle, setCmsTitle] = useState(null); + + useEffect(() => { + (async () => { + try { + const res = await fetch( + `/api/content/page?key=${encodeURIComponent("legal-notice")}&locale=${encodeURIComponent(locale)}`, + ); + const data = await res.json(); + if (data?.content?.content) { + setCmsDoc(data.content.content as JSONContent); + setCmsTitle((data.content.title as string | null) ?? null); + } + } catch { + // ignore; fallback to static content + } + })(); + }, [locale]); + return (
@@ -19,15 +45,15 @@ export default function LegalNotice() { className="mb-8" > - Back to Home + {t("backToHome")}

- Impressum + {cmsTitle || "Impressum"}

@@ -37,47 +63,68 @@ export default function LegalNotice() { transition={{ duration: 0.8, delay: 0.2 }} className="glass-card p-8 rounded-2xl space-y-6" > -
-

- Verantwortlicher für die Inhalte dieser Website -

-
-

Name: Dennis Konkol

-

Adresse: Auf dem Ziegenbrink 2B, 49082 Osnabrück, Deutschland

-

E-Mail: info@dk0.dev

-

Website: dk0.dev

-
-
+ {cmsDoc ? ( + + ) : ( + <> +
+

Verantwortlicher für die Inhalte dieser Website

+
+

+ Name: Dennis Konkol +

+

+ Adresse: Auf dem Ziegenbrink 2B, 49082 Osnabrück, Deutschland +

+

+ E-Mail:{" "} + + info@dk0.dev + +

+

+ Website:{" "} + + dk0.dev + +

+
+
-
-

Haftung für Links

-

- Meine Website enthält Links auf externe Websites. Ich habe keinen Einfluss auf die Inhalte dieser Websites - und kann daher keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der Betreiber oder - Anbieter der Seiten verantwortlich. Jedoch überprüfe ich die verlinkten Seiten zum Zeitpunkt der Verlinkung - auf mögliche Rechtsverstöße. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen. -

-
+
+

Haftung für Links

+

+ Meine Website enthält Links auf externe Websites. Ich habe keinen Einfluss auf die Inhalte dieser + Websites und kann daher keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der + Betreiber oder Anbieter der Seiten verantwortlich. Jedoch überprüfe ich die verlinkten Seiten zum + Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße. Bei Bekanntwerden von Rechtsverletzungen werde + ich derartige Links umgehend entfernen. +

+
-
-

Urheberrecht

-

- Alle Inhalte dieser Website, einschließlich Texte, Fotos und Designs, stehen unter Urheberrechtsschutz. - Jegliche Nutzung ohne vorherige schriftliche Zustimmung des Urhebers ist verboten. -

-
+
+

Urheberrecht

+

+ Alle Inhalte dieser Website, einschließlich Texte, Fotos und Designs, stehen unter + Urheberrechtsschutz. Jegliche Nutzung ohne vorherige schriftliche Zustimmung des Urhebers ist + verboten. +

+
-
-

Gewährleistung

-

- Die Nutzung der Inhalte dieser Website erfolgt auf eigene Gefahr. Als Diensteanbieter kann ich keine - Gewähr übernehmen für Schäden, die entstehen können, durch den Zugriff oder die Nutzung dieser Website. -

-
+
+

Gewährleistung

+

+ Die Nutzung der Inhalte dieser Website erfolgt auf eigene Gefahr. Als Diensteanbieter kann ich keine + Gewähr übernehmen für Schäden, die entstehen können, durch den Zugriff oder die Nutzung dieser + Website. +

+
-
-

Letzte Aktualisierung: 12.02.2025

-
+
+

Letzte Aktualisierung: 12.02.2025

+
+ + )}