From 78e2a9ba1c67cb907ff3b6ec7001ecb88339afaf Mon Sep 17 00:00:00 2001 From: jordanarldt Date: Fri, 20 Dec 2024 17:09:37 -0600 Subject: [PATCH] feat: add support for custom blog url routing, update blog tag filtering, delete /blog/tag/[tagId] route --- .../(default)/blog/[blogId]/page-data.ts | 2 + .../[locale]/(default)/blog/[blogId]/page.tsx | 9 +-- core/app/[locale]/(default)/blog/page-data.ts | 17 ++--- core/app/[locale]/(default)/blog/page.tsx | 30 ++++++--- .../(default)/blog/tag/[tagId]/page.tsx | 67 ------------------- core/components/blog-post-card/fragment.ts | 1 + core/middlewares/with-routes.ts | 15 +++++ 7 files changed, 54 insertions(+), 87 deletions(-) delete mode 100644 core/app/[locale]/(default)/blog/tag/[tagId]/page.tsx diff --git a/core/app/[locale]/(default)/blog/[blogId]/page-data.ts b/core/app/[locale]/(default)/blog/[blogId]/page-data.ts index 6b25c564e..58c3ea289 100644 --- a/core/app/[locale]/(default)/blog/[blogId]/page-data.ts +++ b/core/app/[locale]/(default)/blog/[blogId]/page-data.ts @@ -12,6 +12,8 @@ const BlogPageQuery = graphql( site { content { blog { + name + path post(entityId: $entityId) { author htmlBody diff --git a/core/app/[locale]/(default)/blog/[blogId]/page.tsx b/core/app/[locale]/(default)/blog/[blogId]/page.tsx index 9b16b0d98..a4be6f1bd 100644 --- a/core/app/[locale]/(default)/blog/[blogId]/page.tsx +++ b/core/app/[locale]/(default)/blog/[blogId]/page.tsx @@ -37,9 +37,10 @@ export default async function Blog({ params }: Props) { const format = await getFormatter(); const data = await getBlogPageData({ entityId: Number(blogId) }); + const blog = data?.content.blog; const blogPost = data?.content.blog?.post; - if (!blogPost) { + if (!blogPost || !blog) { return notFound(); } @@ -52,8 +53,8 @@ export default async function Blog({ params }: Props) { href: '/', }, { - label: 'Blog', - href: '/blog', + label: blog.name, + href: blog.path, }, { label: blogPost.name, @@ -70,7 +71,7 @@ export default async function Blog({ params }: Props) { tags={blogPost.tags.map((tag) => ({ label: tag, link: { - href: `/blog/tag/${tag}`, + href: `${blog.path}?tag=${tag}`, }, }))} title={blogPost.name} diff --git a/core/app/[locale]/(default)/blog/page-data.ts b/core/app/[locale]/(default)/blog/page-data.ts index 76ff2e8ed..092b7f897 100644 --- a/core/app/[locale]/(default)/blog/page-data.ts +++ b/core/app/[locale]/(default)/blog/page-data.ts @@ -15,6 +15,7 @@ const BlogQuery = graphql(` blog { name description + path } } } @@ -52,14 +53,14 @@ const BlogPostsPageQuery = graphql( [BlogPostCardFragment, PaginationFragment], ); -interface BlogPostsFiltersInput { - tagId?: string; +export interface BlogPostsFiltersInput { + tag: string | null; } interface Pagination { - limit?: number; - before?: string; - after?: string; + limit: number; + before: string | null; + after: string | null; } export const getBlog = cache(async () => { @@ -72,8 +73,8 @@ export const getBlog = cache(async () => { }); export const getBlogPosts = cache( - async ({ tagId, limit = 9, before, after }: BlogPostsFiltersInput & Pagination) => { - const filterArgs = tagId ? { filters: { tags: [tagId] } } : {}; + async ({ tag, limit = 9, before, after }: BlogPostsFiltersInput & Pagination) => { + const filterArgs = tag ? { filters: { tags: [tag] } } : {}; const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const response = await client.fetch({ @@ -103,7 +104,7 @@ export const getBlogPosts = cache( alt: post.thumbnailImage.altText, } : undefined, - href: `/blog/${post.entityId}`, + href: post.path, title: post.name, })), }; diff --git a/core/app/[locale]/(default)/blog/page.tsx b/core/app/[locale]/(default)/blog/page.tsx index 2f614ffc4..0bf619c24 100644 --- a/core/app/[locale]/(default)/blog/page.tsx +++ b/core/app/[locale]/(default)/blog/page.tsx @@ -1,44 +1,57 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; +import { SearchParams } from 'nuqs'; +import { createSearchParamsCache, parseAsInteger, parseAsString } from 'nuqs/server'; import { FeaturedBlogPostList } from '@/vibes/soul/sections/featured-blog-post-list'; import { defaultPageInfo, pageInfoTransformer } from '~/data-transformers/page-info-transformer'; import { getBlog, getBlogMetaData, getBlogPosts } from './page-data'; -type SearchParams = Record; - interface Props { params: Promise<{ locale: string }>; searchParams: Promise; } +const defaultPostLimit = 9; + +const searchParamsCache = createSearchParamsCache({ + tag: parseAsString, + before: parseAsString, + after: parseAsString, + limit: parseAsInteger.withDefault(defaultPostLimit), +}); + export async function generateMetadata(): Promise { return await getBlogMetaData(); } async function listBlogPosts(searchParamsPromise: Promise) { - const searchParams = await searchParamsPromise; - const blogPosts = await getBlogPosts(searchParams); + const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); + const blogPosts = await getBlogPosts(searchParamsParsed); const posts = blogPosts?.posts ?? []; return posts; } async function getPaginationInfo(searchParamsPromise: Promise) { - const searchParams = await searchParamsPromise; - const blogPosts = await getBlogPosts(searchParams); + const searchParamsParsed = searchParamsCache.parse(await searchParamsPromise); + const blogPosts = await getBlogPosts(searchParamsParsed); return pageInfoTransformer(blogPosts?.pageInfo ?? defaultPageInfo); } export default async function Blog(props: Props) { + const searchParamsParsed = searchParamsCache.parse(await props.searchParams); + const { tag } = searchParamsParsed; const blog = await getBlog(); if (!blog) { return notFound(); } + const tagCrumb = tag ? [{ label: tag, href: '#' }] : []; + return ( ; - -interface Props { - params: Promise<{ tagId: string }>; - searchParams: Promise; -} - -export async function generateMetadata(): Promise { - return await getBlogMetaData(); -} - -async function listBlogPosts(props: Props) { - const { tagId } = await props.params; - const searchParams = await props.searchParams; - const blogPosts = await getBlogPosts({ tagId, ...searchParams }); - const posts = blogPosts?.posts ?? []; - - return posts; -} - -async function getPaginationInfo(props: Props) { - const { tagId } = await props.params; - const searchParams = await props.searchParams; - const blogPosts = await getBlogPosts({ tagId, ...searchParams }); - - return pageInfoTransformer(blogPosts?.pageInfo ?? defaultPageInfo); -} - -export default async function Tag(props: Props) { - const { tagId } = await props.params; - const blog = await getBlog(); - - if (!blog) { - return notFound(); - } - - return ( - - ); -} diff --git a/core/components/blog-post-card/fragment.ts b/core/components/blog-post-card/fragment.ts index ffd7c2de5..dd18c1df6 100644 --- a/core/components/blog-post-card/fragment.ts +++ b/core/components/blog-post-card/fragment.ts @@ -5,6 +5,7 @@ export const BlogPostCardFragment = graphql(` author entityId name + path plainTextSummary publishedDate { utc diff --git a/core/middlewares/with-routes.ts b/core/middlewares/with-routes.ts index 2cb2e34a8..02065db86 100644 --- a/core/middlewares/with-routes.ts +++ b/core/middlewares/with-routes.ts @@ -47,6 +47,9 @@ const GetRouteQuery = graphql(` ... on Brand { entityId } + ... on BlogPost { + entityId + } } } } @@ -152,6 +155,8 @@ const NodeSchema = z.union([ z.object({ __typename: z.literal('ContactPage'), id: z.string() }), z.object({ __typename: z.literal('NormalPage'), id: z.string() }), z.object({ __typename: z.literal('RawHtmlPage'), id: z.string() }), + z.object({ __typename: z.literal('Blog'), id: z.string() }), + z.object({ __typename: z.literal('BlogPost'), entityId: z.number() }), ]); const RouteSchema = z.object({ @@ -330,6 +335,16 @@ export const withRoutes: MiddlewareFactory = () => { }); } + case 'Blog': { + url = `/${locale}/blog`; + break; + } + + case 'BlogPost': { + url = `/${locale}/blog/${node.entityId}`; + break; + } + default: { const { pathname } = new URL(request.url);