Repository navigation
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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 |
|
온보딩은 퍼널 구조를 적용하면 좋을 거 같고, 로그인 기능같은 경우에는 제가 개발해둔 k-connect 로그인을 위한 private npm 패키지를 사용하도록 수정하는 건 어떨까요?! 월요일이나 화요일 중 관련해서 자세한 설명드리도록 하겠습니다. 퍼널 구조 참고용 발표 영상 |
5afef1c to
c304302
Compare
해당 내용 확인해서 수정했습니다. |
|
#97(설정·전화번호 변경·회원 탈퇴) 쪽을 정리하면서 겹치는 부분을 확인해서 공유드립니다. 1. 전화번호 포맷 — 로직은 같고 통과 기준이 다릅니다리뷰 포인트에 적어주신 그 건이 맞습니다. 하이픈을 넣는 로직은 두 구현이 완전히 같습니다.
다만 버튼이 열리는 기준이 다릅니다.
그래서 지금 상태로 둘 다 머지되면 머지 순서는 말씀하신 대로 나중에 들어가는 쪽이 공통 모듈로 빼는 걸로 하면 될 것 같습니다. 2. (리뷰 포인트에 없던 건) 탈퇴·로그아웃이
|
c304302 to
e3417a6
Compare
확인해서 두가지 모두 반영했습니다! |
tnals0924
left a comment
There was a problem hiding this comment.
로그인·온보딩 흐름이 퍼널 하나로 정리돼 있어서 단계별로 따라가기 편했어요. 테스트 방법이랑 트레이드오프도 자세히 적어 주셔서 리뷰하기 수월했습니다.
코멘트는 템플릿 기준으로 [r] 꼭 반영 / [c] 웬만하면 반영 / [a] 사소한 의견으로 달았어요. [r]로 단 네 건(배포 후 홈 진입, 연락처 단계 뒤로가기, 가입 완료 후 뒤로가기, 앱 WebView 동작)은 머지 전에 같이 얘기해 보면 좋겠습니다.
PR 본문도 하나 봐 주세요. 리팩토링하면서 화면 이름이 AuthSignupScreen으로 바뀌었는데 본문에는 아직 AuthFunnelScreen으로 남아 있어요. 제목에도 '스플래시'가 들어가 있는데, 이번 PR에서 빠졌으니 같이 맞춰 주시면 좋을 것 같아요.
걱정하셨던 부분 중에 확인해 본 것도 남겨요.
- 콜백에서 인증 code가 두 번 나가는 문제: 패키지의
useKConnectCallback이 결과를 동기로 돌려주고completeLoginOnce로 저장값도 한 번만 꺼내 써요. 콜백 화면의hasHandled까지 있어서 괜찮아 보여요. history.replace("complete", {}): use-funnel이 이전 context와 합쳐 줘서name이 유지되고 guard도 통과해요.src/lib/useFunnel.ts: 0.0.16 원본 어댑터랑 비교해 보니 옵션 이름만 바뀐 게 맞았어요.
| element: <HomeScreen />, | ||
| // 앱 첫 진입점 — 로그인한 기록이 없으면 스플래시(→ 로그인 화면)로 보낸다 | ||
| element: ( | ||
| <AuthEntryGate> |
There was a problem hiding this comment.
[r] 이대로 머지되면 dev 배포에서 홈에 들어갈 방법이 없어질 것 같아요.
main에 push되면 dev가 자동 배포되는데, 지금 상태에서는 이렇게 돼요.
/에 들어가면AuthEntryGate가/auth로 보내요.- Docker 빌드에
VITE_KCONNECT_CLIENT_ID가 안 넘어가서K-CONNECT로 시작하기를 누르면 실패 토스트만 떠요. - 우회용
?mockKConnect=는import.meta.env.DEV에서만 동작해서 배포 빌드에서는 막혀 있어요.
결국 홈을 보려면 콘솔에서 sessionStorage를 직접 넣는 방법밖에 없어요. 그리고 WDS_NPM_TOKEN에 billilge 패키지 읽기 권한이 없으면 pnpm install 단계에서 배포가 아예 실패하고요. 리뷰 포인트에 남겨 주신 CI 건은 후속으로 미루기보다 이 PR에서 같이 처리하거나, 처리 전까지는 가드를 잠시 꺼 두는 게 좋을 것 같은데 어떻게 생각하세요?
| <AuthPhoneStep | ||
| initialPhone={context.phone ?? ""} | ||
| name={context.name} | ||
| onBack={() => history.back()} |
There was a problem hiding this comment.
[r] 실제 K-CONNECT로 로그인한 신규 사용자가 연락처 단계에서 뒤로가기를 누르면 K-CONNECT 쪽 페이지로 나갈 것 같아요.
콜백 화면이 /auth로 replace하면서 initial로 연락처 단계부터 시작하면, 퍼널 history에는 연락처 단계 하나만 있어요. 이 상태에서 history.back()은 navigate(-1)이라 브라우저 history의 바로 앞, 즉 K-CONNECT 페이지로 가게 돼요.
?mockKConnect=new로 테스트하면 앞에 로그인 단계가 있어서 재현되지 않고 실제 로그인에서만 생겨서 놓치기 쉬울 것 같아요. 연락처 단계가 퍼널의 첫 기록일 때는 로그인 단계로 replace하는 식으로 처리하면 어떨까요?
| steps: AUTH_SIGNUP_STEP_OPTIONS, | ||
| }); | ||
|
|
||
| const goHome = () => navigate("/", { replace: true }); |
There was a problem hiding this comment.
[r] 가입을 마치고 홈에서 뒤로가기를 누르면 연락처 단계로 다시 들어가져요.
약관 동의 → 가입 완료 → 홈이 모두 replace라서, 홈 바로 앞 history가 /auth?auth.step=phone(이름·연락처가 채워진 상태)으로 남아요. 거기서 다시 진행하면 submitOnboarding이 한 번 더 호출되고요.
/auth에서 이미 로그인한 상태면 홈으로 보내는 처리를 두면, 이 경우와 로그인한 사용자가 /auth로 직접 들어오는 경우를 같이 막을 수 있을 것 같아요. 다만 가입 완료 단계 직전에 markLoggedIn()을 부르고 있어서, 가입 완료 단계는 예외로 둬야 할 것 같습니다.
| // 로그인 표시는 sessionStorage에만 있어서, 같은 탭의 새로고침은 홈이 유지되고 탭·브라우저를 새로 열 때만 | ||
| // 이 가드가 로그인 화면으로 보낸다. | ||
| function AuthEntryGate({ children }: AuthEntryGateProps) { | ||
| if (!isLoggedIn()) { |
There was a problem hiding this comment.
[r] 앱 WebView에서는 어떻게 동작하는 게 맞을지 확인 부탁드려요.
PR 본문에는 "앱 WebView에서는 이 웹 로그인을 쓰면 안 된다(RFC 8252)"고 되어 있는데, 이 가드는 WebView에서도 똑같이 /auth로 보내고 있어요. AuthLoginStep도 isInAppShell()로 앱 레이아웃을 따로 잡아 두셔서, 앱에서 버튼을 누르면 WebView 안에서 그대로 K-CONNECT로 이동하게 될 것 같아요.
앱에서는 가드를 건너뛸지, 브리지로 네이티브 로그인을 띄울지 정해 두고 머지하면 좋을 것 같습니다.
| }) { | ||
| const { login } = useKConnectLogin({ clientId: KCONNECT_CLIENT_ID }); | ||
| const [isLoggingIn, setIsLoggingIn] = useState(false); | ||
| const [toastOpen, setToastOpen] = useState(hasFailedLogin); |
There was a problem hiding this comment.
[c] 로그인 실패 토스트가 실패하지 않았는데도 다시 뜨는 경우가 있어요.
loginFailed가 location.state에 남아 있고, 퍼널이 이동할 때도 ...location.state로 같이 넘기고 있어요. 그래서 아래 경우에 AuthLoginStep이 다시 마운트되면서 토스트가 또 떠요.
- 실패 후
/auth에서 새로고침할 때 - 목 흐름에서 연락처 단계로 갔다가 뒤로 돌아올 때
한 번 읽고 나면 state에서 loginFailed를 지워 주면 좋을 것 같아요.
| }) { | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
| const isAllAgreed = TERMS_IDS.every((id) => agreements[id]); | ||
| const isRequiredAgreed = agreements.service && agreements.privacy; |
There was a problem hiding this comment.
[c] 필수 여부를 service && privacy로 직접 적고 있는데, TERMS[id].isRequired가 이미 있으니 거기서 꺼내 쓰면 약관이 바뀌어도 한 곳만 고치면 될 것 같아요.
const isRequiredAgreed = TERMS_IDS.filter((id) => TERMS[id].isRequired).every(
(id) => agreements[id],
);| @@ -0,0 +1,65 @@ | |||
| import type { KConnectUser, OnboardingSubmission } from "@/entities/auth/types"; | |||
|
|
|||
| const MOCK_DELAY_MS = 700; | |||
There was a problem hiding this comment.
[a] coding-style.md에서 목데이터는 <도메인>Mock.ts에 두기로 해서, MOCK_NEW_USER·MOCK_EXISTING_USER는 authMock.ts로 옮기면 다른 도메인과 맞을 것 같아요. 로그인에 mockupApi를 안 쓰신 이유(요청마다 결과가 달라짐)는 주석 보고 이해했어요. 그래도 지연 시간은 mockupApi처럼 배포 빌드에서 0으로 두면 어떨까요? 지금은 배포에서도 700ms씩 기다려요.
| return; | ||
| } | ||
|
|
||
| exchangeKConnectCode(result) |
There was a problem hiding this comment.
[a] result를 그대로 넘기면 type: "success"도 같이 들어가요. 지금은 목이라 상관없지만, 실 API에서 이 값을 그대로 요청 body로 보내면 type까지 전송될 수 있어요. 필요한 값만 꺼내서 넘기면 좋을 것 같아요.
const { code, codeVerifier, redirectUri } = result;
exchangeKConnectCode({ code, codeVerifier, redirectUri })| children: [ | ||
| { | ||
| element: <HomeScreen />, | ||
| // 앱 첫 진입점 — 로그인한 기록이 없으면 스플래시(→ 로그인 화면)로 보낸다 |
There was a problem hiding this comment.
[a] 스플래시가 빠졌는데 주석에는 "스플래시(→ 로그인 화면)로 보낸다"가 남아 있어요. src/entities/auth/session.ts 2번째 줄 주석도 같이 고쳐 주시면 좋을 것 같아요.
| @@ -0,0 +1,161 @@ | |||
| import eventsIllustration from "@/assets/images/auth/login-events.png"; | |||
There was a problem hiding this comment.
[a] 로그인 일러스트 PNG가 장당 500KB 정도라 첫 화면에서 1.5MB를 받아요. 투명 배경 때문에 PNG로 두셨다고 했는데, WebP나 AVIF도 투명 배경을 지원해서 용량을 꽤 줄일 수 있을 것 같아요. 변환 도구를 새로 들이는 게 부담스러우면 이미지만 미리 변환해서 넣는 것도 방법일 것 같습니다.
- 계정 정보와 앱 버전을 `entities/users/`로 옮기고 화면은 `use()`로 읽는다. 다른 기능과 같은
`<도메인>Api` + `mockupApi` + Suspense 구조다. 카드 안쪽만 데이터 컴포넌트로 빼서
껍데기·라벨은 그대로 그려지고, 받는 동안은 전용 스켈레톤이 행 자리를 채운다
- 앱 버전을 `{ version, isLatest }`로 나눠 받는다 — "26.0.1 · 최신버전" 문구 조합은 화면이 한다
- 전화번호 하이픈 포맷·유효성을 `lib/phone.ts`로 뺀다. #110(PR #112)의 연락처 입력과 같은
로직이라 그쪽이 import만 하면 되도록 먼저 공용 위치에 둔다. 통과 기준은 `010` 고정으로
맞췄다 — 01X는 2G 종료로 전부 010 번호이동이 끝나서 새로 입력될 일이 없다
- `SettingsRow` props를 variant별로 나눠 `variant="account"` + `tone` 조합이 타입에서 걸리게 한다
- 로그아웃 확정과 탈퇴 완료의 "계정에서 나가기"를 `useAccountExit` 훅 하나로 묶는다 —
세션 정리를 붙일 때 한쪽을 빠뜨리지 않게 한다
- 탈퇴 완료 화면을 주소로 바로 열면 홈으로 보낸다(사물함 신청 완료와 같은 처리). navigate
state는 꾸밀 수 있는 값이라 실제 본인 확인은 서버 몫이라는 걸 주석으로 남겼다
- 탈퇴 확인 모달도 로그아웃처럼 닫고 이동하도록 순서를 맞춘다
- 경로를 `/settings/withdrawal`로 바꿔 컴포넌트 이름(`Withdrawal*`)과 형태를 맞춘다
- `settings`가 용어 사전에 없는 이름이라는 점과, 백엔드 엔드포인트가 나오면 맞춰야 한다는 걸
경로 상수와 `usersApi`에 주석으로 남겼다
|
앞서 공유드린 전화번호 포맷 건, #97 리뷰에서 "나중에 머지되는 쪽이 정리하는 것보다 먼저 공용 위치에 빼두는 게 낫다"는 의견을 받아서 #97에서 먼저 빼놨습니다. #112 쪽은 import만 해주시면 됩니다! // src/lib/phone.ts
export const PHONE_NUMBER_MAX_LENGTH = 13; // 010-1234-5678
export function toPhoneDigits(value: string): string;
export function formatPhoneNumber(value: string): string;
export function isValidPhoneNumber(value: string): boolean;
1. 2. 통과 기준을 01X(011·016~019)는 2G 종료와 함께 전부 010으로 번호이동이 끝나서 지금 새로 입력될 일이 없다고 보고 좁은 쪽으로 정했습니다. 같은 앱에서 가입은 되는데 설정에서 같은 번호로 못 바꾸는 상황을 막는 게 더 중요하다고 판단했고요. 혹시 온보딩에서는 넓게 받아야 할 이유가 있으면 알려주세요 — 그러면 공용 쪽 정규식을 넓히고 #97도 같이 맞추겠습니다.
#97은 이 변경까지 올려뒀습니다. |
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
앱을 처음 열었을 때의 진입 흐름(로그인, 온보딩)이 없어서, 모든 화면이 로그인 없이 바로 열립니다. Figma의 스플래시 & 로그인(3658:112096)과 온보딩(3738:73115) 화면을 화면설계서(스플래시/로그인 3747:74283, 온보딩 3747:74298, 화면 ID FLW_HOM_001)에 맞춰 구현하고, 로그인은 K-CONNECT(국민대 OIDC) 인증과
@billilge/oauth-kconnect패키지로 연결합니다. 스플래시는 웹에서 쓰지 않기로 해서 이 PR에서 제외했습니다.❓ 왜 해결해야 하나요?
신규 사용자는 K-CONNECT 인증 후 연락처와 약관 동의를 받아야 서비스를 쓸 수 있습니다. 이 입구가 없으면 행사 신청 같은 기능에 필요한 연락처를 받을 방법이 없고, 앱을 처음 여는 사용자가 어디로 시작해야 하는지도 정해지지 않습니다.
⭐ 어떻게 해결했나요?
1. 로그인·온보딩 퍼널 (
AuthFunnelScreen)@use-funnel/react-router-dom의useFunnel하나로 로그인과 온보딩을 한 화면에 묶었습니다. 라우트는/auth하나이고, 단계는?auth.step=으로 나뉩니다.loginK-CONNECT로 시작하기phone다음활성화terms다음활성화(선택 약관은 미동의여도 진행)termsDetail확인했어요·뒤로가기로 복귀하고 동의 상태는 바뀌지 않음completestream 시작하기·닫기는 홈으로 이동history.replace로 context만 갈아 끼워서 히스토리가 늘지 않습니다.guard가 걸러서 로그인 단계를 보여 줍니다.src/lib/useFunnel.ts에 같은 어댑터를createUseFunnel로 직접 만들고 옵션 이름만viewTransition으로 바꿨습니다.2. K-CONNECT 로그인 (
@billilge/oauth-kconnect)/oauth/kconnect는 K-CONNECT에 등록하는 redirect URI이자 패키지가 정한 경로라 바꾸지 않았습니다. 어느 경우든 주소창에code가 남지 않도록replace로 이동합니다./auth로 이동하면서 이름을 navigate state로 넘기고, 퍼널의initial(주소에 단계가 없을 때만 쓰임)이 연락처 단계에서 시작합니다.POST /auth/kconnect(요청{ code, codeVerifier, redirectUri })의 주소와 응답 형식이 아직 없어서exchangeKConnectCode는 응답만 흉내 냅니다. 실 API가 붙으면 이 함수만 교체합니다.client_id는 코드에 넣지 않고 환경 변수VITE_KCONNECT_CLIENT_ID로 받습니다(.env.example참고).expo-auth-session을 optional peer로 선언하는데 pnpmautoInstallPeers가 설치까지 해서 웹에 필요 없는 Expo 의존성 수백 개(lockfile 약 4,450줄)가 딸려 와서,pnpm-workspace.yaml의overrides로 뺐습니다. lockfile 변경은 15줄입니다. Docker 빌드의--frozen-lockfile이 이 설정을 보도록Dockerfile의COPY에pnpm-workspace.yaml을 추가했습니다.3. 진입 흐름
/에AuthEntryGate를 두었습니다. 이 탭에서 로그인한 기록이 없으면/auth(로그인)로 보내고(replace), 있으면 홈을 그대로 보여 줍니다. 다른 화면 경로(/events등)에는 가드를 걸지 않았습니다.stream:auth:logged-in = "1"만 저장합니다(이름 등 로그인 정보는 저장하지 않음). 같은 탭에서는 새로고침해도 홈이 유지되고, 탭·브라우저를 새로 열면 로그인부터 시작합니다.🧪 목데이터로 테스트하는 방법
K-CONNECT 계정과 client_id 없이도 로그인 이후 흐름을 확인할 수 있습니다. 개발 서버(
pnpm dev)에서 아래 주소를 열고K-CONNECT로 시작하기를 누릅니다. 로그인 상태는 탭 단위라서 이미 로그인된 탭이면 홈으로 이동하니, 새 탭(또는 시크릿 창)으로 여세요./auth?mockKConnect=new/auth?mockKConnect=existing//oauth/kconnect?error=access_denied&mockLogin=failsessionStorage.setItem('stream:auth:logged-in','1')후/mockKConnect는import.meta.env.DEV일 때만 읽어서 배포 빌드에는 들어가지 않습니다(pnpm build결과물에 문자열이 없음을 확인했습니다).code를 돌려준 상황이 됩니다(패키지가 로그인 시작 때 저장하는state를 흉내 내는 값이고, 주소의state와 같아야 통과합니다). 붙여넣기가 막히면allow pasting을 먼저 입력하세요. 기존 사용자는 주소 끝에&mockLogin=existing을 붙입니다.state불일치(state=WRONG)나 pending 값이 없는 콜백은 오류로 처리되어 로그인 화면과 실패 토스트가 뜹니다..env에VITE_KCONNECT_CLIENT_ID를 채우고, K-CONNECT의 허가된 Redirect URI에http://localhost:5173/oauth/kconnect를 등록한 뒤/auth에서K-CONNECT로 시작하기를 누르면 됩니다. 폰에서192.168.x.x:5173으로 접속하면 등록되지 않은 주소라 거부됩니다. 이 환경에서는 K-CONNECT 계정으로 실제 로그인은 확인하지 못했고, 인증 요청 주소(client_id,redirect_uri,scope,state, PKCES256)가 올바르게 만들어져 K-CONNECT 로그인 페이지로 이동하는 것까지 확인했습니다.커밋 구성: 문서 → 목 로그인 API·로그인 표시 → 로그인 화면 → 온보딩 화면 → 라우트·첫 진입 가드 → use-funnel 의존성 → View Transition 어댑터 → 퍼널 통합(스플래시 제거) → 퍼널 문서 → oauth-kconnect 의존성·설정 → K-CONNECT 연동·인증 우회 → K-CONNECT 문서
🧩 이 PR의 한계 & 트레이드오프
POST /auth/kconnect의 주소와 응답 형식(서비스 토큰, 신규 사용자 여부)이 정해지지 않아서exchangeKConnectCode가 지연 후 결과만 돌려줍니다. 서버가 발급한 서비스 토큰은 앱·웹이 직접 저장해야 한다고 패키지가 안내하는데(README), 지금은 토큰이 없어서 로그인 표시만 있고 저장 방식은 서버 연동 때 정해야 합니다.VITE_KCONNECT_CLIENT_ID로 넣어야 실제 로그인이 시작됩니다. 없으면 버튼이 경고와 실패 토스트만 띄웁니다.@billilge/oauth-kconnect/react-native로 시스템 브라우저에서 로그인해야 합니다. 이 PR은 웹(브라우저) 로그인만 다룹니다.viewTransition으로 고치면src/lib/useFunnel.ts를 지우고 기본useFunnel로 돌아가면 됩니다.auth아래에서 처리했습니다. 백엔드 엔드포인트 네이밍과 맞는지 확인이 필요합니다.sm:h-[14px](2곳)은 행사 신청·사물함 등 저장소 전체가 같은 표기라 그대로 두었습니다.⛓️ 기존 기능에 미치는 영향
/(홈) 진입이 바뀝니다. 이 PR이 머지되면 브라우저(탭)에 로그인 표시가 없을 때/가/auth(로그인)로 갑니다. 개발·QA에서 홈을 바로 보려면 한 번 로그인해야 합니다(/auth?mockKConnect=existing이 가장 빠름)./events,/notices등)는 그대로 열립니다./로 가는 경우, 로그인 표시가 없으면 로그인으로 한 번 더 넘어갑니다.@use-funnel/react-router-dom(react-router-dom 6 이상, React 16.8 이상 필요, 현재 환경과 호환)과 GitHub Packages 비공개 패키지@billilge/oauth-kconnect입니다. 후자는 설치에read:packages권한 토큰이 필요합니다..npmrc에@billilge:registry를 추가했고,pnpm-workspace.yaml(새 파일)과.env.example(새 파일)이 생깁니다.index.css에 토큰 1개(--color-k-connect)를 추가했고, 기존 화면 스타일은 바뀌지 않습니다.ResultScreen과CompleteCheck는 수정 없이 재사용했습니다.🔀 Edge Case & 실패 시나리오
state불일치, 서버 실패는 모두/auth로 돌아와 실패 토스트를 띄우고 버튼은 다시 눌러집니다.code가 StrictMode 이중 실행으로 서버에 두 번 가지 않도록 콜백에서 한 번만 처리합니다.aria-busy) 중복 요청을 막습니다.VITE_KCONNECT_CLIENT_ID가 없으면 인증 사이트로 이동하지 않고 경고와 실패 토스트만 띄웁니다.?auth.step=terms같은 주소로 들어오면 로그인 단계가 보입니다.replace)하므로 약관 동의로 돌아오는 경로가 없습니다.📋 검토한 대안과 선택 이유
state검증, 콜백 처리가 보안에 민감해서 팀의 공용 패키지(@billilge/oauth-kconnect)를 쓰고 앱과 웹이 같은 결과 형태를 공유하도록 했습니다.overrides로 제외했습니다.mockKConnect)와 콜백 쿼리(mockLogin)로 나눴습니다.navigate state로, 약관 동의는 모듈 저장소로 따로 넘겨야 했습니다. 퍼널의 context로 한 번에 풀려서 퍼널 하나로 합쳤습니다.useFunnel그대로 사용: 단계 전환 슬라이드가 무시되어서 어댑터를 직접 만들었습니다.PaginationDots사용: Figma 닷은 지름 6px이고 중심 간격이 12px인데, WDS는small이 8px, 간격 16px이고 크기를 CSS 변수로만 바꿀 수 있어서 로컬AuthPaginationDots로 만들었습니다.ActionAreaButton사용: 브랜드색#004F9F와 높이 57px이 WDS 버튼 스케일에 없어서 로컬 버튼으로 만들었습니다.small(20px)이 아니라 Figma Control이 24px이라medium을 썼습니다./하나에만 걸었습니다.💬 리뷰 포인트
[r]배포(CI) 확인이 필요합니다.deploy-dev.yml은WDS_NPM_TOKEN으로@wanteddev패키지를 설치하는데,@billilge/oauth-kconnect도 같은 레지스트리(npm.pkg.github.com)의 비공개 패키지라 이 토큰이billilge조직 패키지 읽기 권한도 가져야 합니다. 권한이 없으면 Docker 빌드의pnpm install이 실패합니다. 같은 이유로Dockerfile빌드에는VITE_KCONNECT_CLIENT_ID를 넘기는ARG와 워크플로우의build-args가 아직 없어서, 배포 빌드에서는 client_id가 비어 로그인이 시작되지 않습니다. 이 PR에서는 인프라 설정은 건드리지 않았고Dockerfile의COPY에pnpm-workspace.yaml만 추가했습니다(없으면--frozen-lockfile이ERR_PNPM_LOCKFILE_CONFIG_MISMATCH로 실패하는 것을 확인했습니다). 이 부분은 후속 작업이나 이 PR에서 같이 처리할지 알려주세요.[r]앱 WebView와 웹 브라우저의 위치 처리가 다릅니다. 로그인 본문은 Figma 프레임에서 상태 표시줄(54px)을 뺀 앱 WebView 영역 기준 값입니다. 브라우저는 창 전체가 웹 영역이라 같은 값이면 위로 치우쳐 보여서,isInAppShell()(앱 연동에 이미 쓰는window.ReactNativeWebView판별)일 때와 아닐 때를 나눴습니다.ReactNativeWebView가 주입되는지는 실기기에서 확인해 주세요.[r]용어auth아래에서 로그인·온보딩을 처리하는 것이 백엔드 엔드포인트 네이밍과 맞는지 확인해 주세요.[c]pnpm-workspace.yaml의overrides: expo-auth-session: "-"로 Expo 의존성을 제외하는 방식이 괜찮은지 봐주세요. 패키지가expo-auth-session을 optional peer로만 선언해서 웹에는 필요 없습니다.[c]useFunnel도입이 컨벤션에 없는 새 패턴입니다. 다른 다단계 화면(행사 신청 등)에도 같은 방식을 쓸지, 이 PR에서는 인증에만 쓰는 것으로 둘지 의견 부탁드립니다.[c]src/lib/useFunnel.ts(View Transition 어댑터)를 직접 두는 것이 괜찮은지, 아니면 라이브러리에 이슈를 올리고 기다릴지 봐주세요.[c]/에서 로그인 표시가 없을 때 로그인으로 보내는 진입 가드가 의도에 맞는지 봐주세요.[c][Feat/#96] 설정 화면과 전화번호 변경·회원 탈퇴 화면 구현 #97(설정·전화번호 변경·회원 탈퇴)과 전화번호 입력·포맷(formatPhoneNumber)이 겹칠 수 있습니다. 머지 순서에 따라 한쪽에서 공통화가 필요합니다.[a]이 환경에서는 Pretendard가 로드되지 않아 대체 폰트 기준으로 줄바꿈을 확인했습니다. 실기기에서 로그인 문구와 약관 본문 줄바꿈이 Figma와 같은지 봐주세요.[a]Figma 대조는 측정 기준으로 했고, 🟢 수준의 차이가 남아 있습니다. 연락처 입력칸에 Figma의 약한 그림자가 없는 것(WDSTextField기본값), 로그인 배경 빛의 가로 위치가 4.5px 다를 수 있는 것입니다.