Skip to content

Commit d668520

Browse files
authored
Merge pull request #391 from codeunia-dev/feat/shimmer
Replace simple loading spinner with a detailed skeleton UI for blog post pages
2 parents 3d08075 + 46c1e32 commit d668520

File tree

2 files changed

+246
-77
lines changed

2 files changed

+246
-77
lines changed

app/blog/[slug]/page.tsx

Lines changed: 103 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default function BlogPostPage() {
8888
const [likedByUser, setLikedByUser] = useState(false);
8989
const [views, setViews] = useState<number>(0);
9090
const params = useParams()
91-
91+
9292
const slug = params?.slug as string
9393

9494
useEffect(() => {
@@ -115,8 +115,8 @@ export default function BlogPostPage() {
115115
tags: Array.isArray(data.tags)
116116
? data.tags
117117
: (typeof data.tags === 'string' && data.tags
118-
? (data.tags as string).split(',').map((t: string) => t.trim())
119-
: []),
118+
? (data.tags as string).split(',').map((t: string) => t.trim())
119+
: []),
120120
})
121121
}
122122
setIsLoading(false)
@@ -195,19 +195,97 @@ export default function BlogPostPage() {
195195

196196
if (isLoading) {
197197
return (
198-
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-background via-background to-muted/20">
199-
<div className="relative">
200-
<div className="animate-spin rounded-full h-12 w-12 border-4 border-primary border-t-transparent"></div>
201-
<div className="absolute inset-0 rounded-full border-4 border-primary/20 animate-ping"></div>
202-
</div>
198+
<div className="min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
199+
<Header />
200+
<header className="border-b bg-background/80 backdrop-blur-sm sticky top-0 z-50 shadow-lg">
201+
<div className="container px-4 mx-auto py-4">
202+
<div className="flex items-center justify-between">
203+
<div className="h-9 w-32 bg-muted/50 rounded-md animate-pulse relative overflow-hidden">
204+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
205+
</div>
206+
<div className="flex items-center space-x-4">
207+
<div className="h-9 w-24 bg-muted/50 rounded-md animate-pulse relative overflow-hidden">
208+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
209+
</div>
210+
<div className="h-9 w-16 bg-muted/50 rounded-md animate-pulse relative overflow-hidden">
211+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
212+
</div>
213+
</div>
214+
</div>
215+
</div>
216+
</header>
217+
218+
<article className="container px-4 mx-auto py-12">
219+
<div className="max-w-4xl mx-auto">
220+
{/* article header skeleton */}
221+
<div className="space-y-6 mb-12">
222+
<div className="flex items-center space-x-4">
223+
<div className="h-6 w-24 bg-muted/50 rounded-full animate-pulse relative overflow-hidden">
224+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
225+
</div>
226+
</div>
227+
228+
<div className="space-y-4">
229+
<div className="h-12 w-3/4 bg-muted/50 rounded-lg animate-pulse relative overflow-hidden">
230+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
231+
</div>
232+
<div className="h-12 w-1/2 bg-muted/50 rounded-lg animate-pulse relative overflow-hidden">
233+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
234+
</div>
235+
</div>
236+
237+
<div className="space-y-3">
238+
<div className="h-6 w-full bg-muted/30 rounded animate-pulse relative overflow-hidden">
239+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
240+
</div>
241+
<div className="h-6 w-5/6 bg-muted/30 rounded animate-pulse relative overflow-hidden">
242+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
243+
</div>
244+
</div>
245+
246+
<div className="flex flex-wrap items-center gap-6 pt-4">
247+
{[1, 2, 3, 4, 5].map((i) => (
248+
<div key={i} className="h-8 w-24 bg-muted/50 rounded-full animate-pulse relative overflow-hidden" style={{ animationDelay: `${i * 100}ms` }}>
249+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" style={{ animationDelay: `${i * 100}ms` }} />
250+
</div>
251+
))}
252+
</div>
253+
</div>
254+
255+
{/* article image skeleton */}
256+
<div className="mb-12">
257+
<div className="aspect-[1280/1080] bg-muted/50 rounded-2xl animate-pulse relative overflow-hidden">
258+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" />
259+
</div>
260+
</div>
261+
262+
{/* article content skeleton */}
263+
<div className="space-y-6">
264+
{[1, 2, 3, 4].map((i) => (
265+
<div key={i} className="space-y-3" style={{ animationDelay: `${i * 150}ms` }}>
266+
<div className="h-4 w-full bg-muted/40 rounded animate-pulse relative overflow-hidden">
267+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" style={{ animationDelay: `${i * 150}ms` }} />
268+
</div>
269+
<div className="h-4 w-full bg-muted/40 rounded animate-pulse relative overflow-hidden">
270+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" style={{ animationDelay: `${i * 150 + 50}ms` }} />
271+
</div>
272+
<div className="h-4 w-3/4 bg-muted/40 rounded animate-pulse relative overflow-hidden">
273+
<div className="absolute inset-0 -translate-x-full animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/10 to-transparent" style={{ animationDelay: `${i * 150 + 100}ms` }} />
274+
</div>
275+
</div>
276+
))}
277+
</div>
278+
</div>
279+
</article>
280+
<Footer />
203281
</div>
204282
)
205283
}
206284

207285
if (fetchError) {
208286
return (
209287
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-background via-background to-muted/20">
210-
<motion.div
288+
<motion.div
211289
className="text-center space-y-4"
212290
initial={{ opacity: 0, scale: 0.9 }}
213291
animate={{ opacity: 1, scale: 1 }}
@@ -232,7 +310,7 @@ export default function BlogPostPage() {
232310
return (
233311
<div className="min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
234312
{/* header */}
235-
<Header/>
313+
<Header />
236314
<header className="border-b bg-background/80 backdrop-blur-sm sticky top-0 z-50 shadow-lg">
237315
<div className="container px-4 mx-auto py-4">
238316
<div className="flex items-center justify-between">
@@ -248,13 +326,13 @@ export default function BlogPostPage() {
248326
</Link>
249327
</Button>
250328
</motion.div>
251-
<motion.div
329+
<motion.div
252330
className="flex items-center space-x-4"
253331
initial={{ opacity: 0, x: 20 }}
254332
animate={{ opacity: 1, x: 0 }}
255333
transition={{ duration: 0.3, delay: 0.1 }}
256334
>
257-
<ShareButton
335+
<ShareButton
258336
url={`${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/blog/${slug}`}
259337
title={post?.title || ''}
260338
description={post?.excerpt || ''}
@@ -270,7 +348,7 @@ export default function BlogPostPage() {
270348
<article className="container px-4 mx-auto py-12">
271349
<div className="max-w-4xl mx-auto">
272350
{/* article header */}
273-
<motion.div
351+
<motion.div
274352
initial={{ opacity: 0, y: 20 }}
275353
animate={{ opacity: 1, y: 0 }}
276354
transition={{ duration: 0.5 }}
@@ -286,26 +364,26 @@ export default function BlogPostPage() {
286364
</Badge>
287365
)}
288366
</div>
289-
367+
290368
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight bg-gradient-to-r from-foreground via-foreground to-muted-foreground bg-clip-text text-transparent">
291369
{post?.title}
292370
</h1>
293-
371+
294372
<p className="text-xl md:text-2xl text-muted-foreground leading-relaxed">
295373
{post?.excerpt}
296374
</p>
297-
375+
298376
<div className="flex flex-wrap items-center gap-6 text-sm text-muted-foreground">
299377
<div className="flex items-center space-x-2 bg-background/80 backdrop-blur-sm px-3 py-2 rounded-full">
300378
<User className="h-4 w-4" />
301379
<span>{post?.author}</span>
302380
</div>
303381
<div className="flex items-center space-x-2 bg-background/80 backdrop-blur-sm px-3 py-2 rounded-full">
304382
<Calendar className="h-4 w-4" />
305-
<span>{new Date(post?.date || '').toLocaleDateString('en-US', {
306-
year: 'numeric',
307-
month: 'long',
308-
day: 'numeric'
383+
<span>{new Date(post?.date || '').toLocaleDateString('en-US', {
384+
year: 'numeric',
385+
month: 'long',
386+
day: 'numeric'
309387
})}</span>
310388
</div>
311389
<div className="flex items-center space-x-2 bg-background/80 backdrop-blur-sm px-3 py-2 rounded-full">
@@ -324,7 +402,7 @@ export default function BlogPostPage() {
324402
</motion.div>
325403

326404
{/* article image */}
327-
<motion.div
405+
<motion.div
328406
initial={{ opacity: 0, scale: 0.95 }}
329407
animate={{ opacity: 1, scale: 1 }}
330408
transition={{ duration: 0.5, delay: 0.2 }}
@@ -355,7 +433,7 @@ export default function BlogPostPage() {
355433
</motion.div>
356434

357435
{/* article content */}
358-
<motion.div
436+
<motion.div
359437
initial={{ opacity: 0, y: 20 }}
360438
animate={{ opacity: 1, y: 0 }}
361439
transition={{ duration: 0.5, delay: 0.3 }}
@@ -369,7 +447,7 @@ export default function BlogPostPage() {
369447
{post?.content}
370448
</ReactMarkdown>
371449
</div>
372-
450+
373451
{/* tags */}
374452
<div className="flex flex-wrap gap-2 pt-8 border-t border-primary/10">
375453
{post?.tags.map((tag: string) => (
@@ -387,7 +465,7 @@ export default function BlogPostPage() {
387465
{post?.content.split('\n\n').slice(0, 3).join('\n\n')}
388466
</ReactMarkdown>
389467
</div>
390-
468+
391469
{/* authentication prompt */}
392470
<motion.div
393471
initial={{ opacity: 0, y: 20 }}
@@ -428,7 +506,7 @@ export default function BlogPostPage() {
428506
</motion.div>
429507
</div>
430508
</article>
431-
<Footer/>
509+
<Footer />
432510
</div>
433511
)
434512
}

0 commit comments

Comments
 (0)