'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 (
{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 (
)}
>
);
}