Uh oh!
There was an error while loading. Please reload this page.
✨ 로그인 필요로 리디렉션될 때 안내 토스트 표시 - #629
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
Walkthrough
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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 `@apps/web/src/lib/toast/pendingToast.ts`:
- Around line 40-43: Update the parsed-data validation in the pending toast
parser to verify that parsed.icon is one of the allowed ToastIconKey values and
parsed.message is a string before returning the toast. Reject invalid or
malformed storage data by returning null, while preserving the existing
valid-toast return shape.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3ea48653-42ac-4419-be23-b5942fc52775
📒 Files selected for processing (8)
apps/web/src/app/community/[boardCode]/create/PostForm.tsxapps/web/src/app/layout.tsxapps/web/src/app/mentor/_ui/MentorClient/index.tsxapps/web/src/app/my/_ui/MyProfileContent/index.tsxapps/web/src/lib/toast/PendingToastPresenter.tsxapps/web/src/lib/toast/pendingToast.tsapps/web/src/utils/authRedirect.tsapps/web/src/utils/axiosInstance.ts
| const parsed = JSON.parse(raw) as Partial<PendingToast>; | ||
| if (!parsed?.message || !parsed?.icon) return null; | ||
| return { icon: parsed.icon, message: parsed.message }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
1. 손상된 저장 데이터의 형식을 검증하세요.
as Partial<PendingToast>는 런타임 검증을 하지 않습니다. {"icon":"invalid","message":"..."} 또는 {"icon":"logo","message":{}}는 현재 검사를 통과합니다. 이후 showIconToast가 유효하지 않은 Icon 또는 React 자식 값을 렌더링하여 로그인 페이지에서 오류를 발생시킬 수 있습니다.
icon을 ToastIconKey 허용 목록으로 확인하고, message가 문자열인지 확인한 뒤에만 반환하세요.
수정 예시
+const TOAST_ICON_KEYS: readonly ToastIconKey[] = ["like", "link", "univ", "cap", "logo"];++const isPendingToast = (value: unknown): value is PendingToast => {+ if (!value || typeof value !== "object") return false;++ const { icon, message } = value as Record<string, unknown>;+ return typeof message === "string" && TOAST_ICON_KEYS.includes(icon as ToastIconKey);+};+
export const consumePendingToast = (): PendingToast | null => {
// ...
- const parsed = JSON.parse(raw) as Partial<PendingToast>;- if (!parsed?.message || !parsed?.icon) return null;+ const parsed: unknown = JSON.parse(raw);+ if (!isPendingToast(parsed)) return null;
return { icon: parsed.icon, message: parsed.message };
};📝 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.
| constparsed=JSON.parse(raw)asPartial<PendingToast>; | |
| if(!parsed?.message||!parsed?.icon)returnnull; | |
| return{icon: parsed.icon,message: parsed.message}; | |
| constTOAST_ICON_KEYS: readonlyToastIconKey[]=["like","link","univ","cap","logo"]; | |
| constisPendingToast=(value: unknown): value is PendingToast=>{ | |
| if(!value||typeofvalue!=="object")returnfalse; | |
| const{ icon, message }=valueasRecord<string,unknown>; | |
| returntypeofmessage==="string"&&TOAST_ICON_KEYS.includes(iconasToastIconKey); | |
| }; | |
| constparsed: unknown=JSON.parse(raw); | |
| if(!isPendingToast(parsed))returnnull; | |
| return{icon: parsed.icon,message: parsed.message}; |
🤖 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 `@apps/web/src/lib/toast/pendingToast.ts` around lines 40 - 43, Update the
parsed-data validation in the pending toast parser to verify that parsed.icon is
one of the allowed ToastIconKey values and parsed.message is a string before
returning the toast. Reject invalid or malformed storage data by returning null,
while preserving the existing valid-toast return shape.
Uh oh!
There was an error while loading. Please reload this page.
문제
로그인이 필요한 화면에 들어가면 아무 설명 없이 로그인 페이지로 튕깁니다. 커뮤니티 상세처럼 사용자가 방금 누른 링크가 그냥 사라지는 것처럼 보여서 어색합니다.
원인이 두 갈래였습니다.
1. 토스트를 띄우지만 이동이 그걸 지워버림 —
axiosInstanceredirectToLogin()은 이미 토스트를 띄우고 있었는데, 바로 뒤에서window.location.replace("/login")을 호출합니다. 이건 하드 내비게이션이라 React 트리가 통째로 버려지고,<Toaster>도 함께 사라집니다. 토스트가 화면에 그려지기 전에 페이지가 날아가서 사용자는 아무것도 못 봅니다.커뮤니티 상세 페이지가 정확히 이 경로입니다 — 별도 가드 없이 API 401 → 인터셉터 → 하드 리디렉션.
2. 아예 토스트가 없음 — 페이지 가드 3곳
router.replace("/login")만 호출하고 안내가 전혀 없었습니다.MentorClient(멘토)MyProfileContent(마이페이지)PostForm(커뮤니티 글쓰기)수정
하드 내비게이션을 건너 토스트 전달
setPendingToast()/consumePendingToast()를 추가했습니다. 메시지를sessionStorage에 넘겨두고, 도착한 페이지에서PendingToastPresenter(루트 레이아웃의<Toaster>옆에 마운트)가 대신 띄웁니다. 한 번 읽으면 즉시 비워서 다음 이동에 다시 뜨지 않습니다.axiosInstance.redirectToLogin()이showIconToast대신 이 방식을 씁니다. 기존 메시지("로그인이 필요합니다...", "세션이 만료되었습니다...")는 그대로 두고, 이제 실제로 보이게만 했습니다.페이지 가드에는 토스트 추가
세 곳 모두
router.replace전에showIconToast("logo", LOGIN_REQUIRED_MESSAGE)를 호출합니다. 이건 SPA 이동이라 React 트리가 유지되므로 토스트가 로그인 페이지까지 그대로 살아있습니다 — 여기엔 sessionStorage 우회가 필요 없습니다.메시지는
LOGIN_REQUIRED_MESSAGE = "로그인이 필요한 페이지입니다."로authRedirect.ts에 모아 뒀습니다.검증
pnpm --filter @solid-connect/web run typecheck— 통과pnpm --filter @solid-connect/web run lint:check— 482 files 통과pnpm --filter @solid-connect/web run build— 통과pendingToast로직 실행 확인:참고
sessionStorage를 못 쓰는 환경(프라이빗 모드 등)에서는 토스트를 조용히 포기합니다. 리디렉션 자체는 정상 동작합니다.apps/university-web에도 같은 구조의redirectToLogin이 있지만, 이번 요청 범위(커뮤니티 등apps/web화면)에 맞춰 포함하지 않았습니다. 필요하면 별도 PR로 맞추겠습니다.🤖 Generated with Claude Code