Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m19s
Fixed missing types, import errors, and updated test suites to match the new editorial design. Verified Docker container build.
179 lines
7.0 KiB
TypeScript
179 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { ExternalLink, ArrowLeft, Github as GithubIcon, Calendar } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import ReactMarkdown from "react-markdown";
|
|
import { useTranslations } from "next-intl";
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/navigation";
|
|
import { Skeleton } from "../components/ui/Skeleton";
|
|
|
|
export type ProjectDetailData = {
|
|
id: number;
|
|
slug: string;
|
|
title: string;
|
|
description: string;
|
|
content: string;
|
|
tags: string[];
|
|
featured: boolean;
|
|
category: string;
|
|
date?: string;
|
|
created_at?: string;
|
|
github?: string | null;
|
|
github_url?: string | null;
|
|
live?: string | null;
|
|
button_live_label?: string | null;
|
|
button_github_label?: string | null;
|
|
imageUrl?: string | null;
|
|
image_url?: string | null;
|
|
technologies?: string[];
|
|
};
|
|
|
|
export default function ProjectDetailClient({
|
|
project,
|
|
locale,
|
|
}: {
|
|
project: ProjectDetailData;
|
|
locale: string;
|
|
}) {
|
|
const tCommon = useTranslations("common");
|
|
const tDetail = useTranslations("projects.detail");
|
|
const router = useRouter();
|
|
const [canGoBack, setCanGoBack] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Prüfen, ob wir eine History haben (von Home gekommen)
|
|
if (typeof window !== 'undefined' && window.history.length > 1) {
|
|
setCanGoBack(true);
|
|
}
|
|
|
|
try {
|
|
navigator.sendBeacon?.(
|
|
"/api/analytics/track",
|
|
new Blob([JSON.stringify({ type: "pageview", projectId: project.id.toString(), page: `/${locale}/projects/${project.slug}` })], { type: "application/json" }),
|
|
);
|
|
} catch {}
|
|
}, [project.id, project.slug, locale]);
|
|
|
|
const handleBack = (e: React.MouseEvent) => {
|
|
e.preventDefault();
|
|
// Wenn wir direkt auf die Seite gekommen sind (Deep Link), gehen wir zur Projektliste
|
|
// Ansonsten nutzen wir den Browser-Back, um an die exakte Stelle der Home oder Liste zurückzukehren
|
|
if (canGoBack) {
|
|
router.back();
|
|
} else {
|
|
router.push(`/${locale}/projects`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#fdfcf8] dark:bg-stone-950 pt-32 pb-20 px-6 transition-colors duration-500">
|
|
<div className="max-w-7xl mx-auto">
|
|
|
|
{/* Navigation - Intelligent Back */}
|
|
<button
|
|
onClick={handleBack}
|
|
className="inline-flex items-center gap-2 text-stone-500 hover:text-stone-900 dark:hover:text-white transition-colors mb-12 group bg-transparent border-none cursor-pointer"
|
|
>
|
|
<ArrowLeft size={20} className="group-hover:-translate-x-1 transition-transform" />
|
|
<span className="font-bold uppercase tracking-widest text-xs">
|
|
{tCommon("back")}
|
|
</span>
|
|
</button>
|
|
|
|
{/* Title Section */}
|
|
<div className="mb-20">
|
|
<h1 className="text-6xl md:text-[10rem] font-black tracking-tighter text-stone-900 dark:text-stone-50 leading-[0.85] uppercase mb-8">
|
|
{project.title}<span className="text-liquid-mint">.</span>
|
|
</h1>
|
|
<p className="text-xl md:text-3xl font-light text-stone-500 dark:text-stone-400 max-w-4xl leading-snug tracking-tight">
|
|
{project.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Feature Image Box */}
|
|
<div className="bg-white dark:bg-stone-900 rounded-[3rem] p-4 md:p-8 border border-stone-200/60 dark:border-stone-800/60 shadow-sm mb-12 overflow-hidden">
|
|
<div className="relative aspect-video rounded-[2rem] overflow-hidden border-4 border-stone-50 dark:border-stone-800 shadow-2xl">
|
|
{project.imageUrl ? (
|
|
<Image src={project.imageUrl} alt={project.title} fill className="object-cover" priority sizes="100vw" />
|
|
) : (
|
|
<div className="absolute inset-0 bg-stone-100 dark:bg-stone-800 flex items-center justify-center">
|
|
<span className="text-[15rem] font-black text-stone-200 dark:text-stone-700">{project.title.charAt(0)}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
|
<div className="lg:col-span-8 space-y-8">
|
|
<div className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm">
|
|
<div className="prose prose-stone dark:prose-invert max-w-none text-lg md:text-xl font-light leading-relaxed">
|
|
<ReactMarkdown>{project.content}</ReactMarkdown>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="lg:col-span-4 space-y-8">
|
|
|
|
|
|
|
|
{/* Quick Links Box - Only show if links exist */}
|
|
|
|
{((project.live && project.live !== "#") || (project.github && project.github !== "#")) && (
|
|
|
|
<div className="bg-stone-900 dark:bg-stone-800 rounded-[3rem] p-10 border border-stone-800 dark:border-stone-700 shadow-2xl text-white">
|
|
|
|
<h3 className="text-xl font-black mb-8 uppercase tracking-widest text-liquid-mint">Links</h3>
|
|
|
|
<div className="space-y-4">
|
|
|
|
{project.live && project.live !== "#" && (
|
|
|
|
<a href={project.live} target="_blank" rel="noopener noreferrer" className="flex items-center justify-between w-full p-5 bg-white text-stone-900 rounded-2xl font-black hover:scale-105 transition-transform group">
|
|
|
|
<span>{project.button_live_label || tDetail("liveDemo")}</span>
|
|
|
|
<ExternalLink size={20} className="group-hover:translate-x-1 transition-transform" />
|
|
|
|
</a>
|
|
|
|
)}
|
|
|
|
{project.github && project.github !== "#" && (
|
|
|
|
<a href={project.github} target="_blank" rel="noopener noreferrer" className="flex items-center justify-between w-full p-5 bg-stone-800 text-white border border-stone-700 rounded-2xl font-black hover:bg-stone-700 transition-colors group">
|
|
|
|
<span>{project.button_github_label || tDetail("viewSource")}</span>
|
|
|
|
<GithubIcon size={20} className="group-hover:rotate-12 transition-transform" />
|
|
|
|
</a>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<div className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 border border-stone-200/60 dark:border-stone-800/60 shadow-sm">
|
|
<h3 className="text-xl font-black mb-8 uppercase tracking-widest text-stone-400">Stack</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{project.tags.map((tag) => (
|
|
<span key={tag} className="px-4 py-2 bg-stone-50 dark:bg-stone-800 rounded-xl text-xs font-bold border border-stone-100 dark:border-stone-700">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|