feat: add general category, fix header wrapping, show last 5 live blog updates

This commit is contained in:
echo 2026-02-28 18:47:11 +01:00
parent 3e61fe5694
commit fa7dcc2b08
4 changed files with 107 additions and 4 deletions

View File

@ -131,6 +131,7 @@ export class StrapiService {
// Map CMS category slugs to Macedonian display names
const categoryMap: Record<string, { name: string; description: string }> = {
general: { name: 'Општо', description: 'Општи вести и теми' },
sport: { name: 'Спорт', description: 'Спортски вести и анализи' },
art: { name: 'Уметност', description: 'Уметност, култура и забава' },
science: { name: 'Наука', description: 'Научни откритија и технологија' },

View File

@ -65,8 +65,8 @@
},
"category": {
"type": "enumeration",
"enum": ["sport", "art", "science"],
"default": "sport",
"enum": ["general", "sport", "art", "science"],
"default": "general",
"required": true
}
}

View File

@ -2,9 +2,12 @@ import { useQuery } from '@tanstack/react-query';
import { Link } from '@tanstack/react-router';
import { fetchPinnedLiveBlogs } from '@/lib/api';
import { Button } from '@/components/ui/button';
import { Calendar, Eye, MessageSquare, Pin } from 'lucide-react';
import { Calendar, Eye, MessageSquare, Pin, ChevronDown, ChevronUp, Clock } from 'lucide-react';
import { useState } from 'react';
export function PinnedLiveBlogsSidebar() {
const [showUpdates, setShowUpdates] = useState(false);
const { data: liveBlogs, isLoading, error } = useQuery({
queryKey: ['pinned-live-blogs'],
queryFn: fetchPinnedLiveBlogs,
@ -47,6 +50,52 @@ export function PinnedLiveBlogsSidebar() {
});
};
const formatRelativeTime = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'сега';
if (diffMins < 60) return `${diffMins}м`;
if (diffHours < 24) return `${diffHours}ч`;
return `${diffDays}д`;
};
// Collect last 5 updates from all pinned live blogs
const getLastFiveUpdates = () => {
if (!liveBlogs) return [];
const allUpdates: Array<{
id: string;
content: string;
createdAt: string;
liveBlogTitle: string;
liveBlogSlug: string;
}> = [];
liveBlogs.forEach((liveBlog) => {
if (liveBlog.updates && liveBlog.updates.length > 0) {
liveBlog.updates.forEach((update) => {
allUpdates.push({
...update,
liveBlogTitle: liveBlog.title,
liveBlogSlug: liveBlog.slug,
});
});
}
});
// Sort by date descending and take first 5
return allUpdates
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
.slice(0, 5);
};
const lastFiveUpdates = getLastFiveUpdates();
if (isLoading) {
return (
<div className="border-brutal-sm bg-card p-6">
@ -102,6 +151,59 @@ export function PinnedLiveBlogsSidebar() {
return (
<div className="border-brutal-sm bg-card p-6">
{/* Latest Updates Section - Collapsible */}
{lastFiveUpdates.length > 0 && (
<div className="mb-6 pb-6 border-b-2 border-foreground/10">
<button
onClick={() => setShowUpdates(!showUpdates)}
className="w-full flex items-center justify-between mb-4 group"
>
<div className="flex items-center gap-2">
<Clock className="h-5 w-5 text-accent" />
<h3 className="text-xl font-display">Последни Update</h3>
</div>
<div className="flex items-center gap-2">
<span className="px-2 py-1 border-2 border-foreground bg-foreground text-background text-xs font-body font-bold uppercase">
{lastFiveUpdates.length}
</span>
{showUpdates ? (
<ChevronUp className="h-4 w-4 transition-transform" />
) : (
<ChevronDown className="h-4 w-4 transition-transform" />
)}
</div>
</button>
{showUpdates && (
<div className="space-y-3 animate-scale-in">
{lastFiveUpdates.map((update) => (
<Link
key={update.id}
to="/live-blogs/$slug"
params={{ slug: update.liveBlogSlug }}
className="block group"
>
<div className="p-3 border-2 border-foreground/10 hover:border-accent hover:bg-accent/5 transition-all duration-150">
<div className="flex items-start justify-between gap-2 mb-2">
<span className="text-xs font-body font-bold text-muted-foreground line-clamp-1">
{update.liveBlogTitle}
</span>
<span className="text-xs font-body text-muted-foreground whitespace-nowrap">
{formatRelativeTime(update.createdAt)}
</span>
</div>
<p className="text-sm font-body text-foreground line-clamp-2 group-hover:text-accent transition-colors">
{update.content.replace(/<[^>]*>/g, '').substring(0, 120)}
{update.content.length > 120 ? '...' : ''}
</p>
</div>
</Link>
))}
</div>
)}
</div>
)}
<div className="flex items-center justify-between mb-6 pb-4 border-b-2 border-foreground/10">
<div className="flex items-center gap-2">
<Pin className="h-5 w-5 text-accent" />

View File

@ -58,7 +58,7 @@ export function Header() {
<div className="container mx-auto max-w-6xl px-4 py-4">
<div className="flex items-center justify-between">
<Link to="/" className="group">
<h1 className="text-4xl md:text-5xl font-display tracking-tight">
<h1 className="text-4xl md:text-5xl font-display tracking-tight whitespace-nowrap">
<span className="inline-block transition-transform group-hover:-translate-y-1 group-hover:translate-x-1">P</span>
<span className="inline-block transition-transform group-hover:-translate-y-1 group-hover:translate-x-0.5 delay-75">l</span>
<span className="inline-block transition-transform group-hover:-translate-y-1 group-hover:translate-x-0 delay-100">a</span>