diff options
| author | Bertrand Yuan <bert.yuan@outlook.com> | 2025-12-15 23:48:10 +0800 |
|---|---|---|
| committer | Bertrand Yuan <bert.yuan@outlook.com> | 2025-12-15 23:48:10 +0800 |
| commit | 5b7ccf0b671e2999b62befc729a3e517a0433728 (patch) | |
| tree | 8bf476dc7c75914c221042546840dc76267366df /src/app/(home)/posts/page.tsx | |
initial commit -- the front-end prototype
The initial code is base on Anirudh's work.
More to see at:
https://github.com/techwithanirudh/shadcn-blog
Therefore, the code in this commit is under MIT license.
Diffstat (limited to 'src/app/(home)/posts/page.tsx')
| -rw-r--r-- | src/app/(home)/posts/page.tsx | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/src/app/(home)/posts/page.tsx b/src/app/(home)/posts/page.tsx new file mode 100644 index 0000000..fd0f912 --- /dev/null +++ b/src/app/(home)/posts/page.tsx @@ -0,0 +1,133 @@ +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, + }, + }); +} |
