From 78a554b67267f3a7191edf6a9ee38ab9b9c343be Mon Sep 17 00:00:00 2001 From: thuanbui Date: Thu, 19 Mar 2026 13:04:58 +0900 Subject: [PATCH] feat: add Destinations component and integrate destination data in blog posts --- src/components/blog/PostItem.astro | 8 +++ src/components/ui/Destinations.astro | 39 +++++++++++ src/content.config.ts | 1 + src/pages/[...slug].astro | 20 +++++- .../destination/[destination]/[...page].astro | 64 +++++++++++++++++++ 5 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/components/ui/Destinations.astro create mode 100644 src/pages/destination/[destination]/[...page].astro diff --git a/src/components/blog/PostItem.astro b/src/components/blog/PostItem.astro index fe3d36c..37043c0 100644 --- a/src/components/blog/PostItem.astro +++ b/src/components/blog/PostItem.astro @@ -1,6 +1,7 @@ --- import Tags from "@/components/ui/Tags.astro"; import Categories from "@/components/ui/Categories.astro"; +import Destinations from "@/components/ui/Destinations.astro"; import { type CollectionEntry, render } from "astro:content"; import { toR2Url } from "@/utils/r2"; import Picture from "@/components/ui/Picture.astro"; @@ -89,5 +90,12 @@ const readingTime = remarkPluginFrontmatter?.minutesRead ) } + { + post.data.destination && post.data.destination.length > 0 && ( +
+ +
+ ) + } diff --git a/src/components/ui/Destinations.astro b/src/components/ui/Destinations.astro new file mode 100644 index 0000000..4ab9c09 --- /dev/null +++ b/src/components/ui/Destinations.astro @@ -0,0 +1,39 @@ +--- +export interface Props { + destinations: string[]; + class?: string; +} + +const { destinations, class: className = "text-sm" } = Astro.props; +--- + +{ + destinations && Array.isArray(destinations) && ( + + ) +} diff --git a/src/content.config.ts b/src/content.config.ts index 364bed4..d0c2772 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -11,6 +11,7 @@ const blog = defineCollection({ image: z.string().optional(), tags: z.array(z.string()).optional(), categories: z.array(z.string()).optional(), + destination: z.array(z.string()).optional(), }), }); diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro index e3d042d..4a38141 100644 --- a/src/pages/[...slug].astro +++ b/src/pages/[...slug].astro @@ -3,6 +3,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro"; import Schema from "@/components/seo/Schema.astro"; import Tags from "@/components/ui/Tags.astro"; import Categories from "@/components/ui/Categories.astro"; +import Destinations from "@/components/ui/Destinations.astro"; import { toR2Url } from "@/utils/r2"; import Picture from "@/components/ui/Picture.astro"; @@ -18,8 +19,16 @@ export async function getStaticPaths() { const { post } = Astro.props; const { Content, remarkPluginFrontmatter } = await render(post); -const { title, description, pubDate, author, image, categories, tags } = - post.data; +const { + title, + description, + pubDate, + author, + image, + categories, + tags, + destination, +} = post.data; const coverImage = toR2Url(image); const formattedDate = pubDate.toLocaleDateString("vi-VN", { @@ -70,6 +79,13 @@ const metadata = { > {title} + { + destination && destination.length > 0 && ( +
+ +
+ ) + } { diff --git a/src/pages/destination/[destination]/[...page].astro b/src/pages/destination/[destination]/[...page].astro new file mode 100644 index 0000000..bc948cf --- /dev/null +++ b/src/pages/destination/[destination]/[...page].astro @@ -0,0 +1,64 @@ +--- +import BaseLayout from "@/layouts/BaseLayout.astro"; +import { getCollection } from "astro:content"; +import Headline from "@/components/ui/Headline.astro"; +import PostItem from "@/components/blog/PostItem.astro"; +import Pagination from "@/components/ui/Pagination.astro"; +import type { GetStaticPathsOptions } from "astro"; + +export async function getStaticPaths({ paginate }: GetStaticPathsOptions) { + const allPosts = await getCollection("blog"); + const posts = allPosts.filter((post) => post.data && post.data.pubDate); + + const destinations = new Set(); + posts.forEach((post) => { + post.data.destination?.forEach((d) => destinations.add(d)); + }); + + return Array.from(destinations).flatMap((destination) => { + const destinationPosts = posts + .filter((post) => post.data.destination?.includes(destination)) + .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); + + return paginate(destinationPosts, { + params: { destination }, + pageSize: 6, + }); + }); +} + +const { page } = Astro.props; +const { destination } = Astro.params; + +const metadata = { + title: `Destination: ${destination}`, + description: `Travel articles about ${destination}.`, +}; +--- + + +
+
+ + + + +
+ {page.data.map((post) => )} +
+ +
+
+