From 7b3c27bd2112db9294e00d4d600595740039644f Mon Sep 17 00:00:00 2001 From: joyeongchan Date: Wed, 12 Aug 2026 20:40:39 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=95=B1=20=EA=B3=B5=EC=9C=A0=20?= =?UTF-8?q?=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실패 사유와 무관하게 같은 에러 화면만 보여주던 문제를 시안대로 나눈다. - postWishLinkFromShare 가 message 대신 사유(reason)를 반환하도록 변경 응답 body 를 파싱해 서버 code 도 함께 담는다 - refreshToken 이 없어 갱신 못 한 401 도 sessionExpired 로 분류 - 로그인 유도 / 재시도 가능 / 재시도 불가 3갈래로 화면 분기 - 재시도는 1회까지, 이후엔 확인 버튼만 노출 - 로그인 유도 화면 태그 일러스트 추가 (Figma 벡터 합성 @3x) --- .../images/share-bottom-sheet/login-tag.png | Bin 0 -> 41109 bytes apps/app/components/ShareBottomSheet.tsx | 192 ++++++++++++++---- apps/app/utils/postWishLinkFromShare.ts | 47 ++++- 3 files changed, 192 insertions(+), 47 deletions(-) create mode 100644 apps/app/assets/images/share-bottom-sheet/login-tag.png diff --git a/apps/app/assets/images/share-bottom-sheet/login-tag.png b/apps/app/assets/images/share-bottom-sheet/login-tag.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1e40fe8e508eccc9017349071f7812d7e42f82 GIT binary patch literal 41109 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 = { + unauthenticated: '', + sessionExpired: '로그인이 만료됐어요', + network: '네트워크 연결을 확인해주세요', + server: '일시적인 오류가 발생했어요', +}; + +/** 로그인으로 유도할 사유 — 재시도해도 결과가 같다 */ +const LOGIN_REQUIRED_REASONS: ShareFailureReasonT[] = ['unauthenticated', 'sessionExpired']; + +/** 재시도는 1회까지. 또 실패하면 확인 버튼만 남긴다. */ +const MAX_RETRY_COUNT = 1; + +type SheetStateT = + | { status: 'loading' } + | { status: 'success' } + | { status: 'failure'; reason: ShareFailureReasonT }; export default function ShareBottomSheet(props: ShareExtensionProps) { return ( @@ -15,13 +34,17 @@ export default function ShareBottomSheet(props: ShareExtensionProps) { } function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { - const [sheetStatus, setSheetStatus] = useState<'loading' | 'success' | 'error'>('loading'); + const [sheetState, setSheetState] = useState({ status: 'loading' }); + const [retryCount, setRetryCount] = useState(0); - const handleOpenWishlist = () => { - /** openHostApp path 규칙: `/{path}?{query}` — `web=...`만 넘기면 `/web=...` 라우트로 해석됨 */ - openHostApp(`/?web=${encodeURIComponent('/archive/wish')}`); + /** openHostApp path 규칙: `/{path}?{query}` — `web=...`만 넘기면 `/web=...` 라우트로 해석됨 */ + const openHostAppAt = (webPath: string) => { + openHostApp(`/?web=${encodeURIComponent(webPath)}`); }; + const handleOpenWishlist = () => openHostAppAt('/archive/wish'); + const handleOpenLogin = () => openHostAppAt('/login'); + useEffect(() => { /** * NOTE: 웹은 url, 앱은 text로 링크와 상품 설명이 함께 오는 경우가 많음 @@ -33,8 +56,9 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { const urlFromText = text?.match(/https?:\/\/[^\s]+/)?.[0]?.replace(/[),.]+$/, ''); const productUrl = url ?? urlFromText; + /** 링크를 못 찾은 경우 — 재시도해도 같으므로 서버 오류와 같은 화면으로 둔다 */ if (!productUrl) { - setSheetStatus('error'); + setSheetState({ status: 'failure', reason: 'server' }); return; } @@ -45,12 +69,9 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { if (!isMounted) return; - if (result.ok) { - setSheetStatus('success'); - return; - } - - setSheetStatus('error'); + setSheetState( + result.ok ? { status: 'success' } : { status: 'failure', reason: result.reason } + ); }; void registerWish(); @@ -58,15 +79,21 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { return () => { isMounted = false; }; - }, [url, text]); + // retryCount 가 바뀌면 같은 링크로 다시 등록을 시도한다 + }, [url, text, retryCount]); - if (sheetStatus === 'loading') + const handleRetry = () => { + setSheetState({ status: 'loading' }); + setRetryCount(count => count + 1); + }; + + if (sheetState.status === 'loading') return ( - 위시템을 담고 있어요 + 위시를 담고 있어요 @@ -80,41 +107,83 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { ); - if (sheetStatus === 'error') + if (sheetState.status === 'failure') { + const { reason } = sheetState; + const isLoginRequired = LOGIN_REQUIRED_REASONS.includes(reason); + /** 토큰 자체가 없으면 실패가 아니라 로그인 유도 화면 */ + const isLoginPrompt = reason === 'unauthenticated'; + const canRetry = !isLoginRequired && retryCount < MAX_RETRY_COUNT; + return ( close()}> - - 위시템을 저장하지 못했어요 - - - - - - + + + {isLoginPrompt ? '위시를 담으려면 로그인 해주세요' : '위시를 저장하지 못했어요'} + + {FAILURE_DESCRIPTION[reason] ? ( + + {FAILURE_DESCRIPTION[reason]} + + ) : null} - close()}> - - 확인 - - + {isLoginPrompt ? ( + + + + ) : ( + + + + + + )} + + {isLoginRequired || canRetry ? ( + + close()}> + + {isLoginRequired ? '나중에 할게요' : '확인'} + + + + + + {isLoginRequired ? '로그인하기' : '다시 시도'} + + + + ) : ( + close()}> + + 확인 + + + )} ); + } return ( close()}> - 위시템을 저장했어요 + 위시를 저장 했어요 @@ -129,9 +198,9 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { /> - + - 위시템 보러가기 + 위시 보러가기 @@ -215,9 +284,23 @@ const styles = StyleSheet.create({ borderRadius: 24, marginBottom: 20, }, + titleGroup: { + alignItems: 'center', + gap: 4, + }, title: { fontSize: 20, + lineHeight: 28, fontWeight: 'bold', + color: '#2D3037', + textAlign: 'center', + }, + description: { + fontSize: 16, + lineHeight: 22, + fontWeight: '500', + color: '#686F7E', + textAlign: 'center', }, image: { width: 145, @@ -225,6 +308,12 @@ const styles = StyleSheet.create({ marginTop: 52, marginBottom: 45, }, + /** 시안 태그 실측 117.6x142.3 — 169px 영역 안에 여백 13 을 두고 들어간다 */ + loginImage: { + width: 118, + height: 143, + marginVertical: 13, + }, imageContainer: { position: 'relative', }, @@ -244,17 +333,40 @@ const styles = StyleSheet.create({ backgroundColor: '#F4F4F6', }, - button: { + /** 시안 버튼 폭 175/176 + gap 12 — 기기 폭에 맞춰 균등 분할한다 */ + buttonRow: { + flexDirection: 'row', + gap: 12, width: '100%', + }, + button: { height: 54, - backgroundColor: '#191B1F', borderRadius: 12, alignItems: 'center', justifyContent: 'center', }, + buttonFull: { + width: '100%', + backgroundColor: '#191B1F', + }, + buttonPrimary: { + flex: 1, + backgroundColor: '#191B1F', + }, + buttonSecondary: { + flex: 1, + backgroundColor: '#E9E9ED', + }, buttonText: { color: '#FFFFFF', fontSize: 16, + lineHeight: 22, + fontWeight: '600', + }, + buttonSecondaryText: { + color: '#686F7E', + fontSize: 16, + lineHeight: 22, fontWeight: '600', }, diff --git a/apps/app/utils/postWishLinkFromShare.ts b/apps/app/utils/postWishLinkFromShare.ts index f42c99da..218e6cdf 100644 --- a/apps/app/utils/postWishLinkFromShare.ts +++ b/apps/app/utils/postWishLinkFromShare.ts @@ -2,7 +2,23 @@ import { postTokenRefresh } from '@/apis/postTokenRefresh'; import { TokenStorage } from './tokenStorage'; -type PostWishLinkFromShareResultT = { ok: true } | { ok: false; message: string }; +/** + * 실패 사유. 문구는 시트가 정하고 여기서는 사유만 식별한다. + * - `unauthenticated` 토큰 없음 → 로그인 유도 + * - `sessionExpired` refresh 실패 → 로그인 유도 + * - `network` 네트워크 예외 → 재시도 가능 + * - `server` 서버 오류 → 재시도 가능 + */ +export type ShareFailureReasonT = 'unauthenticated' | 'sessionExpired' | 'network' | 'server'; + +export type PostWishLinkFromShareResultT = + | { ok: true } + | { + ok: false; + reason: ShareFailureReasonT; + /** 서버가 내려준 에러 코드 (로컬 판단 실패면 없음) */ + code?: string; + }; const postWishLink = async (productUrl: string, accessToken: string) => fetch(`${process.env.EXPO_PUBLIC_API_URL}/api/v1/wishlists`, { @@ -15,17 +31,27 @@ const postWishLink = async (productUrl: string, accessToken: string) => body: JSON.stringify({ url: productUrl }), }); +/** 실패 응답 body 의 code 만 뽑는다. 파싱 실패는 무시 — 사유는 status 로 이미 갈렸다. */ +const readErrorCode = async (response: Response) => { + try { + const body = (await response.json()) as { code?: string | null }; + return body.code ?? undefined; + } catch { + return undefined; + } +}; + /** Share Extension에서 링크로 위시 등록 */ export const postWishLinkFromShare = async ( productUrl: string ): Promise => { - if (!process.env.EXPO_PUBLIC_API_URL) - return { ok: false, message: 'API 주소가 설정되지 않았어요' }; + /** 빌드 설정 누락 — 재시도해도 동일하므로 서버 오류로 묶는다 */ + if (!process.env.EXPO_PUBLIC_API_URL) return { ok: false, reason: 'server' }; const accessToken = await TokenStorage.getAccessToken(); const refreshToken = await TokenStorage.getRefreshToken(); - if (!accessToken) return { ok: false, message: '로그인이 필요해요' }; + if (!accessToken) return { ok: false, reason: 'unauthenticated' }; try { /** 위시 등록 시도 */ @@ -38,7 +64,7 @@ export const postWishLinkFromShare = async ( if (!refreshResponse.ok) { /** 죽은 토큰으로 재시도가 반복되지 않도록 정리 */ if (refreshResponse.status === 401) await TokenStorage.clearTokens(); - return { ok: false, message: '로그인이 만료됐어요' }; + return { ok: false, reason: 'sessionExpired' }; } /** 토큰 갱신 후 토큰 저장 */ @@ -51,10 +77,17 @@ export const postWishLinkFromShare = async ( postWishResponse = await postWishLink(productUrl, refreshBody.data.accessToken); } - if (!postWishResponse.ok) return { ok: false, message: '요청 처리 중 오류가 발생했습니다.' }; + if (!postWishResponse.ok) { + const code = await readErrorCode(postWishResponse); + /** refreshToken 이 없어 갱신조차 못 한 401 도 세션 만료로 본다 */ + const reason: ShareFailureReasonT = + postWishResponse.status === 401 ? 'sessionExpired' : 'server'; + + return { ok: false, reason, ...(code ? { code } : {}) }; + } return { ok: true }; } catch { - return { ok: false, message: '네트워크 오류가 발생했어요' }; + return { ok: false, reason: 'network' }; } }; From 5ba4d5d4c4af442236cf18f56a2efc9413298785 Mon Sep 17 00:00:00 2001 From: joyeongchan Date: Thu, 13 Aug 2026 02:01:49 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=EC=A0=81=20=ED=95=84=EB=93=9C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit server 사유가 재시도 가능한 5xx 와 불가능한 로컬 오류를 함께 나타내, API 주소 누락·링크 추출 실패에도 "다시 시도" 가 떴다. - 결과 타입에 retryable 추가, 각 실패 지점이 직접 판단 - canRetry 를 로그인 여부의 부정값 대신 retryable 로 계산 --- apps/app/components/ShareBottomSheet.tsx | 16 +++++++++------- apps/app/utils/postWishLinkFromShare.ts | 23 ++++++++++++++--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/app/components/ShareBottomSheet.tsx b/apps/app/components/ShareBottomSheet.tsx index 787055d7..8d5274bd 100644 --- a/apps/app/components/ShareBottomSheet.tsx +++ b/apps/app/components/ShareBottomSheet.tsx @@ -23,7 +23,7 @@ const MAX_RETRY_COUNT = 1; type SheetStateT = | { status: 'loading' } | { status: 'success' } - | { status: 'failure'; reason: ShareFailureReasonT }; + | { status: 'failure'; reason: ShareFailureReasonT; retryable: boolean }; export default function ShareBottomSheet(props: ShareExtensionProps) { return ( @@ -56,9 +56,9 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { const urlFromText = text?.match(/https?:\/\/[^\s]+/)?.[0]?.replace(/[),.]+$/, ''); const productUrl = url ?? urlFromText; - /** 링크를 못 찾은 경우 — 재시도해도 같으므로 서버 오류와 같은 화면으로 둔다 */ + /** 링크를 못 찾은 경우 — 공유된 내용이 그대로라 다시 눌러도 같다 */ if (!productUrl) { - setSheetState({ status: 'failure', reason: 'server' }); + setSheetState({ status: 'failure', reason: 'server', retryable: false }); return; } @@ -70,7 +70,9 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { if (!isMounted) return; setSheetState( - result.ok ? { status: 'success' } : { status: 'failure', reason: result.reason } + result.ok + ? { status: 'success' } + : { status: 'failure', reason: result.reason, retryable: result.retryable } ); }; @@ -108,11 +110,11 @@ function ShareBottomSheetContent({ url, text }: ShareExtensionProps) { ); if (sheetState.status === 'failure') { - const { reason } = sheetState; + const { reason, retryable } = sheetState; const isLoginRequired = LOGIN_REQUIRED_REASONS.includes(reason); - /** 토큰 자체가 없으면 실패가 아니라 로그인 유도 화면 */ + /** 토큰 자체가 없으면 실패가 아니라 로그인 유도 화면 (세션 만료는 실패 화면 + 로그인 버튼) */ const isLoginPrompt = reason === 'unauthenticated'; - const canRetry = !isLoginRequired && retryCount < MAX_RETRY_COUNT; + const canRetry = retryable && retryCount < MAX_RETRY_COUNT; return ( close()}> diff --git a/apps/app/utils/postWishLinkFromShare.ts b/apps/app/utils/postWishLinkFromShare.ts index 218e6cdf..040d40f0 100644 --- a/apps/app/utils/postWishLinkFromShare.ts +++ b/apps/app/utils/postWishLinkFromShare.ts @@ -16,6 +16,11 @@ export type PostWishLinkFromShareResultT = | { ok: false; reason: ShareFailureReasonT; + /** + * 다시 시도할 가치가 있는지. 같은 reason 이라도 갈린다 — + * 서버 5xx 는 재시도 가능하지만 설정 누락은 몇 번을 눌러도 같다. + */ + retryable: boolean; /** 서버가 내려준 에러 코드 (로컬 판단 실패면 없음) */ code?: string; }; @@ -32,12 +37,12 @@ const postWishLink = async (productUrl: string, accessToken: string) => }); /** 실패 응답 body 의 code 만 뽑는다. 파싱 실패는 무시 — 사유는 status 로 이미 갈렸다. */ -const readErrorCode = async (response: Response) => { +const readErrorCode = async (response: Response): Promise => { try { const body = (await response.json()) as { code?: string | null }; - return body.code ?? undefined; + return body.code ?? null; } catch { - return undefined; + return null; } }; @@ -45,13 +50,13 @@ const readErrorCode = async (response: Response) => { export const postWishLinkFromShare = async ( productUrl: string ): Promise => { - /** 빌드 설정 누락 — 재시도해도 동일하므로 서버 오류로 묶는다 */ - if (!process.env.EXPO_PUBLIC_API_URL) return { ok: false, reason: 'server' }; + /** 빌드 설정 누락 — 다시 눌러도 결과가 같다 */ + if (!process.env.EXPO_PUBLIC_API_URL) return { ok: false, reason: 'server', retryable: false }; const accessToken = await TokenStorage.getAccessToken(); const refreshToken = await TokenStorage.getRefreshToken(); - if (!accessToken) return { ok: false, reason: 'unauthenticated' }; + if (!accessToken) return { ok: false, reason: 'unauthenticated', retryable: false }; try { /** 위시 등록 시도 */ @@ -64,7 +69,7 @@ export const postWishLinkFromShare = async ( if (!refreshResponse.ok) { /** 죽은 토큰으로 재시도가 반복되지 않도록 정리 */ if (refreshResponse.status === 401) await TokenStorage.clearTokens(); - return { ok: false, reason: 'sessionExpired' }; + return { ok: false, reason: 'sessionExpired', retryable: false }; } /** 토큰 갱신 후 토큰 저장 */ @@ -83,11 +88,11 @@ export const postWishLinkFromShare = async ( const reason: ShareFailureReasonT = postWishResponse.status === 401 ? 'sessionExpired' : 'server'; - return { ok: false, reason, ...(code ? { code } : {}) }; + return { ok: false, reason, retryable: reason === 'server', ...(code ? { code } : {}) }; } return { ok: true }; } catch { - return { ok: false, reason: 'network' }; + return { ok: false, reason: 'network', retryable: true }; } };