From 5a2d770589ae3a68945575b91552a7d9999a6278 Mon Sep 17 00:00:00 2001 From: wallace Date: Thu, 27 Nov 2025 00:45:55 +0900 Subject: [PATCH] =?UTF-8?q?global=20css=20=EC=A0=81=EC=9A=A9,=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=EC=84=A4=EC=A0=95=20=EC=99=84=EB=A3=8C1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/id/page.tsx | 176 +++++++++++++++++++-------- src/app/course-list/page.tsx | 22 ++-- src/app/globals.css | 40 +++--- src/app/login/LoginErrorModal.tsx | 21 +++- src/app/login/page.tsx | 9 +- src/app/menu/ChangePasswordModal.tsx | 36 +++--- src/app/menu/account/page.tsx | 16 +-- src/app/menu/courses/CourseCard.tsx | 36 +++--- src/app/page.tsx | 74 ++++++++--- 9 files changed, 285 insertions(+), 145 deletions(-) diff --git a/src/app/admin/id/page.tsx b/src/app/admin/id/page.tsx index 4bfac39..b5207d1 100644 --- a/src/app/admin/id/page.tsx +++ b/src/app/admin/id/page.tsx @@ -179,13 +179,76 @@ export default function AdminIdPage() { setIsActivateModalOpen(true); } - function handleActivateConfirm() { - if (selectedUserId) { + async function handleActivateConfirm() { + if (!selectedUserId) { + setIsActivateModalOpen(false); + setSelectedUserId(null); + return; + } + + try { + // selectedUserId로 사용자 찾기 + const user = users.find(u => u.id === selectedUserId); + if (!user || !user.email || user.email === '-') { + setToastMessage('사용자 정보를 찾을 수 없습니다.'); + setShowToast(true); + setTimeout(() => { + setShowToast(false); + }, 3000); + setIsActivateModalOpen(false); + setSelectedUserId(null); + return; + } + + const token = localStorage.getItem('token') || document.cookie + .split('; ') + .find(row => row.startsWith('token=')) + ?.split('=')[1]; + + if (!token) { + setToastMessage('로그인이 필요합니다.'); + setShowToast(true); + setTimeout(() => { + setShowToast(false); + }, 3000); + setIsActivateModalOpen(false); + setSelectedUserId(null); + return; + } + + const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL + ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/${selectedUserId}/unsuspend` + : `https://hrdi.coconutmeet.net/admin/users/${selectedUserId}/unsuspend`; + + const response = await fetch(apiUrl, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + let errorMessage = `계정 활성화 실패 (${response.status})`; + try { + const errorData = await response.json(); + if (errorData.error) { + errorMessage = errorData.error; + } else if (errorData.message) { + errorMessage = errorData.message; + } + } catch (parseError) { + // ignore + } + throw new Error(errorMessage); + } + + // API 호출 성공 시 로컬 상태 업데이트 setUsers(prevUsers => - prevUsers.map(user => - user.id === selectedUserId - ? { ...user, status: 'active' } - : user + prevUsers.map(u => + u.id === selectedUserId + ? { ...u, status: 'active' } + : u ) ); setToastMessage('계정을 활성화했습니다.'); @@ -193,9 +256,18 @@ export default function AdminIdPage() { setTimeout(() => { setShowToast(false); }, 3000); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '계정 활성화 중 오류가 발생했습니다.'; + setToastMessage(errorMessage); + setShowToast(true); + setTimeout(() => { + setShowToast(false); + }, 3000); + console.error('계정 활성화 오류:', err); + } finally { + setIsActivateModalOpen(false); + setSelectedUserId(null); } - setIsActivateModalOpen(false); - setSelectedUserId(null); } function handleActivateCancel() { @@ -337,13 +409,13 @@ export default function AdminIdPage() {
{/* 제목 영역 */}
-

+

권한 설정

{/* 탭 네비게이션 */}
-
+
{[ { id: 'all' as TabType, label: '전체' }, { id: 'learner' as TabType, label: '학습자' }, @@ -355,15 +427,15 @@ export default function AdminIdPage() { type="button" onClick={() => setActiveTab(tab.id)} className={[ - "pb-4 px-1 text-[16px] font-medium leading-[1.5] transition-colors relative cursor-pointer", + "pb-4 px-1 text-[16px] font-medium leading-normal transition-colors relative cursor-pointer", activeTab === tab.id - ? "text-[#1f2b91] font-semibold" - : "text-[#6c7682]", + ? "text-active-button font-semibold" + : "text-text-label", ].join(" ")} > {tab.label} {activeTab === tab.id && ( - + )} ))} @@ -373,27 +445,27 @@ export default function AdminIdPage() { {/* 콘텐츠 영역 */}
{isLoading ? ( -
-

+

+

데이터를 불러오는 중...

) : error ? ( -
-

+

+

{error}

) : filteredUsers.length === 0 ? ( -
-

+

+

현재 관리할 수 있는 회원 계정이 없습니다.

) : ( <>
-
+
@@ -404,11 +476,11 @@ export default function AdminIdPage() { - - - - - + + + + + @@ -417,31 +489,31 @@ export default function AdminIdPage() { key={user.id} className="h-12" > - - - - -
가입일성명아이디(이메일)계정상태계정관리가입일성명아이디(이메일)계정상태계정관리
+ {user.joinDate} + {user.name} + {user.email} + {user.status === 'active' ? ( -
- +
+ {statusLabels[user.status]}
) : ( -
- +
+ {statusLabels[user.status]}
)}
+ ); })} @@ -517,7 +589,7 @@ export default function AdminIdPage() { type="button" onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))} aria-label="다음 페이지" - className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40 cursor-pointer disabled:cursor-not-allowed" + className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-neutral-700 disabled:opacity-40 cursor-pointer disabled:cursor-not-allowed" disabled={currentPage === totalPages} > @@ -528,7 +600,7 @@ export default function AdminIdPage() { type="button" onClick={() => setCurrentPage(totalPages)} aria-label="맨 뒤 페이지" - className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40 cursor-pointer disabled:cursor-not-allowed" + className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-neutral-700 disabled:opacity-40 cursor-pointer disabled:cursor-not-allowed" disabled={currentPage === totalPages} >
@@ -559,7 +631,7 @@ export default function AdminIdPage() {
-

+

계정을 활성화하시겠습니까?

@@ -568,14 +640,14 @@ export default function AdminIdPage() { @@ -594,10 +666,10 @@ export default function AdminIdPage() { />
-

+

계정을 비활성화 하시겠습니까?

-

+

학습자가 강좌를 수강 중일 경우 강좌 수강이 어렵습니다.
계정을 비활성화 처리하시겠습니까? @@ -608,7 +680,7 @@ export default function AdminIdPage() { value={deactivateReason} onChange={(e) => setDeactivateReason(e.target.value)} placeholder="비활성화 사유를 입력해주세요" - className="w-full h-[40px] px-[12px] rounded-[8px] border border-[#dee1e6] text-[14px] leading-[1.5] text-[#1b2027] placeholder:text-[#9ca3af] focus:outline-none focus:ring-2 focus:ring-[#1f2b91] focus:border-transparent" + className="w-full h-[40px] px-[12px] rounded-[8px] border border-input-border text-[14px] leading-normal text-text-body placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-active-button focus:border-transparent" />

@@ -616,14 +688,14 @@ export default function AdminIdPage() { @@ -635,14 +707,14 @@ export default function AdminIdPage() { {/* 활성화 완료 토스트 */} {showToast && (
-
+
- +
-

+

{toastMessage}

diff --git a/src/app/course-list/page.tsx b/src/app/course-list/page.tsx index 829bde1..3ad2b2b 100644 --- a/src/app/course-list/page.tsx +++ b/src/app/course-list/page.tsx @@ -16,8 +16,8 @@ const imgThumbD = '/imgs/thumb-d.png'; // public/imgs/thumb-d.png function ColorfulTag({ text }: { text: string }) { return ( -
-

{text}

+
+

{text}

); } @@ -56,15 +56,15 @@ export default function CourseListPage() {
{/* 상단 타이틀 영역 */}
-

교육 과정 목록

+

교육 과정 목록

{/* 콘텐츠 래퍼: Figma 기준 1440 컨테이너, 내부 1376 그리드 폭 */}
{/* 상단 카운트/정렬 영역 */}
-

- 총 {courses.length}건 +

+ 총 {courses.length}

@@ -90,13 +90,13 @@ export default function CourseListPage() {
{c.inProgress && } -

+

{c.title}

-

+

VOD · 총 6강 · 4시간 20분

@@ -113,7 +113,7 @@ export default function CourseListPage() { type="button" onClick={() => setPage((p) => Math.max(1, p - 1))} aria-label="이전 페이지" - className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40" + className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-neutral-700 disabled:opacity-40" disabled={page === 1} > @@ -130,10 +130,10 @@ export default function CourseListPage() { aria-current={active ? 'page' : undefined} className={[ 'flex items-center justify-center rounded-[1000px] size-[32px]', - active ? 'bg-[#ecf0ff]' : 'bg-white', + active ? 'bg-bg-primary-light' : 'bg-white', ].join(' ')} > - {n} + {n} ); })} @@ -143,7 +143,7 @@ export default function CourseListPage() { type="button" onClick={() => setPage((p) => Math.min(totalPages, p + 1))} aria-label="다음 페이지" - className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-[#333c47] disabled:opacity-40" + className="flex items-center justify-center rounded-[1000px] p-[8.615px] size-[32px] text-neutral-700 disabled:opacity-40" disabled={page === totalPages} > diff --git a/src/app/globals.css b/src/app/globals.css index f2cdd58..0a120f7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -11,30 +11,42 @@ --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); - - /*login color start*/ - --color-input-placeholder-text: #b1b8c0; - - --color-neutral-40: #dee1e6; - --color-input-border: #dee1e6; - + /* ===== 텍스트 색상 ===== */ + --color-text-title: #1b2027; + --color-text-body: #1b2027; + --color-text-label: #6c7682; + --color-text-meta: #8c95a1; + --color-text-placeholder: #9ca3af; + --color-text-placeholder-alt: #b1b8c0; --color-basic-text: #4c5561; - - /* color natural 700*/ --color-neutral-700: #333c47; --color-logo-text: #333c47; - --color-input-border-select: #333c47; --color-input-alert-text: #333c47; - --color-error: #f64c4c; + /* ===== 배경 색상 ===== */ + --color-bg-gray-light: #f1f3f5; + --color-bg-gray-hover: #e5e7eb; + --color-bg-success-light: #e5f5ec; + --color-bg-primary-light: #ecf0ff; + --color-bg-header: #060958; - --color-inactive-checkbox: #c2c9cf; + /* ===== 테두리 색상 ===== */ + --color-neutral-40: #dee1e6; + --color-input-border: #dee1e6; + --color-input-border-select: #333c47; + /* ===== 버튼/액션 색상 ===== */ --color-inactive-button: #8598e8; --color-active-button: #1f2b91; - /*login color end*/ - + --color-active-button-hover: #1a2478; + --color-primary: #384fbf; + --color-primary-alt: #384FBF; + /* ===== 상태 색상 ===== */ + --color-error: #f64c4c; + --color-success: #0c9d61; + --color-inactive-checkbox: #c2c9cf; + --color-input-placeholder-text: #b1b8c0; } body { diff --git a/src/app/login/LoginErrorModal.tsx b/src/app/login/LoginErrorModal.tsx index 5713089..e7fe4ca 100644 --- a/src/app/login/LoginErrorModal.tsx +++ b/src/app/login/LoginErrorModal.tsx @@ -5,11 +5,14 @@ import React from "react"; type LoginErrorModalProps = { open: boolean; onClose: () => void; + errorMessage?: string; }; -export default function LoginErrorModal({ open, onClose }: LoginErrorModalProps) { +export default function LoginErrorModal({ open, onClose, errorMessage }: LoginErrorModalProps) { if (!open) return null; + const isSuspendedAccount = errorMessage?.includes("정지된 계정입니다"); + return (
@@ -113,14 +113,14 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif {/* body */}
- +
setEmail(e.target.value)} className={[ - "h-10 flex-1 rounded-[8px] border border-[#dee1e6] px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none", + "h-10 flex-1 rounded-[8px] border border-input-border px-3 text-[16px] leading-[1.5] text-neutral-700 placeholder:text-text-placeholder-alt outline-none", hasError ? "bg-white" : isCodeSent ? "bg-neutral-50" : "bg-white", ].join(" ")} placeholder="이메일" @@ -132,7 +132,7 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif setIsCodeSent(true); setError(null); }} - className="h-10 w-[136px] rounded-[8px] bg-[#f1f3f5] px-3 text-[16px] font-semibold leading-[1.5] text-[#333c47] cursor-pointer" + className="h-10 w-[136px] rounded-[8px] bg-bg-gray-light px-3 text-[16px] font-semibold leading-[1.5] text-neutral-700 cursor-pointer" > {isCodeSent ? "인증번호 재전송" : "인증번호 전송"} @@ -140,13 +140,13 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif
{requireCode ? (
-
인증번호
+
인증번호
setCode(e.target.value)} - className="h-10 flex-1 rounded-[8px] border border-[#dee1e6] bg-white px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none" + className="h-10 flex-1 rounded-[8px] border border-input-border bg-white px-3 text-[16px] leading-[1.5] text-neutral-700 placeholder:text-text-placeholder-alt outline-none" placeholder="인증번호를 입력해 주세요." />
{isCodeSent && !hasError && !isVerified ? (
-

+

인증 확인을 위해 작성한 이메일로 인증번호를 발송했습니다.

-

+

이메일을 확인해 주세요.

) : null} {error ? ( -

+

{error}

) : null}
) : null}
- + setNewPassword(e.target.value)} disabled={!isVerified} className={[ - "h-10 rounded-[8px] border border-[#dee1e6] px-3 text-[16px] leading-[1.5] text-[#333c47] placeholder-[#b1b8c0] outline-none", + "h-10 rounded-[8px] border border-input-border px-3 text-[16px] leading-[1.5] text-neutral-700 placeholder:text-text-placeholder-alt outline-none", isVerified ? "bg-white" : "bg-neutral-50", ].join(" ")} placeholder="새 비밀번호" />
-
-
+
-