.
All checks were successful
Deploy blog-frontend / build-and-deploy (push) Successful in 2m7s

This commit is contained in:
wypark
2026-05-28 22:01:57 +09:00
parent 0273cae6e4
commit 390778da3e
8 changed files with 218 additions and 55 deletions

9
LOG.md
View File

@@ -26,3 +26,12 @@
- `NEXT_PUBLIC_API_URL=https://blogserver.wypark.me npm run build`를 네트워크 허용 상태로 실행해 sitemap fetch까지 포함한 production build를 검증했다. - `NEXT_PUBLIC_API_URL=https://blogserver.wypark.me npm run build`를 네트워크 허용 상태로 실행해 sitemap fetch까지 포함한 production build를 검증했다.
- 검증: `npm run lint` 통과, `NEXT_PUBLIC_API_URL=https://blogserver.wypark.me npm run build` 통과. 로컬 `localhost:3000`에서 배포 API를 직접 붙이는 브라우저 검증은 백엔드 CORS 정책상 `https://blog.wypark.me`만 허용되어 제한된다. - 검증: `npm run lint` 통과, `NEXT_PUBLIC_API_URL=https://blogserver.wypark.me npm run build` 통과. 로컬 `localhost:3000`에서 배포 API를 직접 붙이는 브라우저 검증은 백엔드 CORS 정책상 `https://blog.wypark.me`만 허용되어 제한된다.
- 다음 추천 작업: 프론트 배포 후 `https://blog.wypark.me/admin`에서 실제 관리자 로그인 상태로 dashboard 실데이터 렌더링과 range 전환을 최종 확인한다. - 다음 추천 작업: 프론트 배포 후 `https://blog.wypark.me/admin`에서 실제 관리자 로그인 상태로 dashboard 실데이터 렌더링과 range 전환을 최종 확인한다.
## 2026-05-28
- Browser로 배포 블로그(`https://blog.wypark.me`)를 직접 둘러보고 대표글 카드가 비어 보이는 현상, 검색 입력/로그인/관리자 하위 화면의 다크모드 누락을 확인했다.
- 대표글이 1개뿐일 때 최신 글 패널이 빈 영역처럼 보이지 않도록 홈 featured/latest 레이아웃을 조정했다.
- 검색 입력, 로그인 화면, 댓글 UI를 토큰 기반 surface/text/input 스타일로 정리했다.
- 관리자 게시글/댓글/카테고리/프로필처럼 기존 Tailwind neutral class를 쓰는 화면도 다크모드에서 깨지지 않도록 전역 dark compatibility 레이어를 보강했다.
- 검증: `npm run lint` 통과, `npm run build` 통과. build 중 sitemap fetch는 샌드박스 네트워크 제한으로 경고가 출력됐지만 exit 0으로 완료됐다. `npm run dev` 후 Browser로 `/`, `/?keyword=network`, `/login`, 로그아웃 상태 `/admin` 리다이렉트, 관리자 하위 화면 로컬 미리보기를 확인했다.
- 다음 추천 작업: 프론트 배포 후 실제 관리자 로그인 상태에서 `/admin/posts`, `/admin/comments`, `/admin/categories`, `/admin/profile`의 데이터 로딩 상태와 다크모드 hover/focus를 production Origin에서 최종 확인한다.

View File

