diff --git a/frontend/.claude/skills/design-reference/SKILL.md b/frontend/.claude/skills/design-reference/SKILL.md new file mode 100644 index 0000000..7ff077b --- /dev/null +++ b/frontend/.claude/skills/design-reference/SKILL.md @@ -0,0 +1,14 @@ +--- +name: design-reference +description: 프론트엔드 UI 구현/수정 시 디자인 목업(Claude Design) 참고용. "디자인 목업", "목업 참고", "V1.dc.html", "피그마 대신" 등 화면 디자인을 확인해야 할 때 사용. +--- + +# 디자인 참고 (Claude Design MCP) + +디자인은 Figma 아님, Claude Design에 있음. 목업 내용 추측 금지 — 매번 MCP로 직접 읽고 구현. + +- project_id: `cb3076cd-70f8-4ff6-82a0-d45df794cbc8` +- 참고 파일: `디자인 목업 V1.dc.html` +- 토큰: `_ds/commit-grow-design-system-*/tokens/*.css` + +절차: (로그인 필요시 `/design-login`) → `read_design_skill`(hifi-design) → `read_file(project_id, "디자인 목업 V1.dc.html")` → 구현 → `render_preview`로 대조. diff --git a/frontend/.env.example b/frontend/.env.example index e7b7f81..3201b38 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1 +1,2 @@ NEXT_PUBLIC_API_URL="http://localhost:3000" +SESSION_COOKIE_NAME="local-sid" diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx new file mode 100644 index 0000000..78fee78 --- /dev/null +++ b/frontend/src/app/dashboard/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "대시보드", +}; + +export default function DashboardPage() { + return ( +
+

대시보드 준비중입니다

