diff --git a/source.config.ts b/source.config.ts index bb5677b..4b1b2e4 100644 --- a/source.config.ts +++ b/source.config.ts @@ -26,6 +26,8 @@ export const docs = defineDocs({ /** * Changelog + * + * title is required, but description is optional in frontmatter */ export const changelog = defineCollections({ type: 'doc', @@ -39,6 +41,8 @@ export const changelog = defineCollections({ /** * Pages, like privacy policy, terms of service, etc. + * + * title is required, but description is optional in frontmatter */ export const pages = defineCollections({ type: 'doc', @@ -52,39 +56,48 @@ export const pages = defineCollections({ /** * Blog authors * - * description is optional, but we must add it to the schema + * description is optional in frontmatter, but we must add it to the schema */ export const author = defineCollections({ type: 'doc', dir: 'content/author', - schema: frontmatterSchema.extend({ + schema: z.object({ name: z.string(), avatar: z.string(), + description: z.string().optional(), }), }); /** * Blog categories + * + * description is optional in frontmatter, but we must add it to the schema */ export const category = defineCollections({ type: 'doc', dir: 'content/category', - schema: frontmatterSchema.extend({ + schema: z.object({ name: z.string(), + description: z.string().optional(), }), }); /** * Blog posts + * + * dtitle is required, but description is optional in frontmatter */ export const blog = defineCollections({ type: 'doc', dir: 'content/blog', - schema: frontmatterSchema.extend({ - image: z.string(), - date: z.string().date(), - published: z.boolean().default(true), - categories: z.array(z.string()), - author: z.string(), - }), + schema: (ctx) => { + // console.log('ctx', ctx); // {source, path} + return frontmatterSchema.extend({ + image: z.string(), + date: z.string().date(), + published: z.boolean().default(true), + categories: z.array(z.string()), + author: z.string(), + }); + }, }); diff --git a/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx b/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx index 1302a1a..19197f7 100644 --- a/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx +++ b/src/app/[locale]/(marketing)/blog/(blog)/layout.tsx @@ -1,24 +1,28 @@ import { BlogCategoryFilter } from '@/components/blog/blog-category-filter'; import Container from '@/components/layout/container'; -import type { NextPageProps } from '@/types/next-page-props'; -import { allCategories } from 'content-collections'; +import { categorySource } from '@/lib/docs/source'; import { getTranslations } from 'next-intl/server'; import type { PropsWithChildren } from 'react'; -interface BlogListLayoutProps extends PropsWithChildren, NextPageProps {} +interface BlogListLayoutProps extends PropsWithChildren { + params: Promise<{ locale: string }>; +} export default async function BlogListLayout({ children, params, }: BlogListLayoutProps) { - const resolvedParams = await params; - const { locale } = resolvedParams; + const { locale } = await params; const t = await getTranslations('BlogPage'); // Filter categories by locale - const categoryList = allCategories.filter( - (category) => category.locale === locale - ); + const language = locale as string; + const categoryList = categorySource.getPages(language).map((category) => ({ + slug: category.slugs[0], + name: category.data.name, + description: category.data.description || '', + })); + // console.log('categoryList', categoryList); return (
diff --git a/src/components/blog/blog-card.tsx b/src/components/blog/blog-card.tsx index 4ecc365..4c7d583 100644 --- a/src/components/blog/blog-card.tsx +++ b/src/components/blog/blog-card.tsx @@ -1,20 +1,25 @@ import { Skeleton } from '@/components/ui/skeleton'; import { LocaleLink } from '@/i18n/navigation'; import { PLACEHOLDER_IMAGE } from '@/lib/constants'; +import { type BlogType, authorSource, categorySource } from '@/lib/docs/source'; import { formatDate } from '@/lib/formatter'; -import type { Post } from 'content-collections'; import Image from 'next/image'; interface BlogCardProps { - post: Post; + locale: string; + post: BlogType; } -export default function BlogCard({ post }: BlogCardProps) { - const publishDate = post.date; - const date = formatDate(new Date(publishDate)); +export default function BlogCard({ locale, post }: BlogCardProps) { + const { date, title, description, image, author, categories } = post.data; + const publishDate = formatDate(new Date(date)); + const blogAuthor = authorSource.getPage([author], locale); + const blogCategories = categorySource + .getPages(locale) + .filter((category) => categories.includes(category.slugs[0] ?? '')); // Extract the slug parts for the Link component - const slugParts = post.slugAsParams.split('/'); + const slugParts = post.slugs[0].split('/'); // console.log('BlogCard, slugParts', slugParts); return ( @@ -22,27 +27,27 @@ export default function BlogCard({ post }: BlogCardProps) {
{/* Image container - fixed aspect ratio */}
- {post.image && ( + {image && (
{post.title - {post.categories && post.categories.length > 0 && ( + {blogCategories && blogCategories.length > 0 && (
- {post.categories.map((category, index) => ( + {blogCategories.map((category, index) => ( - {category?.name} + {category?.data.name} ))}
@@ -58,7 +63,7 @@ export default function BlogCard({ post }: BlogCardProps) { {/* Post title */}

- {post.title} + {title}

{/* Post excerpt */}
- {post.description && ( + {description && (

- {post.description} + {description}

)}
@@ -84,20 +89,20 @@ export default function BlogCard({ post }: BlogCardProps) {
- {post?.author?.avatar && ( + {blogAuthor?.data.avatar && ( {`avatar )}
- {post?.author?.name} + {blogAuthor?.data.name}
diff --git a/src/components/blog/blog-category-filter.tsx b/src/components/blog/blog-category-filter.tsx index 879701c..18792d9 100644 --- a/src/components/blog/blog-category-filter.tsx +++ b/src/components/blog/blog-category-filter.tsx @@ -1,10 +1,10 @@ import Container from '@/components/layout/container'; -import type { Category } from 'content-collections'; +import type { BlogCategory } from '@/types/blog-types'; import { BlogCategoryListDesktop } from './blog-category-list-desktop'; import { BlogCategoryListMobile } from './blog-category-list-mobile'; interface BlogCategoryFilterProps { - categoryList: Category[]; + categoryList: BlogCategory[]; } export function BlogCategoryFilter({ categoryList }: BlogCategoryFilterProps) { diff --git a/src/components/blog/blog-category-list-desktop.tsx b/src/components/blog/blog-category-list-desktop.tsx index bf169a3..6f0444d 100644 --- a/src/components/blog/blog-category-list-desktop.tsx +++ b/src/components/blog/blog-category-list-desktop.tsx @@ -3,12 +3,12 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import { LocaleLink } from '@/i18n/navigation'; import { cn } from '@/lib/utils'; -import type { Category } from 'content-collections'; +import type { BlogCategory } from '@/types/blog-types'; import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; export type BlogCategoryListDesktopProps = { - categoryList: Category[]; + categoryList: BlogCategory[]; }; export function BlogCategoryListDesktop({ diff --git a/src/components/blog/blog-category-list-mobile.tsx b/src/components/blog/blog-category-list-mobile.tsx index 501a992..fa9daa2 100644 --- a/src/components/blog/blog-category-list-mobile.tsx +++ b/src/components/blog/blog-category-list-mobile.tsx @@ -9,14 +9,14 @@ import { DrawerTitle, DrawerTrigger, } from '@/components/ui/drawer'; -import type { Category } from 'content-collections'; +import type { BlogCategory } from '@/types/blog-types'; import { LayoutListIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; import { useState } from 'react'; export type BlogCategoryListMobileProps = { - categoryList: Category[]; + categoryList: BlogCategory[]; }; export function BlogCategoryListMobile({ diff --git a/src/components/blog/blog-grid-with-pagination.tsx b/src/components/blog/blog-grid-with-pagination.tsx index 77c892b..dbc1744 100644 --- a/src/components/blog/blog-grid-with-pagination.tsx +++ b/src/components/blog/blog-grid-with-pagination.tsx @@ -1,15 +1,17 @@ -import type { Post } from 'content-collections'; +import type { BlogType } from '@/lib/docs/source'; import EmptyGrid from '../shared/empty-grid'; import CustomPagination from '../shared/pagination'; import BlogGrid from './blog-grid'; interface BlogGridWithPaginationProps { - posts: Post[]; + locale: string; + posts: BlogType[]; totalPages: number; routePrefix: string; } export default function BlogGridWithPagination({ + locale, posts, totalPages, routePrefix, @@ -19,7 +21,7 @@ export default function BlogGridWithPagination({ {posts.length === 0 && } {posts.length > 0 && (
- +
{posts?.length > 0 && (
{posts.map((post) => ( - + ))}
)} diff --git a/src/types/blog-types.ts b/src/types/blog-types.ts new file mode 100644 index 0000000..76f2b52 --- /dev/null +++ b/src/types/blog-types.ts @@ -0,0 +1,25 @@ +/** + * Blog Category + * + * we can not pass CategoryType from server component to client component + * so we need to define a new type, and use it in the client component + */ +export type BlogCategory = { + slug: string; + name: string; + description: string; +}; + +export type BlogAuthor = { + slug: string; + name: string; + description: string; + avatar: string; +}; + +export type BlogPost = { + slug: string; + title: string; + description: string; + date: string; +};