@@ -61,6 +61,147 @@ body {
color: var(--color-text); color: var(--color-text);
} }
@media (prefers-color-scheme: dark) {
/* Keep older Tailwind-only surfaces aligned with the app color tokens. */
.bg-white {
background-color: var(--color-surface-strong);
}
.bg-gray-50,
.bg-gray-100 {
background-color: rgba(255, 255, 255, 0.08);
}
.bg-blue-50,
.bg-blue-100 {
background-color: rgba(10, 132, 255, 0.14);
}
.bg-red-50,
.bg-red-100 {
background-color: rgba(255, 69, 58, 0.13);
}
.bg-green-100 {
background-color: rgba(48, 209, 88, 0.14);
}
.border-gray-100,
.border-gray-200,
.border-gray-300 {
border-color: var(--color-line);
}
.border-blue-100 {
border-color: rgba(10, 132, 255, 0.28);
}
.border-red-100 {
border-color: rgba(255, 105, 97, 0.28);
}
.text-gray-950,
.text-gray-900,
.text-gray-800,
.text-gray-700 {
color: var(--color-text);
}
.text-gray-600,
.text-gray-500 {
color: var(--color-text-muted);
}
.text-gray-400,
.text-gray-300 {
color: var(--color-text-subtle);
}
.text-blue-700,
.text-blue-600 {
color: #8ecbff;
}
.text-red-950,
.text-red-700,
.text-red-600 {
color: #ffb4ad;
}
.text-green-600 {
color: #8ff0a4;
}
.divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
border-color: var(--color-line);
}
.hover\:bg-white:hover,
.hover\:bg-gray-50:hover,
.hover\:bg-gray-100:hover,
.hover\:bg-gray-200:hover,
.focus\:bg-white:focus {
background-color: rgba(255, 255, 255, 0.11);
}
.hover\:bg-blue-50:hover {
background-color: rgba(10, 132, 255, 0.16);
}
.hover\:bg-red-50:hover {
background-color: rgba(255, 69, 58, 0.16);
}
.hover\:text-gray-600:hover,
.hover\:text-gray-700:hover,
.hover\:text-gray-800:hover {
color: var(--color-text);
}
.placeholder\:text-gray-300::placeholder,
.placeholder\:text-gray-400::placeholder {
color: var(--color-text-subtle);
}
.disabled\:bg-gray-50:disabled,
.disabled\:bg-gray-300:disabled,
.disabled\:bg-gray-400:disabled {
background-color: rgba(255, 255, 255, 0.1);
color: var(--color-text-subtle);
}
input:not([type="checkbox"]):not([type="radio"]),
textarea,
select {
background-color: rgba(255, 255, 255, 0.08);
border-color: var(--color-line);
color: var(--color-text);
}
input:not([type="checkbox"]):not([type="radio"]):focus,
textarea:focus,
select:focus {
background-color: rgba(255, 255, 255, 0.11);
border-color: var(--color-accent);
}
input::placeholder,
textarea::placeholder {
color: var(--color-text-subtle);
}
option {
background-color: var(--color-surface-strong);
color: var(--color-text);
}
.shadow-xl,
.shadow-md,
.shadow-sm {
box-shadow: var(--shadow-card);
}
}
.w-md-editor { .w-md-editor {
border-radius: 0.75rem !important; /* 둥근 모서리 */ border-radius: 0.75rem !important; /* 둥근 모서리 */
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
@@ -71,3 +212,15 @@ body {
border-radius: 0.75rem 0.75rem 0 0 !important; border-radius: 0.75rem 0.75rem 0 0 !important;
background-color: #f9fafb !important; background-color: #f9fafb !important;
} }
@media (prefers-color-scheme: dark) {
.w-md-editor {
background-color: var(--color-surface-strong) !important;
border-color: var(--color-line) !important;
}
.w-md-editor-toolbar {
background-color: rgba(255, 255, 255, 0.08) !important;
border-color: var(--color-line) !important;
}
}

View File

@@ -58,33 +58,33 @@ function LoginForm() {
}; };
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white p-4"> <div className="flex min-h-screen flex-col items-center justify-center bg-[var(--color-page)] p-4">
<div className="w-full max-w-md overflow-hidden rounded-2xl border border-gray-100 bg-white p-8 shadow-xl"> <div className="w-full max-w-md overflow-hidden rounded-lg border border-[var(--color-line)] bg-[var(--color-surface)] p-8 shadow-[var(--shadow-panel)] backdrop-blur-xl">
<div className="mb-8 text-center"> <div className="mb-8 text-center">
<h1 className="text-3xl font-bold text-gray-900"></h1> <h1 className="text-3xl font-bold text-[var(--color-text)]"></h1>
<p className="mt-2 text-gray-500"> .</p> <p className="mt-2 text-[var(--color-text-muted)]"> .</p>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="mb-1 block text-sm font-medium text-gray-700"></label> <label className="mb-1 block text-sm font-medium text-[var(--color-text-muted)]"></label>
<input <input
type="email" type="email"
value={formData.email} value={formData.email}
onChange={(event) => setFormData({ ...formData, email: event.target.value })} onChange={(event) => setFormData({ ...formData, email: event.target.value })}
className="w-full rounded-lg border border-gray-200 px-4 py-2 outline-none transition-all focus:ring-2 focus:ring-blue-500" className="w-full rounded-lg border border-[var(--color-line)] bg-white/70 px-4 py-2 text-[var(--color-text)] outline-none transition-all placeholder:text-[var(--color-text-subtle)] focus:border-[var(--color-accent)] focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10"
placeholder="example@email.com" placeholder="example@email.com"
required required
/> />
</div> </div>
<div> <div>
<label className="mb-1 block text-sm font-medium text-gray-700"></label> <label className="mb-1 block text-sm font-medium text-[var(--color-text-muted)]"></label>
<input <input
type="password" type="password"
value={formData.password} value={formData.password}
onChange={(event) => setFormData({ ...formData, password: event.target.value })} onChange={(event) => setFormData({ ...formData, password: event.target.value })}
className="w-full rounded-lg border border-gray-200 px-4 py-2 outline-none transition-all focus:ring-2 focus:ring-blue-500" className="w-full rounded-lg border border-[var(--color-line)] bg-white/70 px-4 py-2 text-[var(--color-text)] outline-none transition-all placeholder:text-[var(--color-text-subtle)] focus:border-[var(--color-accent)] focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10"
placeholder="••••••••" placeholder="••••••••"
required required
/> />
@@ -102,7 +102,7 @@ function LoginForm() {
</button> </button>
</form> </form>
<div className="mt-6 text-center text-sm text-gray-500"> <div className="mt-6 text-center text-sm text-[var(--color-text-muted)]">
?{' '} ?{' '}
<Link href="/signup" className="font-medium text-blue-600 hover:underline"> <Link href="/signup" className="font-medium text-blue-600 hover:underline">
@@ -118,7 +118,7 @@ export default function LoginPage() {
<Suspense <Suspense
fallback={ fallback={
<div className="flex min-h-screen items-center justify-center"> <div className="flex min-h-screen items-center justify-center">
<Loader2 className="animate-spin text-blue-500" size={36} /> <Loader2 className="animate-spin text-[var(--color-accent)]" size={36} />
</div> </div>
} }
> >

View File

@@ -153,14 +153,14 @@ function FeaturedPost({ post }: { post?: Post }) {
return ( return (
<Link href={`/posts/${post.slug}`} className="group block h-full"> <Link href={`/posts/${post.slug}`} className="group block h-full">
<Surface as="article" strong interactive className="flex h-full min-h-72 flex-col p-6"> <Surface as="article" strong interactive className="flex h-full min-h-64 flex-col p-6">
<div className="mb-5 flex items-center justify-between gap-3"> <div className="mb-5 flex items-center justify-between gap-3">
<StatusBadge tone="info">{post.categoryName || '미분류'}</StatusBadge> <StatusBadge tone="info">{post.categoryName || '미분류'}</StatusBadge>
<time className="text-xs font-medium text-[var(--color-text-subtle)]"> <time className="text-xs font-medium text-[var(--color-text-subtle)]">
{formatDate(post.createdAt)} {formatDate(post.createdAt)}
</time> </time>
</div> </div>
<div className="flex flex-1 flex-col justify-end"> <div className="flex flex-1 flex-col">
<p className="mb-3 text-sm font-semibold text-[var(--color-accent)]"> </p> <p className="mb-3 text-sm font-semibold text-[var(--color-accent)]"> </p>
<h2 className="text-2xl font-bold leading-tight tracking-normal text-[var(--color-text)] md:text-3xl"> <h2 className="text-2xl font-bold leading-tight tracking-normal text-[var(--color-text)] md:text-3xl">
{post.title} {post.title}
@@ -375,31 +375,29 @@ function HomeContent() {
<NoticeStrip notices={notices} /> <NoticeStrip notices={notices} />
<section className="grid gap-6 lg:grid-cols-[1.05fr_0.95fr]"> <section className={latestList.length > 0 ? 'grid gap-6 lg:grid-cols-[1.05fr_0.95fr]' : 'grid gap-6'}>
<FeaturedPost post={featuredPost} /> <FeaturedPost post={featuredPost} />
<Surface as="section" className="p-5"> {latestList.length > 0 && (
<div className="mb-3 flex items-center justify-between gap-3"> <Surface as="section" className="p-5">
<div className="flex items-center gap-2"> <div className="mb-3 flex items-center justify-between gap-3">
<Clock size={18} className="text-[var(--color-accent)]" /> <div className="flex items-center gap-2">
<h2 className="text-lg font-bold text-[var(--color-text)]"> </h2> <Clock size={18} className="text-[var(--color-accent)]" />
<h2 className="text-lg font-bold text-[var(--color-text)]"> </h2>
</div>
<Link href="/archive" className="inline-flex items-center gap-1 text-sm font-semibold text-[var(--color-accent)]">
<ChevronRight size={15} />
</Link>
</div> </div>
<Link href="/archive" className="inline-flex items-center gap-1 text-sm font-semibold text-[var(--color-accent)]">
<ChevronRight size={15} />
</Link>
</div>
{latestList.length > 0 ? (
<div className="divide-y divide-[var(--color-line)]"> <div className="divide-y divide-[var(--color-line)]">
{latestList.map((post) => ( {latestList.map((post) => (
<CompactPostRow key={post.id} post={post} /> <CompactPostRow key={post.id} post={post} />
))} ))}
</div> </div>
) : ( </Surface>
<EmptyState title="최신 글 목록을 기다리고 있습니다." /> )}
)}
</Surface>
</section> </section>
<section> <section>

View File

@@ -62,7 +62,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac
}; };
return ( return (
<form onSubmit={handleSubmit} className="bg-gray-50 p-4 rounded-xl border border-gray-100"> <form onSubmit={handleSubmit} className="rounded-lg border border-[var(--color-line)] bg-white/50 p-4 dark:bg-white/5">
{/* 비회원 입력 필드 */} {/* 비회원 입력 필드 */}
{!isLoggedIn && ( {!isLoggedIn && (
<div className="flex gap-2 mb-3"> <div className="flex gap-2 mb-3">
@@ -71,7 +71,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac
placeholder="닉네임" placeholder="닉네임"
value={guestInfo.nickname} value={guestInfo.nickname}
onChange={(e) => setGuestInfo({ ...guestInfo, nickname: e.target.value })} onChange={(e) => setGuestInfo({ ...guestInfo, nickname: e.target.value })}
className="w-1/2 px-3 py-2 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-500" className="w-1/2 rounded-lg border border-[var(--color-line)] bg-white/70 px-3 py-2 text-sm text-[var(--color-text)] outline-none transition focus:border-[var(--color-accent)] focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10"
required required
/> />
<input <input
@@ -79,7 +79,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac
placeholder="비밀번호" placeholder="비밀번호"
value={guestInfo.password} value={guestInfo.password}
onChange={(e) => setGuestInfo({ ...guestInfo, password: e.target.value })} onChange={(e) => setGuestInfo({ ...guestInfo, password: e.target.value })}
className="w-1/2 px-3 py-2 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-500" className="w-1/2 rounded-lg border border-[var(--color-line)] bg-white/70 px-3 py-2 text-sm text-[var(--color-text)] outline-none transition focus:border-[var(--color-accent)] focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10"
required required
/> />
</div> </div>
@@ -91,7 +91,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac
value={content} value={content}
onChange={(e) => setContent(e.target.value)} onChange={(e) => setContent(e.target.value)}
placeholder={isLoggedIn ? placeholder : '댓글을 남겨보세요.'} placeholder={isLoggedIn ? placeholder : '댓글을 남겨보세요.'}
className="w-full p-3 pr-12 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-500 resize-none h-24 bg-white" className="h-24 w-full resize-none rounded-lg border border-[var(--color-line)] bg-white/70 p-3 pr-12 text-sm text-[var(--color-text)] outline-none transition placeholder:text-[var(--color-text-subtle)] focus:border-[var(--color-accent)] focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10"
required required
/> />
<button <button

View File

@@ -84,17 +84,19 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
<div <div
className={clsx( className={clsx(
"relative p-4 rounded-xl transition-colors group", "relative p-4 rounded-xl transition-colors group",
comment.isPostAuthor ? "bg-blue-50/50 border border-blue-100" : "bg-white border border-gray-100" comment.isPostAuthor
? "border border-blue-500/15 bg-blue-500/10"
: "border border-[var(--color-line)] bg-white/60 dark:bg-white/5"
)} )}
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className={clsx( className={clsx(
"p-1.5 rounded-full flex items-center justify-center", "flex items-center justify-center rounded-full p-1.5",
comment.isPostAuthor ? "bg-blue-100 text-blue-600" : comment.isPostAuthor ? "bg-blue-500/15 text-blue-600 dark:text-blue-300" :
!isGuestComment ? "bg-green-100 text-green-600" : !isGuestComment ? "bg-green-500/15 text-green-600 dark:text-green-300" :
"bg-gray-100 text-gray-500" "bg-black/[0.05] text-[var(--color-text-muted)] dark:bg-white/10"
)} )}
> >
{comment.isPostAuthor ? <CheckCircle2 size={14} /> : {comment.isPostAuthor ? <CheckCircle2 size={14} /> :
@@ -103,11 +105,11 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
</div> </div>
<div className="flex flex-col sm:flex-row sm:items-center gap-0 sm:gap-2"> <div className="flex flex-col sm:flex-row sm:items-center gap-0 sm:gap-2">
<span className={clsx("text-sm font-bold flex items-center gap-1", comment.isPostAuthor ? "text-blue-700" : "text-gray-700")}> <span className={clsx("flex items-center gap-1 text-sm font-bold", comment.isPostAuthor ? "text-blue-700 dark:text-blue-300" : "text-[var(--color-text)]")}>
{comment.author} {comment.author}
{comment.isPostAuthor && <span className="px-1.5 py-0.5 bg-blue-100 text-blue-600 text-[10px] rounded-full font-medium"></span>} {comment.isPostAuthor && <span className="px-1.5 py-0.5 bg-blue-100 text-blue-600 text-[10px] rounded-full font-medium"></span>}
</span> </span>
<span className="text-xs text-gray-400"> <span className="text-xs text-[var(--color-text-subtle)]">
{format(new Date(comment.createdAt), 'yyyy.MM.dd HH:mm')} {format(new Date(comment.createdAt), 'yyyy.MM.dd HH:mm')}
</span> </span>
</div> </div>
@@ -116,7 +118,7 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity"> <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button <button
onClick={() => setIsReplying(!isReplying)} onClick={() => setIsReplying(!isReplying)}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded" className="rounded p-1.5 text-[var(--color-text-subtle)] hover:bg-blue-500/10 hover:text-blue-600 dark:hover:text-blue-300"
title="답글 달기" title="답글 달기"
> >
<MessageSquare size={14} /> <MessageSquare size={14} />
@@ -127,8 +129,8 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
onClick={handleDeleteClick} onClick={handleDeleteClick}
className={clsx( className={clsx(
"p-1.5 rounded transition-colors", "p-1.5 rounded transition-colors",
isDeleting ? "bg-red-50 text-red-600" : isDeleting ? "bg-red-500/10 text-red-600" :
"text-gray-400 hover:text-red-600 hover:bg-red-50" "text-[var(--color-text-subtle)] hover:bg-red-500/10 hover:text-red-600"
)} )}
title="삭제" title="삭제"
> >
@@ -138,19 +140,19 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
</div> </div>
</div> </div>
<p className="text-gray-800 text-sm whitespace-pre-wrap leading-relaxed pl-1"> <p className="pl-1 text-sm leading-relaxed text-[var(--color-text)] whitespace-pre-wrap">
{comment.content} {comment.content}
</p> </p>
{/* 🎨 비회원 비밀번호 입력창 (인라인) */} {/* 🎨 비회원 비밀번호 입력창 (인라인) */}
{isDeleting && isGuestComment && ( {isDeleting && isGuestComment && (
<div className="mt-3 flex items-center gap-2 p-2 bg-gray-50 rounded-lg animate-in fade-in slide-in-from-top-1 duration-200"> <div className="mt-3 flex animate-in items-center gap-2 rounded-lg bg-black/[0.04] p-2 duration-200 fade-in slide-in-from-top-1 dark:bg-white/10">
<input <input
type="password" type="password"
placeholder="비밀번호 입력" placeholder="비밀번호 입력"
value={guestPassword} value={guestPassword}
onChange={(e) => setGuestPassword(e.target.value)} onChange={(e) => setGuestPassword(e.target.value)}
className="text-xs px-2 py-1.5 border border-gray-200 rounded focus:outline-none focus:border-blue-500 bg-white" className="rounded border border-[var(--color-line)] bg-white/70 px-2 py-1.5 text-xs text-[var(--color-text)] focus:border-[var(--color-accent)] focus:outline-none dark:bg-white/10"
autoFocus autoFocus
/> />
<button <button
@@ -161,7 +163,7 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
</button> </button>
<button <button
onClick={() => { setIsDeleting(false); setGuestPassword(''); }} onClick={() => { setIsDeleting(false); setGuestPassword(''); }}
className="p-1 text-gray-400 hover:bg-gray-200 rounded-full" className="rounded-full p-1 text-[var(--color-text-subtle)] hover:bg-black/[0.06] dark:hover:bg-white/10"
> >
<X size={14} /> <X size={14} />
</button> </button>
@@ -170,7 +172,7 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
</div> </div>
{isReplying && ( {isReplying && (
<div className="mt-2 pl-4 border-l-2 border-gray-200 ml-4 animate-in fade-in slide-in-from-top-2"> <div className="mt-2 ml-4 animate-in border-l-2 border-[var(--color-line)] pl-4 fade-in slide-in-from-top-2">
<CommentForm <CommentForm
postSlug={postSlug} postSlug={postSlug}
parentId={comment.id} parentId={comment.id}
@@ -181,7 +183,7 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
)} )}
{comment.children && comment.children.length > 0 && ( {comment.children && comment.children.length > 0 && (
<div className="mt-2 pl-4 md:pl-8 border-l-2 border-gray-100 ml-2 md:ml-4 space-y-3"> <div className="mt-2 ml-2 space-y-3 border-l-2 border-[var(--color-line)] pl-4 md:ml-4 md:pl-8">
{comment.children.map((child) => ( {comment.children.map((child) => (
<CommentItem <CommentItem
key={child.id} key={child.id}

View File

@@ -35,10 +35,10 @@ export default function CommentList({ postSlug }: CommentListProps) {
} }
return ( return (
<div className="mt-16 pt-10 border-t border-gray-100"> <div className="mt-16 border-t border-[var(--color-line)] pt-10">
<div className="flex items-center gap-2 mb-6"> <div className="flex items-center gap-2 mb-6">
<MessageCircle className="text-blue-600" size={24} /> <MessageCircle className="text-blue-600" size={24} />
<h3 className="text-xl font-bold text-gray-800"> <h3 className="text-xl font-bold text-[var(--color-text)]">
<span className="text-blue-600">{totalCount}</span> <span className="text-blue-600">{totalCount}</span>
</h3> </h3>
</div> </div>
@@ -55,7 +55,7 @@ export default function CommentList({ postSlug }: CommentListProps) {
<CommentItem key={comment.id} comment={comment} postSlug={postSlug} /> <CommentItem key={comment.id} comment={comment} postSlug={postSlug} />
)) ))
) : ( ) : (
<div className="text-center py-10 bg-gray-50 rounded-xl text-gray-500 text-sm"> <div className="rounded-lg border border-dashed border-[var(--color-line)] bg-white/45 py-10 text-center text-sm text-[var(--color-text-muted)] dark:bg-white/5">
. ! . !
</div> </div>
)} )}

View File

@@ -48,17 +48,18 @@ export default function PostSearch({
onChange={(e) => setKeyword(e.target.value)} onChange={(e) => setKeyword(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={placeholder} placeholder={placeholder}
className="w-full pl-10 pr-10 py-2.5 bg-gray-50 border border-gray-200 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-500 transition-all placeholder:text-gray-400" className="w-full rounded-full border border-[var(--color-line)] bg-white/70 py-2.5 pl-10 pr-10 text-sm text-[var(--color-text)] shadow-sm outline-none transition-all placeholder:text-[var(--color-text-subtle)] focus:border-[var(--color-accent)] focus:bg-white focus:ring-2 focus:ring-blue-500/15 dark:bg-white/10 dark:focus:bg-white/15"
/> />
<Search <Search
className="absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 cursor-pointer hover:text-blue-500 transition-colors" className="absolute left-3.5 top-1/2 -translate-y-1/2 cursor-pointer text-[var(--color-text-subtle)] transition-colors hover:text-[var(--color-accent)]"
size={18} size={18}
onClick={handleSearch} onClick={handleSearch}
/> />
{keyword && ( {keyword && (
<button <button
onClick={handleClear} onClick={handleClear}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-200 transition-colors" className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-1 text-[var(--color-text-subtle)] transition-colors hover:bg-black/[0.06] hover:text-[var(--color-text)] dark:hover:bg-white/10"
aria-label="검색어 지우기"
> >
<X size={14} /> <X size={14} />
</button> </button>