Add pagination to article categories and video pages, and enhance home page with more content and ads

Implement paginated API endpoints for articles and videos, add pagination UI to category and video pages, and inject additional ads and content widgets into the home page.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c201f10b-bbd3-4402-9212-e0b79bfa670f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/EtK2Sno
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic
2026-03-05 09:48:56 +00:00
parent 365161a381
commit 3d672766b8
12 changed files with 1258 additions and 633 deletions
+63 -19
View File
@@ -8,7 +8,7 @@ import { Eye, Play, Images } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import AdSense, { ArticleCardAd, MultiplexAd, SidebarAd } from "@/components/adsense";
import AdSense, { ArticleCardAd, InArticleAd, MultiplexAd, SidebarAd } from "@/components/adsense";
import ArtistPatternBg from "@/components/artist-pattern-bg";
import { PhotoGalleryWidget } from "@/components/photo-gallery";
import { HoroscopeWidget } from "@/components/horoscope-widget";
@@ -516,6 +516,10 @@ export default function Home() {
{ id: "recipe", el: <RecipeWidget key="recipe" /> },
{ id: "breaking", el: <div key="breaking" className="flex flex-col gap-4"><BreakingNewsWidget /></div> },
{ id: "gallery2", el: <PhotoGalleryWidget key="gallery2" reverseOrder={true} /> },
{ id: "horoscope2", el: <HoroscopeWidget key="horoscope2" /> },
{ id: "news2", el: <div key="news2" className="flex flex-col gap-4"><NewsWidget /></div> },
{ id: "recipe2", el: <RecipeWidget key="recipe2" /> },
{ id: "breaking2", el: <div key="breaking2" className="flex flex-col gap-4"><BreakingNewsWidget /></div> },
], []);
const gridItems = useMemo(() => {
@@ -546,7 +550,7 @@ export default function Home() {
}
const totalRows = items.length / 4;
const adRows = [1, 3, 5, 7];
const adRows = [1, 3, 5, 7, 9, 11, 13];
let adCount = 0;
for (const row of adRows) {
if (row >= totalRows) continue;
@@ -599,6 +603,16 @@ export default function Home() {
return articles.filter((a) => !usedIds.has(a.id)).slice(0, 3);
}, [articles, widePickedArticles]);
const extraBottomArticles = useMemo(() => {
if (!articles || articles.length < 15) return [];
const usedIds = new Set([
...articles.slice(0, 9).map((a) => a.id),
...widePickedArticles.map((a) => a.id),
...bottomArticles.map((a) => a.id),
]);
return articles.filter((a) => !usedIds.has(a.id)).slice(0, 6);
}, [articles, widePickedArticles, bottomArticles]);
const bottomSection = useMemo(() => {
const items: { type: "widget" | "ad" | "article"; el: JSX.Element }[] = [
{ type: "widget", el: <NewsWidget key="bottom-news" /> },
@@ -611,9 +625,14 @@ export default function Home() {
})),
{ type: "ad", el: <div key="ad-bottom-1"><ArticleCardAd /></div> },
{ type: "widget", el: <PhotoGalleryWidget key="bottom-gallery" /> },
{ type: "ad", el: <div key="ad-bottom-2"><ArticleCardAd /></div> },
...extraBottomArticles.slice(0, 3).map((a) => ({
type: "article" as const,
el: <BlogCard key={`bottom-extra-${a.id}`} article={a} focalPoints={focalPoints} />,
})),
];
return items;
}, [bottomArticles, focalPoints]);
}, [bottomArticles, extraBottomArticles, focalPoints]);
if (isLoading || !articles) {
return (
@@ -661,6 +680,8 @@ export default function Home() {
</div>
</div>
<InArticleAd />
{widePickedArticles.length > 0 && (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
<ArtistPatternBg className="hidden lg:block rounded-lg overflow-hidden bg-card border border-card-border" seed={99}>
@@ -683,25 +704,30 @@ export default function Home() {
)}
{gridRows.map((row, ri) => (
<div key={`row-${ri}`} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row.map((item) =>
item.type === "widget"
? <div key={item.key}>{item.widget!.el}</div>
: item.type === "ad"
? <div key={item.key} className="h-full" data-testid={`ad-grid-${item.key}`}><ArticleCardAd /></div>
: item.article
? <MediumCard key={item.key} article={item.article} focalPoints={focalPoints} />
: null
)}
{ri === gridRows.length - 1 && widePickedArticles.length > 0 && (
<div className="sm:col-span-2 lg:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
<WideCardClassic article={widePickedArticles[0]} focalPoints={focalPoints} />
{widePickedArticles[1] && <WideCardClassic article={widePickedArticles[1]} focalPoints={focalPoints} />}
</div>
)}
<div key={`row-group-${ri}`}>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row.map((item) =>
item.type === "widget"
? <div key={item.key}>{item.widget!.el}</div>
: item.type === "ad"
? <div key={item.key} className="h-full" data-testid={`ad-grid-${item.key}`}><ArticleCardAd /></div>
: item.article
? <MediumCard key={item.key} article={item.article} focalPoints={focalPoints} />
: null
)}
{ri === gridRows.length - 1 && widePickedArticles.length > 0 && (
<div className="sm:col-span-2 lg:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
<WideCardClassic article={widePickedArticles[0]} focalPoints={focalPoints} />
{widePickedArticles[1] && <WideCardClassic article={widePickedArticles[1]} focalPoints={focalPoints} />}
</div>
)}
</div>
{ri % 3 === 2 && ri < gridRows.length - 1 && <InArticleAd />}
</div>
))}
<InArticleAd />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{bottomSection.map((item, i) => (
<div key={`bottom-${i}`}>
@@ -710,6 +736,24 @@ export default function Home() {
))}
</div>
<InArticleAd />
{extraBottomArticles.length > 3 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{extraBottomArticles.slice(3, 6).map((a) => (
<BlogCard key={`extra-bottom-${a.id}`} article={a} focalPoints={focalPoints} />
))}
</div>
)}
<MultiplexAd />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<PhotoGalleryWidget key="extra-gallery" />
<NewsWidget key="extra-news" />
<RecipeWidget key="extra-recipe" />
</div>
<MultiplexAd />
</main>