import React, { useEffect, useState, useCallback } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import { motion } from 'framer-motion'; import { Calendar, ArrowLeft, Tag, Loader2, Folder } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import PageTransition from '../../../components/PageTransition'; import SEO from '../../../components/SEO'; import { trackBlogPostView } from '../../../services/gtm'; import { blog as blogDe } from '../../../i18n/locales/de/blog/'; import { blog as blogEn } from '../../../i18n/locales/en/blog'; import { blog as blogSr } from '../../../i18n/locales/sr/blog'; /* ======================= Typdefinitionen ========================== */ // Typ für einen Blog-Post, wie er in der Komponente genutzt wird (enthält slug) interface BlogPostType { slug: string; title: string; excerpt: string; date: string; coverImage?: string; category?: string; tags?: string[]; content: { intro: { title: string; description: string; }; background: { title: string; systems: Record; challenges: Record; }; tech: { title: string; components: { title: string; code: string; }; }; api: { title: string; apparel_magic: { title: string; description: string; }; }; conclusion: { title: string; requirements: Record; results: string; }; }; } // Typ für einen Blog-Post in der Konfiguration (ohne slug) type BlogPostConfig = Omit; // Typ für die Blog-Konfiguration (UI-Text, Kategorien, Posts) interface BlogConfig { meta?: { title: string; description: string; header: unknown; }; ui: { loading: { post: string; }; notFound: { title: string; message: string; }; backToBlog: string; }; categories: Record; posts: Record; } /* ======================= BlogPost Component ========================== */ const BlogPost: React.FC = () => { const { slug } = useParams(); const navigate = useNavigate(); const { i18n } = useTranslation(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Memoized Funktion zur Ermittlung der aktuellen Blog-Konfiguration anhand der Sprache. const getBlogConfig = useCallback((): BlogConfig => { switch (i18n.language) { case 'en': return blogEn as unknown as BlogConfig; case 'sr': return blogSr as unknown as BlogConfig; case 'de': default: return blogDe as unknown as BlogConfig; } }, [i18n.language]); const blog = getBlogConfig(); useEffect(() => { const loadPost = async () => { setLoading(true); try { if (slug) { const currentBlog = getBlogConfig(); const postData = currentBlog.posts[slug]; if (postData) { // Wir fügen hier den slug hinzu, da er in der Konfiguration nicht enthalten ist. setPost({ ...postData, slug, }); // Track blog post view trackBlogPostView(postData.title, postData.category); } else { // Statt eine Exception zu werfen, setzen wir den Fehler direkt. setError(new Error('Post not found')); } } } catch (err) { console.error('Error loading blog post:', err); setError(err instanceof Error ? err : new Error('Failed to load post')); } finally { setLoading(false); } }; loadPost(); }, [slug, getBlogConfig]); const containerVariants = { hidden: {}, visible: { transition: { staggerChildren: 0.1, }, }, }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 }, }; const getFormattedDate = (date: string): string => { const languageMap: Record = { de: 'de-DE', en: 'en-US', sr: 'sr-RS', }; return new Date(date).toLocaleDateString( languageMap[i18n.language] || 'de-DE', { year: 'numeric', month: 'long', day: 'numeric', } ); }; if (loading) { return (

{blog.ui.loading.post}

); } if (error || !post) { return (

{blog.ui.notFound.title}

{error?.message || blog.ui.notFound.message}

{blog.ui.backToBlog}
); } // Article Schema for SEO const articleSchema = { "@context": "https://schema.org", "@type": "Article", "headline": post.title, "description": post.excerpt, "datePublished": post.date, "dateModified": post.date, "author": { "@type": "Person", "name": "Damjan Savić", "url": "https://damjan-savic.com" }, "publisher": { "@type": "Person", "name": "Damjan Savić", "logo": { "@type": "ImageObject", "url": "https://damjan-savic.com/logo.svg" } }, "mainEntityOfPage": { "@type": "WebPage", "@id": `https://damjan-savic.com/blog/${post.slug}` }, "articleSection": post.category ? blog.categories[post.category] : undefined, "keywords": post.tags?.join(', ') }; return (