+
+ ); +} diff --git a/frontend/src/app/login/_components/LoginPanel.tsx b/frontend/src/app/login/_components/LoginPanel.tsx new file mode 100644 index 0000000..3cd754c --- /dev/null +++ b/frontend/src/app/login/_components/LoginPanel.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useState } from "react"; + +import { GithubIcon } from "@/components/icons/GithubIcon"; +import { Button } from "@/components/ui/button"; + +export function LoginPanel() { + const [isRedirecting, setIsRedirecting] = useState(false); + + function handleLogin() { + setIsRedirecting(true); + window.location.href = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/auth/github`; + } + + return ( +
+
+

로그인

+

+ 오늘의 커밋에서 시작해 혼자서도 꾸준히 회고해요 +

+
+ + + 로그인하면 서비스 이용약관에 동의하게 돼요 + +
+ ); +} diff --git a/frontend/src/app/login/_components/MiniHeatmap.tsx b/frontend/src/app/login/_components/MiniHeatmap.tsx new file mode 100644 index 0000000..74413a4 --- /dev/null +++ b/frontend/src/app/login/_components/MiniHeatmap.tsx @@ -0,0 +1,23 @@ +const WEEKS = 12; +const DAYS = 7; +const HEAT_VARS = ["--heat-0", "--heat-1", "--heat-2", "--heat-3", "--heat-4"]; + +// 로그인 화면 프리뷰용 정적 목업 데이터 — 실제 히트맵은 S-03 대시보드에서 구현 +export function MiniHeatmap() { + return ( +
+ {Array.from({ length: WEEKS * DAYS }, (_, i) => { + const week = Math.floor(i / DAYS); + const day = i % DAYS; + const level = (week * 3 + day * 5) % 5; + return ( + + ); + })} +
+ ); +} diff --git a/frontend/src/app/login/_components/OnboardingPreview.tsx b/frontend/src/app/login/_components/OnboardingPreview.tsx new file mode 100644 index 0000000..399be45 --- /dev/null +++ b/frontend/src/app/login/_components/OnboardingPreview.tsx @@ -0,0 +1,58 @@ +import { CaretLeftIcon } from "@/components/icons/CaretLeftIcon"; +import { CaretRightIcon } from "@/components/icons/CaretRightIcon"; + +import { MiniHeatmap } from "./MiniHeatmap"; +import { StatCard } from "./StatCard"; + +const stats = [ + { label: "연속 회고", value: "7", unit: "일" }, + { label: "전체 회고", value: "23", unit: "회" }, + { label: "완료 액션포인트", value: "12", unit: "개" }, +]; + +export function OnboardingPreview() { + return ( +
+
+
+ C +
+ Commit Grow +
+ +
+
+ {stats.map((stat) => ( + + ))} +
+
+
+ + 회고 히트맵 · 최근 12주 + + +
+
+ +
+
+

활동에서 시작하는 회고

+

+ GitHub 커밋과 PR을 바탕으로 질문을 받고, 답변은 Keep · Problem · + Try로 자동 정리돼요 +

+
+
+ +
+ + + +
+ +
+
+
+ ); +} diff --git a/frontend/src/app/login/_components/StatCard.tsx b/frontend/src/app/login/_components/StatCard.tsx new file mode 100644 index 0000000..a46b8fb --- /dev/null +++ b/frontend/src/app/login/_components/StatCard.tsx @@ -0,0 +1,17 @@ +type StatCardProps = { + label: string; + value: string; + unit: string; +}; + +export function StatCard({ label, value, unit }: StatCardProps) { + return ( +
+ {label} + + {value} + {unit} + +
+ ); +} diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx new file mode 100644 index 0000000..7b6b877 --- /dev/null +++ b/frontend/src/app/login/page.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from "next"; + +import { LoginPanel } from "./_components/LoginPanel"; +import { OnboardingPreview } from "./_components/OnboardingPreview"; + +export const metadata: Metadata = { + title: "로그인", +}; + +export default function LoginPage() { + return ( +
+ +
+ +
+
+ ); +} diff --git a/frontend/src/components/icons/CaretLeftIcon.tsx b/frontend/src/components/icons/CaretLeftIcon.tsx new file mode 100644 index 0000000..40b669a --- /dev/null +++ b/frontend/src/components/icons/CaretLeftIcon.tsx @@ -0,0 +1,18 @@ +import type { SVGProps } from "react"; + +export function CaretLeftIcon(props: SVGProps) { + return ( + + ); +} diff --git a/frontend/src/components/icons/CaretRightIcon.tsx b/frontend/src/components/icons/CaretRightIcon.tsx new file mode 100644 index 0000000..9b5fcf9 --- /dev/null +++ b/frontend/src/components/icons/CaretRightIcon.tsx @@ -0,0 +1,18 @@ +import type { SVGProps } from "react"; + +export function CaretRightIcon(props: SVGProps) { + return ( + + ); +} diff --git a/frontend/src/components/icons/GithubIcon.tsx b/frontend/src/components/icons/GithubIcon.tsx new file mode 100644 index 0000000..4610a92 --- /dev/null +++ b/frontend/src/components/icons/GithubIcon.tsx @@ -0,0 +1,9 @@ +import type { SVGProps } from "react"; + +export function GithubIcon(props: SVGProps) { + return ( + + ); +} diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 8d55553..1becb88 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,10 +1,21 @@ import { type NextRequest, NextResponse } from "next/server"; -// 뼈대만 — 실제 인증 검증(세션 쿠키 확인 등)은 API 준비 후. -export function proxy(_request: NextRequest) { +// 세션 쿠키 존재 여부만 확인(값 유효성 검증 아님) — 백엔드 session.cookieName과 값 동기화 필요. +const SESSION_COOKIE_NAME = process.env.SESSION_COOKIE_NAME ?? "local-sid"; + +export function proxy(request: NextRequest) { + const hasSession = request.cookies.has(SESSION_COOKIE_NAME); + const { pathname } = request.nextUrl; + + if (pathname === "/login" && hasSession) { + return NextResponse.redirect(new URL("/dashboard", request.url)); + } + if (pathname.startsWith("/dashboard") && !hasSession) { + return NextResponse.redirect(new URL("/login", request.url)); + } return NextResponse.next(); } export const config = { - matcher: [], + matcher: ["/login", "/dashboard/:path*"], };