mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 08:31:13 -05:00
Approve blog post migration
This commit is contained in:
88
src/components/blog/BlogPostCard.tsx
Normal file
88
src/components/blog/BlogPostCard.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Eye, Calendar } from 'lucide-react';
|
||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
interface BlogPostCardProps {
|
||||
slug: string;
|
||||
title: string;
|
||||
content: string;
|
||||
featuredImageId?: string;
|
||||
author: {
|
||||
username: string;
|
||||
displayName?: string;
|
||||
avatarUrl?: string;
|
||||
};
|
||||
publishedAt: string;
|
||||
viewCount: number;
|
||||
}
|
||||
|
||||
export function BlogPostCard({
|
||||
slug,
|
||||
title,
|
||||
content,
|
||||
featuredImageId,
|
||||
author,
|
||||
publishedAt,
|
||||
viewCount,
|
||||
}: BlogPostCardProps) {
|
||||
const excerpt = content.substring(0, 150) + (content.length > 150 ? '...' : '');
|
||||
|
||||
return (
|
||||
<Link to={`/blog/${slug}`}>
|
||||
<Card className="overflow-hidden hover:scale-[1.02] hover:shadow-xl transition-all duration-300 group">
|
||||
<div className="aspect-[16/9] overflow-hidden bg-gradient-to-br from-primary/20 to-secondary/20">
|
||||
{featuredImageId ? (
|
||||
<img
|
||||
src={getCloudflareImageUrl(featuredImageId, 'public')}
|
||||
alt={title}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<span className="text-6xl opacity-20">📝</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-3">
|
||||
<h3 className="text-xl font-bold line-clamp-2 group-hover:text-primary transition-colors">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
<p className="text-sm text-muted-foreground line-clamp-3">
|
||||
{excerpt}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between pt-3 border-t">
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="w-6 h-6">
|
||||
<AvatarImage src={author.avatarUrl} />
|
||||
<AvatarFallback>
|
||||
{author.displayName?.[0] || author.username[0]}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{author.displayName || author.username}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
{formatDistanceToNow(new Date(publishedAt), { addSuffix: true })}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Eye className="w-3 h-3" />
|
||||
{viewCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
31
src/components/blog/MarkdownRenderer.tsx
Normal file
31
src/components/blog/MarkdownRenderer.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface MarkdownRendererProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function MarkdownRenderer({ content, className }: MarkdownRendererProps) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={cn(
|
||||
'prose dark:prose-invert max-w-none',
|
||||
'prose-headings:font-bold prose-headings:tracking-tight',
|
||||
'prose-h1:text-4xl prose-h2:text-3xl prose-h3:text-2xl',
|
||||
'prose-p:text-base prose-p:leading-relaxed',
|
||||
'prose-a:text-primary prose-a:no-underline hover:prose-a:underline',
|
||||
'prose-strong:text-foreground prose-strong:font-semibold',
|
||||
'prose-code:bg-muted prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm',
|
||||
'prose-pre:bg-muted prose-pre:border prose-pre:border-border',
|
||||
'prose-blockquote:border-l-4 prose-blockquote:border-primary prose-blockquote:italic',
|
||||
'prose-img:rounded-lg prose-img:shadow-lg',
|
||||
'prose-hr:border-border',
|
||||
'prose-ul:list-disc prose-ol:list-decimal',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user