Skip to content

Commit 817bc3f

Browse files
authored
Apply DESIGN.md visual system (#57)
1 parent b469559 commit 817bc3f

27 files changed

Lines changed: 944 additions & 639 deletions

app/admin/members/page.tsx

Lines changed: 76 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { requireAdmin } from "@/lib/guard";
22
import { db } from "@/lib/db";
3+
import {
4+
PageHeader,
5+
PageShell,
6+
SectionLabel,
7+
SubNav,
8+
UtilityCard,
9+
} from "@/components/layout/AppChrome";
310

411
import { PendingMemberActions } from "./_components/PendingMemberActions";
512

@@ -28,74 +35,78 @@ export default async function AdminMembersPage() {
2835
});
2936

3037
return (
31-
<main className="container mx-auto max-w-2xl p-6">
32-
<header className="mb-6 flex flex-col gap-1">
33-
<h1 className="text-2xl font-bold tracking-tight">회원 관리</h1>
34-
<p className="text-xs text-muted-foreground">
35-
관리자: {admin.email}
36-
</p>
37-
</header>
38+
<>
39+
<SubNav title="회원 관리" />
40+
<PageShell width="content" surface="parchment">
41+
<PageHeader
42+
eyebrow="Admin"
43+
title="회원 관리"
44+
description={`관리자: ${admin.email}`}
45+
/>
3846

39-
<section className="mb-10">
40-
<h2 className="mb-3 text-sm font-semibold uppercase text-muted-foreground">
41-
승인 대기 ({pending.length})
42-
</h2>
43-
{pending.length === 0 ? (
44-
<p className="text-sm text-muted-foreground">대기 중인 가입 요청이 없습니다.</p>
45-
) : (
46-
<ul className="flex flex-col gap-2">
47-
{pending.map((u) => (
48-
<li
49-
key={u.id}
50-
className="flex flex-col gap-2 rounded-md border p-3 sm:flex-row sm:items-center sm:justify-between"
51-
>
52-
<div className="flex flex-col">
53-
<span className="font-medium">{u.name}</span>
54-
<span className="font-mono text-xs text-muted-foreground">
55-
{u.email}
56-
</span>
57-
<span className="text-xs text-muted-foreground">
58-
가입 요청 {formatDate(u.joinedAt)}
59-
</span>
60-
</div>
61-
<PendingMemberActions userId={u.id} />
62-
</li>
63-
))}
64-
</ul>
65-
)}
66-
</section>
47+
<div className="grid gap-8 lg:grid-cols-[1fr_1fr]">
48+
<UtilityCard>
49+
<SectionLabel>승인 대기 ({pending.length})</SectionLabel>
50+
{pending.length === 0 ? (
51+
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">
52+
대기 중인 가입 요청이 없습니다.
53+
</p>
54+
) : (
55+
<ul className="flex flex-col gap-3">
56+
{pending.map((u) => (
57+
<li
58+
key={u.id}
59+
className="flex flex-col gap-4 rounded-[18px] border border-apple-hairline bg-apple-parchment p-4"
60+
>
61+
<div className="flex flex-col gap-1">
62+
<span className="font-text text-[17px] font-semibold leading-[1.24] tracking-[-0.374px]">
63+
{u.name}
64+
</span>
65+
<span className="font-mono text-xs text-apple-muted-48">
66+
{u.email}
67+
</span>
68+
<span className="font-text text-xs leading-none tracking-[-0.12px] text-apple-muted-48">
69+
가입 요청 {formatDate(u.joinedAt)}
70+
</span>
71+
</div>
72+
<PendingMemberActions userId={u.id} />
73+
</li>
74+
))}
75+
</ul>
76+
)}
77+
</UtilityCard>
6778

68-
<section>
69-
<h2 className="mb-3 text-sm font-semibold uppercase text-muted-foreground">
70-
활동 회원 ({active.length})
71-
</h2>
72-
<ul className="flex flex-col gap-2">
73-
{active.map((u) => (
74-
<li
75-
key={u.id}
76-
className="flex flex-col gap-1 rounded-md border p-3 sm:flex-row sm:items-center sm:justify-between"
77-
>
78-
<div className="flex flex-col">
79-
<span className="font-medium">
80-
{u.name}
81-
{u.role === "ADMIN" && (
82-
<span className="ml-2 rounded bg-secondary px-1.5 py-0.5 text-xs">
83-
ADMIN
79+
<UtilityCard>
80+
<SectionLabel>활동 회원 ({active.length})</SectionLabel>
81+
<ul className="flex flex-col gap-3">
82+
{active.map((u) => (
83+
<li
84+
key={u.id}
85+
className="flex flex-col gap-3 rounded-[18px] border border-apple-hairline bg-apple-parchment p-4 sm:flex-row sm:items-center sm:justify-between"
86+
>
87+
<div className="flex flex-col gap-1">
88+
<span className="font-text text-[17px] font-semibold leading-[1.24] tracking-[-0.374px]">
89+
{u.name}
90+
{u.role === "ADMIN" && (
91+
<span className="ml-2 rounded-full bg-apple-canvas px-2 py-1 font-text text-xs font-normal leading-none tracking-[-0.12px] text-apple-primary">
92+
ADMIN
93+
</span>
94+
)}
8495
</span>
85-
)}
86-
</span>
87-
<span className="font-mono text-xs text-muted-foreground">
88-
{u.email}
89-
</span>
90-
</div>
91-
<span className="text-xs text-muted-foreground">
92-
승인 {u.approvedAt ? formatDate(u.approvedAt) : "—"}
93-
</span>
94-
</li>
95-
))}
96-
</ul>
97-
</section>
98-
</main>
96+
<span className="font-mono text-xs text-apple-muted-48">
97+
{u.email}
98+
</span>
99+
</div>
100+
<span className="font-text text-xs leading-none tracking-[-0.12px] text-apple-muted-48">
101+
승인 {u.approvedAt ? formatDate(u.approvedAt) : "—"}
102+
</span>
103+
</li>
104+
))}
105+
</ul>
106+
</UtilityCard>
107+
</div>
108+
</PageShell>
109+
</>
99110
);
100111
}
101112

