Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
</div>

{/* 오른쪽 메인 영역 */}
<div className="flex-1 flex flex-col h-screen ml-[240px]" style={{ backgroundColor: "#F9FAFB" }}>
<div className="flex-1 flex flex-col h-screen ml-[240px] bg-app-surface-muted">
{/* 메인 컨텐츠 영역 */}
<main className="flex-1 overflow-y-auto">
{children}
Expand Down
35 changes: 35 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
/* App-wide UI tokens (additive — does not override shadcn primary) */
--app-surface: oklch(0.96 0 0);
--app-surface-muted: oklch(0.985 0 0);
--app-border: oklch(0.922 0 0);
--app-accent: oklch(0.205 0 0);
--app-accent-muted: oklch(0.94 0 0);
/* Subtle blue-gray accents (not primary black) */
--app-accent-soft: oklch(0.93 0.02 245);
--app-accent-soft-foreground: oklch(0.42 0.045 245);
--app-focus: oklch(0.48 0.06 245);
--app-ring: oklch(0.72 0.03 245);
--app-spinner-track: oklch(0.78 0.018 250);
--app-sidebar-active: oklch(0.92 0.028 245);
}

.dark {
Expand Down Expand Up @@ -72,6 +85,17 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
--app-surface: oklch(0.18 0 0);
--app-surface-muted: oklch(0.16 0 0);
--app-border: oklch(0.32 0 0);
--app-accent: oklch(0.985 0 0);
--app-accent-muted: oklch(0.28 0 0);
--app-accent-soft: oklch(0.28 0.025 245);
--app-accent-soft-foreground: oklch(0.78 0.04 245);
--app-focus: oklch(0.72 0.05 245);
--app-ring: oklch(0.45 0.04 245);
--app-spinner-track: oklch(0.38 0.02 250);
--app-sidebar-active: oklch(0.3 0.03 245);
}

@theme inline {
Expand Down Expand Up @@ -113,6 +137,17 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-app-surface: var(--app-surface);
--color-app-surface-muted: var(--app-surface-muted);
--color-app-border: var(--app-border);
--color-app-accent: var(--app-accent);
--color-app-accent-muted: var(--app-accent-muted);
--color-app-accent-soft: var(--app-accent-soft);
--color-app-accent-soft-foreground: var(--app-accent-soft-foreground);
--color-app-focus: var(--app-focus);
--color-app-ring: var(--app-ring);
--color-app-spinner-track: var(--app-spinner-track);
--color-app-sidebar-active: var(--app-sidebar-active);
}

