All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 24s
Build & Deploy / 🧪 QA (push) Successful in 1m9s
Build & Deploy / 🏗️ Build (push) Successful in 2m49s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 45s
Build & Deploy / 🔔 Notify (push) Successful in 2s
187 lines
8.8 KiB
TypeScript
187 lines
8.8 KiB
TypeScript
import { notFound } from 'next/navigation';
|
|
import { Container, Badge, Heading } from '@/components/ui';
|
|
import { setRequestLocale, getTranslations } from 'next-intl/server';
|
|
import { Metadata } from 'next';
|
|
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
|
|
import { MDXRemote } from 'next-mdx-remote/rsc';
|
|
import { SITE_URL } from '@/lib/schema';
|
|
import TrackedLink from '@/components/analytics/TrackedLink';
|
|
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
|
import { MapPin, Calendar, Briefcase, ChevronLeft } from 'lucide-react';
|
|
import Image from 'next/image';
|
|
|
|
interface PageProps {
|
|
params: Promise<{
|
|
locale: string;
|
|
slug: string;
|
|
}>;
|
|
}
|
|
|
|
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
const { locale, slug } = await params;
|
|
if (locale !== 'de' && locale !== 'en') return {};
|
|
|
|
const reference = await getReferenceBySlug(slug, locale);
|
|
if (!reference) return {};
|
|
|
|
return {
|
|
title: `${reference.frontmatter.title} | Referenzen | E-TIB Gruppe`,
|
|
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
|
|
alternates: {
|
|
canonical: `${SITE_URL}/${locale}/referenzen/${slug}`,
|
|
},
|
|
openGraph: {
|
|
title: `${reference.frontmatter.title} | E-TIB`,
|
|
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
|
|
url: `${SITE_URL}/${locale}/referenzen/${slug}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function generateStaticParams(props: any) {
|
|
const params = await props.params;
|
|
const locale = params?.locale || 'de'; // fallback to 'de' if undefined
|
|
const references = await getAllReferences(locale);
|
|
return references.map((ref) => ({
|
|
slug: ref.slug,
|
|
locale
|
|
}));
|
|
}
|
|
|
|
export default async function ReferenceDetail(props: { params: Promise<{ locale: string; slug: string }> }) {
|
|
const { locale, slug } = await props.params;
|
|
|
|
if (locale !== 'de' && locale !== 'en') {
|
|
notFound();
|
|
}
|
|
|
|
setRequestLocale(locale);
|
|
const reference = await getReferenceBySlug(slug, locale);
|
|
const t = await getTranslations('ReferenceDetail');
|
|
|
|
if (!reference) {
|
|
notFound();
|
|
}
|
|
|
|
// MDX components mapping for industrial typography
|
|
const mdxComponents = {
|
|
Heading,
|
|
h1: (props: any) => <Heading level={1} size="2" className="hidden" {...props} />, // Hidden because Hero handles H1
|
|
h2: (props: any) => <Heading level={2} size="3" className="mt-16 mb-6 border-b border-neutral-100 pb-4" {...props} />,
|
|
h3: (props: any) => <Heading level={3} size="4" className="mt-12 mb-4 text-primary" {...props} />,
|
|
h4: (props: any) => <Heading level={4} size="5" className="mt-8 mb-4 uppercase tracking-widest text-neutral-500" {...props} />,
|
|
p: (props: any) => <p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl" {...props} />,
|
|
ul: (props: any) => <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 my-12" {...props} />,
|
|
ol: (props: any) => <ol className="list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl" {...props} />,
|
|
li: (props: any) => (
|
|
<li className="bg-neutral-50 p-6 rounded-2xl border border-neutral-100 flex items-start gap-4 hover:shadow-md transition-shadow group" {...props}>
|
|
<div className="w-6 h-6 rounded-full bg-primary/10 flex items-center justify-center shrink-0 mt-0.5 group-hover:bg-primary/20 transition-colors">
|
|
<svg className="w-3.5 h-3.5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
</div>
|
|
<span className="text-neutral-700 font-semibold leading-snug">{props.children}</span>
|
|
</li>
|
|
),
|
|
a: (props: any) => <a className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold" {...props} />,
|
|
strong: (props: any) => <strong className="font-bold text-neutral-900" {...props} />,
|
|
blockquote: (props: any) => (
|
|
<blockquote className="border-l-4 border-primary pl-6 py-3 my-10 italic bg-neutral-50 rounded-r-2xl text-neutral-700 font-medium max-w-3xl shadow-sm" {...props} />
|
|
),
|
|
hr: (props: any) => <hr className="my-16 border-t-2 border-neutral-100 max-w-3xl" {...props} />,
|
|
img: (props: any) => <img className="rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100" {...props} />,
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col min-h-screen bg-white">
|
|
{/* Hero Section */}
|
|
<section className="bg-[#050B14] text-white pt-40 pb-16 md:pt-48 md:pb-20 min-h-[35vh] flex flex-col justify-end relative overflow-hidden">
|
|
{reference.frontmatter.featuredImage && (
|
|
<div className="absolute inset-0 z-0">
|
|
<Image
|
|
src={reference.frontmatter.featuredImage}
|
|
alt={reference.frontmatter.title}
|
|
fill
|
|
className="object-cover opacity-25 mix-blend-luminosity scale-105"
|
|
priority
|
|
/>
|
|
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] via-[#050B14]/60 to-transparent" />
|
|
<div className="absolute inset-0 bg-gradient-to-r from-[#050B14] via-transparent to-transparent" />
|
|
</div>
|
|
)}
|
|
<div className="absolute inset-0 opacity-20 z-0">
|
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/30 via-transparent to-transparent" />
|
|
</div>
|
|
<Container className="relative z-10">
|
|
<div className="mb-12">
|
|
<TrackedLink
|
|
href={`/${locale}/referenzen`}
|
|
className="inline-flex items-center gap-2 text-white/60 hover:text-white transition-colors"
|
|
eventProperties={{ location: 'reference_back_btn' }}
|
|
>
|
|
<ChevronLeft className="w-5 h-5" />
|
|
{t('backToOverview')}
|
|
</TrackedLink>
|
|
</div>
|
|
<div className="max-w-4xl">
|
|
<Badge variant="accent" className="mb-4 md:mb-6">
|
|
{t('projectReference')}
|
|
</Badge>
|
|
<Heading level={1} variant="white" className="mb-8 leading-tight">
|
|
{reference.frontmatter.title}
|
|
</Heading>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12">
|
|
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
|
<MapPin className="w-8 h-8 text-primary shrink-0" />
|
|
<div>
|
|
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('location')}</p>
|
|
<p className="font-semibold text-lg">{reference.frontmatter.location}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
|
<Briefcase className="w-8 h-8 text-primary shrink-0" />
|
|
<div>
|
|
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('client')}</p>
|
|
<p className="font-semibold text-lg">{reference.frontmatter.client}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-start gap-4 p-6 bg-white/5 rounded-2xl border border-white/10 backdrop-blur-sm">
|
|
<Calendar className="w-8 h-8 text-primary shrink-0" />
|
|
<div>
|
|
<p className="text-sm text-white/50 mb-1 uppercase tracking-wider font-bold">{t('period')}</p>
|
|
<p className="font-semibold text-lg">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
|
|
{/* Main Content Area */}
|
|
<Container className="py-16 md:py-24">
|
|
<div className="max-w-4xl mx-auto">
|
|
<h2 className="text-3xl font-bold text-neutral-dark mb-8">{t('scopeTitle')}</h2>
|
|
|
|
<div className="w-full">
|
|
<MDXRemote source={reference.content} components={mdxComponents} />
|
|
</div>
|
|
|
|
<div className="mt-20 flex justify-center">
|
|
<TrackedLink
|
|
href={`/${locale}/referenzen`}
|
|
className={getButtonClasses('primary', 'lg')}
|
|
eventProperties={{ location: 'reference_bottom_back_btn' }}
|
|
>
|
|
<span className="relative z-10 flex items-center justify-center gap-2">
|
|
{t('viewAll')}
|
|
</span>
|
|
<ButtonOverlay variant="primary" />
|
|
</TrackedLink>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</div>
|
|
);
|
|
}
|