feat(WEB): 글로벌 검색, 토큰 갱신 개선, 템플릿 기능 확장

- CommandMenuSearch 컴포넌트 추가 (Cmd+K 글로벌 메뉴 검색)
- AuthenticatedLayout: 검색 통합, 모바일/데스크톱 스켈레톤 분리
- middleware: 토큰 갱신 후 리다이렉트 방식으로 변경 (race condition 방지)
- IntegratedDetailTemplate: stickyButtons 옵션 추가 (하단 고정 버튼)
- UniversalListPage: 컬럼 정렬 기능 추가 (sortBy, sortOrder)
- Sidebar: 축소 모드 패딩/간격 최적화
- 각종 컴포넌트 버그 수정 및 경로 정규화

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-26 15:07:10 +09:00
parent cd060ec562
commit a15132d75d
38 changed files with 927 additions and 443 deletions

View File

@@ -18,7 +18,7 @@
import { useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { format } from 'date-fns';
import { FileText, Download, ArrowLeft } from 'lucide-react';
import { FileText, Download, ArrowLeft, Trash2, Edit } from 'lucide-react';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Button } from '@/components/ui/button';
@@ -34,6 +34,7 @@ import { CommentSection } from '../CommentSection';
import { deletePost } from '../actions';
import type { Post, Comment } from '../types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { useMenuStore } from '@/store/menuStore';
interface BoardDetailProps {
post: Post;
@@ -43,6 +44,7 @@ interface BoardDetailProps {
export function BoardDetail({ post, comments: initialComments, currentUserId }: BoardDetailProps) {
const router = useRouter();
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [comments, setComments] = useState<Comment[]>(initialComments);
@@ -119,28 +121,9 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
title="게시글 상세"
description="게시글을 조회합니다."
icon={FileText}
actions={
<div className="flex gap-2">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
{isMyPost && (
<>
<Button
variant="outline"
className="text-red-500 border-red-200 hover:bg-red-50 hover:text-red-600"
onClick={() => setShowDeleteDialog(true)}
>
</Button>
<Button onClick={handleEdit}></Button>
</>
)}
</div>
}
/>
<div className="space-y-6 pb-24">
{/* 게시글 카드 */}
<Card className="mb-6">
<CardHeader className="pb-4">
@@ -215,6 +198,31 @@ export function BoardDetail({ post, comments: initialComments, currentUserId }:
onDeleteComment={handleDeleteComment}
/>
)}
</div>
{/* 하단 액션 버튼 (sticky) */}
<div className={`fixed bottom-6 ${sidebarCollapsed ? 'left-[156px]' : 'left-[316px]'} right-[48px] px-6 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 flex items-center justify-between`}>
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
{isMyPost && (
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={() => setShowDeleteDialog(true)}
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
>
<Trash2 className="h-4 w-4 mr-2" />
</Button>
<Button onClick={handleEdit}>
<Edit className="h-4 w-4 mr-2" />
</Button>
</div>
)}
</div>
{/* 삭제 확인 다이얼로그 */}
<DeleteConfirmDialog