fix(web): 닉네임 중복 토스트 이슈 포함 react-hot-toast 전면 전환#506
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
Walkthrough이 변경사항은 기존 Zustand 기반 토스트 시스템을
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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. Review rate limit: 0/1 reviews remaining, refill in 40 minutes and 40 seconds.Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a3320d2e9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const axiosError = error as AxiosError<ErrorResponse>; | ||
| const status = axiosError?.response?.status; | ||
| if (status === 401) return; // 인증 오류는 토스트 표시 X | ||
| if (isUnauthorized(status)) return; |
There was a problem hiding this comment.
Skip auth-gated interceptor errors in global toast handler
axiosInstance can reject with AuthenticationRequiredError after already showing the login-redirect toast (redirectToLogin(...)), but this handler only suppresses status === 401. Because AuthenticationRequiredError has no HTTP status, it falls through and triggers toast.error(error.message), so users can see an extra generic "Authentication required" toast on top of the redirect message whenever protected requests run without a token. Add a guard for this auth-redirect error path (and apply it to both query and mutation cache handlers) before emitting global error toasts.
Useful? React with 👍 / 👎.
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 (2)
apps/web/src/apis/news/deleteNews.ts (1)
23-37:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
- 롤백 시 캐시 데이터 형태가 깨질 수 있습니다.
Line 23에서
Article[]를 저장하고 Line 37에서 동일 키에Article[]를 복구하지만, 같은 키를 Line 25에서는ArticleListResponse로 다루고 있습니다. 실패 롤백 이후newsResponseList를 기대하는 화면에서 런타임 오류가 날 수 있습니다.🔧 제안 패치
type ArticleDeleteMutationContext = { - previousArticleList?: Article[]; + previousArticleContainer?: ArticleListResponse; }; ... - const previousArticleList = queryClient.getQueryData<Article[]>(queryKey); + const previousArticleContainer = queryClient.getQueryData<ArticleListResponse>(queryKey); queryClient.setQueryData<ArticleListResponse>(queryKey, (oldData) => { if (!oldData) return { newsResponseList: [] }; return { newsResponseList: oldData.newsResponseList.filter((article) => article.id !== deletedArticleId), }; }); - return { previousArticleList }; + return { previousArticleContainer }; }, onError: (_error, _variables, context) => { - if (context?.previousArticleList) { - queryClient.setQueryData<Article[]>(queryKey, context.previousArticleList); + if (context?.previousArticleContainer) { + queryClient.setQueryData<ArticleListResponse>(queryKey, context.previousArticleContainer); } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/apis/news/deleteNews.ts` around lines 23 - 37, The rollback corrupts cache shape: you fetch previousArticleList as Article[] via queryClient.getQueryData but you update the cache as ArticleListResponse (with newsResponseList) in the mutation and later restore the raw Article[] in onError; instead capture and restore the same shape used by setQueryData. Change the optimistic snapshot to read previousData as ArticleListResponse (or wrap the Article[] as { newsResponseList }) and in onError restore that ArticleListResponse to the same queryKey so the UI still finds newsResponseList; adjust references to previousArticleList, getQueryData, setQueryData, ArticleListResponse and newsResponseList accordingly.apps/web/src/apis/news/putUpdateNews.ts (1)
21-24:⚠️ Potential issue | 🟠 Major | ⚡ Quick win1) 롤백 캐시 타입 불일치로 실패 시 캐시 shape가 깨질 수 있습니다.
- Line 21에서 스냅샷을
Article[]로 저장하고, Line 23에서는ArticleListResponse를 캐시에 쓰고 있습니다.- 이후 Line 47에서
Article[]를 그대로 복원하면newsResponseList구조가 사라져 조회 코드가 깨질 수 있습니다.🔧 제안 수정안
type ArticleMutationContext = { - previousArticleList?: Article[]; + previousArticleList?: ArticleListResponse; }; @@ - const previousArticleList = queryClient.getQueryData<Article[]>(queryKey); + const previousArticleList = queryClient.getQueryData<ArticleListResponse>(queryKey); @@ - queryClient.setQueryData(queryKey, context.previousArticleList); + queryClient.setQueryData<ArticleListResponse>(queryKey, context.previousArticleList);Also applies to: 45-48
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/apis/news/putUpdateNews.ts` around lines 21 - 24, previousArticleList is saved as Article[] but setQueryData expects an ArticleListResponse, causing rollback to restore the wrong shape and break consumers; change the snapshot to capture the same shape you write (ArticleListResponse) or, when saving the Article[] snapshot, wrap it into { newsResponseList: previousArticleList } before calling setQueryData. Locate the queryClient usage in putUpdateNews.ts (variables: previousArticleList, queryKey, setQueryData) and update both places where you save/restore the cache (including the other occurrence similar at lines 45-48) so the rollback always restores an ArticleListResponse object shape.
🧹 Nitpick comments (1)
apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx (1)
55-56: React Query 패턴을 더 명확하게 따르도록mutate+onSuccess콜백으로 변경해 주세요.현재 코드의 빈
catch블록은 작동하지만, 이 프로젝트의 React Query 전역 에러 처리 패턴과 맞지 않습니다. 다음과 같이 정리하면 의도가 더 명확하고 유지보수하기 좋습니다:
mutateAsync→mutate로 변경- 폼 제출 핸들러를 async에서 동기 함수로 단순화
mutate호출 시onSuccess콜백으로 성공 상태 관리- try-catch 제거 (에러는 전역 핸들러가 이미 처리 중)
🔧 제안 diff
- const { mutateAsync: postLanguageTestScore } = usePostLanguageTestScore(); + const { mutate: postLanguageTestScore } = usePostLanguageTestScore(); - const onSubmit: SubmitHandler<LanguageTestFormData> = async (data) => { - try { - await postLanguageTestScore({ - languageTestScoreRequest: { - languageTestType: data.testType, - languageTestScore: data.score, - issueDate: "2025-01-01", - }, - file: data.file[0], - }); - - // 성공 시에만 실행 - reset(); - setShowResult(true); - setSubmittedData(data); - } catch (_error) { - // 실패 토스트는 React Query 전역 onError에서 단일 처리 - } - }; + const onSubmit: SubmitHandler<LanguageTestFormData> = (data) => { + postLanguageTestScore( + { + languageTestScoreRequest: { + languageTestType: data.testType, + languageTestScore: data.score, + issueDate: "2025-01-01", + }, + file: data.file[0], + }, + { + onSuccess: () => { + reset(); + setShowResult(true); + setSubmittedData(data); + }, + }, + ); + };프로젝트의 전역 에러 처리 설정이 이미 모든 mutation 실패를 담당하고 있으므로,
mutate만으로 충분합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx` around lines 55 - 56, The submit handler in LanguageTestSubmitForm currently uses mutateAsync with an empty catch; change it to call the mutation's mutate (not mutateAsync), make handleSubmit synchronous (remove async/await and the try-catch block), and pass an onSuccess callback to mutate to update success state (e.g., call setIsSubmitted(true) or whatever success handler you already have). Locate the useMutation instance (the submit mutation used in LanguageTestSubmitForm) and replace its mutateAsync usage with mutate({...payload}, { onSuccess: () => { /* success handling */ }}) so global React Query error handling remains responsible for failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/web/src/apis/news/deleteNews.ts`:
- Around line 23-37: The rollback corrupts cache shape: you fetch
previousArticleList as Article[] via queryClient.getQueryData but you update the
cache as ArticleListResponse (with newsResponseList) in the mutation and later
restore the raw Article[] in onError; instead capture and restore the same shape
used by setQueryData. Change the optimistic snapshot to read previousData as
ArticleListResponse (or wrap the Article[] as { newsResponseList }) and in
onError restore that ArticleListResponse to the same queryKey so the UI still
finds newsResponseList; adjust references to previousArticleList, getQueryData,
setQueryData, ArticleListResponse and newsResponseList accordingly.
In `@apps/web/src/apis/news/putUpdateNews.ts`:
- Around line 21-24: previousArticleList is saved as Article[] but setQueryData
expects an ArticleListResponse, causing rollback to restore the wrong shape and
break consumers; change the snapshot to capture the same shape you write
(ArticleListResponse) or, when saving the Article[] snapshot, wrap it into {
newsResponseList: previousArticleList } before calling setQueryData. Locate the
queryClient usage in putUpdateNews.ts (variables: previousArticleList, queryKey,
setQueryData) and update both places where you save/restore the cache (including
the other occurrence similar at lines 45-48) so the rollback always restores an
ArticleListResponse object shape.
---
Nitpick comments:
In
`@apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx`:
- Around line 55-56: The submit handler in LanguageTestSubmitForm currently uses
mutateAsync with an empty catch; change it to call the mutation's mutate (not
mutateAsync), make handleSubmit synchronous (remove async/await and the
try-catch block), and pass an onSuccess callback to mutate to update success
state (e.g., call setIsSubmitted(true) or whatever success handler you already
have). Locate the useMutation instance (the submit mutation used in
LanguageTestSubmitForm) and replace its mutateAsync usage with
mutate({...payload}, { onSuccess: () => { /* success handling */ }}) so global
React Query error handling remains responsible for failures.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: c5595e3a-c1de-4daa-8c3b-f06453584c37
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (60)
apps/web/package.jsonapps/web/src/apis/Auth/deleteAccount.tsapps/web/src/apis/Auth/postAppleAuth.tsapps/web/src/apis/Auth/postEmailLogin.tsapps/web/src/apis/Auth/postEmailVerification.tsapps/web/src/apis/Auth/postKakaoAuth.tsapps/web/src/apis/Auth/postSignUp.tsapps/web/src/apis/MyPage/patchPassword.tsapps/web/src/apis/MyPage/patchProfile.tsapps/web/src/apis/Scores/postCreateGpa.tsapps/web/src/apis/Scores/postCreateLanguageTest.tsapps/web/src/apis/applications/postSubmitApplication.tsapps/web/src/apis/community/deleteComment.tsapps/web/src/apis/community/deleteLikePost.tsapps/web/src/apis/community/deletePost.tsapps/web/src/apis/community/patchUpdatePost.tsapps/web/src/apis/community/postCreateComment.tsapps/web/src/apis/community/postCreatePost.tsapps/web/src/apis/community/postLikePost.tsapps/web/src/apis/image-upload/postUploadProfileImageBeforeSignup.tsapps/web/src/apis/mentor/postApplyMentoring.tsapps/web/src/apis/mentor/postMentorApplication.tsapps/web/src/apis/news/deleteNews.tsapps/web/src/apis/news/postCreateNews.tsapps/web/src/apis/news/putUpdateNews.tsapps/web/src/apis/reports/postReport.tsapps/web/src/apis/universities/postAddWish.tsapps/web/src/app/(home)/_ui/FindLastYearScoreBar/index.tsxapps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsxapps/web/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsxapps/web/src/app/community/[boardCode]/create/PostForm.tsxapps/web/src/app/community/_hooks/useCommunityImageUpload.tsapps/web/src/app/layout.tsxapps/web/src/app/login/LoginContent.tsxapps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsxapps/web/src/app/my/_ui/MyProfileContent/index.tsxapps/web/src/app/my/apply-mentor/_components/UniversityScreen/index.tsxapps/web/src/app/my/apply-mentor/page.tsxapps/web/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSelectUniversities.tsapps/web/src/app/sign-up/email/EmailSignUpForm.tsxapps/web/src/app/university/[homeUniversity]/[id]/_ui/UniversityDetail/_ui/UniversityBtns.tsxapps/web/src/app/university/application/apply/ApplyPageContent.tsxapps/web/src/app/university/application/apply/GpaStep.tsxapps/web/src/app/university/application/apply/LanguageStep.tsxapps/web/src/app/university/score/ScoreCard.tsxapps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsxapps/web/src/components/layout/GlobalLayout/ui/AIInspectorFab/index.tsxapps/web/src/components/login/signup/SignupPrepareScreen.tsxapps/web/src/components/login/signup/SignupProfileScreen.tsxapps/web/src/components/login/signup/SignupRegionScreen.tsxapps/web/src/components/login/signup/SignupSurvey.tsxapps/web/src/components/modal/SurveyModal.tsxapps/web/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.tsapps/web/src/components/ui/Toast/index.tsxapps/web/src/lib/react-query/queryClient.tsapps/web/src/lib/toast/options.tsapps/web/src/lib/zustand/useToastStore.tsapps/web/src/utils/authUtils.tsapps/web/src/utils/axiosInstance.tsapps/web/src/utils/errorHandler.ts
💤 Files with no reviewable changes (14)
- apps/web/src/apis/universities/postAddWish.ts
- apps/web/src/apis/mentor/postMentorApplication.ts
- apps/web/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts
- apps/web/src/apis/mentor/postApplyMentoring.ts
- apps/web/src/apis/applications/postSubmitApplication.ts
- apps/web/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.ts
- apps/web/src/apis/Auth/postSignUp.ts
- apps/web/src/apis/community/deleteLikePost.ts
- apps/web/src/apis/community/postLikePost.ts
- apps/web/src/lib/zustand/useToastStore.ts
- apps/web/src/apis/Auth/postEmailVerification.ts
- apps/web/src/apis/Auth/deleteAccount.ts
- apps/web/src/components/ui/Toast/index.tsx
- apps/web/src/utils/errorHandler.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4cf3c4483b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // 인증 오류는 인터셉터 리다이렉트 토스트에서만 처리 | ||
| if (isUnauthorized(status)) return; |
There was a problem hiding this comment.
Do not suppress all 401 mutation toasts globally
The new global if (isUnauthorized(status)) return; in mutation errors drops every 401 toast, but not all 401s are interceptor redirect cases. Public-auth mutations like usePostEmailAuth (apps/web/src/apis/Auth/postEmailLogin.ts) now have no local onError toast, so a 401 from /auth/email/sign-in can fail silently (no redirect toast and no global toast). This regresses core login feedback for invalid/expired credentials unless each affected mutation adds its own error UI.
Useful? React with 👍 / 👎.
관련 이슈
작업 내용
react-hot-toast단일 체계로 전환했습니다.<Toaster />를 1회만 마운트하도록 변경했습니다.QueryClient전역queryCache/mutationCache onError로 일원화했습니다.id기반 dedupe를 적용했습니다.toast.info의미 유지를 위해 공통 옵션(infoToastOptions,warningToastOptions)을 추가했습니다.apps/web/src/lib/zustand/useToastStore.tsapps/web/src/components/ui/Toast/index.tsxapps/web/src/utils/errorHandler.ts특이 사항
onError보다 전역 정책 메시지를 우선합니다.리뷰 요구사항 (선택)
검증
pnpm --filter @solid-connect/web run ci:checkpnpm --filter @solid-connect/web run build