summaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorBertrand Yuan <bert.yuan@outlook.com>2025-12-16 00:12:49 +0800
committerBertrand Yuan <bert.yuan@outlook.com>2025-12-16 00:12:49 +0800
commit02ae938c238c9d18448d17a8ec92c0edd8c17463 (patch)
treedcd6a30505adb52522b20af2c0ac27f713403f10 /src/app
parent48b07bc308a35734a6a7a305c8fdccbfa47de7d8 (diff)
feat(back-end): introduce payload
Payload is the next.js Headless CMS and App Framework, I would like to pick it up and modify it as it is MIT licensed. Many features in Payload is not applicable for our project. So, I modify it so that it is light and clear.
Diffstat (limited to 'src/app')
-rw-r--r--src/app/(home)/_components/posts.tsx40
-rw-r--r--src/app/(home)/posts/[slug]/page.tsx145
-rw-r--r--src/app/(home)/posts/page.tsx133
-rw-r--r--src/app/(main)/(auth)/login/page.tsx (renamed from src/app/(auth)/login/page.tsx)2
-rw-r--r--src/app/(main)/(home)/(mdx)/about/page.mdx (renamed from src/app/(home)/(mdx)/about/page.mdx)0
-rw-r--r--src/app/(main)/(home)/_components/call-to-action.tsx (renamed from src/app/(home)/_components/call-to-action.tsx)0
-rw-r--r--src/app/(main)/(home)/_components/hero.tsx (renamed from src/app/(home)/_components/hero.tsx)4
-rw-r--r--src/app/(main)/(home)/_components/posts.tsx89
-rw-r--r--src/app/(main)/(home)/actions.ts (renamed from src/app/(home)/actions.ts)0
-rw-r--r--src/app/(main)/(home)/layout.tsx (renamed from src/app/(home)/layout.tsx)0
-rw-r--r--src/app/(main)/(home)/page.tsx (renamed from src/app/(home)/page.tsx)17
-rw-r--r--src/app/(main)/(home)/posts/[slug]/page.client.tsx (renamed from src/app/(home)/posts/[slug]/page.client.tsx)0
-rw-r--r--src/app/(main)/(home)/posts/[slug]/page.tsx266
-rw-r--r--src/app/(main)/(home)/posts/page.tsx203
-rw-r--r--src/app/(main)/(home)/tags/[...slug]/page.tsx (renamed from src/app/(home)/tags/[...slug]/page.tsx)2
-rw-r--r--src/app/(main)/(home)/tags/page.tsx (renamed from src/app/(home)/tags/page.tsx)2
-rw-r--r--src/app/(main)/api/auth/[...all]/route.ts (renamed from src/app/api/auth/[...all]/route.ts)0
-rw-r--r--src/app/(main)/api/comments/[...comment]/route.ts (renamed from src/app/api/comments/[...comment]/route.ts)0
-rw-r--r--src/app/(main)/api/search/route.ts (renamed from src/app/api/search/route.ts)0
-rw-r--r--src/app/(main)/banner.png/fonts/geist-regular-otf.json (renamed from src/app/banner.png/fonts/geist-regular-otf.json)0
-rw-r--r--src/app/(main)/banner.png/fonts/geist-semibold-otf.json (renamed from src/app/banner.png/fonts/geist-semibold-otf.json)0
-rw-r--r--src/app/(main)/banner.png/fonts/geistmono-regular-otf.json (renamed from src/app/banner.png/fonts/geistmono-regular-otf.json)0
-rw-r--r--src/app/(main)/banner.png/og.tsx (renamed from src/app/banner.png/og.tsx)0
-rw-r--r--src/app/(main)/banner.png/route.tsx (renamed from src/app/banner.png/route.tsx)2
-rw-r--r--src/app/(main)/layout.client.tsx (renamed from src/app/layout.client.tsx)0
-rw-r--r--src/app/(main)/layout.config.tsx (renamed from src/app/layout.config.tsx)0
-rw-r--r--src/app/(main)/layout.tsx (renamed from src/app/layout.tsx)6
-rw-r--r--src/app/(main)/not-found.tsx (renamed from src/app/not-found.tsx)0
-rw-r--r--src/app/(main)/og/[...slug]/fonts/geist-regular-otf.json (renamed from src/app/og/[...slug]/fonts/geist-regular-otf.json)0
-rw-r--r--src/app/(main)/og/[...slug]/fonts/geist-semibold-otf.json (renamed from src/app/og/[...slug]/fonts/geist-semibold-otf.json)0
-rw-r--r--src/app/(main)/og/[...slug]/fonts/geistmono-regular-otf.json (renamed from src/app/og/[...slug]/fonts/geistmono-regular-otf.json)0
-rw-r--r--src/app/(main)/og/[...slug]/og.tsx (renamed from src/app/og/[...slug]/og.tsx)0
-rw-r--r--src/app/(main)/og/[...slug]/route.tsx (renamed from src/app/og/[...slug]/route.tsx)2
-rw-r--r--src/app/(main)/provider.tsx (renamed from src/app/provider.tsx)0
-rw-r--r--src/app/(main)/rss.xml/route.ts (renamed from src/app/rss.xml/route.ts)4
-rw-r--r--src/app/(payload)/admin/[[...segments]]/not-found.tsx24
-rw-r--r--src/app/(payload)/admin/[[...segments]]/page.tsx24
-rw-r--r--src/app/(payload)/admin/importMap.js49
-rw-r--r--src/app/(payload)/api/[...slug]/route.ts20
-rw-r--r--src/app/(payload)/api/graphql-playground/route.ts6
-rw-r--r--src/app/(payload)/api/graphql/route.ts6
-rw-r--r--src/app/(payload)/custom.scss1
-rw-r--r--src/app/(payload)/layout.tsx30
-rw-r--r--src/app/icon.pngbin212793 -> 0 bytes
44 files changed, 741 insertions, 336 deletions
diff --git a/src/app/(home)/_components/posts.tsx b/src/app/(home)/_components/posts.tsx
deleted file mode 100644
index 8c8dc33..0000000
--- a/src/app/(home)/_components/posts.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Icons } from '@/components/icons/icons';
-import { PostCard } from '@/components/posts/post-card';
-import { Section } from '@/components/section';
-import { buttonVariants } from '@/components/ui/button';
-import type { Page } from '@/lib/source';
-import Link from 'next/link';
-
-export default function Posts({ posts }: { posts: Page[] }) {
- return (
- <Section>
- <div className='grid divide-y divide-dashed divide-border/70 text-left dark:divide-border'>
- {posts.map((post) => {
- const date = new Date(post.data.date).toDateString();
- return (
- <PostCard
- title={post.data.title}
- description={post.data.description ?? ''}
- image={post.data.image}
- url={post.url}
- date={date}
- key={post.url}
- author={post.data.author}
- tags={post.data.tags}
- />
- );
- })}
- <Link
- href='/posts'
- className={buttonVariants({
- variant: 'default',
- className: 'group rounded-none py-4 sm:py-8',
- })}
- >
- View More
- <Icons.arrowUpRight className='group-hover:-rotate-12 ml-2 size-5 transition-transform' />
- </Link>
- </div>
- </Section>
- );
-}
diff --git a/src/app/(home)/posts/[slug]/page.tsx b/src/app/(home)/posts/[slug]/page.tsx
deleted file mode 100644
index 15a6bfd..0000000
--- a/src/app/(home)/posts/[slug]/page.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { PostComments, Share } from '@/app/(home)/posts/[slug]/page.client';
-import { PostJsonLd } from '@/components/json-ld';
-import { Section } from '@/components/section';
-import { TagCard } from '@/components/tags/tag-card';
-import { createMetadata } from '@/lib/metadata';
-import { metadataImage } from '@/lib/metadata-image';
-import { type Page as MDXPage, getPost, getPosts } from '@/lib/source';
-import { cn } from '@/lib/utils';
-import { File, Files, Folder } from 'fumadocs-ui/components/files';
-import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
-import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
-import defaultMdxComponents from 'fumadocs-ui/mdx';
-import type { Metadata } from 'next';
-import { notFound } from 'next/navigation';
-import Balancer from 'react-wrap-balancer';
-import { description as homeDescription } from 'src/app/layout.config';
-
-function Header(props: { page: MDXPage; tags?: string[] }) {
- const { page, tags } = props;
-
- return (
- <Section className='p-4 lg:p-6'>
- <div
- className={cn(
- 'flex flex-col items-start justify-center gap-4 py-8 md:gap-6',
- 'sm:items-center sm:rounded-lg sm:border sm:bg-muted/70 sm:px-8 sm:py-20 sm:shadow-xs sm:dark:bg-muted',
- )}
- >
- <div className='flex flex-col gap-2 sm:text-center md:gap-4'>
- <h1 className='max-w-4xl font-bold text-3xl leading-tight tracking-tight sm:text-4xl sm:leading-tight md:text-5xl md:leading-tight'>
- <Balancer>{page.data.title}</Balancer>
- </h1>
- <p className='mx-auto max-w-4xl'>
- <Balancer>{page.data.description}</Balancer>
- </p>
- </div>
- <div className='flex flex-wrap gap-2'>
- {tags?.map((tag) => (
- <TagCard name={tag} key={tag} className=' border border-border ' />
- ))}
- </div>
- </div>
- </Section>
- );
-}
-
-export default async function Page(props: {
- params: Promise<{ slug: string }>;
-}) {
- const params = await props.params;
- const page = getPost([params.slug]);
-
- if (!page) notFound();
- const { body: Mdx, toc, tags, lastModified } = page.data;
-
- const lastUpdate = lastModified ? new Date(lastModified) : undefined;
-
- return (
- <>
- <Header page={page} tags={tags} />
-
- <Section className='h-full' sectionClassName='flex flex-1'>
- <article className='flex min-h-full flex-col lg:flex-row'>
- <div className='flex flex-1 flex-col gap-4'>
- <InlineTOC
- items={toc}
- className='rounded-none border-0 border-border/70 border-b border-dashed dark:border-border'
- />
- <div className='prose min-w-0 flex-1 px-4'>
- <Mdx
- components={{
- ...defaultMdxComponents,
- File,
- Files,
- Folder,
- Tabs,
- Tab,
- }}
- />
- </div>
- <PostComments
- slug={params.slug}
- className='[&_form>div]:!rounded-none rounded-none border-0 border-border/70 border-t border-dashed dark:border-border'
- />
- </div>
- <div className='flex flex-col gap-4 p-4 text-sm lg:sticky lg:top-[4rem] lg:h-[calc(100vh-4rem)] lg:w-[250px] lg:self-start lg:overflow-y-auto lg:border-border/70 lg:border-l lg:border-dashed lg:dark:border-border'>
- <div>
- <p className='mb-1 text-fd-muted-foreground'>Written by</p>
- <p className='font-medium'>{page.data.author}</p>
- </div>
- <div>
- <p className='mb-1 text-fd-muted-foreground text-sm'>
- Created At
- </p>
- <p className='font-medium'>
- {new Date(page.data.date ?? page.file.name).toDateString()}
- </p>
- </div>
- {lastUpdate && (
- <div>
- <p className='mb-1 text-fd-muted-foreground text-sm'>
- Updated At
- </p>
- <p className='font-medium'>{lastUpdate.toDateString()}</p>
- </div>
- )}
- <Share url={page.url} />
- </div>
- </article>
- </Section>
- <PostJsonLd page={page} />
- </>
- );
-}
-
-export async function generateMetadata(props: {
- params: Promise<{ slug: string }>;
-}): Promise<Metadata> {
- const params = await props.params;
- const page = getPost([params.slug]);
-
- if (!page) notFound();
-
- const title = page.data.title;
- const description = page.data.description ?? homeDescription;
-
- return createMetadata(
- metadataImage.withImage(page.slugs, {
- title,
- description,
- openGraph: {
- url: `/posts/${page.slugs.join('/')}`,
- },
- alternates: {
- canonical: page.url,
- },
- }),
- );
-}
-
-export function generateStaticParams(): { slug: string | undefined }[] {
- return getPosts().map((page) => ({
- slug: page.slugs[0],
- }));
-}
diff --git a/src/app/(home)/posts/page.tsx b/src/app/(home)/posts/page.tsx
deleted file mode 100644
index fd0f912..0000000
--- a/src/app/(home)/posts/page.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import { postsPerPage } from '@/app/layout.config';
-import { NumberedPagination } from '@/components/numbered-pagination';
-import { PostCard } from '@/components/posts/post-card';
-import { Section } from '@/components/section';
-import { createMetadata } from '@/lib/metadata';
-import { getSortedByDatePosts } from '@/lib/source';
-import type { Metadata, ResolvingMetadata } from 'next';
-import { notFound, redirect } from 'next/navigation';
-
-export const dynamicParams = false;
-
-const totalPosts = getSortedByDatePosts().length;
-const pageCount = Math.ceil(totalPosts / postsPerPage);
-
-const CurrentPostsCount = ({
- startIndex,
- endIndex,
-}: {
- startIndex: number;
- endIndex: number;
-}) => {
- const start = startIndex + 1;
- const end = endIndex < totalPosts ? endIndex : totalPosts;
- if (start === end) return <span>({start})</span>;
- return (
- <span>
- ({start}-{end})
- </span>
- );
-};
-
-const Pagination = ({ pageIndex }: { pageIndex: number }) => {
- const handlePageChange = async (page: number) => {
- 'use server';
- redirect(`/posts?page=${page}`);
- };
-
- return (
- <Section className='bg-dashed'>
- <NumberedPagination
- currentPage={pageIndex + 1}
- totalPages={pageCount}
- paginationItemsToDisplay={5}
- onPageChange={handlePageChange}
- />
- </Section>
- );
-};
-
-export default async function Page(props: {
- searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
-}) {
- const searchParams = await props.searchParams;
- const pageIndex = searchParams.page
- ? Number.parseInt(searchParams.page[0] ?? '', 10) - 1
- : 0;
- if (pageIndex < 0 || pageIndex >= pageCount) notFound();
-
- const startIndex = pageIndex * postsPerPage;
- const endIndex = startIndex + postsPerPage;
- const posts = getSortedByDatePosts().slice(startIndex, endIndex);
-
- return (
- <>
- <Section className='p-4 lg:p-6'>
- <h1 className='font-bold text-3xl leading-tight tracking-tighter md:text-4xl'>
- All {totalPosts} Posts{' '}
- <CurrentPostsCount startIndex={startIndex} endIndex={endIndex} />
- </h1>
- </Section>
- <Section className='h-full' sectionClassName='flex flex-1'>
- <div className='grid divide-y divide-dashed divide-border/70 text-left dark:divide-border'>
- {posts.map((post) => {
- const date = new Date(post.data.date).toDateString();
- return (
- <PostCard
- title={post.data.title}
- description={post.data.description ?? ''}
- image={post.data.image}
- url={post.url}
- date={date}
- key={post.url}
- author={post.data.author}
- tags={post.data.tags}
- />
- );
- })}
- </div>
- </Section>
- {pageCount > 1 && <Pagination pageIndex={pageIndex} />}
- </>
- );
-}
-
-export const generateStaticParams = () => {
- const slugs = Array.from({ length: pageCount }, (_, index) => ({
- slug: [(index + 1).toString()],
- }));
-
- return [{ slug: [] }, ...slugs];
-};
-
-type Props = {
- params: Promise<{ slug: string[] }>;
- searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
-};
-
-export async function generateMetadata(
- props: Props,
- parent: ResolvingMetadata,
-): Promise<Metadata> {
- const params = await props.params;
- const searchParams = await props.searchParams;
-
- const pageIndex = searchParams.page
- ? Number.parseInt(searchParams.page as string, 10)
- : 1;
-
- const isFirstPage = pageIndex === 1 || !searchParams.page;
- const pageTitle = isFirstPage ? 'Posts' : `Posts - Page ${pageIndex}`;
- const canonicalUrl = isFirstPage ? '/posts' : `/posts?page=${pageIndex}`;
-
- return createMetadata({
- title: pageTitle,
- description: `Posts${!isFirstPage ? ` - Page ${pageIndex}` : ''}`,
- openGraph: {
- url: canonicalUrl,
- },
- alternates: {
- canonical: canonicalUrl,
- },
- });
-}
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(main)/(auth)/login/page.tsx
index 2469097..3ff59d3 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(main)/(auth)/login/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import { baseOptions, linkItems } from '@/app/layout.config';
+import { baseOptions, linkItems } from '@/app/(main)/layout.config';
import { Icons } from '@/components/icons/icons';
import { Header } from '@/components/sections/header';
import { Button } from '@/components/ui/button';
diff --git a/src/app/(home)/(mdx)/about/page.mdx b/src/app/(main)/(home)/(mdx)/about/page.mdx
index 675f3a8..675f3a8 100644
--- a/src/app/(home)/(mdx)/about/page.mdx
+++ b/src/app/(main)/(home)/(mdx)/about/page.mdx
diff --git a/src/app/(home)/_components/call-to-action.tsx b/src/app/(main)/(home)/_components/call-to-action.tsx
index b75298e..b75298e 100644
--- a/src/app/(home)/_components/call-to-action.tsx
+++ b/src/app/(main)/(home)/_components/call-to-action.tsx
diff --git a/src/app/(home)/_components/hero.tsx b/src/app/(main)/(home)/_components/hero.tsx
index 8ac251b..04371ca 100644
--- a/src/app/(home)/_components/hero.tsx
+++ b/src/app/(main)/(home)/_components/hero.tsx
@@ -1,4 +1,4 @@
-import { baseOptions, linkItems } from '@/app/layout.config';
+import { baseOptions, linkItems } from '@/app/(main)/layout.config';
import { Icons } from '@/components/icons/icons';
import { Section } from '@/components/section';
import { buttonVariants } from '@/components/ui/button';
@@ -8,7 +8,7 @@ import * as motion from 'motion/react-client';
import Image from 'next/image';
import Link from 'next/link';
import Balancer from 'react-wrap-balancer';
-import heroImage from '../../../../public/images/gradient-noise-purple-azure-light.png';
+import heroImage from '../../../../../public/images/gradient-noise-purple-azure-light.png';
const Hero = () => {
const links = getLinks(linkItems, baseOptions.githubUrl);
diff --git a/src/app/(main)/(home)/_components/posts.tsx b/src/app/(main)/(home)/_components/posts.tsx
new file mode 100644
index 0000000..00ada0c
--- /dev/null
+++ b/src/app/(main)/(home)/_components/posts.tsx
@@ -0,0 +1,89 @@
+import { Icons } from '@/components/icons/icons';
+import { PostCard } from '@/components/posts/post-card';
+import { Section } from '@/components/section';
+import { buttonVariants } from '@/components/ui/button';
+import type { Page } from '@/lib/source';
+import type { BlogPost } from '@/lib/payload-posts';
+import Link from 'next/link';
+
+// 统一的文章数据格式
+interface UnifiedPost {
+ title: string;
+ description: string;
+ image?: string | null;
+ url: string;
+ date: string;
+ author: string;
+ tags?: string[];
+}
+
+// 将 MDX Page 转换为统一格式
+function transformMdxPost(post: Page): UnifiedPost {
+ return {
+ title: post.data.title,
+ description: post.data.description ?? '',
+ image: post.data.image,
+ url: post.url,
+ date: new Date(post.data.date).toDateString(),
+ author: post.data.author,
+ tags: post.data.tags,
+ };
+}
+
+// 将 Payload BlogPost 转换为统一格式
+function transformPayloadPost(post: BlogPost): UnifiedPost {
+ return {
+ title: post.title,
+ description: post.description,
+ image: post.image,
+ url: post.url,
+ date: post.date.toDateString(),
+ author: post.author,
+ tags: post.tags,
+ };
+}
+
+interface PostsProps {
+ mdxPosts?: Page[];
+ payloadPosts?: BlogPost[];
+}
+
+export default function Posts({ mdxPosts = [], payloadPosts = [] }: PostsProps) {
+ // 转换并合并所有文章
+ const allPosts: UnifiedPost[] = [
+ ...mdxPosts.map(transformMdxPost),
+ ...payloadPosts.map(transformPayloadPost),
+ ];
+
+ // 按日期排序
+ allPosts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
+
+ return (
+ <Section>
+ <div className='grid divide-y divide-dashed divide-border/70 text-left dark:divide-border'>
+ {allPosts.map((post) => (
+ <PostCard
+ title={post.title}
+ description={post.description}
+ image={post.image}
+ url={post.url}
+ date={post.date}
+ key={post.url}
+ author={post.author}
+ tags={post.tags}
+ />
+ ))}
+ <Link
+ href='/posts'
+ className={buttonVariants({
+ variant: 'default',
+ className: 'group rounded-none py-4 sm:py-8',
+ })}
+ >
+ View More
+ <Icons.arrowUpRight className='group-hover:-rotate-12 ml-2 size-5 transition-transform' />
+ </Link>
+ </div>
+ </Section>
+ );
+}
diff --git a/src/app/(home)/actions.ts b/src/app/(main)/(home)/actions.ts
index fdb16ca..fdb16ca 100644
--- a/src/app/(home)/actions.ts
+++ b/src/app/(main)/(home)/actions.ts
diff --git a/src/app/(home)/layout.tsx b/src/app/(main)/(home)/layout.tsx
index bd641df..bd641df 100644
--- a/src/app/(home)/layout.tsx
+++ b/src/app/(main)/(home)/layout.tsx
diff --git a/src/app/(home)/page.tsx b/src/app/(main)/(home)/page.tsx
index da7da0f..a94193c 100644
--- a/src/app/(home)/page.tsx
+++ b/src/app/(main)/(home)/page.tsx
@@ -1,13 +1,18 @@
-import Hero from '@/app/(home)/_components/hero';
-import Posts from '@/app/(home)/_components/posts';
+import Hero from '@/app/(main)/(home)/_components/hero';
+import Posts from '@/app/(main)/(home)/_components/posts';
import { Icons } from '@/components/icons/icons';
import { Section } from '@/components/section';
import Separator from '@/components/separator';
import { getSortedByDatePosts } from '@/lib/source';
+import { getPublishedPosts } from '@/lib/payload-posts';
import { CTA } from './_components/call-to-action';
-export default function Home() {
- const posts = getSortedByDatePosts().slice(0, 3);
+export default async function Home() {
+ // 获取 MDX 文章(保留原有功能)
+ // const mdxPosts = getSortedByDatePosts().slice(0, 3);
+
+ // 获取 Payload CMS 文章
+ const { posts: payloadPosts } = await getPublishedPosts({ limit: 3 });
return (
<>
@@ -15,13 +20,13 @@ export default function Home() {
<Section className='py-8 sm:py-16'>
<h2 className='text-center font-semibold text-2xl sm:text-3xl md:text-4xl lg:text-5xl'>
<span className='inline-flex items-center gap-3'>
- Posts{' '}
+ Posts
<Icons.posts className='size-10 fill-fd-primary/30 text-fd-primary transition-transform hover:rotate-12 hover:scale-125' />
</span>
</h2>
</Section>
<Separator />
- <Posts posts={posts} />
+ <Posts mdxPosts={[]} payloadPosts={payloadPosts} />
<Separator />
<CTA />
</>
diff --git a/src/app/(home)/posts/[slug]/page.client.tsx b/src/app/(main)/(home)/posts/[slug]/page.client.tsx
index 7a97f56..7a97f56 100644
--- a/src/app/(home)/posts/[slug]/page.client.tsx
+++ b/src/app/(main)/(home)/posts/[slug]/page.client.tsx
diff --git a/src/app/(main)/(home)/posts/[slug]/page.tsx b/src/app/(main)/(home)/posts/[slug]/page.tsx
new file mode 100644
index 0000000..fa096b6
--- /dev/null
+++ b/src/app/(main)/(home)/posts/[slug]/page.tsx
@@ -0,0 +1,266 @@
+import {
+ PostComments,
+ Share,
+} from '@/app/(main)/(home)/posts/[slug]/page.client';
+import { PostJsonLd } from '@/components/json-ld';
+import { RichText } from '@/components/rich-text';
+import { Section } from '@/components/section';
+import { TagCard } from '@/components/tags/tag-card';
+import { createMetadata } from '@/lib/metadata';
+import { metadataImage } from '@/lib/metadata-image';
+import {
+ getPostBySlug,
+ getAllPostSlugs,
+ type BlogPost,
+} from '@/lib/payload-posts';
+import { type Page as MDXPage, getPost, getPosts } from '@/lib/source';
+import { cn } from '@/lib/utils';
+import { File, Files, Folder } from 'fumadocs-ui/components/files';
+import { InlineTOC } from 'fumadocs-ui/components/inline-toc';
+import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
+import defaultMdxComponents from 'fumadocs-ui/mdx';
+import type { Metadata } from 'next';
+import { notFound } from 'next/navigation';
+import Balancer from 'react-wrap-balancer';
+import { description as homeDescription } from '@/app/(main)/layout.config';
+
+// MDX 文章 Header
+function MdxHeader(props: { page: MDXPage; tags?: string[] }) {
+ const { page, tags } = props;
+
+ return (
+ <Section className="p-4 lg:p-6">
+ <div
+ className={cn(
+ 'flex flex-col items-start justify-center gap-4 py-8 md:gap-6',
+ 'sm:items-center sm:rounded-lg sm:border sm:bg-muted/70 sm:px-8 sm:py-20 sm:shadow-xs sm:dark:bg-muted'
+ )}
+ >
+ <div className="flex flex-col gap-2 sm:text-center md:gap-4">
+ <h1 className="max-w-4xl font-bold text-3xl leading-tight tracking-tight sm:text-4xl sm:leading-tight md:text-5xl md:leading-tight">
+ <Balancer>{page.data.title}</Balancer>
+ </h1>
+ <p className="mx-auto max-w-4xl">
+ <Balancer>{page.data.description}</Balancer>
+ </p>
+ </div>
+ <div className="flex flex-wrap gap-2">
+ {tags?.map((tag) => (
+ <TagCard name={tag} key={tag} className=" border border-border " />
+ ))}
+ </div>
+ </div>
+ </Section>
+ );
+}
+
+// Payload 文章 Header
+function PayloadHeader(props: { post: BlogPost }) {
+ const { post } = props;
+
+ return (
+ <Section className="p-4 lg:p-6">
+ <div
+ className={cn(
+ 'flex flex-col items-start justify-center gap-4 py-8 md:gap-6',
+ 'sm:items-center sm:rounded-lg sm:border sm:bg-muted/70 sm:px-8 sm:py-20 sm:shadow-xs sm:dark:bg-muted'
+ )}
+ >
+ <div className="flex flex-col gap-2 sm:text-center md:gap-4">
+ <h1 className="max-w-4xl font-bold text-3xl leading-tight tracking-tight sm:text-4xl sm:leading-tight md:text-5xl md:leading-tight">
+ <Balancer>{post.title}</Balancer>
+ </h1>
+ <p className="mx-auto max-w-4xl">
+ <Balancer>{post.description}</Balancer>
+ </p>
+ </div>
+ <div className="flex flex-wrap gap-2">
+ {post.tags?.map((tag) => (
+ <TagCard name={tag} key={tag} className=" border border-border " />
+ ))}
+ </div>
+ </div>
+ </Section>
+ );
+}
+
+// MDX 文章内容
+function MdxContent({ page }: { page: MDXPage }) {
+ const { body: Mdx, toc, tags, lastModified } = page.data;
+ const lastUpdate = lastModified ? new Date(lastModified) : undefined;
+
+ return (
+ <>
+ <MdxHeader page={page} tags={tags} />
+
+ <Section className="h-full" sectionClassName="flex flex-1">
+ <article className="flex min-h-full flex-col lg:flex-row">
+ <div className="flex flex-1 flex-col gap-4">
+ <InlineTOC
+ items={toc}
+ className="rounded-none border-0 border-border/70 border-b border-dashed dark:border-border"
+ />
+ <div className="prose min-w-0 flex-1 px-4">
+ <Mdx
+ components={{
+ ...defaultMdxComponents,
+ File,
+ Files,
+ Folder,
+ Tabs,
+ Tab,
+ }}
+ />
+ </div>
+ <PostComments
+ slug={page.slugs[0] ?? ''}
+ className="[&_form>div]:!rounded-none rounded-none border-0 border-border/70 border-t border-dashed dark:border-border"
+ />
+ </div>
+ <div className="flex flex-col gap-4 p-4 text-sm lg:sticky lg:top-[4rem] lg:h-[calc(100vh-4rem)] lg:w-[250px] lg:self-start lg:overflow-y-auto lg:border-border/70 lg:border-l lg:border-dashed lg:dark:border-border">
+ <div>
+ <p className="mb-1 text-fd-muted-foreground">Written by</p>
+ <p className="font-medium">{page.data.author}</p>
+ </div>
+ <div>
+ <p className="mb-1 text-fd-muted-foreground text-sm">Created At</p>
+ <p className="font-medium">
+ {new Date(page.data.date ?? page.file.name).toDateString()}
+ </p>
+ </div>
+ {lastUpdate && (
+ <div>
+ <p className="mb-1 text-fd-muted-foreground text-sm">Updated At</p>
+ <p className="font-medium">{lastUpdate.toDateString()}</p>
+ </div>
+ )}
+ <Share url={page.url} />
+ </div>
+ </article>
+ </Section>
+ <PostJsonLd page={page} />
+ </>
+ );
+}
+
+// Payload 文章内容
+function PayloadContent({ post }: { post: BlogPost }) {
+ return (
+ <>
+ <PayloadHeader post={post} />
+
+ <Section className="h-full" sectionClassName="flex flex-1">
+ <article className="flex min-h-full flex-col lg:flex-row">
+ <div className="flex flex-1 flex-col gap-4">
+ <RichText
+ content={post.content as Record<string, unknown>}
+ className="flex-1 px-4"
+ enableProse={true}
+ />
+ <PostComments
+ slug={post.slug}
+ className="[&_form>div]:!rounded-none rounded-none border-0 border-border/70 border-t border-dashed dark:border-border"
+ />
+ </div>
+ <div className="flex flex-col gap-4 p-4 text-sm lg:sticky lg:top-[4rem] lg:h-[calc(100vh-4rem)] lg:w-[250px] lg:self-start lg:overflow-y-auto lg:border-border/70 lg:border-l lg:border-dashed lg:dark:border-border">
+ <div>
+ <p className="mb-1 text-fd-muted-foreground">Written by</p>
+ <p className="font-medium">{post.author}</p>
+ </div>
+ <div>
+ <p className="mb-1 text-fd-muted-foreground text-sm">Created At</p>
+ <p className="font-medium">{post.date.toDateString()}</p>
+ </div>
+ <div>
+ <p className="mb-1 text-fd-muted-foreground text-sm">Updated At</p>
+ <p className="font-medium">{post.updatedAt.toDateString()}</p>
+ </div>
+ <Share url={post.url} />
+ </div>
+ </article>
+ </Section>
+ </>
+ );
+}
+
+export default async function Page(props: {
+ params: Promise<{ slug: string }>;
+}) {
+ const params = await props.params;
+
+ // 先尝试获取 MDX 文章
+ const mdxPage = getPost([params.slug]);
+
+ if (mdxPage) {
+ return <MdxContent page={mdxPage} />;
+ }
+
+ // 再尝试获取 Payload 文章
+ const payloadPost = await getPostBySlug(params.slug);
+
+ if (payloadPost) {
+ return <PayloadContent post={payloadPost} />;
+ }
+
+ // 都找不到则 404
+ notFound();
+}
+
+export async function generateMetadata(props: {
+ params: Promise<{ slug: string }>;
+}): Promise<Metadata> {
+ const params = await props.params;
+
+ // 先尝试 MDX 文章
+ const mdxPage = getPost([params.slug]);
+
+ if (mdxPage) {
+ const title = mdxPage.data.title;
+ const description = mdxPage.data.description ?? homeDescription;
+
+ return createMetadata(
+ metadataImage.withImage(mdxPage.slugs, {
+ title,
+ description,
+ openGraph: {
+ url: `/posts/${mdxPage.slugs.join('/')}`,
+ },
+ alternates: {
+ canonical: mdxPage.url,
+ },
+ })
+ );
+ }
+
+ // 再尝试 Payload 文章
+ const payloadPost = await getPostBySlug(params.slug);
+
+ if (payloadPost) {
+ return createMetadata({
+ title: payloadPost.title,
+ description: payloadPost.description || homeDescription,
+ openGraph: {
+ url: `/posts/${payloadPost.slug}`,
+ },
+ alternates: {
+ canonical: `/posts/${payloadPost.slug}`,
+ },
+ });
+ }
+
+ return {};
+}
+
+export async function generateStaticParams(): Promise<{ slug: string }[]> {
+ // MDX 文章的 slugs
+ const mdxSlugs = getPosts()
+ .map((page) => page.slugs[0])
+ .filter((slug): slug is string => !!slug)
+ .map((slug) => ({ slug }));
+
+ // Payload 文章的 slugs
+ const payloadSlugs = await getAllPostSlugs();
+ const payloadParams = payloadSlugs.map((slug) => ({ slug }));
+
+ return [...mdxSlugs, ...payloadParams];
+}
diff --git a/src/app/(main)/(home)/posts/page.tsx b/src/app/(main)/(home)/posts/page.tsx
new file mode 100644
index 0000000..00e51c4
--- /dev/null
+++ b/src/app/(main)/(home)/posts/page.tsx
@@ -0,0 +1,203 @@
+import { postsPerPage } from '@/app/(main)/layout.config';
+import { NumberedPagination } from '@/components/numbered-pagination';
+import { PostCard } from '@/components/posts/post-card';
+import { Section } from '@/components/section';
+import { createMetadata } from '@/lib/metadata';
+import { getPublishedPosts, type BlogPost } from '@/lib/payload-posts';
+import { getSortedByDatePosts, type Page } from '@/lib/source';
+import type { Metadata, ResolvingMetadata } from 'next';
+import { notFound, redirect } from 'next/navigation';
+
+// 统一的文章类型
+interface UnifiedPost {
+ id: string;
+ title: string;
+ description: string;
+ image?: string;
+ url: string;
+ date: Date;
+ author: string;
+ tags?: string[];
+ source: 'mdx' | 'payload';
+}
+
+// 将 MDX 文章转换为统一格式
+function transformMdxPost(post: Page): UnifiedPost {
+ return {
+ id: `mdx-${post.url}`,
+ title: post.data.title,
+ description: post.data.description ?? '',
+ image: post.data.image,
+ url: post.url,
+ date: new Date(post.data.date),
+ author: post.data.author,
+ tags: post.data.tags,
+ source: 'mdx',
+ };
+}
+
+// 将 Payload 文章转换为统一格式
+function transformPayloadPost(post: BlogPost): UnifiedPost {
+ return {
+ id: `payload-${post.id}`,
+ title: post.title,
+ description: post.description,
+ image: post.image,
+ url: post.url,
+ date: post.date,
+ author: post.author,
+ tags: post.tags,
+ source: 'payload',
+ };
+}
+
+// 获取所有文章(合并 MDX 和 Payload)
+async function getAllPosts(): Promise<UnifiedPost[]> {
+ // 获取 MDX 文章
+ const mdxPosts = getSortedByDatePosts().map(transformMdxPost);
+
+ // 获取 Payload 文章(获取全部,用于分页计算)
+ const { posts: payloadPosts } = await getPublishedPosts({ limit: 1000 });
+ const transformedPayloadPosts = payloadPosts.map(transformPayloadPost);
+
+ // 合并并按日期排序
+ const allPosts = [...mdxPosts, ...transformedPayloadPosts];
+ allPosts.sort((a, b) => b.date.getTime() - a.date.getTime());
+
+ return allPosts;
+}
+
+const CurrentPostsCount = ({
+ startIndex,
+ endIndex,
+ totalPosts,
+}: {
+ startIndex: number;
+ endIndex: number;
+ totalPosts: number;
+}) => {
+ const start = startIndex + 1;
+ const end = endIndex < totalPosts ? endIndex : totalPosts;
+ if (start === end) return <span>({start})</span>;
+ return (
+ <span>
+ ({start}-{end})
+ </span>
+ );
+};
+
+const Pagination = ({
+ pageIndex,
+ pageCount,
+}: {
+ pageIndex: number;
+ pageCount: number;
+}) => {
+ const handlePageChange = async (page: number) => {
+ 'use server';
+ redirect(`/posts?page=${page}`);
+ };
+
+ return (
+ <Section className='bg-dashed'>
+ <NumberedPagination
+ currentPage={pageIndex + 1}
+ totalPages={pageCount}
+ paginationItemsToDisplay={5}
+ onPageChange={handlePageChange}
+ />
+ </Section>
+ );
+};
+
+export default async function Page(props: {
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+}) {
+ const searchParams = await props.searchParams;
+
+ // 获取所有文章
+ const allPosts = await getAllPosts();
+ const totalPosts = allPosts.length;
+ const pageCount = Math.ceil(totalPosts / postsPerPage);
+
+ const pageIndex = searchParams.page
+ ? Number.parseInt(
+ Array.isArray(searchParams.page)
+ ? searchParams.page[0] ?? ''
+ : searchParams.page,
+ 10
+ ) - 1
+ : 0;
+
+ if (pageIndex < 0 || (pageCount > 0 && pageIndex >= pageCount)) notFound();
+
+ const startIndex = pageIndex * postsPerPage;
+ const endIndex = startIndex + postsPerPage;
+ const posts = allPosts.slice(startIndex, endIndex);
+
+ return (
+ <>
+ <Section className='p-4 lg:p-6'>
+ <h1 className='font-bold text-3xl leading-tight tracking-tighter md:text-4xl'>
+ All {totalPosts} Posts{' '}
+ <CurrentPostsCount
+ startIndex={startIndex}
+ endIndex={endIndex}
+ totalPosts={totalPosts}
+ />
+ </h1>
+ </Section>
+ <Section className='h-full' sectionClassName='flex flex-1'>
+ <div className='grid divide-y divide-dashed divide-border/70 text-left dark:divide-border'>
+ {posts.map((post) => {
+ const date = post.date.toDateString();
+ return (
+ <PostCard
+ title={post.title}
+ description={post.description}
+ image={post.image}
+ url={post.url}
+ date={date}
+ key={post.id}
+ author={post.author}
+ tags={post.tags}
+ />
+ );
+ })}
+ </div>
+ </Section>
+ {pageCount > 1 && <Pagination pageIndex={pageIndex} pageCount={pageCount} />}
+ </>
+ );
+}
+
+type Props = {
+ params: Promise<{ slug: string[] }>;
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+};
+
+export async function generateMetadata(
+ props: Props,
+ parent: ResolvingMetadata
+): Promise<Metadata> {
+ const searchParams = await props.searchParams;
+
+ const pageIndex = searchParams.page
+ ? Number.parseInt(searchParams.page as string, 10)
+ : 1;
+
+ const isFirstPage = pageIndex === 1 || !searchParams.page;
+ const pageTitle = isFirstPage ? 'Posts' : `Posts - Page ${pageIndex}`;
+ const canonicalUrl = isFirstPage ? '/posts' : `/posts?page=${pageIndex}`;
+
+ return createMetadata({
+ title: pageTitle,
+ description: `Posts${!isFirstPage ? ` - Page ${pageIndex}` : ''}`,
+ openGraph: {
+ url: canonicalUrl,
+ },
+ alternates: {
+ canonical: canonicalUrl,
+ },
+ });
+}
diff --git a/src/app/(home)/tags/[...slug]/page.tsx b/src/app/(main)/(home)/tags/[...slug]/page.tsx
index 9479705..c1ce96b 100644
--- a/src/app/(home)/tags/[...slug]/page.tsx
+++ b/src/app/(main)/(home)/tags/[...slug]/page.tsx
@@ -1,4 +1,4 @@
-import { postsPerPage } from '@/app/layout.config';
+import { postsPerPage } from '@/app/(main)/layout.config';
import { Icons } from '@/components/icons/icons';
import { TagJsonLd } from '@/components/json-ld';
import { NumberedPagination } from '@/components/numbered-pagination';
diff --git a/src/app/(home)/tags/page.tsx b/src/app/(main)/(home)/tags/page.tsx
index 54fb423..9138fde 100644
--- a/src/app/(home)/tags/page.tsx
+++ b/src/app/(main)/(home)/tags/page.tsx
@@ -1,4 +1,4 @@
-import { title as homeTitle } from '@/app/layout.config';
+import { title as homeTitle } from '@/app/(main)/layout.config';
import { Section } from '@/components/section';
import { TagCard } from '@/components/tags/tag-card';
import { createMetadata } from '@/lib/metadata';
diff --git a/src/app/api/auth/[...all]/route.ts b/src/app/(main)/api/auth/[...all]/route.ts
index 677b24c..677b24c 100644
--- a/src/app/api/auth/[...all]/route.ts
+++ b/src/app/(main)/api/auth/[...all]/route.ts
diff --git a/src/app/api/comments/[...comment]/route.ts b/src/app/(main)/api/comments/[...comment]/route.ts
index 1da87db..1da87db 100644
--- a/src/app/api/comments/[...comment]/route.ts
+++ b/src/app/(main)/api/comments/[...comment]/route.ts
diff --git a/src/app/api/search/route.ts b/src/app/(main)/api/search/route.ts
index 3c99f5c..3c99f5c 100644
--- a/src/app/api/search/route.ts
+++ b/src/app/(main)/api/search/route.ts
diff --git a/src/app/banner.png/fonts/geist-regular-otf.json b/src/app/(main)/banner.png/fonts/geist-regular-otf.json
index f220c87..f220c87 100644
--- a/src/app/banner.png/fonts/geist-regular-otf.json
+++ b/src/app/(main)/banner.png/fonts/geist-regular-otf.json
diff --git a/src/app/banner.png/fonts/geist-semibold-otf.json b/src/app/(main)/banner.png/fonts/geist-semibold-otf.json
index c119360..c119360 100644
--- a/src/app/banner.png/fonts/geist-semibold-otf.json
+++ b/src/app/(main)/banner.png/fonts/geist-semibold-otf.json
diff --git a/src/app/banner.png/fonts/geistmono-regular-otf.json b/src/app/(main)/banner.png/fonts/geistmono-regular-otf.json
index f4200df..f4200df 100644
--- a/src/app/banner.png/fonts/geistmono-regular-otf.json
+++ b/src/app/(main)/banner.png/fonts/geistmono-regular-otf.json
diff --git a/src/app/banner.png/og.tsx b/src/app/(main)/banner.png/og.tsx
index 1a520c0..1a520c0 100644
--- a/src/app/banner.png/og.tsx
+++ b/src/app/(main)/banner.png/og.tsx
diff --git a/src/app/banner.png/route.tsx b/src/app/(main)/banner.png/route.tsx
index 1cd53ac..d3bfdc8 100644
--- a/src/app/banner.png/route.tsx
+++ b/src/app/(main)/banner.png/route.tsx
@@ -1,4 +1,4 @@
-import { generateOGImage } from '@/app/banner.png/og';
+import { generateOGImage } from '@/app/(main)/banner.png/og';
async function loadAssets(): Promise<
{ name: string; data: Buffer; weight: 400 | 600; style: 'normal' }[]
diff --git a/src/app/layout.client.tsx b/src/app/(main)/layout.client.tsx
index 35726ba..35726ba 100644
--- a/src/app/layout.client.tsx
+++ b/src/app/(main)/layout.client.tsx
diff --git a/src/app/layout.config.tsx b/src/app/(main)/layout.config.tsx
index f9efebb..f9efebb 100644
--- a/src/app/layout.config.tsx
+++ b/src/app/(main)/layout.config.tsx
diff --git a/src/app/layout.tsx b/src/app/(main)/layout.tsx
index c7d2aaf..c9e7dee 100644
--- a/src/app/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -5,9 +5,9 @@ import type { ReactNode } from 'react';
import '@/styles/globals.css';
import 'katex/dist/katex.css';
import { baseUrl } from '@/lib/constants';
-import { Body } from './layout.client';
-import { description as homeDescription } from './layout.config';
-import { Provider } from './provider';
+import { Body } from '@/app/(main)/layout.client';
+import { description as homeDescription } from '@/app/(main)/layout.config';
+import { Provider } from '@/app/(main)/provider';
const geistSans = Geist({
variable: '--font-geist-sans',
diff --git a/src/app/not-found.tsx b/src/app/(main)/not-found.tsx
index ecec57a..ecec57a 100644
--- a/src/app/not-found.tsx
+++ b/src/app/(main)/not-found.tsx
diff --git a/src/app/og/[...slug]/fonts/geist-regular-otf.json b/src/app/(main)/og/[...slug]/fonts/geist-regular-otf.json
index f220c87..f220c87 100644
--- a/src/app/og/[...slug]/fonts/geist-regular-otf.json
+++ b/src/app/(main)/og/[...slug]/fonts/geist-regular-otf.json
diff --git a/src/app/og/[...slug]/fonts/geist-semibold-otf.json b/src/app/(main)/og/[...slug]/fonts/geist-semibold-otf.json
index c119360..c119360 100644
--- a/src/app/og/[...slug]/fonts/geist-semibold-otf.json
+++ b/src/app/(main)/og/[...slug]/fonts/geist-semibold-otf.json
diff --git a/src/app/og/[...slug]/fonts/geistmono-regular-otf.json b/src/app/(main)/og/[...slug]/fonts/geistmono-regular-otf.json
index f4200df..f4200df 100644
--- a/src/app/og/[...slug]/fonts/geistmono-regular-otf.json
+++ b/src/app/(main)/og/[...slug]/fonts/geistmono-regular-otf.json
diff --git a/src/app/og/[...slug]/og.tsx b/src/app/(main)/og/[...slug]/og.tsx
index 5754e96..5754e96 100644
--- a/src/app/og/[...slug]/og.tsx
+++ b/src/app/(main)/og/[...slug]/og.tsx
diff --git a/src/app/og/[...slug]/route.tsx b/src/app/(main)/og/[...slug]/route.tsx
index 8738616..d713923 100644
--- a/src/app/og/[...slug]/route.tsx
+++ b/src/app/(main)/og/[...slug]/route.tsx
@@ -1,4 +1,4 @@
-import { generateOGImage } from '@/app/og/[...slug]/og';
+import { generateOGImage } from '@/app/(main)/og/[...slug]/og';
import { metadataImage } from '@/lib/metadata-image';
import type { ImageResponse } from 'next/og';
diff --git a/src/app/provider.tsx b/src/app/(main)/provider.tsx
index 085bf50..085bf50 100644
--- a/src/app/provider.tsx
+++ b/src/app/(main)/provider.tsx
diff --git a/src/app/rss.xml/route.ts b/src/app/(main)/rss.xml/route.ts
index 6a3acf6..ee06a40 100644
--- a/src/app/rss.xml/route.ts
+++ b/src/app/(main)/rss.xml/route.ts
@@ -1,5 +1,5 @@
-import { description, title } from '@/app/layout.config';
-import { owner } from '@/app/layout.config';
+import { description, title } from '@/app/(main)/layout.config';
+import { owner } from '@/app/(main)/layout.config';
import { baseUrl } from '@/lib/constants';
import { getPosts } from '@/lib/source';
import { Feed } from 'feed';
diff --git a/src/app/(payload)/admin/[[...segments]]/not-found.tsx b/src/app/(payload)/admin/[[...segments]]/not-found.tsx
new file mode 100644
index 0000000..b28df09
--- /dev/null
+++ b/src/app/(payload)/admin/[[...segments]]/not-found.tsx
@@ -0,0 +1,24 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import type { Metadata } from 'next';
+
+import config from '@payload-config';
+import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views';
+import { importMap } from '../importMap';
+
+type Args = {
+ params: Promise<{
+ segments: string[];
+ }>;
+ searchParams: Promise<{
+ [key: string]: string | string[];
+ }>;
+};
+
+export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
+ generatePageMetadata({ config, params, searchParams });
+
+const NotFound = ({ params, searchParams }: Args) =>
+ NotFoundPage({ config, importMap, params, searchParams });
+
+export default NotFound;
diff --git a/src/app/(payload)/admin/[[...segments]]/page.tsx b/src/app/(payload)/admin/[[...segments]]/page.tsx
new file mode 100644
index 0000000..f04f258
--- /dev/null
+++ b/src/app/(payload)/admin/[[...segments]]/page.tsx
@@ -0,0 +1,24 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import type { Metadata } from 'next';
+
+import config from '@payload-config';
+import { RootPage, generatePageMetadata } from '@payloadcms/next/views';
+import { importMap } from '../importMap';
+
+type Args = {
+ params: Promise<{
+ segments: string[];
+ }>;
+ searchParams: Promise<{
+ [key: string]: string | string[];
+ }>;
+};
+
+export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
+ generatePageMetadata({ config, params, searchParams });
+
+const Page = ({ params, searchParams }: Args) =>
+ RootPage({ config, params, searchParams, importMap });
+
+export default Page;
diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js
new file mode 100644
index 0000000..5bc8ec3
--- /dev/null
+++ b/src/app/(payload)/admin/importMap.js
@@ -0,0 +1,49 @@
+import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
+import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
+import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
+import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
+
+export const importMap = {
+ "@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
+ "@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
+ "@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
+ "@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
+ "@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864
+}
diff --git a/src/app/(payload)/api/[...slug]/route.ts b/src/app/(payload)/api/[...slug]/route.ts
new file mode 100644
index 0000000..c3de612
--- /dev/null
+++ b/src/app/(payload)/api/[...slug]/route.ts
@@ -0,0 +1,20 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import config from '@payload-config'
+import '@payloadcms/next/css'
+import {
+ REST_DELETE,
+ REST_GET,
+ REST_OPTIONS,
+ REST_PATCH,
+ REST_POST,
+ REST_PUT,
+} from '@payloadcms/next/routes'
+
+export const GET = REST_GET(config)
+export const POST = REST_POST(config)
+export const DELETE = REST_DELETE(config)
+export const PATCH = REST_PATCH(config)
+
+export const PUT = REST_PUT(config)
+export const OPTIONS = REST_OPTIONS(config)
diff --git a/src/app/(payload)/api/graphql-playground/route.ts b/src/app/(payload)/api/graphql-playground/route.ts
new file mode 100644
index 0000000..c14156d
--- /dev/null
+++ b/src/app/(payload)/api/graphql-playground/route.ts
@@ -0,0 +1,6 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import config from '@payload-config';
+import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes';
+
+export const GET = GRAPHQL_PLAYGROUND_GET(config);
diff --git a/src/app/(payload)/api/graphql/route.ts b/src/app/(payload)/api/graphql/route.ts
new file mode 100644
index 0000000..65fcf23
--- /dev/null
+++ b/src/app/(payload)/api/graphql/route.ts
@@ -0,0 +1,6 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import config from '@payload-config';
+import { GRAPHQL_POST } from '@payloadcms/next/routes';
+
+export const POST = GRAPHQL_POST(config);
diff --git a/src/app/(payload)/custom.scss b/src/app/(payload)/custom.scss
new file mode 100644
index 0000000..f38c2f0
--- /dev/null
+++ b/src/app/(payload)/custom.scss
@@ -0,0 +1 @@
+/* Add custom Payload admin styles here */
diff --git a/src/app/(payload)/layout.tsx b/src/app/(payload)/layout.tsx
new file mode 100644
index 0000000..f14247a
--- /dev/null
+++ b/src/app/(payload)/layout.tsx
@@ -0,0 +1,30 @@
+/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
+/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+import config from '@payload-config';
+import '@payloadcms/next/css';
+import type { ServerFunctionClient } from 'payload';
+import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts';
+import React from 'react';
+import { importMap } from './admin/importMap.js';
+import './custom.scss';
+
+type Args = {
+ children: React.ReactNode;
+};
+
+const serverFunction: ServerFunctionClient = async function (args) {
+ 'use server';
+ return handleServerFunctions({
+ ...args,
+ config,
+ importMap,
+ });
+};
+
+const Layout = ({ children }: Args) => (
+ <RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
+ {children}
+ </RootLayout>
+);
+
+export default Layout;
diff --git a/src/app/icon.png b/src/app/icon.png
deleted file mode 100644
index 7532d9b..0000000
--- a/src/app/icon.png
+++ /dev/null
Binary files differ