placebo.mk/frontend/src/components/features/live-blog/PinnedLiveBlogSidebar.tsx
2026-01-29 11:27:45 +01:00

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>
);
}