@layer base {
Expand Down
5 changes: 1 addition & 4 deletions app/master/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ export default function MasterLayout({
<Sidebar />
</div>

<div
className="ml-[240px] flex h-screen flex-1 flex-col"
style={{ backgroundColor: "#F9FAFB" }}
>
<div className="ml-[240px] flex h-screen flex-1 flex-col bg-app-surface-muted">
<main className="flex-1 overflow-y-auto">{children}</main>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/test/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default function TestPage() {

if (isRestoring || !examId || !participantId) {
return (
<div className="min-h-screen w-full bg-[#F5F5F5] flex items-center justify-center">
<div className="min-h-screen w-full bg-app-surface flex items-center justify-center">
<div className="text-center">
<p className="text-base font-medium text-[#1F2937]">
{restoreError ?? "시험 세션을 복구하는 중입니다..."}
Expand Down
6 changes: 3 additions & 3 deletions app/waiting/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,21 @@ export default function WaitingPage() {
}, [examId, router]);

return (
<div className="min-h-screen w-full bg-[#F5F5F5] flex flex-col">
<div className="min-h-screen w-full bg-app-surface flex flex-col">
<header className="w-full h-[72px] bg-white flex items-center justify-start pl-16 shadow-sm">
<h1 className="text-xl font-medium text-foreground">Vibe Coding Evaluator</h1>
</header>

{/* 🔥 여기 flex-1 추가 */}
<main className="flex-1 flex flex-col items-center justify-center gap-8">
<div
className="w-20 h-20 rounded-full border-[5px] border-violet-200 border-t-violet-500"
className="w-20 h-20 rounded-full border-[5px] border-app-spinner-track border-t-foreground"
style={{ animation: "spin 1.5s linear infinite" }}
/>

<div className="flex flex-col items-center gap-1 text-center">
<p className="text-lg font-bold text-foreground">시험 대기 중입니다.</p>
<p className="text-base text-gray-500">
<p className="text-base text-muted-foreground">
관리자가 시험을 시작하면 자동으로 진행됩니다.
</p>
{errorMessage && (
Expand Down
2 changes: 1 addition & 1 deletion components/account-delete-success-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function AccountDeleteSuccessDialog({
<DialogDescription className="mt-2 text-[#6B7280]">{description}</DialogDescription>
</DialogHeader>
<DialogFooter className={ACCOUNT_DELETE_SUCCESS_FOOTER_CLASS}>
<Button onClick={onConfirm} className="bg-[#3B82F6] text-white hover:bg-[#2563EB]">
<Button onClick={onConfirm} className="bg-primary text-primary-foreground hover:bg-primary/90">
{buttonLabel}
</Button>
</DialogFooter>
Expand Down
53 changes: 17 additions & 36 deletions components/admin-accounts-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,18 +377,28 @@ export function AdminAccountsContent() {
}

return (
<div className="flex h-full flex-1 flex-col">
<div className="flex h-full min-w-0 flex-1 flex-col">
<AdminPageHeader
title="관리자 계정 관리"
description="관리자 계정과 접근 권한을 관리합니다."
actions={
<Button
size="sm"
className="shrink-0 whitespace-nowrap bg-primary px-3 text-primary-foreground hover:bg-primary/90 md:px-4"
onClick={handleOpenGenerateModal}
>
<span className="md:hidden">+ 발급</span>
<span className="hidden md:inline">+ 관리자 번호 발급</span>
</Button>
}
/>

<main className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-6">
<main className="flex min-h-0 min-w-0 flex-1 flex-col gap-4 overflow-x-hidden overflow-y-auto p-4 sm:p-6">
{/* Admin Users Card */}
<Card className="flex min-w-0 flex-1 flex-col border border-[#E5E5E5] shadow-sm">
<CardHeader className="flex min-w-0 flex-col gap-3 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<CardHeader className="px-4 py-3 sm:px-6">
<CardTitle
className="min-w-0 shrink"
className="min-w-0"
style={{
fontSize: "20px",
fontWeight: 700,
Expand All @@ -397,19 +407,6 @@ export function AdminAccountsContent() {
>
관리자 목록
</CardTitle>
<Button
size="sm"
className="w-full shrink-0 sm:w-auto"
onClick={handleOpenGenerateModal}
style={{
backgroundColor: "#3B82F6",
color: "#FFFFFF",
fontSize: "14px",
fontWeight: 500,
}}
>
+ 관리자 번호 발급
</Button>
</CardHeader>
<CardContent className="flex min-w-0 flex-1 flex-col overflow-hidden px-0 pb-0 pt-0">
{listError && !isLoading && (
Expand Down Expand Up @@ -762,12 +759,7 @@ export function AdminAccountsContent() {
{!generatedKey && (
<Button
onClick={handleCreateKey}
style={{
backgroundColor: "#3B82F6",
color: "#FFFFFF",
fontSize: "14px",
fontWeight: 500,
}}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
관리자 번호 발급
</Button>
Expand Down Expand Up @@ -891,13 +883,7 @@ export function AdminAccountsContent() {
<Button
onClick={handleConfirmResetPassword}
disabled={isResettingPassword}
style={{
backgroundColor: "#3B82F6",
color: "#FFFFFF",
fontSize: "14px",
fontWeight: 500,
opacity: isResettingPassword ? 0.6 : 1,
}}
className="bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-60"
>
{isResettingPassword ? "처리 중..." : "비밀번호 재설정"}
</Button>
Expand Down Expand Up @@ -971,12 +957,7 @@ export function AdminAccountsContent() {
<DialogFooter>
<Button
onClick={handleCloseResetPasswordResult}
style={{
backgroundColor: "#3B82F6",
color: "#FFFFFF",
fontSize: "14px",
fontWeight: 500,
}}
className="bg-primary text-primary-foreground hover:bg-primary/90"
>
닫기
</Button>
Expand Down
8 changes: 4 additions & 4 deletions components/admin-page-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export type AdminPageHeaderProps = {

export function AdminPageHeader({ title, description, actions }: AdminPageHeaderProps) {
return (
<header className="flex h-[88px] shrink-0 items-center justify-between border-b border-[#E5E5E5] bg-white px-8">
<div>
<h1 className="text-2xl font-semibold text-[#1A1A1A]">{title}</h1>
{description ? <p className="text-sm text-[#6B7280]">{description}</p> : null}
<header className="flex h-[88px] min-w-0 shrink-0 items-center justify-between gap-3 border-b border-[#E5E5E5] bg-white px-4 sm:px-6 lg:px-8">
<div className="min-w-0 flex-1 overflow-hidden">
<h1 className="truncate text-2xl font-semibold text-[#1A1A1A]">{title}</h1>
{description ? <p className="truncate text-sm text-[#6B7280]">{description}</p> : null}
</div>
{actions ? <div className="flex shrink-0 items-center gap-2">{actions}</div> : null}
</header>
Expand Down
38 changes: 20 additions & 18 deletions components/admin-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ function MenuItem({
<Link
href={href}
className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors ${
active ? "bg-[#E0EDFF] font-medium text-[#3B82F6]" : "text-[#6B7280] hover:bg-[#F0F7FF] hover:text-[#1A1A1A]"
active
? "border-l-[3px] border-app-focus bg-app-sidebar-active font-medium text-app-accent-soft-foreground"
: "border-l-[3px] border-transparent text-muted-foreground hover:bg-app-accent-soft/60 hover:text-foreground"
}`}
>
<Icon className="h-5 w-5" strokeWidth={1.5} />
Expand Down Expand Up @@ -129,40 +131,40 @@ export function AdminSidebar() {
}

return (
<aside className="flex w-[240px] h-screen flex-col border-r border-[#E5E5E5] bg-white overflow-y-auto">
<aside className="flex w-[240px] h-screen flex-col border-r border-app-border bg-white overflow-y-auto">
{/* SECTION 1 — Logo Area */}
<div className="flex items-center gap-3 px-5 py-6">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[#7C3AED]">
<span className="text-sm font-bold text-white">AI</span>
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary">
<span className="text-sm font-bold text-primary-foreground">AI</span>
</div>
<span className="text-sm font-semibold text-[#1A1A1A]">AI Vibe Coding Test</span>
<span className="text-sm font-semibold text-foreground">AI Vibe Coding Test</span>
</div>

{/* Divider 1 */}
<div className="mx-4 border-t border-[#E5E5E5]" />
<div className="mx-4 border-t border-app-border" />

{/* SECTION 2 — Profile Area */}
<div className="flex items-center justify-between px-5 py-4">
<div className="flex items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-[#F3E8FF]">
<User className="h-5 w-5 text-[#7C3AED]" strokeWidth={1.5} />
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-muted">
<User className="h-5 w-5 text-foreground" strokeWidth={1.5} />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium text-[#1A1A1A]">{displayName}</span>
<span className="text-xs text-[#6B7280]">관리자</span>
<span className="text-sm font-medium text-foreground">{displayName}</span>
<span className="text-xs text-muted-foreground">관리자</span>
</div>
</div>
<button
onClick={handleLogoutClick}
className="flex items-center justify-center rounded-lg p-1.5 text-[#6B7280] transition-colors hover:bg-[#F3F4F6] hover:text-[#1A1A1A]"
className="flex items-center justify-center rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
title="로그아웃"
>
<LogOut className="h-4 w-4" strokeWidth={1.5} />
</button>
</div>

{/* Divider 2 */}
<div className="mx-4 border-t border-[#E5E5E5]" />
<div className="mx-4 border-t border-app-border" />

{/* SECTION 3 — Menu Group A (Main management) */}
<nav className="flex flex-col gap-1 px-3 py-4">
Expand All @@ -178,7 +180,7 @@ export function AdminSidebar() {
</nav>

{/* Divider 3 */}
<div className="mx-4 border-t border-[#E5E5E5]" />
<div className="mx-4 border-t border-app-border" />

{/* SECTION 4 — Menu Group B (Exam/operation) */}
<nav className="flex flex-col gap-1 px-3 py-4">
Expand All @@ -194,7 +196,7 @@ export function AdminSidebar() {
</nav>

{/* Divider 4 */}
<div className="mx-4 border-t border-[#E5E5E5]" />
<div className="mx-4 border-t border-app-border" />

{/* SECTION 5 — Menu Group C (System/config) */}
<nav className="flex flex-col gap-1 px-3 py-4">
Expand All @@ -213,25 +215,25 @@ export function AdminSidebar() {
<Dialog open={showLogoutModal} onOpenChange={setShowLogoutModal}>
<DialogContent className="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle className="text-lg font-semibold text-[#1A1A1A]">
<DialogTitle className="text-lg font-semibold text-foreground">
로그아웃 하시겠습니까?
</DialogTitle>
<DialogDescription className="text-[#6B7280] mt-2">
<DialogDescription className="text-muted-foreground mt-2">
로그아웃하면 관리자 대시보드에서 나가게 됩니다.
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex flex-row justify-end gap-3 mt-4">
<Button
variant="outline"
onClick={() => setShowLogoutModal(false)}
className="border-[#E5E5E5] text-[#374151]"
className="border-app-border text-foreground"
>
취소
</Button>
<Button
onClick={handleConfirmLogout}
disabled={isLoggingOut}
className="bg-[#3B82F6] hover:bg-[#2563EB] text-white disabled:opacity-50"
className="bg-primary hover:bg-primary/90 text-primary-foreground disabled:opacity-50"
>
{isLoggingOut ? "로그아웃 중..." : "로그아웃"}
</Button>
Expand Down
Loading
Loading