@@ -2,10 +2,12 @@ import Link from "next/link";
22import { redirect } from "next/navigation" ;
33
44import { auth , signOut } from "@/lib/auth" ;
5+ import { db } from "@/lib/db" ;
56import { Button } from "@/components/ui/button" ;
67import {
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 } ›
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+ }
0 commit comments