137 lines
5.5 KiB
TypeScript
137 lines
5.5 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
||
import { Link } from '@tanstack/react-router'
|
||
import * as api from '@/lib/api'
|
||
import { SocialShareButtons } from '@/components/features/social-share'
|
||
import { ArrowRight } from 'lucide-react'
|
||
|
||
export function LatestArticlesGrid() {
|
||
const { data, isLoading, error } = useQuery({
|
||
queryKey: ['latest-articles'],
|
||
queryFn: () => api.fetchLatestArticles(12),
|
||
})
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||
{Array.from({ length: 12 }).map((_, i) => (
|
||
<div key={i} className="border-brutal-sm bg-card p-4 animate-pulse">
|
||
<div className="h-40 bg-muted mb-4"></div>
|
||
<div className="h-6 bg-muted rounded mb-2"></div>
|
||
<div className="h-4 bg-muted rounded mb-2 w-3/4"></div>
|
||
<div className="h-3 bg-muted rounded w-1/2"></div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="border-brutal bg-destructive/10 p-8 text-center">
|
||
<div className="text-destructive text-2xl font-display mb-2">ГРЕШКА</div>
|
||
<p className="font-body text-sm text-destructive">Обидете се повторно</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const articles = data?.data || []
|
||
|
||
if (articles.length === 0) {
|
||
return (
|
||
<div className="border-brutal bg-card p-8 text-center">
|
||
<div className="font-display text-2xl mb-2">НЕМА СТАТИИ</div>
|
||
<p className="font-body text-sm text-muted-foreground">Проверете подоцна</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<div className="flex items-center justify-between border-b-4 border-foreground pb-4">
|
||
<h2 className="text-3xl md:text-4xl font-display">Најнови</h2>
|
||
<Link
|
||
to="/archive"
|
||
className="font-body text-sm uppercase tracking-wider border-2 border-foreground px-4 py-2 hover:bg-accent hover:border-accent transition-all duration-150 flex items-center gap-2"
|
||
>
|
||
Сите
|
||
<ArrowRight className="w-4 h-4" />
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||
{articles.map((article, index) => (
|
||
<article
|
||
key={article.id}
|
||
className={`group border-brutal-sm bg-card hover:shadow-brutal transition-all duration-150 hover:-translate-y-1 animate-fade-in-up stagger-${Math.min(index + 1, 12)}`}
|
||
>
|
||
<Link
|
||
to={`/articles/${article.id}`}
|
||
className="block"
|
||
>
|
||
{article.featuredImage ? (
|
||
<div className="relative h-40 overflow-hidden border-b-2 border-foreground">
|
||
<img
|
||
src={article.featuredImage}
|
||
alt={article.title}
|
||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||
/>
|
||
<div className="absolute inset-0 bg-foreground/0 group-hover:bg-foreground/10 transition-colors duration-300" />
|
||
</div>
|
||
) : (
|
||
<div className="h-40 bg-secondary border-b-2 border-foreground flex items-center justify-center relative overflow-hidden">
|
||
<div className="absolute inset-0 opacity-10" style={{ backgroundImage: 'repeating-linear-gradient(45deg, currentColor 0, currentColor 1px, transparent 0, transparent 50%)', backgroundSize: '10px 10px' }}></div>
|
||
<span className="font-display text-4xl text-foreground/30">N</span>
|
||
</div>
|
||
)}
|
||
|
||
<div className="p-4">
|
||
<h3 className="text-lg font-display leading-tight mb-2 line-clamp-2 group-hover:text-accent transition-colors">
|
||
{article.title}
|
||
</h3>
|
||
|
||
{article.excerpt && (
|
||
<p className="text-muted-foreground text-xs font-body line-clamp-2 mb-3">
|
||
{article.excerpt}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</Link>
|
||
|
||
<div className="px-4 pb-4 border-t-2 border-foreground/10 pt-3 mt-auto">
|
||
<div className="flex items-center justify-between font-body text-xs uppercase tracking-wider text-muted-foreground">
|
||
<span>
|
||
{new Date(article.createdAt).toLocaleDateString('mk-MK', {
|
||
day: 'numeric',
|
||
month: 'short',
|
||
})}
|
||
</span>
|
||
|
||
{article.category && (
|
||
<Link
|
||
to={`/${article.category.slug}`}
|
||
className="px-2 py-0.5 border border-foreground bg-background text-foreground text-[10px] hover:bg-accent hover:border-accent transition-colors"
|
||
>
|
||
{article.category.name}
|
||
</Link>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-3">
|
||
<SocialShareButtons
|
||
articleId={article.id}
|
||
title={article.title}
|
||
url={`${window.location.origin}/articles/${article.id}`}
|
||
excerpt={article.excerpt}
|
||
image={article.featuredImage}
|
||
tags={article.tags}
|
||
variant="compact"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|