app/globals.css

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@
55
@layer base {
66
:root {
77
--background: 0 0% 100%;
8-
--foreground: 222.2 84% 4.9%;
8+
--foreground: 240 3% 12%;
99
--card: 0 0% 100%;
10-
--card-foreground: 222.2 84% 4.9%;
11-
--primary: 222.2 47.4% 11.2%;
12-
--primary-foreground: 210 40% 98%;
13-
--secondary: 210 40% 96.1%;
14-
--secondary-foreground: 222.2 47.4% 11.2%;
15-
--muted: 210 40% 96.1%;
16-
--muted-foreground: 215.4 16.3% 46.9%;
17-
--accent: 210 40% 96.1%;
18-
--accent-foreground: 222.2 47.4% 11.2%;
19-
--destructive: 0 84.2% 60.2%;
20-
--destructive-foreground: 210 40% 98%;
21-
--border: 214.3 31.8% 91.4%;
22-
--input: 214.3 31.8% 91.4%;
23-
--ring: 222.2 84% 4.9%;
24-
--radius: 0.5rem;
10+
--card-foreground: 240 3% 12%;
11+
--primary: 210 100% 40%;
12+
--primary-foreground: 0 0% 100%;
13+
--secondary: 240 11% 96%;
14+
--secondary-foreground: 240 3% 12%;
15+
--muted: 240 11% 96%;
16+
--muted-foreground: 0 0% 48%;
17+
--accent: 210 100% 40%;
18+
--accent-foreground: 0 0% 100%;
19+
--destructive: 0 72% 51%;
20+
--destructive-foreground: 0 0% 100%;
21+
--border: 0 0% 88%;
22+
--input: 0 0% 88%;
23+
--ring: 209 100% 45%;
24+
--radius: 18px;
2525
}
2626
}
2727

@@ -30,7 +30,11 @@
3030
@apply border-border;
3131
}
3232
body {
33-
@apply bg-background text-foreground;
33+
@apply bg-apple-canvas font-text text-[17px] font-normal leading-[1.47] tracking-[-0.374px] text-apple-ink antialiased;
3434
font-feature-settings: "rlig" 1, "calt" 1;
3535
}
36+
37+
::selection {
38+
@apply bg-apple-primary text-white;
39+
}
3640
}

