Skip to content

Commit 3d896c1

Browse files
authored
Deepen DESIGN.md gallery treatment across chrome and pages (#59)
1 parent d334e73 commit 3d896c1

9 files changed

Lines changed: 245 additions & 121 deletions

File tree

app/layout.tsx

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

66
// NEXT_PUBLIC_APP_URL 이 비어 있으면 Next 기본(요청 host)을 쓴다.
@@ -42,14 +42,15 @@ export default async function RootLayout({
4242
}) {
4343
const session = await auth();
4444

45+
const approved = Boolean(session?.user.approved);
46+
const isAdmin = session?.user.role === "ADMIN";
47+
4548
return (
4649
<html lang="ko">
4750
<body>
48-
<GlobalNav
49-
approved={Boolean(session?.user.approved)}
50-
isAdmin={session?.user.role === "ADMIN"}
51-
/>
51+
<GlobalNav approved={approved} isAdmin={isAdmin} />
5252
{children}
53+
<SiteFooter approved={approved} isAdmin={isAdmin} />
5354
</body>
5455
</html>
5556
);

app/login/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
4646
return (
4747
<>
4848
<HeroBand
49+
align="center"
4950
eyebrow="SNU Google only"
5051
title="뮤런"
5152
description={

app/page.tsx

Lines changed: 94 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import Link from "next/link";
22
import { redirect } from "next/navigation";
33

44
import { auth, signOut } from "@/lib/auth";
5+
import { db } from "@/lib/db";
56
import { Button } from "@/components/ui/button";
67
import {
78
HeroBand,
89
PageShell,
10+
StatStrip,
911
UtilityCard,
1012
} from "@/components/layout/AppChrome";
1113

@@ -16,6 +18,13 @@ export default async function HomePage() {
1618
if (!session?.user) redirect("/login");
1719
if (!session.user.approved) redirect("/pending");
1820

21+
const [sessionCount, memberCount, distanceSum] = await Promise.all([
22+
db.session.count(),
23+
db.user.count({ where: { approved: true } }),
24+
db.participation.aggregate({ _sum: { distanceKm: true } }),
25+
]);
26+
const totalKm = distanceSum._sum.distanceKm ?? 0;
27+
1928
async function logout() {
2029
"use server";
2130
await signOut({ redirectTo: "/login" });
@@ -24,85 +33,114 @@ export default async function HomePage() {
2433
return (
2534
<>
2635
<HeroBand
36+
align="center"
2737
eyebrow="Animu Running Archive"
2838
title="뮤런"
29-
description={<>안녕하세요, {session.user.name}. 오늘의 러닝 기록을 남겨요.</>}
39+
description={<>안녕하세요, {session.user.name}. 달린 만큼 남습니다.</>}
3040
actions={
3141
<>
3242
<Button asChild size="lg">
3343
<Link href="/sessions/new">새 세션 만들기</Link>
3444
</Button>
35-
<Button asChild variant="outline" size="lg" className="border-white bg-transparent text-white">
45+
<Button
46+
asChild
47+
variant="outline"
48+
size="lg"
49+
className="border-white bg-transparent text-white"
50+
>
3651
<Link href="/sessions">아카이브 보기</Link>
3752
</Button>
3853
</>
3954
}
4055
>
41-
<div className="grid gap-px overflow-hidden rounded-[18px] border border-white/10 bg-white/10 sm:grid-cols-3">
42-
<HeroStat label="Surface" value="Parchment" />
43-
<HeroStat label="Accent" value="Action Blue" />
44-
<HeroStat label="Rhythm" value="Run log" />
45-
</div>
56+
<StatStrip
57+
items={[
58+
{ label: "Sessions", value: `${sessionCount}회` },
59+
{ label: "Distance", value: `${formatKm(totalKm)} km` },
60+
{ label: "Runners", value: `${memberCount}명` },
61+
]}
62+
/>
4663
</HeroBand>
4764

4865
<PageShell width="content" surface="parchment">
66+
<div className="grid gap-5 md:grid-cols-3">
67+
<HomeCard
68+
eyebrow="Create"
69+
title="새 세션"
70+
body="오늘의 세션을 만들고 사진과 기록을 이어 붙입니다."
71+
href="/sessions/new"
72+
cta="세션 만들기"
73+
/>
74+
<HomeCard
75+
eyebrow="Archive"
76+
title="아카이브"
77+
body="날짜, 장소, 멤버로 지난 러닝의 맥락을 다시 찾습니다."
78+
href="/sessions"
79+
cta="전체 보기"
80+
/>
81+
<HomeCard
82+
eyebrow="Me"
83+
title="내 기록"
84+
body="누적 거리와 페이스 흐름을 한 화면에서 확인합니다."
85+
href="/me"
86+
cta="기록 보기"
87+
/>
88+
</div>
4989

50-
<div className="grid gap-4 md:grid-cols-3">
51-
<UtilityCard className="flex flex-col gap-4">
52-
<span className="font-display text-[28px] font-semibold leading-[1.14] tracking-[0.196px]">Create</span>
53-
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">
54-
오늘의 세션을 만들고 사진과 기록을 이어 붙입니다.
55-
</p>
56-
<Button asChild className="mt-auto w-full">
57-
<Link href="/sessions/new">새 세션</Link>
58-
</Button>
59-
</UtilityCard>
60-
<UtilityCard className="flex flex-col gap-4">
61-
<span className="font-display text-[28px] font-semibold leading-[1.14] tracking-[0.196px]">Archive</span>
62-
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">
63-
날짜, 장소, 멤버로 지난 러닝의 맥락을 다시 찾습니다.
64-
</p>
65-
<Button asChild variant="outline" className="mt-auto w-full">
66-
<Link href="/sessions">전체 아카이브</Link>
67-
</Button>
68-
</UtilityCard>
69-
<UtilityCard className="flex flex-col gap-4">
70-
<span className="font-display text-[28px] font-semibold leading-[1.14] tracking-[0.196px]">Me</span>
71-
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">
72-
누적 거리와 페이스 흐름을 한 화면에서 확인합니다.
73-
</p>
74-
<Button asChild variant="secondary" className="mt-auto w-full">
75-
<Link href="/me">내 기록</Link>
76-
</Button>
77-
</UtilityCard>
78-
</div>
79-
80-
<div className="mt-8 flex items-center justify-center gap-5 font-text text-sm leading-[1.29] tracking-[-0.224px]">
81-
{session.user.role === "ADMIN" && (
82-
<Link href="/admin/members" className="text-apple-primary underline-offset-4 hover:underline">
83-
회원 관리
84-
</Link>
85-
)}
86-
<form action={logout}>
87-
<Button type="submit" variant="ghost" size="sm">
88-
로그아웃
89-
</Button>
90-
</form>
91-
</div>
90+
<div className="mt-10 flex items-center justify-center gap-5 font-text text-sm leading-[1.29] tracking-[-0.224px]">
91+
{session.user.role === "ADMIN" && (
92+
<Link
93+
href="/admin/members"
94+
className="text-apple-primary underline-offset-4 hover:underline"
95+
>
96+
회원 관리
97+
</Link>
98+
)}
99+
<form action={logout}>
100+
<Button type="submit" variant="ghost" size="sm">
101+
로그아웃
102+
</Button>
103+
</form>
104+
</div>
92105
</PageShell>
93106
</>
94107
);
95108
}
96109

97-
function HeroStat({ label, value }: { label: string; value: string }) {
110+
function HomeCard({
111+
eyebrow,
112+
title,
113+
body,
114+
href,
115+
cta,
116+
}: {
117+
eyebrow: string;
118+
title: string;
119+
body: string;
120+
href: string;
121+
cta: string;
122+
}) {
98123
return (
99-
<div className="bg-white/[0.03] p-5">
100-
<span className="block font-text text-xs uppercase leading-none tracking-[-0.12px] text-apple-body-muted">
101-
{label}
124+
<UtilityCard className="flex flex-col gap-3 p-7">
125+
<span className="font-text text-xs font-semibold uppercase leading-none tracking-[0.8px] text-apple-primary">
126+
{eyebrow}
102127
</span>
103-
<span className="mt-2 block font-display text-[21px] font-semibold leading-[1.19] tracking-[0.231px] text-white">
104-
{value}
128+
<span className="font-display text-[28px] font-semibold leading-[1.14] tracking-[-0.28px]">
129+
{title}
105130
</span>
106-
</div>
131+
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">
132+
{body}
133+
</p>
134+
<Link
135+
href={href}
136+
className="mt-auto pt-2 font-text text-[15px] leading-[1.29] tracking-[-0.224px] text-apple-primary underline-offset-4 hover:underline"
137+
>
138+
{cta} &rsaquo;
139+
</Link>
140+
</UtilityCard>
107141
);
108142
}
143+
144+
function formatKm(km: number): string {
145+
return km >= 100 ? String(Math.round(km)) : km.toFixed(1);
146+
}

app/pending/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export default async function PendingPage() {
2121
return (
2222
<>
2323
<HeroBand
24+
align="center"
25+
size="compact"
2426
eyebrow="Approval required"
2527
title="승인 대기 중"
2628
description={

app/runners/_components/MemberView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function MemberView({ stats, isSelf }: Props) {
3030
<BackLink href="/sessions">아카이브</BackLink>
3131
</SubNav>
3232
<HeroBand
33+
size="compact"
3334
eyebrow={
3435
<>
3536
{isSelf ? "My running" : "Runner"} · 가입 {formatDate(stats.joinedAt)}

app/sessions/[id]/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export default async function SessionDetailPage({ params }: PageProps) {
101101
</SubNav>
102102

103103
<HeroBand
104+
size="compact"
104105
eyebrow={formatDateHeader(sessionRow.date)}
105106
title={sessionRow.location}
106107
description={metaLine}

app/sessions/_components/SessionCard.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export function SessionCard(props: Props) {
2222
return (
2323
<Link
2424
href={`/sessions/${props.id}`}
25-
className="group block overflow-hidden rounded-[18px] border border-apple-hairline bg-apple-canvas p-3 text-apple-ink transition-transform active:scale-[0.99]"
25+
className="group block overflow-hidden rounded-[18px] border border-apple-hairline bg-apple-canvas text-apple-ink transition-transform active:scale-[0.99]"
2626
>
27-
<div className="relative aspect-video w-full overflow-hidden rounded-lg bg-apple-parchment">
27+
<div className="relative aspect-video w-full overflow-hidden bg-apple-parchment">
2828
{photoSrc ? (
2929
<Image
3030
src={photoSrc}
@@ -39,17 +39,15 @@ export function SessionCard(props: Props) {
3939
사진 없음
4040
</div>
4141
)}
42+
<span className="absolute right-3 top-3 rounded-full bg-apple-chip/70 px-3 py-1.5 font-text text-xs font-semibold leading-none tracking-[-0.12px] text-apple-ink backdrop-blur-md">
43+
참여 {props.participantCount}
44+
</span>
4245
</div>
43-
<div className="flex flex-col gap-2 px-1 pt-4">
44-
<div className="flex items-start justify-between gap-3">
45-
<span className="font-text text-[17px] font-semibold leading-[1.24] tracking-[-0.374px]">
46-
{formatDate(props.date)}
47-
</span>
48-
<span className="rounded-full bg-apple-parchment px-3 py-1 font-text text-xs leading-none tracking-[-0.12px] text-apple-muted-48">
49-
참여 {props.participantCount}
50-
</span>
51-
</div>
52-
<p className="font-display text-[21px] font-semibold leading-[1.19] tracking-[0.231px]">
46+
<div className="flex flex-col gap-1.5 p-5">
47+
<span className="font-text text-sm font-semibold leading-[1.29] tracking-[-0.224px] text-apple-muted-48">
48+
{formatDate(props.date)}
49+
</span>
50+
<p className="font-display text-[24px] font-semibold leading-[1.19] tracking-[-0.28px]">
5351
{props.location}
5452
</p>
5553
<p className="font-text text-sm leading-[1.43] tracking-[-0.224px] text-apple-muted-48">

app/sessions/page.tsx

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Link from "next/link";
22

33
import { Button } from "@/components/ui/button";
4+
import { db } from "@/lib/db";
45
import { requireApproved } from "@/lib/guard";
56
import { listApprovedMembers, listSessions } from "@/lib/sessions";
67
import {
@@ -33,9 +34,10 @@ export default async function SessionsArchivePage({ searchParams }: PageProps) {
3334
const sp = await searchParams;
3435
const { cursorId, filters, hasActiveFilters } = parseSessionArchiveParams(sp);
3536

36-
const [page, members] = await Promise.all([
37+
const [page, members, totalCount] = await Promise.all([
3738
listSessions({ cursorId, filters }),
3839
listApprovedMembers(),
40+
db.session.count(),
3941
]);
4042

4143
const nextHref =
@@ -52,20 +54,18 @@ export default async function SessionsArchivePage({ searchParams }: PageProps) {
5254
</SubNav>
5355

5456
<HeroBand
57+
size="compact"
5558
eyebrow="Sessions"
5659
title="러닝 아카이브"
57-
description="날짜, 장소, 참여 멤버로 세션을 찾아보세요."
58-
actions={
59-
<Button asChild size="lg">
60-
<Link href="/sessions/new">새 세션 만들기</Link>
61-
</Button>
60+
description={
61+
<>
62+
지금까지 {totalCount}번 달렸습니다. 날짜, 장소, 참여 멤버로 세션을
63+
찾아보세요.
64+
</>
6265
}
63-
>
64-
<ArchiveSummary />
65-
</HeroBand>
66+
/>
6667

6768
<PageShell width="content" surface="parchment">
68-
6969
<FilterBar
7070
q={filters.q ?? ""}
7171
memberIds={filters.memberIds ?? []}
@@ -121,26 +121,3 @@ function EmptyFilterResult() {
121121
</UtilityCard>
122122
);
123123
}
124-
125-
function ArchiveSummary() {
126-
return (
127-
<div className="grid gap-px overflow-hidden rounded-[18px] border border-white/10 bg-white/10 sm:grid-cols-3">
128-
<SummaryCell label="Surface" value="Dark tile" />
129-
<SummaryCell label="Flow" value="Filter first" />
130-
<SummaryCell label="Focus" value="Action Blue" />
131-
</div>
132-
);
133-
}
134-
135-
function SummaryCell({ label, value }: { label: string; value: string }) {
136-
return (
137-
<div className="bg-white/[0.03] p-5">
138-
<span className="block font-text text-xs uppercase leading-none tracking-[-0.12px] text-apple-body-muted">
139-
{label}
140-
</span>
141-
<span className="mt-2 block font-display text-[21px] font-semibold leading-[1.19] tracking-[0.231px] text-white">
142-
{value}
143-
</span>
144-
</div>
145-
);
146-
}

0 commit comments

Comments
 (0)