From 390778da3e12fa42d022e81c97d4e64071d92b9f Mon Sep 17 00:00:00 2001 From: wypark Date: Thu, 28 May 2026 22:01:57 +0900 Subject: [PATCH] . --- LOG.md | 9 ++ src/app/globals.css | 153 +++++++++++++++++++++++++ src/app/login/page.tsx | 20 ++-- src/app/page.tsx | 34 +++--- src/components/comment/CommentForm.tsx | 8 +- src/components/comment/CommentItem.tsx | 34 +++--- src/components/comment/CommentList.tsx | 6 +- src/components/post/PostSearch.tsx | 9 +- 8 files changed, 218 insertions(+), 55 deletions(-) diff --git a/LOG.md b/LOG.md index da08f3d..3b5b464 100644 --- a/LOG.md +++ b/LOG.md @@ -26,3 +26,12 @@ - `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`만 허용되어 제한된다. - 다음 추천 작업: 프론트 배포 후 `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에서 최종 확인한다. diff --git a/src/app/globals.css b/src/app/globals.css index a71df80..2f6eb92 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -61,6 +61,147 @@ body { 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 { border-radius: 0.75rem !important; /* 둥근 모서리 */ 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; 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; + } +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 75c0f9e..9a91b02 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -58,33 +58,33 @@ function LoginForm() { }; return ( -
-
+
+
-

로그인

-

블로그에 오신 것을 환영합니다.

+

로그인

+

블로그에 오신 것을 환영합니다.

- + 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" required />
- + 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="••••••••" required /> @@ -102,7 +102,7 @@ function LoginForm() { -
+
계정이 없으신가요?{' '} 회원가입 @@ -118,7 +118,7 @@ export default function LoginPage() { - +
} > diff --git a/src/app/page.tsx b/src/app/page.tsx index 3c3d749..a024d73 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -153,14 +153,14 @@ function FeaturedPost({ post }: { post?: Post }) { return ( - +
{post.categoryName || '미분류'}
-
+

대표 글

{post.title} @@ -375,31 +375,29 @@ function HomeContent() { -
+
0 ? 'grid gap-6 lg:grid-cols-[1.05fr_0.95fr]' : 'grid gap-6'}> - -
-
- -

최신 글

+ {latestList.length > 0 && ( + +
+
+ +

최신 글

+
+ + 전체 보기 + +
- - 전체 보기 - - -
- {latestList.length > 0 ? (
{latestList.map((post) => ( ))}
- ) : ( - - )} - + + )}
diff --git a/src/components/comment/CommentForm.tsx b/src/components/comment/CommentForm.tsx index 969949f..10fb852 100644 --- a/src/components/comment/CommentForm.tsx +++ b/src/components/comment/CommentForm.tsx @@ -62,7 +62,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac }; return ( -
+ {/* 비회원 입력 필드 */} {!isLoggedIn && (
@@ -71,7 +71,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac placeholder="닉네임" value={guestInfo.nickname} 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 /> 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 />
@@ -91,7 +91,7 @@ export default function CommentForm({ postSlug, parentId = null, onSuccess, plac value={content} onChange={(e) => setContent(e.target.value)} 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 />

-

+

{comment.content}

{/* 🎨 비회원 비밀번호 입력창 (인라인) */} {isDeleting && isGuestComment && ( -
+
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 /> @@ -170,7 +172,7 @@ export default function CommentItem({ comment, postSlug, depth = 0 }: CommentIte
{isReplying && ( -
+
0 && ( -
+
{comment.children.map((child) => ( +
-

+

댓글 {totalCount}

@@ -55,7 +55,7 @@ export default function CommentList({ postSlug }: CommentListProps) { )) ) : ( -
+
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!
)} diff --git a/src/components/post/PostSearch.tsx b/src/components/post/PostSearch.tsx index 1247269..dc7ba9e 100644 --- a/src/components/post/PostSearch.tsx +++ b/src/components/post/PostSearch.tsx @@ -48,17 +48,18 @@ export default function PostSearch({ onChange={(e) => setKeyword(e.target.value)} onKeyDown={handleKeyDown} 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" /> {keyword && ( @@ -66,4 +67,4 @@ export default function PostSearch({
); -} \ No newline at end of file +}