diff --git a/CLAUDE.md b/CLAUDE.md index 5ab6ec8..38d86d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,19 +1,88 @@ # CLAUDE.md -## π« Must Not +## νλ‘μ νΈ κ°μ + +**hear-your-voice (λλͺ©λ€)**λ SOPT μ€νλ¦°νΈ νκ³ μ€λ¬Έ μΉ μ ν리μΌμ΄μ μ λλ€. +λ©€λ²λ€μ΄ μ€νλ¦°νΈ μΈμ¦ μ½λλ₯Ό μ λ ₯ν λ€ Stop / Start / Continue λ°©μμ νκ³ νΌλλ°±κ³Ό MVPλ₯Ό μ μΆνλ νλ‘μ°λ‘ ꡬμ±λ©λλ€. + +### νμ΄μ§ νλ‘μ° + +``` +StartPage (νμ± μ€νλ¦°νΈ μ¬λΆ λΆκΈ°) + ββ NoticePage (μλ΄) + ββ SprintCodePage (μΈμ¦ μ½λ μ λ ₯) + ββ SprintIntroPage (μ€νλ¦°νΈ μκ°) + ββ UserInfoPage (μ¬μ©μ μ 보 μ λ ₯) + ββ (νκ³ μ€λ¬Έ μ€ν λ€ β μΆν ꡬν μμ ) +``` + +--- + +## νλ‘μ νΈ κ΅¬μ‘° + +``` +src/ +βββ assets/ # μ μ μ΄λ―Έμ§ 리μμ€ +βββ components/ # κ³΅ν΅ μ¬μ¬μ© μ»΄ν¬λνΈ +β βββ CodeInput # μΈμ¦ μ½λ μ λ ₯ νλ +β βββ ContentHeading # νμ΄μ§ μ λͺ© μμ +β βββ ImageSection # μ΄λ―Έμ§ μΉμ +β βββ PageLayout # μ 체 νμ΄μ§ λνΌ +β βββ ProgressBar # μ€ν μ§ν λ° +β βββ SelectField # μ λ νΈ νΌ νλ +β βββ StepLayout # μ€ν κΈ°λ° λ μ΄μμ (νλ¨ λ²νΌ ν¬ν¨) +βββ context/ +β βββ SubmissionContext.tsx # μ€λ¬Έ μ μΆ λ°μ΄ν° μ μ μν +βββ hooks/ +β βββ useErrorHandler.ts # μλ¬ νΈλ€λ§ 컀μ€ν ν +βββ lib/ +β βββ api/ +β β βββ chapter.ts # μ±ν° API +β β βββ comment.ts # λκΈ(νκ³ ) API +β β βββ sprint.ts # μ€νλ¦°νΈ API +β β βββ user.ts # μ μ API +β βββ apiClient.ts # Supabase νΈμΆ λνΌ (μλ¬ λΆλ₯) +β βββ errors.ts # 컀μ€ν μλ¬ ν΄λμ€ +β βββ supabase.ts # Supabase ν΄λΌμ΄μΈνΈ μ΄κΈ°ν +βββ pages/ # λΌμ°νΈ λ¨μ νμ΄μ§ μ»΄ν¬λνΈ +βββ types/ # TypeScript νμ μ μ +βββ App.tsx # λΌμ°ν° μ€μ μ§μ μ +βββ main.tsx # μ± λ§μ΄νΈ +``` + +--- + +## κΈ°μ μ€ν + +| λΆλ₯ | κΈ°μ | +|------|------| +| νλ μμν¬ | React 18 + TypeScript | +| λΉλ | Vite | +| λΌμ°ν | React Router v7 | +| μν κ΄λ¦¬ | React Context (`SubmissionContext`) + Zustand (νμ μ) | +| λ°±μλ/DB | Supabase (RPC κΈ°λ° μΏΌλ¦¬) | +| μ€νμΌλ§ | vanilla-extract (`*.css.ts`) | +| λμμΈ μμ€ν | `@sopt-makers/ui`, `@sopt-makers/colors`, `@sopt-makers/fonts`, `@sopt-makers/icons` | +| λ¦°νΈ/ν¬λ§· | ESLint + Prettier | + +--- + +## κΈμ§ μ¬ν - `.pen` νμΌ μ λ μμ κΈμ§ -- νλμ½λ©λ color / font μ¬μ© κΈμ§ +- νλμ½λ©λ color / font κ° μ¬μ© κΈμ§ β λ°λμ `@sopt-makers/colors`, `@sopt-makers/fonts` μ¬μ© +- Supabase `service_role` key μ¬μ© κΈμ§ +- λ―Όκ° λ°μ΄ν° ν΄λΌμ΄μΈνΈμμ μ§μ μ‘°ν κΈμ§ β RPC λλ μλ² λ‘μ§ μ¬μ© -## π¨ Design System +--- -- color β `@sopt-makers/colors` -- font β `@sopt-makers/fonts` +## λμμΈ μμ€ν -## π§© UI Rules +- μμ β `@sopt-makers/colors` +- ν°νΈ β `@sopt-makers/fonts` +- UI μ»΄ν¬λνΈ β νμ `@sopt-makers/ui` λ¨Όμ νμΈ, μμ λλ§ `src/components/`μ 컀μ€ν ꡬν -- νμ `@sopt-makers/ui` λ¨Όμ μ¬μ© -- μμ λλ§ `/components`μ 컀μ€ν ꡬν +--- ## π¦ ν¨ν€μ§ ꡬ쑰 @@ -40,6 +109,9 @@ src/ β βββ SprintIntroPage β βββ UserInfoPage β βββ StopCommentPage +β βββ StartCommentPage +β βββ ContinueCommentPage +β βββ ClosingPage β βββ ErrorPage βββ types/ β λλ©μΈ νμ μ μ β βββ comment.ts (Comment, Mvp, CommentFormState, CommentSubmissionPayload, CommentsKey, PeerCommentKind, PeerCommentRowState, CommentSubmitResult) @@ -92,6 +164,7 @@ src/ - `src/components/` λ΄λΆ κ° μ°Έμ‘°λ μλ κ²½λ‘ μ μ§ (μνμ°Έμ‘° λ°©μ§) - κ°μ ν΄λ λ΄ μ°Έμ‘°λ `./`, μμΒ·νμ ν΄λ μ°Έμ‘°λ `../` μ¬μ© + ## π ꡬ쑰 μμΉ - μ΅μμ κΈ°μ€: `common/` (λ²μ©) vs λλ©μΈ ν΄λ (νΉν) @@ -102,12 +175,59 @@ src/ - `context/`λ Provider μ»΄ν¬λνΈμ Context κ°μ²΄λ§ export β ν μ λ°λμ `hooks/`μ λΆλ¦¬, νμ μ λ°λμ `types/`μ λΆλ¦¬ - νμ μ νμ `@types`μμ import β `@context` λ± λ€λ₯Έ κ²½λ‘μμ νμ μ re-exportνμ§ μμ -## π Supabase +## μ€νμΌλ§ κ·μΉ + +- μ€νμΌμ λ°λμ **vanilla-extract** (`*.css.ts`) νμΌλ‘ λΆλ¦¬ +- μΈλΌμΈ μ€νμΌ λλ CSS λͺ¨λ(`.module.css`) νΌμ© κΈμ§ +- μ μ»΄ν¬λνΈ/νμ΄μ§ μΆκ° μ κ°μ κ²½λ‘μ `*.css.ts` νμΌ ν¨κ» μμ± + +--- + + +## API κ·μΉ + +- λͺ¨λ Supabase νΈμΆμ `callApi()` λνΌ(`src/lib/apiClient.ts`)λ‘ κ°μΈμΌ ν¨ +- DB μ§μ 쿼리 λμ RPC(`supabase.rpc(...)`) μ¬μ© μμΉ +- API ν¨μλ `src/lib/api/` νμ λλ©μΈλ³ νμΌμ μμΉ + +--- + +## μν κ΄λ¦¬ κ·μΉ + +- μ€λ¬Έ μ μΆ λ°μ΄ν°λ `SubmissionContext` (`src/context/SubmissionContext.tsx`)λ₯Ό ν΅ν΄ κ΄λ¦¬ +- νμ΄μ§ κ° λ°μ΄ν° μ λ¬μ `location.state` λλ `SubmissionContext` μ¬μ© +- Zustandλ SubmissionContextλ‘ μ²λ¦¬νκΈ° μ΄λ €μ΄ μ μ μνμλ§ λμ + +--- + +## AI Agent μμ μ§μΉ¨ + +### μ½λ μμ μ + +- μμ μ λ°λμ κ΄λ ¨ νμΌμ λ¨Όμ μ½κ³ κΈ°μ‘΄ ν¨ν΄Β·κ΅¬μ‘°λ₯Ό νμ ν λ€ μμ +- κΈ°μ‘΄ μ½λ μ€νμΌκ³Ό μΌκ΄μ±μ μ μ§ (λ€μ΄λ°, νμΌ κ΅¬μ± λ°©μ λ±) + +### μ»΄ν¬λνΈ μΆκ° μ + +1. `@sopt-makers/ui`μ μ ν©ν μ»΄ν¬λνΈκ° μλμ§ λ¨Όμ νμΈ +2. μμΌλ©΄ `src/components/`μ `.tsx` + `.css.ts` μμΌλ‘ μμ± +3. `src/components/index.ts`μ export μΆκ° + +### νμ΄μ§ μΆκ° μ + +1. `src/pages/` μλ `XxxPage.tsx` + `XxxPage.css.ts` μμ± +2. `src/App.tsx` λΌμ°ν°μ κ²½λ‘ λ±λ‘ +3. νμν κ²½μ° `SubmissionContext` νμ νμ₯ + +### μλ¬ μ²λ¦¬ -- service_role key μ λ μ¬μ© κΈμ§ -- λ―Όκ° λ°μ΄ν° μ§μ μ‘°ν κΈμ§ -- κ²μ¦μ RPC λλ μλ² λ‘μ§ μ¬μ© +- λ€νΈμν¬ μ€λ₯ β `NetworkError` (toast νμ) +- μλΉμ€ μ€λ₯ β `ServiceError` β `/error` νμ΄μ§λ‘ μ΄λ +- `useErrorHandler` ν νμ© -## π General +### νμ§ λ§μμΌ ν κ² -- κΈ°μ‘΄ μ½λ μ€νμΌ/ꡬ쑰λ₯Ό λ°λμ λ°λ₯Ό κ² +- μμ² λ²μλ₯Ό λ²μ΄λ 리ν©ν λ§, κΈ°λ₯ μΆκ°, μ½λ μ 리 +- λΆνμν μ£ΌμΒ·νμ μ΄λ Έν μ΄μ μΆκ° +- ν¬κΈ°μ μΆμν (λ―Έλ μꡬμ¬ν λλΉ κ΅¬μ‘° μ€κ³) +- λ¨μ λ²κ·Έ μμ μΈλ° μ£Όλ³ μ½λκΉμ§ κ°μ diff --git a/src/App.tsx b/src/App.tsx index 6abb819..c89bc34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import UserInfoPage from '@pages/UserInfoPage'; import StopCommentPage from '@pages/StopCommentPage'; import StartCommentPage from '@pages/StartCommentPage'; import ContinueCommentPage from '@pages/ContinueCommentPage'; +import ClosingPage from '@pages/ClosingPage'; const router = createBrowserRouter([ { @@ -27,7 +28,8 @@ const router = createBrowserRouter([ { path: '/user-info', element: }, { path: '/stop-comment', element: }, { path: '/start-comment', element: }, - { path: '/continue-comment', element: } + { path: '/continue-comment', element: }, + { path: '/closing', element: }, ]); function App() { diff --git a/src/assets/ending.svg b/src/assets/ending.svg new file mode 100644 index 0000000..de25ecb --- /dev/null +++ b/src/assets/ending.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/stop_comment_example.png b/src/assets/stop_comment_example.png new file mode 100644 index 0000000..cc4d324 Binary files /dev/null and b/src/assets/stop_comment_example.png differ diff --git a/src/assets/stop_comment_explanation.png b/src/assets/stop_comment_explanation.png new file mode 100644 index 0000000..b120130 Binary files /dev/null and b/src/assets/stop_comment_explanation.png differ diff --git a/src/components/common/layout/StepLayout.tsx b/src/components/common/layout/StepLayout.tsx index 4ea79a6..f4e09c2 100644 --- a/src/components/common/layout/StepLayout.tsx +++ b/src/components/common/layout/StepLayout.tsx @@ -17,6 +17,8 @@ interface StepLayoutProps { showProgressBar?: boolean; currentStep?: number; totalSteps?: number; + /** ν€λ λ°°λμ νμν μ΄λ―Έμ§ κ²½λ‘. λ―Έμ§μ μ κΈ°λ³Έ header_title.svg μ¬μ© */ + bannerImage?: string; } function StepLayout({ @@ -28,6 +30,7 @@ function StepLayout({ showProgressBar = false, currentStep, totalSteps, + bannerImage, }: StepLayoutProps) { const contentSectionClassName = showProgressBar ? `${styles.contentSection} ${styles.contentSectionWithProgress}` @@ -36,7 +39,7 @@ function StepLayout({ return ( - + {showProgressBar && currentStep !== undefined && totalSteps !== undefined && ( diff --git a/src/pages/ClosingPage.css.ts b/src/pages/ClosingPage.css.ts new file mode 100644 index 0000000..f51dfe6 --- /dev/null +++ b/src/pages/ClosingPage.css.ts @@ -0,0 +1,25 @@ +import { style } from '@vanilla-extract/css'; +import { colors } from '@sopt-makers/colors'; +import { fontsObject } from '@sopt-makers/fonts'; + +export const imageArea = style({ + marginTop: 56, + width: '100%', + height: 276, + objectFit: 'contain', +}); + +export const textArea = style({ + width: '100%', + height: 100, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +export const textContent = style({ + padding: '20px 28px', + color: colors.white, + textAlign: 'center', + ...fontsObject.HEADING_5_20_B, +}); diff --git a/src/pages/ClosingPage.tsx b/src/pages/ClosingPage.tsx new file mode 100644 index 0000000..580c5f7 --- /dev/null +++ b/src/pages/ClosingPage.tsx @@ -0,0 +1,38 @@ +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { StepLayout } from '../components'; +import headerImg from '../assets/header_img.png'; +import endingImg from '../assets/ending.svg'; +import * as styles from './ClosingPage.css'; + +function ClosingPage() { + const navigate = useNavigate(); + + const handleClose = useCallback(() => { + navigate('/'); + }, [navigate]); + + return ( + + + + + + μ΄λ² μ€νλ¦°νΈλ κ³ μνμ ¨μ΄μ + + λ§μ§λ§κΉμ§ νμ΄ν ! + + + + ); +} + +export default ClosingPage;
+ μ΄λ² μ€νλ¦°νΈλ κ³ μνμ ¨μ΄μ + + λ§μ§λ§κΉμ§ νμ΄ν ! +