feat(frontend): Phase 6 프론트 기초(PWA) — 디자인·라우팅·인증#6
Conversation
- 디자인 시스템(index.css): 챌린지 네이비+골드 토큰, 라이트/다크, 공통 컴포넌트 - 라우팅(react-router): /login·/signup + 인증 보호된 앱 셸(홈/마이/랭킹/우리학교), 미인증 리다이렉트 - 인증: fetch 클라이언트(JWT localStorage 저장·첨부), AuthProvider(가입→자동로그인, /me 토큰검증), ProtectedRoute - 로그인/회원가입(학교 선택·필드검증), 홈(현재 세션 상태), 나머지 탭 자리표시 - PWA theme-color, lang=ko/title 브라우저 검증: 가입→자동로그인→홈, 탭 라우팅·활성 상태, 학교목록 API 연동. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthrough기존 단일 React 화면을 React Router 기반 구조로 교체하고, API·토큰·인증 컨텍스트와 보호 라우트를 추가했다. 로그인·회원가입·세션·보류 페이지 및 모바일 앱 셸과 디자인 시스템을 구현했다. Changes스터디카페 앱 전환
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant RouterProvider
participant ProtectedRoute
participant AuthProvider
participant API
participant HomePage
Browser->>RouterProvider: "/" 요청
RouterProvider->>ProtectedRoute: 인증 라우트 실행
ProtectedRoute->>AuthProvider: 인증 상태 확인
AuthProvider->>API: GET /api/auth/me
API-->>AuthProvider: 사용자 또는 오류
AuthProvider-->>ProtectedRoute: ready와 user
ProtectedRoute->>HomePage: 인증된 홈 렌더링
HomePage->>API: GET /api/sessions/current
API-->>HomePage: 현재 세션
HomePage-->>Browser: 공부 중 또는 대기 중 카드 표시
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/index.css`:
- Line 18: 다크 테마의 --primary 색상 대비가 부족하므로 frontend/src/index.css의 다크 테마 토큰에서
--primary를 더 어두운 배경색으로 조정하고, 기본 버튼과 로고 텍스트에 사용되는 전경색 토큰(--primary-ink 등)이 흰색 또는
충분히 대비되는 값이 되도록 업데이트하세요. 동일한 토큰 정의가 있는 81행과 109-112행도 일관되게 수정하세요.
- Line 11: Update every color-scheme declaration in index.css, including the
occurrences near the top, line 28, and line 131, to remove or replace the
unsupported light dark value so all reported Stylelint violations are resolved
while preserving the intended theme behavior.
In `@frontend/src/lib/api.ts`:
- Around line 72-75: 중앙 API 오류 처리와 인증 상태 관리를 일관되게 조정하세요. frontend/src/lib/api.ts
72-75의 응답 처리에서 401/403을 인증 만료 신호로 전달해 토큰과 사용자 상태가 함께 초기화되도록 하고,
frontend/src/lib/auth.tsx 23-27의 인증 검증 로직은 401/403일 때만 인증을 해제하며 네트워크 오류와 5xx 응답은
재시도 가능한 상태로 유지하세요.
- Around line 45-50: Replace the localStorage-based token handling in getToken
and setToken with an HttpOnly, Secure, SameSite cookie issued and managed by the
server, removing client-side token reads and writes. Update the authentication
flow to rely on the server cookie and add the required CSRF protection for
state-changing requests.
In `@frontend/src/lib/auth.tsx`:
- Around line 36-39: Update signup in the auth flow to distinguish successful
account creation from subsequent automatic-login failure. Preserve the
successful signup result and handle login failure separately by guiding the user
to the login flow, or use an API contract that atomically performs signup and
token issuance; do not propagate the login error as a signup failure that
triggers retry.
In `@frontend/src/pages/HomePage.tsx`:
- Around line 14-16: Update the HomePage session-loading flow around
sessionApi.current() to track API failures in a separate error state instead of
swallowing them as a null session. Render a distinct error and retry UI for
request failures, keep the no-active-session message only for successful
responses with no session, and handle authentication errors consistently with
the existing authentication state.
In `@frontend/src/pages/SignupPage.tsx`:
- Around line 22-24: Update the school-loading flow in the SignupPage useEffect
so authApi.schools() failures are surfaced to the user instead of being silently
ignored. Add visible error guidance with a retry action, or prevent signup
submission until the school lookup completes successfully; preserve the existing
school list population on successful requests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ba5bee0c-541c-4fa3-ac75-950e75a10b5b
⛔ Files ignored due to path filters (2)
frontend/package-lock.jsonis excluded by!**/package-lock.jsonfrontend/src/assets/react.svgis excluded by!**/*.svg
📒 Files selected for processing (17)
frontend/index.htmlfrontend/package.jsonfrontend/src/App.cssfrontend/src/App.tsxfrontend/src/components/AppLayout.tsxfrontend/src/components/Placeholder.tsxfrontend/src/components/ProtectedRoute.tsxfrontend/src/index.cssfrontend/src/lib/api.tsfrontend/src/lib/auth.tsxfrontend/src/main.tsxfrontend/src/pages/HomePage.tsxfrontend/src/pages/LoginPage.tsxfrontend/src/pages/MyPage.tsxfrontend/src/pages/RankingPage.tsxfrontend/src/pages/SchoolPage.tsxfrontend/src/pages/SignupPage.tsx
💤 Files with no reviewable changes (2)
- frontend/src/App.css
- frontend/src/App.tsx
| --active:#1f9d63; --danger:#c8402f; | ||
| --shadow:0 1px 2px rgba(16,28,54,.05), 0 8px 24px -12px rgba(16,28,54,.2); | ||
| --radius:20px; --radius-sm:14px; | ||
| color-scheme: light dark; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
보고된 Stylelint 오류를 수정해 주세요.
현재 세 규칙 위반으로 스타일 검사가 실패합니다.
제안 수정
--radius:20px; --radius-sm:14px;
+
color-scheme: light dark;
- font-family: -apple-system, "Apple SD Gothic Neo", "Pretendard", system-ui, "Segoe UI", Roboto, sans-serif;
+ font-family: -apple-system, "Apple SD Gothic Neo", Pretendard, system-ui, "Segoe UI", Roboto, sans-serif;
-.dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
+.dot { width: 7px; height: 7px; border-radius: 50%; background: currentcolor; }Also applies to: 28-28, 131-131
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 11-11: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/index.css` at line 11, Update every color-scheme declaration in
index.css, including the occurrences near the top, line 28, and line 131, to
remove or replace the unsupported light dark value so all reported Stylelint
violations are resolved while preserving the intended theme behavior.
Source: Linters/SAST tools
| const TOKEN_KEY = 'scr.token'; | ||
| export const getToken = (): string | null => localStorage.getItem(TOKEN_KEY); | ||
| export const setToken = (token: string | null): void => { | ||
| if (token) localStorage.setItem(TOKEN_KEY, token); | ||
| else localStorage.removeItem(TOKEN_KEY); | ||
| }; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
JWT를 localStorage에 저장하지 마세요.
페이지에서 실행되는 스크립트가 토큰을 읽을 수 있어 XSS 발생 시 계정 탈취로 이어집니다. 가능하면 서버가 발급하는 HttpOnly; Secure; SameSite 쿠키로 전환하고 필요한 CSRF 방어를 적용해 주세요.
As per path instructions, "토큰 저장 방식(XSS 노출 위험)"을 확인해야 합니다.
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 47-47: Do not store sensitive data (credentials, tokens, PII) in localStorage or sessionStorage; it is readable by any script and persists on the device.
Context: localStorage.setItem(TOKEN_KEY, token)
Note: [CWE-312] Cleartext Storage of Sensitive Information.
(local-storage-sensitive-data-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/api.ts` around lines 45 - 50, Replace the localStorage-based
token handling in getToken and setToken with an HttpOnly, Secure, SameSite
cookie issued and managed by the server, removing client-side token reads and
writes. Update the authentication flow to rely on the server cookie and add the
required CSRF protection for state-changing requests.
Sources: Path instructions, Linters/SAST tools
- 다크모드 대비: --on-primary 토큰 추가. 밝은 파랑(#5a90e6) 위 흰색은 3.2:1로 WCAG AA 미달이라 어두운 전경색(#0d1422, 5.7:1)을 쓴다. .btn / .auth .logo 적용 - 인증 만료 중앙 처리: api.ts에 setUnauthorizedHandler 추가. 토큰을 실어 보낸 요청이 401/403이면 토큰을 지우고 AuthProvider가 user를 비운다. (로그인 실패의 401은 토큰 없이 보낸 요청이라 해당 없음) - 가입 성공 ≠ 자동 로그인 실패 구분: AutoLoginFailedError를 별도 모듈로 분리. 계정이 이미 만들어졌으므로 재가입이 아니라 로그인으로 안내하고 제출을 막는다 - 부팅 실패 구분: 네트워크/5xx는 토큰을 유지한 채 loadError + 다시 시도, 401/403만 로그아웃 처리. ProtectedRoute가 로그인으로 튕기지 않는다 - 홈/회원가입 에러 노출: 실패를 '기록 없음'·'무소속 가입'으로 조용히 삼키지 않고 안내 + 다시 시도 제공 - .dot 색을 currentcolor로 바꿔 pill 색과 항상 일치시킴 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/lib/api.ts`:
- Around line 82-85: Guard the 401/403 handling in api.ts by applying
setToken(null) and unauthorizedHandler only when getToken() still equals the
request’s token. In auth.tsx, manage bootstrap request generations or an
AbortController, apply setUser, setLoadError, and setReady only for the latest
bootstrap, and invalidate in-flight bootstrap work whenever login or logout
starts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 000a0e51-bcba-49fc-826a-5448db7018da
📒 Files selected for processing (7)
frontend/src/components/ProtectedRoute.tsxfrontend/src/index.cssfrontend/src/lib/api.tsfrontend/src/lib/auth.tsxfrontend/src/lib/authErrors.tsfrontend/src/pages/HomePage.tsxfrontend/src/pages/SignupPage.tsx
|
리뷰 반영했습니다. (커밋 d13403b) 반영
보류 (근거)
린트·타입체크·빌드 통과, 가입→자동 로그인→홈 해피패스 브라우저 검증 완료. |
만료 토큰이 남은 채 /login 에 들어오면 부트스트랩의 me() 가 백그라운드로 떠 있는데, 그 401이 도착하기 전에 로그인에 성공하면 뒤늦은 401이 방금 발급받은 토큰을 지우고 로그아웃시켰다. (재현 확인: 로그인 직후 user=null, token=null) - api.ts: 보낸 토큰이 아직 현재 토큰일 때만(getToken() === token) 토큰을 정리하고 unauthorizedHandler 를 호출한다. 경쟁이 없는 정상 만료 처리는 그대로 동작 - auth.tsx: 부트스트랩에 세대(generation) 번호를 둬, 늦게 도착한 검증 결과가 setUser/setLoadError/setReady 를 덮어쓰지 못하게 한다. 로그인·로그아웃·만료 처리는 세대를 올려 진행 중인 검증을 무효화하고 ready 를 직접 확정한다 (retry 연타로 부트스트랩이 겹쳐도 마지막 것만 반영됨) - 401/403 시 토큰 정리 주체를 api 계층 하나로 통일 (bootstrap 의 중복 setToken 제거) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
지적하신 경쟁 조건 반영했습니다. (커밋 d2dfcb9) — 타당한 지적이었고, 실제로 재현됐습니다. 재현 시나리오: 만료 토큰이 남은 채 수정 전 코드로 재현 (401 응답 600ms 지연, 그 사이 로그인 성공): 수정 후 같은 시나리오: 수정 내용
AbortController 대신 세대 번호를 쓴 이유: 린트·타입체크·빌드 통과. 로그인→홈, 새로고침 세션 유지, 로그아웃 해피패스도 브라우저에서 재확인했습니다. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/lib/auth.tsx`:
- Around line 69-73: 인증 만료 처리의 setUnauthorizedHandler에서 사용자와 준비 상태를 갱신할 때 이전
부트스트랩 오류도 함께 초기화하세요. 해당 콜백에 loadError를 null로 재설정하는 상태 업데이트를 추가해 401/403 이후 로그인
화면에 이전 오류가 남지 않도록 하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 34a6938a-6881-4798-aeeb-01d2729d944b
📒 Files selected for processing (2)
frontend/src/lib/api.tsfrontend/src/lib/auth.tsx
| setUnauthorizedHandler(() => { | ||
| invalidateBootstrap(); | ||
| setUser(null); | ||
| setReady(true); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
인증 만료 시 loadError도 초기화하세요.
재시도 가능한 부트스트랩 오류가 남아 있던 중 401/403이 발생하면, 사용자와 준비 상태만 갱신되어 이전 오류 안내가 로그인 상태에도 남습니다.
수정 예시
setUnauthorizedHandler(() => {
invalidateBootstrap();
setUser(null);
+ setLoadError(false);
setReady(true);
});As per path instructions, “API 에러 처리, 상태관리 일관성, 타입 안전성을 확인한다.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setUnauthorizedHandler(() => { | |
| invalidateBootstrap(); | |
| setUser(null); | |
| setReady(true); | |
| }); | |
| setUnauthorizedHandler(() => { | |
| invalidateBootstrap(); | |
| setUser(null); | |
| setLoadError(false); | |
| setReady(true); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/auth.tsx` around lines 69 - 73, 인증 만료 처리의
setUnauthorizedHandler에서 사용자와 준비 상태를 갱신할 때 이전 부트스트랩 오류도 함께 초기화하세요. 해당 콜백에
loadError를 null로 재설정하는 상태 업데이트를 추가해 401/403 이후 로그인 화면에 이전 오류가 남지 않도록 하세요.
Source: Path instructions
재시도를 연달아 눌러 먼저 뜬 검증이 5xx로 loadError를 남긴 뒤 다른 검증이 401로 돌아오면, 토큰은 정리됐는데도 ProtectedRoute가 loadError를 먼저 보고 로그인 대신 '연결 문제' 화면에 머물렀다. (재현 확인: 401 도착 후에도 loadError=true) 만료는 확정된 상태이므로 unauthorizedHandler에서 loadError도 해제한다. login/logout이 이미 하던 것과 동일한 처리다. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/lib/auth.tsx (1)
80-87: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftJWT를 localStorage에 저장하지 마세요.
frontend/src/lib/auth.tsx:83의setToken(res.token)은frontend/src/lib/api.ts:45-49를 통해 Bearer 토큰을localStorage에 남깁니다. XSS가 한 번만 나도 세션 탈취로 이어지므로, HttpOnly·Secure·SameSite 쿠키 또는 BFF 방식으로 바꿔야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/lib/auth.tsx` around lines 80 - 87, Update the login flow in login so res.token is no longer persisted through setToken or localStorage; migrate authentication to an HttpOnly, Secure, SameSite cookie or BFF-backed session, and update the related authApi/API handling to use that session while preserving user and readiness state updates.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@frontend/src/lib/auth.tsx`:
- Around line 80-87: Update the login flow in login so res.token is no longer
persisted through setToken or localStorage; migrate authentication to an
HttpOnly, Secure, SameSite cookie or BFF-backed session, and update the related
authApi/API handling to use that session while preserving user and readiness
state updates.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 028a4d79-8ebd-4cbd-b901-fcc2c51ff516
📒 Files selected for processing (1)
frontend/src/lib/auth.tsx
Phase 6 — 프론트 기초 (PWA)
CLAUDE.md§7 Phase 6. React + Vite + TS PWA의 뼈대: 디자인 시스템 · 라우팅 · 인증 · 앱 셸. 화면 시안(스터디카페 챌린지 네이비+골드)을 승인받아 그대로 구현했다.변경
index.css): 챌린지 브랜드 토큰(네이비 주색 + 골드 포인트), 라이트/다크, 공통 컴포넌트(버튼·입력·카드·pill·모바일 셸·하단 탭). 시간 숫자 tabular.react-router-dom):/login·/signup(공개) + 인증 보호된 앱 셸(/홈,/my,/ranking,/school). 미인증 시/login리다이렉트.lib/api.ts— fetch 클라이언트(/api프록시), JWT localStorage 저장·헤더 첨부,ApiError, 타입.lib/auth.tsx—AuthProvider(가입→자동로그인, 앱 시작 시/users/me로 토큰 검증, 만료 토큰 정리),useAuth.ProtectedRoute.GET /api/sessions/current+ 로그아웃).theme-color(라이트/다크),lang="ko", viewport-fit.실행 검증 (백엔드 + vite dev, 브라우저)
초기 진입 →
/login리다이렉트 / 회원가입(학교 목록 API 로드) → 가입→자동로그인→홈 / 홈에서 사용자·세션 상태 표시 / 탭 라우팅 + 활성 상태(네이비). 프론트npm run build그린(tsc + vite + PWA).범위 밖(다음)
Phase 7 체크인(html5-qrcode + 데스크톱 수동 토큰), Phase 8 마이페이지 차트, Phase 9 랭킹/우리학교 실제 데이터. (자리표시 3개 페이지가 그 자리.)
Summary by CodeRabbit