folx-tv/client/src/pages/category.tsx
2026-02-28 20:36:50 +00:00

114 lines
4.8 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { useParams, Link } from "wouter";
import { type Article } from "@shared/schema";
import { format } from "date-fns";
import { de } from "date-fns/locale";
import { Eye, ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { ArticleCardAd } from "@/components/adsense";
export default function CategoryPage() {
const { category } = useParams<{ category: string }>();
const { data: articles, isLoading } = useQuery<Article[]>({
queryKey: ["/api/articles/category", category],
});
return (
<div className="min-h-screen bg-background">
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Link href="/">
<Button variant="ghost" size="sm" className="mb-6 gap-2" data-testid="button-back">
<ArrowLeft className="w-4 h-4" />
Zurück
</Button>
</Link>
<h1 className="text-2xl font-bold text-foreground mb-6" data-testid="text-category-title">
{category}
</h1>
{isLoading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="bg-card rounded-md border border-card-border">
<Skeleton className="w-full aspect-video rounded-t-md" />
<div className="p-4 space-y-3">
<Skeleton className="h-3 w-2/3" />
<Skeleton className="h-5 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>
</div>
))}
</div>
) : articles && articles.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{articles.flatMap((article, index) => {
const items = [
<Link key={article.id} href={`/article/${article.slug}`}>
<article
className="group cursor-pointer bg-card rounded-md border border-card-border transition-all duration-300 h-full"
data-testid={`card-article-${article.id}`}
>
<div className="relative rounded-t-md">
<div className="overflow-hidden rounded-t-md">
<img
src={article.coverImage || "/images/article-1.png"}
alt={article.title}
className="w-full aspect-video object-cover transition-transform duration-500 group-hover:scale-105"
style={{ objectPosition: "center 25%" }}
loading="lazy"
/>
</div>
</div>
<div className="p-4">
<div className="flex items-center gap-2 text-muted-foreground text-xs mb-2">
<span>{article.author}</span>
<span>&middot;</span>
<span>{format(new Date(article.publishedAt), "d. MMMM yyyy", { locale: de })}</span>
</div>
<h3 className="font-semibold text-card-foreground mb-2 line-clamp-2 group-hover:text-primary transition-colors">
{article.title}
</h3>
<p className="text-muted-foreground text-sm line-clamp-3 mb-3">
{article.excerpt}
</p>
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<Eye className="w-3.5 h-3.5" />
{article.views.toLocaleString()}
</span>
<Button size="sm" data-testid={`button-read-${article.id}`}>
Weiterlesen
</Button>
</div>
</div>
</article>
</Link>,
];
if (index === 2) {
items.push(<ArticleCardAd key="ad-cat-1" />);
}
return items;
})}
</div>
) : (
<div className="text-center py-16">
<p className="text-muted-foreground text-lg">
Keine Artikel in dieser Kategorie gefunden.
</p>
<Link href="/">
<Button className="mt-4" data-testid="button-back-home">Zur Startseite</Button>
</Link>
</div>
)}
</main>
<Footer />
</div>
);
}