From e35b42c4d9a152a1a1b38c95632a11bb09ffa047 Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:42:46 +0100 Subject: [PATCH] chore: blog category selector (#1540) * chore: blog category selector * fix: all categories * fix: height --- .../blog/_components/category-select.tsx | 44 +++++++++++++++++++ .../src/app/(pages)/(content)/blog/page.tsx | 40 ++++++++++++----- .../(pages)/(content)/blog/search-params.ts | 18 ++++++-- apps/web/src/components/content/timeline.tsx | 2 +- 4 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/app/(pages)/(content)/blog/_components/category-select.tsx diff --git a/apps/web/src/app/(pages)/(content)/blog/_components/category-select.tsx b/apps/web/src/app/(pages)/(content)/blog/_components/category-select.tsx new file mode 100644 index 00000000..36a0edd2 --- /dev/null +++ b/apps/web/src/app/(pages)/(content)/blog/_components/category-select.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@openstatus/ui/src/components/select"; +import { allPosts } from "content-collections"; +import { useQueryStates } from "nuqs"; +import { searchParamsParsers } from "../search-params"; + +const categories = new Set(allPosts.map((post) => post.tag)); + +export function CategorySelect() { + const [{ category }, setSearchParams] = useQueryStates(searchParamsParsers, { + shallow: false, + }); + + return ( + + ); +} diff --git a/apps/web/src/app/(pages)/(content)/blog/page.tsx b/apps/web/src/app/(pages)/(content)/blog/page.tsx index f3b9b0c8..e9332173 100644 --- a/apps/web/src/app/(pages)/(content)/blog/page.tsx +++ b/apps/web/src/app/(pages)/(content)/blog/page.tsx @@ -15,9 +15,10 @@ import { allPosts } from "content-collections"; import { Rss } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; +import { CategorySelect } from "./_components/category-select"; import { ITEMS_PER_PAGE, - MAX_PAGE_INDEX, + getMaxPageIndex, searchParamsCache, } from "./search-params"; @@ -38,28 +39,45 @@ export default async function Post(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const searchParams = await props.searchParams; - const { pageIndex } = searchParamsCache.parse(searchParams); + const { pageIndex, category } = searchParamsCache.parse(searchParams); + + const maxPageIndex = getMaxPageIndex(category); + const currentPageIndex = Math.min(pageIndex, maxPageIndex); const posts = allPosts .sort( (a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime(), ) - .slice(pageIndex * ITEMS_PER_PAGE, (pageIndex + 1) * ITEMS_PER_PAGE); + .filter((post) => { + if (!category) return true; + return post.tag === category; + }) + .slice( + currentPageIndex * ITEMS_PER_PAGE, + (currentPageIndex + 1) * ITEMS_PER_PAGE, + ); return ( + actions={[ + , + - } + , + ]} > {posts.map((post) => ( - {Array.from({ length: MAX_PAGE_INDEX + 1 }).map((_, index) => { + {Array.from({ length: maxPageIndex + 1 }).map((_, index) => { return ( {index + 1} diff --git a/apps/web/src/app/(pages)/(content)/blog/search-params.ts b/apps/web/src/app/(pages)/(content)/blog/search-params.ts index d47bfc31..02abc3b3 100644 --- a/apps/web/src/app/(pages)/(content)/blog/search-params.ts +++ b/apps/web/src/app/(pages)/(content)/blog/search-params.ts @@ -3,24 +3,34 @@ import { createParser, createSearchParamsCache, parseAsInteger, + parseAsStringEnum, } from "nuqs/server"; +export const ITEMS_PER_PAGE = 10; + +// Helper function to calculate max page index based on filtered posts +export function getMaxPageIndex(category?: string | null) { + let filteredPosts = allPosts; + if (category) { + filteredPosts = allPosts.filter((post) => post.tag === category); + } + return Math.max(0, Math.ceil(filteredPosts.length / ITEMS_PER_PAGE) - 1); +} + const parseAsPageIndex = createParser({ parse(queryValue) { const parsed = parseAsInteger.parse(queryValue); if (!parsed || parsed < 0) return 0; - return Math.min(parsed, MAX_PAGE_INDEX); + return parsed; // We'll validate against max page index in the component }, serialize(value) { return value.toString(); }, }); -export const ITEMS_PER_PAGE = 10; -export const MAX_PAGE_INDEX = Math.ceil(allPosts.length / ITEMS_PER_PAGE) - 1; - export const searchParamsParsers = { pageIndex: parseAsPageIndex.withDefault(0), + category: parseAsStringEnum(allPosts.map((post) => post.tag)), }; export const searchParamsCache = createSearchParamsCache(searchParamsParsers); diff --git a/apps/web/src/components/content/timeline.tsx b/apps/web/src/components/content/timeline.tsx index 89174c4d..329cddf6 100644 --- a/apps/web/src/components/content/timeline.tsx +++ b/apps/web/src/components/content/timeline.tsx @@ -25,7 +25,7 @@ export function Timeline({

{title}

{description}

-
{actions}
+
{actions}
{children} -- 2.51.2