Aktuelles kot na ONE: vrstica rubrik Politik, Promis, Sport, Musik & Film (brez horoskopa), lastna stran in zdruzen API
This commit is contained in:
parent
164775f2b8
commit
056e1d08d6
@ -8,6 +8,7 @@ import NotFound from "@/pages/not-found";
|
|||||||
import Home from "@/pages/home";
|
import Home from "@/pages/home";
|
||||||
import ArticlePage from "@/pages/article";
|
import ArticlePage from "@/pages/article";
|
||||||
import CategoryPage from "@/pages/category";
|
import CategoryPage from "@/pages/category";
|
||||||
|
import AktuellesPage from "@/pages/aktuelles";
|
||||||
import VideosPage from "@/pages/videos";
|
import VideosPage from "@/pages/videos";
|
||||||
import GalleryPageWrapper from "@/pages/gallery";
|
import GalleryPageWrapper from "@/pages/gallery";
|
||||||
import HoroscopePage from "@/pages/horoscope";
|
import HoroscopePage from "@/pages/horoscope";
|
||||||
@ -40,7 +41,7 @@ function Router() {
|
|||||||
<Route path="/search" component={SearchPage} />
|
<Route path="/search" component={SearchPage} />
|
||||||
<Route path="/article/:slug" component={ArticlePage} />
|
<Route path="/article/:slug" component={ArticlePage} />
|
||||||
<Route path="/category/:category" component={CategoryPage} />
|
<Route path="/category/:category" component={CategoryPage} />
|
||||||
<Route path="/aktuelles" component={() => <CategoryPage fixedCategory="Aktuelles" />} />
|
<Route path="/aktuelles" component={AktuellesPage} />
|
||||||
<Route path="/videos" component={VideosPage} />
|
<Route path="/videos" component={VideosPage} />
|
||||||
<Route path="/gallery" component={GalleryPageWrapper} />
|
<Route path="/gallery" component={GalleryPageWrapper} />
|
||||||
<Route path="/horoskop" component={HoroscopePage} />
|
<Route path="/horoskop" component={HoroscopePage} />
|
||||||
|
|||||||
189
client/src/pages/aktuelles.tsx
Normal file
189
client/src/pages/aktuelles.tsx
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { Link, useLocation, useSearch } from "wouter";
|
||||||
|
import { type Article } from "@shared/schema";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { usePageMeta } from "@/hooks/use-page-meta";
|
||||||
|
import { Eye, ChevronLeft, ChevronRight } 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 { PageSideAds, TopBannerAd, MultiplexAd, ArticleCardAd } from "@/components/adsense";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { de } from "date-fns/locale";
|
||||||
|
|
||||||
|
interface Res {
|
||||||
|
articles: Article[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
totalPages: number;
|
||||||
|
hasMore: boolean;
|
||||||
|
rubriken: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const RUBRIKEN = ["Politik", "Promis", "Sport", "Musik & Film"];
|
||||||
|
|
||||||
|
export default function AktuellesPage() {
|
||||||
|
const search = useSearch();
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
const page = Math.max(1, parseInt(params.get("page") || "1"));
|
||||||
|
const rubrik = params.get("rubrik") || "";
|
||||||
|
|
||||||
|
usePageMeta(
|
||||||
|
`Aktuelles${rubrik ? ` – ${rubrik}` : ""}${page > 1 ? ` – Seite ${page}` : ""} | FOLX TV`,
|
||||||
|
"Aktuelle Nachrichten aus Politik, Sport, Promis sowie Musik und Film – täglich neu bei FOLX TV."
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery<Res>({
|
||||||
|
queryKey: ["/api/articles/aktuelles", rubrik, page],
|
||||||
|
queryFn: async () => {
|
||||||
|
const q = new URLSearchParams({ page: String(page), limit: "12" });
|
||||||
|
if (rubrik) q.set("rubrik", rubrik);
|
||||||
|
const r = await fetch(`/api/articles/aktuelles?${q}`);
|
||||||
|
if (!r.ok) throw new Error("laden fehlgeschlagen");
|
||||||
|
return r.json();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo({ top: 0 });
|
||||||
|
}, [page, rubrik]);
|
||||||
|
|
||||||
|
const go = (r: string, p = 1) => {
|
||||||
|
const q = new URLSearchParams();
|
||||||
|
if (r) q.set("rubrik", r);
|
||||||
|
if (p > 1) q.set("page", String(p));
|
||||||
|
setLocation(`/aktuelles${q.toString() ? `?${q}` : ""}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = data?.articles || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<Header />
|
||||||
|
<PageSideAds />
|
||||||
|
|
||||||
|
{/* Vrstica rubrik kot na ONE — brez horoskopa, ta ima na folx.tv svojo stran */}
|
||||||
|
<nav
|
||||||
|
className="border-y border-card-border bg-card overflow-x-auto"
|
||||||
|
style={{ scrollbarWidth: "none" }}
|
||||||
|
data-testid="nav-aktuelles-rubriken"
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto flex items-stretch w-max md:w-full">
|
||||||
|
<button
|
||||||
|
onClick={() => go("")}
|
||||||
|
className={`px-5 py-3 text-sm font-bold uppercase tracking-wide whitespace-nowrap border-r border-card-border transition-colors ${
|
||||||
|
!rubrik ? "bg-primary text-primary-foreground" : "text-foreground/80 hover:text-primary"
|
||||||
|
}`}
|
||||||
|
data-testid="link-rubrik-alle"
|
||||||
|
>
|
||||||
|
Alle
|
||||||
|
</button>
|
||||||
|
{RUBRIKEN.map((r) => (
|
||||||
|
<button
|
||||||
|
key={r}
|
||||||
|
onClick={() => go(r)}
|
||||||
|
className={`px-5 py-3 text-sm font-bold uppercase tracking-wide whitespace-nowrap border-r border-card-border transition-colors ${
|
||||||
|
rubrik === r ? "bg-primary text-primary-foreground" : "text-foreground/80 hover:text-primary"
|
||||||
|
}`}
|
||||||
|
data-testid={`link-rubrik-${r.toLowerCase().replace(/[^a-z]+/g, "-")}`}
|
||||||
|
>
|
||||||
|
{r}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
|
<TopBannerAd />
|
||||||
|
|
||||||
|
<h1 className="text-2xl md:text-3xl font-bold text-foreground mb-4" data-testid="text-aktuelles-title">
|
||||||
|
{rubrik || "Aktuelles"}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<Skeleton key={i} className="w-full h-64 rounded-lg" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<p className="text-muted-foreground py-12">In dieser Rubrik gibt es derzeit keine Beiträge.</p>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{items.map((a, idx) => (
|
||||||
|
<div key={a.id} className="contents">
|
||||||
|
{idx > 0 && idx % 6 === 0 && (
|
||||||
|
<div className="sm:col-span-2 lg:col-span-3">
|
||||||
|
<ArticleCardAd />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Link href={`/article/${a.slug}`}>
|
||||||
|
<article
|
||||||
|
className="group cursor-pointer rounded-lg overflow-hidden bg-card border border-card-border h-full flex flex-col"
|
||||||
|
data-testid={`card-aktuelles-${a.id}`}
|
||||||
|
>
|
||||||
|
{a.coverImage && (
|
||||||
|
<div className="aspect-[16/9] overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={a.coverImage}
|
||||||
|
alt={a.title}
|
||||||
|
loading="lazy"
|
||||||
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="p-4 flex flex-col flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1.5">
|
||||||
|
<span className="text-xs font-semibold text-primary uppercase">{a.category}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{format(new Date(a.publishedAt), "d. MMM yyyy", { locale: de })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="font-bold text-card-foreground leading-snug line-clamp-2 group-hover:text-primary transition-colors">
|
||||||
|
{a.title}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-muted-foreground mt-2 leading-relaxed line-clamp-3 flex-1">
|
||||||
|
{a.excerpt}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-1.5 mt-3 text-muted-foreground text-[11px]">
|
||||||
|
<Eye className="w-3 h-3" />
|
||||||
|
{a.views.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data && data.totalPages > 1 && (
|
||||||
|
<div className="flex items-center justify-center gap-2 mt-8" data-testid="pagination-aktuelles">
|
||||||
|
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => go(rubrik, page - 1)}>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<span className="text-sm text-muted-foreground px-2">
|
||||||
|
Seite {page} von {data.totalPages}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={page >= data.totalPages}
|
||||||
|
onClick={() => go(rubrik, page + 1)}
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<div className="text-[9px] text-muted-foreground/40 text-center pb-1 uppercase tracking-widest">Anzeige</div>
|
||||||
|
<MultiplexAd />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -718,7 +718,10 @@ export default function Home() {
|
|||||||
// Splosne novice imajo svojo rubriko /aktuelles in ne sodijo na naslovnico,
|
// Splosne novice imajo svojo rubriko /aktuelles in ne sodijo na naslovnico,
|
||||||
// ki ostaja Volksmusik in Schlager.
|
// ki ostaja Volksmusik in Schlager.
|
||||||
const articles = useMemo(
|
const articles = useMemo(
|
||||||
() => (allArticles || []).filter((a) => a.category !== "Aktuelles"),
|
() =>
|
||||||
|
(allArticles || []).filter(
|
||||||
|
(a) => !["Aktuelles", "Politik", "Promis", "Sport", "Musik & Film"].includes(a.category)
|
||||||
|
),
|
||||||
[allArticles]
|
[allArticles]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,32 @@ export async function registerRoutes(
|
|||||||
res.json(articles);
|
res.json(articles);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Rubrike, ki skupaj sestavljajo /aktuelles. Horoskop, recepti in vreme so
|
||||||
|
// na folx.tv svoje strani, zato jih tu ni.
|
||||||
|
const AKTUELLES_RUBRIKEN = ["Politik", "Promis", "Sport", "Musik & Film"];
|
||||||
|
|
||||||
|
app.get("/api/articles/aktuelles", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const page = Math.max(1, parseInt(req.query.page as string) || 1);
|
||||||
|
const limit = Math.min(48, parseInt(req.query.limit as string) || 12);
|
||||||
|
const rubrik = (req.query.rubrik as string) || "";
|
||||||
|
const wanted = rubrik && AKTUELLES_RUBRIKEN.includes(rubrik) ? [rubrik] : AKTUELLES_RUBRIKEN;
|
||||||
|
const all = (await storage.getArticles()).filter((a) => wanted.includes(a.category));
|
||||||
|
const total = all.length;
|
||||||
|
const start = (page - 1) * limit;
|
||||||
|
res.json({
|
||||||
|
articles: all.slice(start, start + limit),
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
hasMore: start + limit < total,
|
||||||
|
rubriken: AKTUELLES_RUBRIKEN,
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
res.status(500).json({ message: e?.message || "error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get("/api/articles/category/:category", async (req, res) => {
|
app.get("/api/articles/category/:category", async (req, res) => {
|
||||||
const page = parseInt(req.query.page as string);
|
const page = parseInt(req.query.page as string);
|
||||||
const limit = parseInt(req.query.limit as string) || 12;
|
const limit = parseInt(req.query.limit as string) || 12;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user