238 lines
7.7 KiB
TypeScript
238 lines
7.7 KiB
TypeScript
import { Link } from '@tanstack/react-router';
|
|
import { usePinnedLiveBlogs } from '@/queries/live-blogs';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import type { LiveBlog } from '@/lib/api';
|
|
import {
|
|
Clock,
|
|
MessageSquare,
|
|
Eye,
|
|
Pin,
|
|
Play,
|
|
Square,
|
|
ChevronRight
|
|
} from 'lucide-react';
|
|
|
|
interface PinnedLiveBlogSidebarProps {
|
|
className?: string;
|
|
maxItems?: number;
|
|
}
|
|
|
|
export function PinnedLiveBlogSidebar({
|
|
className = '',
|
|
maxItems = 3
|
|
}: PinnedLiveBlogSidebarProps) {
|
|
const { data: pinnedBlogs, isLoading, error } = usePinnedLiveBlogs();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Pin className="w-4 h-4" />
|
|
Pinned Live Blogs
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{[1, 2, 3].map((i) => (
|
|
<div key={i} className="animate-pulse space-y-2">
|
|
<div className="h-4 bg-muted rounded w-3/4"></div>
|
|
<div className="h-3 bg-muted rounded w-1/2"></div>
|
|
<div className="h-2 bg-muted rounded w-1/4"></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Pin className="w-4 h-4" />
|
|
Live Coverage
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-4">
|
|
{[1, 2, 3].map((i) => (
|
|
<div key={i} className="animate-pulse space-y-2">
|
|
<div className="h-4 bg-muted rounded w-3/4"></div>
|
|
<div className="h-3 bg-muted rounded w-1/2"></div>
|
|
<div className="h-2 bg-muted rounded w-1/4"></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader>
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Pin className="w-4 h-4" />
|
|
Live Coverage
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
Error loading live coverage
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const displayBlogs = (pinnedBlogs || []).slice(0, maxItems);
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'live': return 'bg-green-100 text-green-800 border-green-200';
|
|
case 'ended': return 'bg-red-100 text-red-800 border-red-200';
|
|
default: return 'bg-gray-100 text-gray-800 border-gray-200';
|
|
}
|
|
};
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
switch (status) {
|
|
case 'live': return <Play className="w-3 h-3" />;
|
|
case 'ended': return <Square className="w-3 h-3" />;
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
const formatTime = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - date.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
|
|
if (diffMins < 60) {
|
|
return `${diffMins}m ago`;
|
|
} else if (diffMins < 1440) {
|
|
return `${Math.floor(diffMins / 60)}h ago`;
|
|
} else {
|
|
return `${Math.floor(diffMins / 1440)}d ago`;
|
|
}
|
|
};
|
|
|
|
const getLatestUpdate = (blog: LiveBlog) => {
|
|
if (!blog.updates || blog.updates.length === 0) return null;
|
|
return blog.updates[blog.updates.length - 1];
|
|
};
|
|
|
|
return (
|
|
<Card className={className}>
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Pin className="w-4 h-4" />
|
|
Live Coverage
|
|
</CardTitle>
|
|
<Badge variant="outline" className="text-xs">
|
|
{(pinnedBlogs || []).length} pinned
|
|
</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent className="pt-0">
|
|
{displayBlogs.length === 0 ? (
|
|
<div className="py-6 text-center">
|
|
<p className="text-sm text-muted-foreground">
|
|
No pinned live blogs at the moment
|
|
</p>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
Check back later for live coverage
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="space-y-4">
|
|
{displayBlogs.map((blog) => {
|
|
const latestUpdate = getLatestUpdate(blog);
|
|
|
|
return (
|
|
<Link
|
|
key={blog.id}
|
|
to="/live-blogs/$slug"
|
|
params={{ slug: blog.slug }}
|
|
className="block p-3 rounded-lg border hover:bg-accent/50 transition-colors group"
|
|
>
|
|
<div className="flex items-start justify-between mb-2">
|
|
<div className="flex-1">
|
|
<h4 className="font-medium text-sm mb-1 group-hover:text-primary transition-colors line-clamp-2">
|
|
{blog.title}
|
|
</h4>
|
|
{blog.description && (
|
|
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
{blog.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<Badge
|
|
className={`${getStatusColor(blog.status)} text-xs`}
|
|
variant="outline"
|
|
>
|
|
<div className="flex items-center gap-1">
|
|
{getStatusIcon(blog.status)}
|
|
{blog.status}
|
|
</div>
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Latest update preview */}
|
|
{latestUpdate && (
|
|
<div className="mt-2 p-2 bg-muted/50 rounded text-xs">
|
|
<div className="flex items-center gap-1 text-muted-foreground mb-1">
|
|
<Clock className="w-3 h-3" />
|
|
<span>{formatTime(latestUpdate.createdAt)}</span>
|
|
</div>
|
|
<p className="line-clamp-2">{latestUpdate.content}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Stats */}
|
|
<div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground">
|
|
<div className="flex items-center gap-1">
|
|
<MessageSquare className="w-3 h-3" />
|
|
<span>{blog.updates?.length || 0} updates</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Eye className="w-3 h-3" />
|
|
<span>{blog.viewCount} views</span>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* View all button */}
|
|
{(pinnedBlogs || []).length > maxItems && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="w-full mt-4"
|
|
asChild
|
|
>
|
|
<Link to="/live-blogs">
|
|
View all pinned blogs
|
|
<ChevronRight className="w-3 h-3 ml-1" />
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |