Files
Portfolio/src/app/[locale]/about/page.tsx
T
damjan_savicandClaude Opus 4.5 87c7ebc5e3 feat: Add professional portfolio images and Hero redesign
- Add 15 optimized WebP images for portfolio (21-53KB each)
- Redesign Hero section with full-width background image
- Add SSR support for Hero component
- Update About section with new portrait image
- Update Contact page with professional headshot
- Add images to Services/Leistungen page
- Add image optimization script (sharp)
- Update translations for gallery section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:33:28 +01:00

140 lines
4.0 KiB
TypeScript

import { setRequestLocale } from 'next-intl/server';
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { Hero, Skills, Workflow, Journey } from '@/components/about';
import { ProfilePageJsonLd, BreadcrumbJsonLd } from '@/components/seo';
import { type Locale } from '@/i18n/config';
type Props = {
params: Promise<{ locale: string }>;
};
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'pages.about.seo' });
const keywords: Record<string, string[]> = {
de: [
'Über Damjan Savić',
'KI Entwickler Köln',
'AI Developer Deutschland',
'Fullstack Entwickler',
'Lebenslauf',
'Skills',
'Erfahrung',
'Software Entwickler',
],
en: [
'About Damjan Savić',
'Remote AI Developer',
'AI Developer Europe',
'Fullstack Developer',
'AI Consultant',
'Resume',
'Skills',
'Experience',
'Software Developer',
'Hire AI Developer',
'Freelance AI Developer USA',
'Freelance AI Developer UK',
'Remote Developer',
'n8n Expert',
'Voice AI Developer',
],
sr: [
'O Damjanu Saviću',
'AI Developer Keln',
'AI Developer Nemačka',
'Fullstack Developer',
'Biografija',
'Veštine',
'Iskustvo',
'Software Developer',
],
};
const localePath = locale === 'de' ? '/about' : `/${locale}/about`;
return {
title: t('title'),
description: t('description'),
keywords: keywords[locale] || keywords.de,
alternates: {
canonical: `${BASE_URL}${localePath}`,
languages: {
'x-default': `${BASE_URL}/about`,
de: `${BASE_URL}/about`,
en: `${BASE_URL}/en/about`,
sr: `${BASE_URL}/sr/about`,
},
},
openGraph: {
title: t('title'),
description: t('description'),
url: `${BASE_URL}${localePath}`,
type: 'profile',
locale: locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US',
alternateLocale: ['de_DE', 'en_US', 'sr_RS'].filter(l => l !== (locale === 'de' ? 'de_DE' : locale === 'sr' ? 'sr_RS' : 'en_US')),
},
};
}
export default async function AboutPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('pages.about');
const breadcrumbItems = [
{ name: locale === 'de' ? 'Start' : locale === 'sr' ? 'Početna' : 'Home', url: `/${locale}` },
{ name: locale === 'de' ? 'Über mich' : locale === 'sr' ? 'O meni' : 'About', url: `/${locale}/about` },
];
return (
<div className="relative min-h-screen">
<ProfilePageJsonLd locale={locale as Locale} />
<BreadcrumbJsonLd items={breadcrumbItems} locale={locale as Locale} />
{/* Hero Section */}
<section className="min-h-screen relative">
<Hero />
</section>
{/* Skills Section */}
<section id="skills" className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
{t('skills.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
{t('skills.description')}
</p>
</div>
<Skills />
</div>
</section>
{/* Workflow Section */}
<section className="py-24 relative">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-white mb-4">
{t('workflow.title')}
</h2>
<p className="text-zinc-400 max-w-2xl mx-auto">
{t('workflow.description')}
</p>
</div>
<Workflow />
</div>
</section>
{/* Journey Section */}
<Journey />
</div>
);
}