1
+ import type { Metadata } from "next" ;
2
+ import Image from "next/image" ;
3
+ import Link from "next/link" ;
4
+ import { notFound } from "next/navigation" ;
5
+
6
+ import { AnimatedSection } from "@/components/animations" ;
7
+ import BlogPostContent from "@/components/blog/BlogPostContent" ;
8
+ import { generateOgImageUrl } from "@/lib/og-utils" ;
9
+ import { getBlogPost , StrapiTag } from "@/lib/strapi" ;
10
+
11
+ export const dynamic = "force-dynamic" ;
12
+ export const revalidate = 5 ;
13
+
14
+ export async function generateStaticParams ( ) {
15
+ try {
16
+ return [ ] ;
17
+ } catch ( error ) {
18
+ console . error ( "Error generating static params:" , error ) ;
19
+ return [ ] ;
20
+ }
21
+ }
22
+
23
+ function getImageUrl ( url : string ) {
24
+ if ( ! url ) return '' ;
25
+ if ( url . startsWith ( 'http' ) ) return url ;
26
+ const base = process . env . NEXT_PUBLIC_ETERNALCODE_STRAPI_URL || '' ;
27
+ return `${ base } ${ url } ` ;
28
+ }
29
+
30
+ function getTagsArray ( tags : StrapiTag [ ] | { data : StrapiTag [ ] } | undefined ) : StrapiTag [ ] {
31
+ if ( ! tags ) return [ ] ;
32
+ if ( Array . isArray ( tags ) ) return tags ;
33
+ if ( 'data' in tags && Array . isArray ( tags . data ) ) return tags . data ;
34
+ return [ ] ;
35
+ }
36
+
37
+ export async function generateMetadata ( { params } : { params : Promise < { slug : string } > } ) : Promise < Metadata > {
38
+ try {
39
+ const { slug } = await params ;
40
+ const post = await getBlogPost ( slug ) ;
41
+
42
+ if ( ! post ) {
43
+ return {
44
+ title : "Post Not Found | EternalCode.pl" ,
45
+ } ;
46
+ }
47
+
48
+ const ogImageUrl = post . featuredImage ?. url
49
+ ? getImageUrl ( post . featuredImage . url )
50
+ : generateOgImageUrl ( {
51
+ title : post . title ,
52
+ subtitle : post . excerpt ,
53
+ } ) ;
54
+
55
+ const tagsArr = getTagsArray ( post . tags ) ;
56
+
57
+ return {
58
+ title : `${ post . title } | EternalCode.pl` ,
59
+ description : post . excerpt ,
60
+ keywords : tagsArr . map ( ( tag : StrapiTag ) => tag . name ) || [ ] ,
61
+ authors : [ { name : post . author ?. name || "EternalCode Team" } ] ,
62
+ openGraph : {
63
+ type : "article" ,
64
+ locale : "en_US" ,
65
+ url : `https://eternalcode.pl/blog/${ post . slug } ` ,
66
+ siteName : "EternalCode.pl" ,
67
+ title : post . title ,
68
+ description : post . excerpt ,
69
+ images : [
70
+ {
71
+ url : ogImageUrl ,
72
+ width : 1200 ,
73
+ height : 630 ,
74
+ alt : post . title ,
75
+ } ,
76
+ ] ,
77
+ publishedTime : post . publishedAt ,
78
+ modifiedTime : post . updatedAt ,
79
+ authors : [ post . author ?. name || "EternalCode Team" ] ,
80
+ tags : tagsArr . map ( ( tag : StrapiTag ) => tag . name ) || [ ] ,
81
+ } ,
82
+ twitter : {
83
+ card : "summary_large_image" ,
84
+ site : "@eternalcode" ,
85
+ creator : "@eternalcode" ,
86
+ title : post . title ,
87
+ description : post . excerpt ,
88
+ images : [ ogImageUrl ] ,
89
+ } ,
90
+ alternates : {
91
+ canonical : `https://eternalcode.pl/blog/${ post . slug } ` ,
92
+ } ,
93
+ } ;
94
+ } catch ( error ) {
95
+ console . error ( "Error generating metadata:" , error ) ;
96
+ return {
97
+ title : "Post Not Found | EternalCode.pl" ,
98
+ } ;
99
+ }
100
+ }
101
+
102
+ export default async function BlogPostPage ( { params } : { params : Promise < { slug : string } > } ) {
103
+ try {
104
+ const { slug } = await params ;
105
+ const post = await getBlogPost ( slug ) ;
106
+
107
+ if ( ! post ) {
108
+ notFound ( ) ;
109
+ }
110
+
111
+ const tagsArr = getTagsArray ( post . tags ) ;
112
+
113
+ return (
114
+ < div className = "min-h-screen bg-lightGray-100 dark:bg-gray-900" >
115
+ { /* Hero Section */ }
116
+ < AnimatedSection animationType = "fadeDown" className = "pt-40 md:pt-48 pb-0" >
117
+ < div className = "mx-auto max-w-screen-xl px-4" >
118
+ < h1 className = "mb-4 text-4xl md:text-5xl font-extrabold text-gray-900 dark:text-white text-left" >
119
+ { post . title }
120
+ </ h1 >
121
+ < p className = "mb-4 text-lg text-gray-600 dark:text-gray-300 text-left" >
122
+ { post . excerpt }
123
+ </ p >
124
+ < div className = "flex flex-wrap items-center gap-4 text-sm text-gray-500 dark:text-gray-400 mb-4" >
125
+ { post . author && post . author . slug && (
126
+ < Link href = { `/author/${ post . author . slug } ` } className = "flex items-center gap-2 hover:text-blue-600 dark:hover:text-blue-400 transition-colors" >
127
+ { post . author . avatar && (
128
+ < Image
129
+ src = { getImageUrl ( post . author . avatar . url ) }
130
+ alt = { post . author . name }
131
+ width = { 24 }
132
+ height = { 24 }
133
+ className = "rounded-full"
134
+ />
135
+ ) }
136
+ < span > By { post . author . name } </ span >
137
+ </ Link >
138
+ ) }
139
+ < span > •</ span >
140
+ < time dateTime = { post . publishedAt } >
141
+ { new Date ( post . publishedAt ) . toLocaleDateString ( "en-US" , {
142
+ year : "numeric" ,
143
+ month : "long" ,
144
+ day : "numeric" ,
145
+ } ) }
146
+ </ time >
147
+ { post . readingTime && (
148
+ < >
149
+ < span > •</ span >
150
+ < span > { post . readingTime } min read</ span >
151
+ </ >
152
+ ) }
153
+ </ div >
154
+ { tagsArr . length > 0 && (
155
+ < div className = "flex flex-wrap gap-2 mb-6" >
156
+ { tagsArr . map ( ( tag : StrapiTag ) => (
157
+ < span
158
+ key = { tag . documentId }
159
+ className = "rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200"
160
+ >
161
+ { tag . name }
162
+ </ span >
163
+ ) ) }
164
+ </ div >
165
+ ) }
166
+ </ div >
167
+ </ AnimatedSection >
168
+
169
+ { /* Blog Content */ }
170
+ < AnimatedSection animationType = "fadeUp" className = "py-16" >
171
+ < div className = "mx-auto max-w-screen-xl px-4" >
172
+ < BlogPostContent content = { post . content } />
173
+ </ div >
174
+ </ AnimatedSection >
175
+ </ div >
176
+ ) ;
177
+ } catch ( error ) {
178
+ console . error ( "Error fetching blog post:" , error ) ;
179
+ notFound ( ) ;
180
+ }
181
+ }
0 commit comments