feat: 앱 공유 바텀시트 에러 상태 분기 - #501
Conversation
실패 사유와 무관하게 같은 에러 화면만 보여주던 문제를 시안대로 나눈다. - postWishLinkFromShare 가 message 대신 사유(reason)를 반환하도록 변경 응답 body 를 파싱해 서버 code 도 함께 담는다 - refreshToken 이 없어 갱신 못 한 401 도 sessionExpired 로 분류 - 로그인 유도 / 재시도 가능 / 재시도 불가 3갈래로 화면 분기 - 재시도는 1회까지, 이후엔 확인 버튼만 노출 - 로그인 유도 화면 태그 일러스트 추가 (Figma 벡터 합성 @3x)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다. |
📝 WalkthroughWalkthrough공유 위시 등록 결과가 실패 사유와 서버 코드를 반환하도록 변경되었습니다. 바텀시트는 인증·세션·네트워크·서버 오류를 구분합니다. 인증 오류에는 로그인을 유도하고, 재시도 가능한 오류에는 1회 재시도를 제공합니다. Changes공유 위시 실패 처리
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant 공유 바텀시트
participant 위시 등록 함수
participant 서버
공유 바텀시트->>위시 등록 함수: 공유 위시 등록 요청
위시 등록 함수->>서버: 위시 링크 전송
서버-->>위시 등록 함수: 상태 코드와 오류 code 반환
위시 등록 함수-->>공유 바텀시트: 실패 사유와 재시도 가능 여부 반환
공유 바텀시트-->>공유 바텀시트: 로그인, 재시도 또는 확인 화면 표시
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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: 3
🤖 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/app/components/ShareBottomSheet.tsx`:
- Around line 112-114: Update the isLoginPrompt condition in ShareBottomSheet to
use isLoginRequired, so both unauthenticated and sessionExpired reasons display
the login prompt instead of the generic failure state. Preserve the existing
LOGIN_REQUIRED_REASONS classification and downstream rendering behavior.
In `@apps/app/utils/postWishLinkFromShare.ts`:
- Around line 34-42: Update readErrorCode to eliminate undefined returns for
ESLint no-undefined compliance: return null when the response body lacks a code
or JSON parsing fails, and update its callers to check for a non-null error code
before using it.
- Line 12: ShareFailureReasonT의 server 사유만으로 재시도 여부를 판단하지 않도록 명시적 retryable 필드
또는 로컬 실패 사유를 추가하세요. apps/app/utils/postWishLinkFromShare.ts 12-12에서 결과 타입을 확장하고,
49-49의 API 주소 누락 결과는 재시도 불가로 반환하세요. apps/app/components/ShareBottomSheet.tsx
59-61의 상품 URL 누락 결과도 재시도 불가로 저장하세요. 같은 파일 112-115의 canRetry는 로그인 여부가 아니라 결과의 명시적
재시도 가능 여부를 사용해 계산하세요.
🪄 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: 6974da19-5afd-430c-ac85-9247b7759411
⛔ Files ignored due to path filters (1)
apps/app/assets/images/share-bottom-sheet/login-tag.pngis excluded by!**/*.png
📒 Files selected for processing (2)
apps/app/components/ShareBottomSheet.tsxapps/app/utils/postWishLinkFromShare.ts
| const isLoginRequired = LOGIN_REQUIRED_REASONS.includes(reason); | ||
| /** 토큰 자체가 없으면 실패가 아니라 로그인 유도 화면 */ | ||
| const isLoginPrompt = reason === 'unauthenticated'; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
sessionExpired에도 로그인 유도 화면을 표시하세요.
LOGIN_REQUIRED_REASONS는 sessionExpired를 로그인 유도 사유로 분류합니다. 그러나 Line 114는 unauthenticated만 로그인 유도 화면으로 처리합니다. 따라서 세션 만료는 일반 실패 제목과 오류 이미지를 표시합니다. isLoginPrompt 조건에 isLoginRequired를 사용하세요.
수정 예시
- const isLoginPrompt = reason === 'unauthenticated';
+ const isLoginPrompt = isLoginRequired;Also applies to: 123-136
🤖 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/app/components/ShareBottomSheet.tsx` around lines 112 - 114, Update the
isLoginPrompt condition in ShareBottomSheet to use isLoginRequired, so both
unauthenticated and sessionExpired reasons display the login prompt instead of
the generic failure state. Preserve the existing LOGIN_REQUIRED_REASONS
classification and downstream rendering behavior.
server 사유가 재시도 가능한 5xx 와 불가능한 로컬 오류를 함께 나타내, API 주소 누락·링크 추출 실패에도 "다시 시도" 가 떴다. - 결과 타입에 retryable 추가, 각 실패 지점이 직접 판단 - canRetry 를 로그인 여부의 부정값 대신 retryable 로 계산
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/app/utils/postWishLinkFromShare.ts (2)
85-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win4xx 응답을 재시도 가능으로 분류하지 마세요.
현재 401 이외의 모든 HTTP 오류가
retryable: true가 되어 400, 403, 404, 422에서도 재시도 버튼이 표시됩니다. 4xx는 기본적으로retryable: false로 반환하고, 재시도가 필요한 429만 명시적으로 허용하세요.ShareBottomSheet.tsx가 이 값을 UI에 직접 전달합니다.🤖 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/app/utils/postWishLinkFromShare.ts` around lines 85 - 91, Update the failure classification in the postWish response handling so 4xx statuses are non-retryable by default, while preserving sessionExpired for 401 and explicitly allowing retry only for 429; keep server errors retryable. Ensure the retryable value returned to ShareBottomSheet reflects these status rules.
65-72: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHTTP 상태별 오류를 분리하세요.
- 토큰 갱신 응답은 401일 때만
sessionExpired로 처리하세요. 5xx는server,retryable: true와 응답code를 반환하세요. 그 외 4xx는retryable: false로 반환하세요.- 최종 위시 등록 응답은 401만
sessionExpired로 처리하세요. 5xx만server,retryable: true로 반환하고, 4xx는retryable: false로 반환하세요.🤖 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/app/utils/postWishLinkFromShare.ts` around lines 65 - 72, Update both error-handling sites in apps/app/utils/postWishLinkFromShare.ts: the token-refresh branch at lines 65-72 and the final wish-registration response at lines 85-91. In the postTokenRefresh failure path, reserve sessionExpired for 401, return server with retryable true and the response code for 5xx, and return other 4xx errors with retryable false; apply the same status classification to the final post-wish response, preserving token cleanup for 401 responses.
🧹 Nitpick comments (1)
apps/app/utils/postWishLinkFromShare.ts (1)
91-96: 🗄️ Data Integrity & Integration | 🔵 Trivial재시도 가능한 POST의 멱등성을 확인하세요.
retryable: true가 반환되면/api/v1/wishlistsPOST를 다시 전송합니다. 서버가 요청을 처리한 뒤 응답만 유실된 경우 재시도하면 동일한 위시가 중복 등록될 수 있습니다.서버가 사용자와 상품 URL 기준의 unique 제약 또는 idempotency key를 보장하는지 확인하세요. 보장하지 않으면 재시도 전에 서버 중복 방지 로직을 추가하세요.
PR 목표의 서버·네트워크 재시도 요구사항과
postWishLink의 POST 계약에 근거합니다.🤖 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/app/utils/postWishLinkFromShare.ts` around lines 91 - 96, Verify the retry behavior in postWishLink so repeated POST requests are idempotent for the same user and product URL. Confirm that the /api/v1/wishlists server path enforces a unique constraint or idempotency key; if not, add server-side duplicate prevention before allowing retryable failures to trigger another request.
🤖 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/app/utils/postWishLinkFromShare.ts`:
- Around line 95-96: Update the error handling in postWishLinkFromShare so only
the request’s network/transport failure returns reason "network" with retryable
true. Handle refreshResponse.json() parsing failures and
TokenStorage.setTokens()/clearTokens() failures separately, preserving their
appropriate failure reasons and retry policies instead of converting them to
network errors.
- Around line 56-59: Include the TokenStorage.getAccessToken and getRefreshToken
calls within the try/catch in postWishLinkFromShare so rejected storage reads
return a failure result instead of propagating. Map storage errors to the
appropriate distinct ShareFailureReasonT and retryable policy, preserving
unauthenticated only for a missing access token and the existing registerWish
contract in ShareBottomSheet.
---
Outside diff comments:
In `@apps/app/utils/postWishLinkFromShare.ts`:
- Around line 85-91: Update the failure classification in the postWish response
handling so 4xx statuses are non-retryable by default, while preserving
sessionExpired for 401 and explicitly allowing retry only for 429; keep server
errors retryable. Ensure the retryable value returned to ShareBottomSheet
reflects these status rules.
- Around line 65-72: Update both error-handling sites in
apps/app/utils/postWishLinkFromShare.ts: the token-refresh branch at lines 65-72
and the final wish-registration response at lines 85-91. In the postTokenRefresh
failure path, reserve sessionExpired for 401, return server with retryable true
and the response code for 5xx, and return other 4xx errors with retryable false;
apply the same status classification to the final post-wish response, preserving
token cleanup for 401 responses.
---
Nitpick comments:
In `@apps/app/utils/postWishLinkFromShare.ts`:
- Around line 91-96: Verify the retry behavior in postWishLink so repeated POST
requests are idempotent for the same user and product URL. Confirm that the
/api/v1/wishlists server path enforces a unique constraint or idempotency key;
if not, add server-side duplicate prevention before allowing retryable failures
to trigger another request.
🪄 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: 85359791-34aa-4b2d-baf5-91da2ce55b00
📒 Files selected for processing (2)
apps/app/components/ShareBottomSheet.tsxapps/app/utils/postWishLinkFromShare.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/app/components/ShareBottomSheet.tsx
| const accessToken = await TokenStorage.getAccessToken(); | ||
| const refreshToken = await TokenStorage.getRefreshToken(); | ||
|
|
||
| if (!accessToken) return { ok: false, message: '로그인이 필요해요' }; | ||
| if (!accessToken) return { ok: false, reason: 'unauthenticated', retryable: false }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
토큰 저장소 예외를 실패 결과로 변환하세요.
Line 56-57의 토큰 조회는 try 밖에서 실행됩니다. 저장소 조회가 reject되면 postWishLinkFromShare가 결과를 반환하지 않고 reject됩니다. apps/app/components/ShareBottomSheet.tsx의 registerWish는 이 예외를 처리하지 않으므로 바텀시트가 실패 상태로 전환되지 않습니다.
토큰 조회를 예외 처리 범위에 포함하세요. 저장소 오류를 적절한 ShareFailureReasonT와 retryable 정책으로 매핑하세요. 실제 비로그인과 저장소 장애를 unauthenticated로 합치지 마세요.
apps/app/utils/tokenStorage.ts:30-36의 비동기 조회와 apps/app/components/ShareBottomSheet.tsx:67-77의 호출 계약에 근거합니다.
🤖 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/app/utils/postWishLinkFromShare.ts` around lines 56 - 59, Include the
TokenStorage.getAccessToken and getRefreshToken calls within the try/catch in
postWishLinkFromShare so rejected storage reads return a failure result instead
of propagating. Map storage errors to the appropriate distinct
ShareFailureReasonT and retryable policy, preserving unauthenticated only for a
missing access token and the existing registerWish contract in ShareBottomSheet.
| } catch { | ||
| return { ok: false, message: '네트워크 오류가 발생했어요' }; | ||
| return { ok: false, reason: 'network', retryable: true }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
네트워크 예외와 내부 오류를 구분하세요.
Line 95-96의 catch는 네트워크 예외뿐 아니라 refreshResponse.json(), TokenStorage.setTokens(), TokenStorage.clearTokens()의 실패도 잡습니다. 잘못된 갱신 응답이나 저장소 오류가 network와 retryable: true로 변환됩니다.
전송 오류만 이 경로에서 처리하세요. JSON 파싱 오류와 저장소 오류는 각각의 실패 사유와 재시도 정책으로 분류하세요.
PR 목표의 실패 사유 구분 요구사항에 근거합니다.
🤖 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/app/utils/postWishLinkFromShare.ts` around lines 95 - 96, Update the
error handling in postWishLinkFromShare so only the request’s network/transport
failure returns reason "network" with retryable true. Handle
refreshResponse.json() parsing failures and
TokenStorage.setTokens()/clearTokens() failures separately, preserving their
appropriate failure reasons and retry policies instead of converting them to
network errors.
작업 요약
작업 세부 내용
기존에는 실패 사유와 무관하게 "저장하지 못했어요" 한 화면만 떴습니다. 사유는 이미 구분돼 넘어오는데 시트가 버리고 있었습니다.
1. 사유를 식별자로 반환
postWishLinkFromShare가 문구 대신reason을 반환합니다. 문구는 시트가 정합니다.응답 body 파싱을 추가해 서버
code도 함께 담습니다. 로컬 판단 실패(토큰 없음·네트워크 예외·링크 추출 실패)는 응답이 없으므로code가 비어 있습니다.refreshToken이 없어 갱신조차 못 한 401 도sessionExpired로 묶었습니다 — 기존에는 이 경로가 일반 오류로 빠졌습니다.2. 화면 3갈래 분기
unauthenticatedsessionExpirednetworkserver시안상 토큰 만료는 재시도가 아니라 로그인 유도입니다 — 재시도해도 결과가 같기 때문입니다.
문구는
ERROR_MESSAGE_MAP과 달라 시트에 별도로 뒀습니다.3. 로그인 유도 일러스트
시안의 태그 일러스트는 기존 에셋에 없어 새로 추가했습니다. Figma 벡터 8조각을 좌표대로 합성해 @3x(354×429)로 내보냈습니다. 표시 크기는 시안 실측값 118×143 입니다.
스크린샷
연관 이슈
closes #485
Summary by CodeRabbit