mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 16:11:12 -05:00
149 lines
4.8 KiB
TypeScript
149 lines
4.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useParams, Link } from 'react-router-dom';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
import { MarkdownRenderer } from '@/components/blog/MarkdownRenderer';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { Button } from '@/components/ui/button';
|
|
import { ArrowLeft, Calendar, Eye } from 'lucide-react';
|
|
import { formatDistanceToNow } from 'date-fns';
|
|
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { Header } from '@/components/layout/Header';
|
|
import { Footer } from '@/components/layout/Footer';
|
|
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
|
import { useOpenGraph } from '@/hooks/useOpenGraph';
|
|
|
|
export default function BlogPost() {
|
|
const { slug } = useParams<{ slug: string }>();
|
|
|
|
const { data: post, isLoading } = useQuery({
|
|
queryKey: ['blog-post', slug],
|
|
queryFn: async () => {
|
|
const query = supabase
|
|
.from('blog_posts')
|
|
.select('*, profiles!inner(username, display_name, avatar_url, avatar_image_id)')
|
|
.eq('slug', slug || '')
|
|
.eq('status', 'published')
|
|
.single();
|
|
|
|
const { data, error } = await query;
|
|
if (error) throw error;
|
|
return data;
|
|
},
|
|
enabled: !!slug,
|
|
});
|
|
|
|
// Update document title when post changes
|
|
useDocumentTitle(post?.title || 'Blog Post');
|
|
|
|
// Update Open Graph meta tags
|
|
useOpenGraph({
|
|
title: post?.title || '',
|
|
description: post?.content?.substring(0, 160),
|
|
imageUrl: post?.featured_image_url ?? undefined,
|
|
imageId: post?.featured_image_id ?? undefined,
|
|
type: 'article',
|
|
enabled: !!post
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (slug) {
|
|
supabase.rpc('increment_blog_view_count', { post_slug: slug });
|
|
}
|
|
}, [slug]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Header />
|
|
<div className="container mx-auto px-4 py-12 max-w-4xl">
|
|
<Skeleton className="h-12 w-32 mb-8" />
|
|
<Skeleton className="h-16 w-full mb-4" />
|
|
<Skeleton className="h-8 w-2/3 mb-8" />
|
|
<Skeleton className="h-[400px] w-full mb-8" />
|
|
<Skeleton className="h-96 w-full" />
|
|
</div>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!post) {
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Header />
|
|
<div className="container mx-auto px-4 py-12 max-w-4xl text-center">
|
|
<h1 className="text-2xl font-bold mb-4">Post Not Found</h1>
|
|
<Link to="/blog">
|
|
<Button variant="outline">
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
Back to Blog
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Header />
|
|
|
|
<article className="container mx-auto px-4 py-12 max-w-4xl">
|
|
<Link to="/blog" className="inline-block mb-8">
|
|
<Button variant="ghost" size="sm">
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
Back to Blog
|
|
</Button>
|
|
</Link>
|
|
|
|
<h1 className="text-5xl font-bold mb-6 leading-tight">
|
|
{post.title}
|
|
</h1>
|
|
|
|
<div className="flex items-center justify-between mb-8 pb-6 border-b">
|
|
<div className="flex items-center gap-3">
|
|
<Avatar className="w-12 h-12">
|
|
<AvatarImage src={post.profiles.avatar_url ?? undefined} />
|
|
<AvatarFallback>
|
|
{post.profiles.display_name?.[0] || post.profiles.username[0]}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div>
|
|
<p className="font-medium">
|
|
{post.profiles.display_name ?? post.profiles.username}
|
|
</p>
|
|
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
<div className="flex items-center gap-1">
|
|
<Calendar className="w-3 h-3" />
|
|
{formatDistanceToNow(new Date(post.published_at!), { addSuffix: true })}
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Eye className="w-3 h-3" />
|
|
{post.view_count} views
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{post.featured_image_id && (
|
|
<div className="mb-12 rounded-lg overflow-hidden shadow-2xl">
|
|
<img
|
|
src={getCloudflareImageUrl(post.featured_image_id, 'public')}
|
|
alt={post.title}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<MarkdownRenderer content={post.content} />
|
|
</article>
|
|
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|