This commit is contained in:
Timo Knuth 2025-09-30 23:06:28 +02:00
parent f36fe6276c
commit 70991a4345
1 changed files with 190 additions and 22 deletions

View File

@ -27,6 +27,27 @@ async function getBlogPost(slug: string): Promise<BlogPost | null> {
}
}
async function getAllBlogPosts(): Promise<BlogPost[]> {
const baseUrl = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:4005'
try {
const response = await fetch(`${baseUrl}/posts`, {
cache: 'no-store',
next: { revalidate: 0 }
})
if (!response.ok) {
return []
}
const payload = await response.json()
return Array.isArray(payload.data) ? payload.data : []
} catch (error) {
console.error('[frontend] Failed to load posts', error)
return []
}
}
function formatDate(timestamp: string) {
const date = new Date(timestamp)
return date.toLocaleDateString('en-US', {
@ -81,6 +102,15 @@ function renderSection(section: BlogPostSection) {
)
}
function shuffleArray<T>(array: T[]): T[] {
const shuffled = [...array]
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
}
return shuffled
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getBlogPost(params.slug)
@ -88,6 +118,10 @@ export default async function BlogPostPage({ params }: { params: { slug: string
notFound()
}
const allPosts = await getAllBlogPosts()
const otherPosts = allPosts.filter(p => p.id !== post.id)
const randomPosts = shuffleArray(otherPosts).slice(0, 3)
const heroImage = resolveMediaUrl(post.previewImage)
return (
@ -98,38 +132,41 @@ export default async function BlogPostPage({ params }: { params: { slug: string
width: '100%',
margin: '0 auto',
padding: '48px 20px',
backgroundColor: '#F7F1E1'
backgroundColor: '#F7F1E1',
position: 'relative'
}}
>
<a
href="/"
style={{
position: 'absolute',
top: '16px',
left: '20px',
padding: '8px 12px',
border: '1px solid #8B7D6B',
backgroundColor: '#F7F1E1',
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
letterSpacing: '0.1em',
textTransform: 'uppercase',
textDecoration: 'none',
color: '#1E1A17',
zIndex: 10
}}
>
&larr; Back
</a>
<div
style={{
position: 'relative',
backgroundColor: '#F7F1E1',
border: '2px solid #8B7D6B',
padding: '32px',
boxShadow: '0 20px 60px rgba(0,0,0,0.35)'
boxShadow: '0 20px 60px rgba(0,0,0,0.35)',
marginTop: '48px'
}}
>
<a
href="/"
style={{
position: 'absolute',
top: '16px',
left: '16px',
padding: '8px 12px',
border: '1px solid #8B7D6B',
backgroundColor: 'transparent',
fontFamily: 'Space Mono, monospace',
fontSize: '12px',
letterSpacing: '0.1em',
textTransform: 'uppercase',
textDecoration: 'none',
color: '#1E1A17'
}}
>
&larr; Back
</a>
{heroImage && (
<div
style={{
@ -230,7 +267,138 @@ export default async function BlogPostPage({ params }: { params: { slug: string
</footer>
)}
</div>
{randomPosts.length > 0 && (
<section style={{ marginTop: '64px' }}>
<h2 style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '32px',
color: '#1E1A17',
marginBottom: '32px',
textAlign: 'center'
}}>
More Articles
</h2>
<div style={{ display: 'grid', gap: '24px' }}>
{randomPosts.map(article => {
const previewUrl = resolveMediaUrl(article.previewImage)
return (
<a
key={article.id}
href={`/blog/${article.slug}`}
style={{
display: 'block',
backgroundColor: 'white',
border: '2px solid #8B7D6B',
padding: '16px',
textDecoration: 'none',
color: 'inherit',
transition: 'transform 0.2s'
}}
>
<div style={{ display: 'flex', gap: '16px' }}>
{previewUrl && (
<div
style={{
width: '120px',
height: '120px',
flexShrink: 0,
backgroundImage: `url('${previewUrl}')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
border: '1px solid #8B7D6B'
}}
/>
)}
<div style={{ flex: 1 }}>
<h3 style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '18px',
color: '#1E1A17',
marginBottom: '8px'
}}>
{article.title}
</h3>
{article.excerpt && (
<p style={{
fontFamily: 'Spectral, serif',
fontSize: '14px',
color: '#4A4A4A',
lineHeight: 1.5,
marginBottom: '8px'
}}>
{article.excerpt.substring(0, 100)}...
</p>
)}
<span style={{
fontFamily: 'Space Mono, monospace',
fontSize: '10px',
color: '#8B7D6B',
letterSpacing: '0.1em',
textTransform: 'uppercase'
}}>
Read More
</span>
</div>
</div>
</a>
)
})}
</div>
</section>
)}
</div>
<footer
style={{
backgroundColor: '#F7F1E1',
borderTop: '2px solid #8B7D6B',
padding: '32px 20px',
marginTop: '64px'
}}
>
<div style={{ maxWidth: '820px', margin: '0 auto' }}>
<div style={{ textAlign: 'center' }}>
<h3
style={{
fontFamily: 'Abril Fatface, serif',
fontSize: '24px',
margin: 0,
color: '#1E1A17'
}}
>
The Curated Finds
</h3>
<p
style={{
fontFamily: 'Spectral, serif',
fontSize: '14px',
color: '#4A4A4A',
marginTop: '8px'
}}
>
Handpicked treasures. Honest descriptions. Careful packaging.
</p>
<div
style={{
margin: '16px auto',
width: '120px',
height: '2px',
backgroundColor: '#8B7D6B'
}}
/>
<p
style={{
color: '#8B7D6B',
fontFamily: 'Space Mono, monospace',
fontSize: '12px'
}}
>
(c) {new Date().getFullYear()} The Curated Finds - All rights reserved.
</p>
</div>
</div>
</footer>
</div>
)
}