'use client'; import { Suspense, type ReactNode } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useSearchParams } from 'next/navigation'; import Link from 'next/link'; import { Archive, ChevronRight, Clock, Loader2, Search, TrendingUp, } from 'lucide-react'; import { getPosts } from '@/api/posts'; import EmptyState from '@/components/ui/EmptyState'; import StatusBadge from '@/components/ui/StatusBadge'; import Surface from '@/components/ui/Surface'; import { Post, PostListResponse } from '@/types'; const isNoticePost = (post: Post) => { return post.categoryName === '공지' || post.categoryName.toLowerCase() === 'notice'; }; const formatDate = (value?: string) => { if (!value) return ''; const date = new Date(value); if (Number.isNaN(date.getTime())) return ''; return new Intl.DateTimeFormat('ko-KR', { year: 'numeric', month: 'short', day: 'numeric', }).format(date); }; const getSummary = (content?: string, maxLength = 118) => { if (!content) return ''; return content .replace(/```[\s\S]*?```/g, ' ') .replace(/!\[(.*?)\]\(.*?\)/g, '$1') .replace(/\[(.*?)\]\(.*?\)/g, '$1') .replace(/[#*`_~>]/g, '') .replace(/\s+/g, ' ') .trim() .slice(0, maxLength); }; const getTotalElements = (data?: PostListResponse) => { return data?.page?.totalElements ?? data?.totalElements ?? 0; }; function SearchResults({ keyword, data, isLoading, }: { keyword: string; data?: PostListResponse; isLoading: boolean; }) { const searchResults = data?.content || []; const searchTotalElements = getTotalElements(data); return (

검색 결과

검색어 {keyword}에 대한 글 {searchTotalElements.toLocaleString()}건

{isLoading ? (
) : searchResults.length > 0 ? (
{searchResults.map((post) => ( ))}
) : ( )}
); } function NoticeStrip({ notices }: { notices: Post[] }) { if (notices.length === 0) return null; return (
{notices.slice(0, 3).map((notice) => (
공지 {notice.title}
))}
); } function CompactPostRow({ post, rank, showViews = false, }: { post: Post; rank?: number; showViews?: boolean; }) { const summary = getSummary(post.content, 92); return ( {rank !== undefined && ( {rank} )}
{post.categoryName || '미분류'} {showViews && ( 조회 {post.viewCount.toLocaleString()} )}

{post.title}

{summary && (

{summary}

)}
); } function PostListPanel({ title, icon, posts, isPopular = false, }: { title: string; icon: ReactNode; posts: Post[]; isPopular?: boolean; }) { return (
{icon}

{title}

전체 보기
{posts.length > 0 ? (
{posts.map((post, index) => ( ))}
) : ( )}
); } function HomeContent() { const searchParams = useSearchParams(); const keyword = searchParams.get('keyword') || ''; const { data: noticesData, isLoading: isNoticesLoading } = useQuery({ queryKey: ['posts', 'notices'], queryFn: () => getPosts({ category: '공지', size: 3, sort: 'createdAt,desc' }), retry: 0, }); const { data: latestData, isLoading: isLatestLoading } = useQuery({ queryKey: ['posts', 'latest'], queryFn: () => getPosts({ size: 8, sort: 'createdAt,desc' }), retry: 0, }); const { data: popularData, isLoading: isPopularLoading } = useQuery({ queryKey: ['posts', 'popular'], queryFn: () => getPosts({ size: 8, sort: 'viewCount,desc' }), retry: 0, }); const { data: searchData, isLoading: isSearchLoading } = useQuery({ queryKey: ['posts', 'search', keyword], queryFn: () => getPosts({ keyword, size: 20 }), enabled: !!keyword, retry: 0, }); const notices = noticesData?.content || []; const latestList = (latestData?.content || []).filter((post) => !isNoticePost(post)).slice(0, 5); const popularList = (popularData?.content || []).filter((post) => !isNoticePost(post)).slice(0, 5); const isHomeLoading = !keyword && (isNoticesLoading || isLatestLoading || isPopularLoading); if (keyword) { return (
); } if (isHomeLoading) { return (
); } return (

박원엽의 개발 기록

배운 것과 운영하며 부딪힌 문제를 짧고 정확하게 남깁니다.

전체 글
} posts={popularList} isPopular /> } posts={latestList} />
); } export default function Home() { return ( )} > ); }