feat: add loading skeletons for article list and improve home page structure
This commit is contained in:
@@ -2,18 +2,46 @@ import { getArticlesPaginated } from '@/lib/feature/article/article.external';
|
||||
import { ArticleCard } from '@/ui/components/internal/article-card';
|
||||
import { ArticleListPagination } from '@/ui/components/internal/article-list-pagination';
|
||||
import { FileTextIcon } from 'lucide-react';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
const PAGE_SIZE = 9;
|
||||
|
||||
type HomeProps = {
|
||||
searchParams: Promise<{ page?: string; pageSize?: string }>;
|
||||
};
|
||||
const ArticleCardSkeleton = () => (
|
||||
<div className='flex flex-col overflow-hidden rounded-xl border border-border bg-card'>
|
||||
<div className='aspect-video w-full animate-pulse bg-muted' />
|
||||
<div className='flex flex-1 flex-col gap-3 p-5'>
|
||||
<div className='h-3 w-24 animate-pulse rounded bg-muted' />
|
||||
<div className='space-y-1.5'>
|
||||
<div className='h-4 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-3/4 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
<div className='flex-1 space-y-1.5'>
|
||||
<div className='h-3 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-2/3 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
<div className='mt-1 h-3 w-16 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Home = async ({ searchParams }: HomeProps) => {
|
||||
const { page: pageParam, pageSize: pageSizeParam } = await searchParams;
|
||||
const page = Math.max(1, Number(pageParam) || 1);
|
||||
const pageSize = Number(pageSizeParam) || PAGE_SIZE;
|
||||
const ArticleListSkeleton = () => (
|
||||
<>
|
||||
<div className='mb-10 h-4 w-32 animate-pulse rounded bg-muted' />
|
||||
<div className='grid gap-6 sm:grid-cols-2 xl:grid-cols-3'>
|
||||
{Array.from({ length: PAGE_SIZE }).map((_, i) => (
|
||||
<ArticleCardSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
interface ArticleListProps {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
const ArticleList = async ({ page, pageSize }: ArticleListProps) => {
|
||||
const {
|
||||
data: articles,
|
||||
totalPages,
|
||||
@@ -21,20 +49,12 @@ const Home = async ({ searchParams }: HomeProps) => {
|
||||
} = await getArticlesPaginated(page, pageSize);
|
||||
|
||||
return (
|
||||
<div className='container mx-auto w-full flex-1 px-4 py-12 md:py-16'>
|
||||
<div className='mb-10 border-b border-border pb-8'>
|
||||
<p className='mb-1 font-mono text-xs font-medium uppercase tracking-widest text-muted-foreground'>
|
||||
Dev Blog
|
||||
</p>
|
||||
<h1 className='text-3xl font-bold tracking-tight md:text-4xl'>
|
||||
Latest Articles
|
||||
</h1>
|
||||
<p className='mt-2 text-muted-foreground'>
|
||||
<>
|
||||
<p className='mb-10 text-muted-foreground'>
|
||||
{total === 0
|
||||
? 'No articles published yet.'
|
||||
: `${total} article${total === 1 ? '' : 's'} published`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{articles.length === 0 ? (
|
||||
<div className='flex min-h-64 flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-border text-center'>
|
||||
@@ -65,6 +85,32 @@ const Home = async ({ searchParams }: HomeProps) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type HomeProps = {
|
||||
searchParams: Promise<{ page?: string; pageSize?: string }>;
|
||||
};
|
||||
|
||||
const Home = async ({ searchParams }: HomeProps) => {
|
||||
const { page: pageParam, pageSize: pageSizeParam } = await searchParams;
|
||||
const page = Math.max(1, Number(pageParam) || 1);
|
||||
const pageSize = Number(pageSizeParam) || PAGE_SIZE;
|
||||
|
||||
return (
|
||||
<div className='container mx-auto w-full flex-1 px-4 py-12 md:py-16'>
|
||||
<div className='mb-10 border-b border-border pb-8'>
|
||||
<p className='mb-1 font-mono text-xs font-medium uppercase tracking-widest text-muted-foreground'>
|
||||
Dev Blog
|
||||
</p>
|
||||
<h1 className='text-3xl font-bold tracking-tight md:text-4xl'>
|
||||
Latest Articles
|
||||
</h1>
|
||||
</div>
|
||||
<Suspense fallback={<ArticleListSkeleton />}>
|
||||
<ArticleList page={page} pageSize={pageSize} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user