app/layout.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Metadata, Viewport } from "next";
22
import "./globals.css";
3+
import { GlobalNav } from "@/components/layout/AppChrome";
4+
import { auth } from "@/lib/auth";
35

46
// NEXT_PUBLIC_APP_URL 이 비어 있으면 Next 기본(요청 host)을 쓴다.
57
// local/prod 의 .env 에 외부 URL 을 넣어두면 OG 이미지/링크가 그 URL 기준이 됨.
@@ -30,17 +32,25 @@ export const metadata: Metadata = {
3032
export const viewport: Viewport = {
3133
width: "device-width",
3234
initialScale: 1,
33-
themeColor: "#ffffff",
35+
themeColor: "#0066cc",
3436
};
3537

36-
export default function RootLayout({
38+
export default async function RootLayout({
3739
children,
3840
}: {
3941
children: React.ReactNode;
4042
}) {
43+
const session = await auth();
44+
4145
return (
4246
<html lang="ko">
43-
<body className="min-h-screen antialiased">{children}</body>
47+
<body>
48+
<GlobalNav
49+
approved={Boolean(session?.user.approved)}
50+
isAdmin={session?.user.role === "ADMIN"}
51+
/>
52+
{children}
53+
</body>
4454
</html>
4555
);
4656
}

app/login/page.tsx

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { redirect } from "next/navigation";
22

33
import { auth, signIn } from "@/lib/auth";
44
import { Button } from "@/components/ui/button";
5+
import {
6+
PageHeader,
7+
PageShell,
8+
UtilityCard,
9+
} from "@/components/layout/AppChrome";
510

611
type LoginPageProps = {
712
searchParams: Promise<{ callbackUrl?: string; error?: string }>;
@@ -39,34 +44,39 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
3944
}
4045

4146
return (
42-
<main className="container mx-auto flex min-h-screen max-w-md flex-col items-center justify-center gap-8 p-6">
43-
<div className="flex flex-col items-center gap-3 text-center">
44-
<h1 className="text-3xl font-bold tracking-tight">뮤런</h1>
45-
<p className="text-sm text-muted-foreground">
46-
애니뮤 러닝 소모임 내부 아카이브.
47-
<br />
48-
SNU 구글 계정(<span className="font-mono">@snu.ac.kr</span>)으로 로그인하세요.
49-
</p>
50-
</div>
47+
<PageShell width="narrow" surface="parchment">
48+
<PageHeader
49+
align="center"
50+
eyebrow="SNU Google only"
51+
title="뮤런"
52+
description={
53+
<>
54+
애니뮤 러닝 소모임 내부 아카이브.
55+
<br />SNU 구글 계정으로 로그인하세요.
56+
</>
57+
}
58+
/>
5159

52-
{errorMessage ? (
53-
<div
54-
role="alert"
55-
className="w-full rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive"
56-
>
57-
{errorMessage}
58-
</div>
59-
) : null}
60+
<UtilityCard className="flex flex-col gap-5">
61+
{errorMessage ? (
62+
<div
63+
role="alert"
64+
className="rounded-[18px] border border-destructive/40 bg-white px-5 py-4 font-text text-sm leading-[1.43] tracking-[-0.224px] text-destructive"
65+
>
66+
{errorMessage}
67+
</div>
68+
) : null}
6069

61-
<form action={signInWithGoogle} className="w-full">
62-
<Button type="submit" className="w-full" size="lg">
63-
SNU 구글로 계속하기
64-
</Button>
65-
</form>
70+
<form action={signInWithGoogle}>
71+
<Button type="submit" className="w-full" size="lg">
72+
SNU 구글로 계속하기
73+
</Button>
74+
</form>
75+
</UtilityCard>
6676

67-
<p className="text-center text-xs text-muted-foreground">
77+
<p className="mt-6 text-center font-text text-xs leading-none tracking-[-0.12px] text-apple-muted-48">
6878
첫 로그인 후 관리자 승인이 필요합니다.
6979
</p>
70-
</main>
80+
</PageShell>
7181
);
7282
}

0 commit comments

Comments
 (0)