This commit is contained in:
@@ -8,6 +8,8 @@ import PostListItem from '@/components/post/PostListItem';
|
||||
import PostSearch from '@/components/post/PostSearch'; // 🆕 검색 컴포넌트
|
||||
import { Loader2, ChevronLeft, ChevronRight, LayoutGrid, List, Search as SearchIcon } from 'lucide-react';
|
||||
import { clsx } from 'clsx';
|
||||
import EmptyState from '@/components/ui/EmptyState';
|
||||
import Surface from '@/components/ui/Surface';
|
||||
|
||||
export default function CategoryPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = use(params);
|
||||
@@ -54,18 +56,18 @@ export default function CategoryPage({ params }: { params: Promise<{ id: string
|
||||
|
||||
if (isLoading || (postsData === undefined && !error)) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<Loader2 className="animate-spin text-blue-500" size={40} />
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="animate-spin text-[var(--color-accent)]" size={40} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center py-10 text-red-500">
|
||||
<Surface className="mx-auto max-w-3xl px-5 py-10 text-center text-red-500">
|
||||
게시글을 불러오는 중 오류가 발생했습니다.<br/>
|
||||
<span className="text-sm text-gray-400">카테고리 이름을 확인해주세요.</span>
|
||||
</div>
|
||||
<span className="text-sm text-[var(--color-text-subtle)]">카테고리 이름을 확인해주세요.</span>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,53 +90,61 @@ export default function CategoryPage({ params }: { params: Promise<{ id: string
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||
<div className="mx-auto max-w-5xl px-4 py-8">
|
||||
{/* 헤더 영역 */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<h1 className="text-2xl font-bold text-gray-800 flex items-baseline gap-2 shrink-0">
|
||||
{apiCategoryName} <span className="text-gray-400 text-lg font-normal">글 목록</span>
|
||||
</h1>
|
||||
<Surface strong className="mb-8 p-5">
|
||||
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-center">
|
||||
<h1 className="flex shrink-0 items-baseline gap-2 text-2xl font-bold tracking-normal text-[var(--color-text)]">
|
||||
{apiCategoryName} <span className="text-lg font-normal text-[var(--color-text-subtle)]">글 목록</span>
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center gap-3 w-full md:w-auto">
|
||||
{/* 🔍 카테고리 내 검색바 */}
|
||||
<PostSearch
|
||||
onSearch={handleSearch}
|
||||
placeholder={`${apiCategoryName} 내 검색`}
|
||||
className="w-full md:w-64"
|
||||
/>
|
||||
<div className="flex w-full items-center gap-3 md:w-auto">
|
||||
{/* 🔍 카테고리 내 검색바 */}
|
||||
<PostSearch
|
||||
onSearch={handleSearch}
|
||||
placeholder={`${apiCategoryName} 내 검색`}
|
||||
className="w-full md:w-64"
|
||||
/>
|
||||
|
||||
{/* 뷰 모드 버튼 */}
|
||||
<div className="flex items-center gap-1 bg-gray-100 p-1 rounded-lg shrink-0">
|
||||
<button
|
||||
onClick={() => handleViewModeChange('grid')}
|
||||
className={clsx(
|
||||
"p-2 rounded-md transition-all duration-200",
|
||||
activeViewMode === 'grid' ? "bg-white text-blue-600 shadow-sm" : "text-gray-400 hover:text-gray-600"
|
||||
)}
|
||||
title="카드형 보기"
|
||||
aria-label="카드형 보기"
|
||||
>
|
||||
<LayoutGrid size={18} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleViewModeChange('list')}
|
||||
className={clsx(
|
||||
"p-2 rounded-md transition-all duration-200",
|
||||
activeViewMode === 'list' ? "bg-white text-blue-600 shadow-sm" : "text-gray-400 hover:text-gray-600"
|
||||
)}
|
||||
title="리스트형 보기"
|
||||
aria-label="리스트형 보기"
|
||||
>
|
||||
<List size={18} />
|
||||
</button>
|
||||
{/* 뷰 모드 버튼 */}
|
||||
<div className="flex shrink-0 items-center gap-1 rounded-full border border-[var(--color-line)] bg-[var(--color-control)] p-1 shadow-[var(--shadow-control)] backdrop-blur-xl">
|
||||
<button
|
||||
onClick={() => handleViewModeChange('grid')}
|
||||
className={clsx(
|
||||
'rounded-full p-2 transition-all duration-150',
|
||||
activeViewMode === 'grid'
|
||||
? 'bg-[var(--color-surface-strong)] text-[var(--color-accent)] shadow-sm'
|
||||
: 'text-[var(--color-text-subtle)] hover:bg-black/[0.04] hover:text-[var(--color-text)] dark:hover:bg-white/10',
|
||||
)}
|
||||
title="카드형 보기"
|
||||
aria-label="카드형 보기"
|
||||
aria-pressed={activeViewMode === 'grid'}
|
||||
>
|
||||
<LayoutGrid size={18} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleViewModeChange('list')}
|
||||
className={clsx(
|
||||
'rounded-full p-2 transition-all duration-150',
|
||||
activeViewMode === 'list'
|
||||
? 'bg-[var(--color-surface-strong)] text-[var(--color-accent)] shadow-sm'
|
||||
: 'text-[var(--color-text-subtle)] hover:bg-black/[0.04] hover:text-[var(--color-text)] dark:hover:bg-white/10',
|
||||
)}
|
||||
title="리스트형 보기"
|
||||
aria-label="리스트형 보기"
|
||||
aria-pressed={activeViewMode === 'list'}
|
||||
>
|
||||
<List size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Surface>
|
||||
|
||||
{/* 검색 결과 안내 (검색 중일 때만 표시) */}
|
||||
{keyword && (
|
||||
<div className="mb-6 flex items-center gap-2 text-sm text-gray-600 bg-blue-50 px-4 py-3 rounded-lg border border-blue-100">
|
||||
<SearchIcon size={16} className="text-blue-500" />
|
||||
<div className="mb-6 flex items-center gap-2 rounded-lg border border-[var(--color-line)] bg-[var(--color-accent-soft)] px-4 py-3 text-sm text-[var(--color-text-muted)]">
|
||||
<SearchIcon size={16} className="text-[var(--color-accent)]" />
|
||||
<span>
|
||||
검색어 <strong>{keyword}</strong> 결과: <strong>{totalElements}</strong>건
|
||||
</span>
|
||||
@@ -142,11 +152,10 @@ export default function CategoryPage({ params }: { params: Promise<{ id: string
|
||||
)}
|
||||
|
||||
{posts.length === 0 ? (
|
||||
<div className="text-center py-20 bg-gray-50 rounded-lg border border-gray-100">
|
||||
<p className="text-gray-400 mb-2">
|
||||
{keyword ? `${keyword}에 대한 검색 결과가 없습니다.` : '아직 작성된 글이 없습니다.'}
|
||||
</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
title={keyword ? `${keyword}에 대한 검색 결과가 없습니다.` : '아직 작성된 글이 없습니다.'}
|
||||
className="py-20"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{activeViewMode === 'grid' ? (
|
||||
@@ -156,31 +165,31 @@ export default function CategoryPage({ params }: { params: Promise<{ id: string
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col border-t border-gray-100">
|
||||
<Surface className="flex flex-col p-2 shadow-none">
|
||||
{posts.map((post) => (
|
||||
<PostListItem key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</Surface>
|
||||
)}
|
||||
|
||||
<div className="flex justify-center items-center gap-6 mt-12 mb-8">
|
||||
<div className="mb-8 mt-12 flex items-center justify-center gap-6">
|
||||
<button
|
||||
onClick={handlePrevPage}
|
||||
disabled={page === 0}
|
||||
className="p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors disabled:cursor-not-allowed"
|
||||
className="rounded-full p-2 text-[var(--color-text-muted)] transition-colors hover:bg-black/[0.04] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent dark:hover:bg-white/10"
|
||||
aria-label="이전 페이지"
|
||||
>
|
||||
<ChevronLeft size={24} />
|
||||
</button>
|
||||
|
||||
<span className="text-sm font-medium text-gray-600">
|
||||
페이지 <span className="text-gray-900 font-bold">{page + 1}</span> {totalPages > 0 && `/ ${totalPages}`}
|
||||
<span className="text-sm font-medium text-[var(--color-text-muted)]">
|
||||
페이지 <span className="font-bold text-[var(--color-text)]">{page + 1}</span> {totalPages > 0 && `/ ${totalPages}`}
|
||||
</span>
|
||||
|
||||
<button
|
||||
onClick={handleNextPage}
|
||||
disabled={isLast || isPlaceholderData}
|
||||
className="p-2 rounded-full hover:bg-gray-100 disabled:opacity-30 disabled:hover:bg-transparent transition-colors disabled:cursor-not-allowed"
|
||||
className="rounded-full p-2 text-[var(--color-text-muted)] transition-colors hover:bg-black/[0.04] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent dark:hover:bg-white/10"
|
||||
aria-label="다음 페이지"
|
||||
>
|
||||
<ChevronRight size={24} />
|
||||
|
||||
Reference in New Issue
Block a user