Skip to content

[FEATURE] 회원 탈퇴 API 연동#50

Merged
kbh0218 merged 6 commits into
devfrom
feat/#49-member-withdrawal-api
May 23, 2026
Merged

[FEATURE] 회원 탈퇴 API 연동#50
kbh0218 merged 6 commits into
devfrom
feat/#49-member-withdrawal-api

Conversation

@kbh0218

@kbh0218 kbh0218 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Closes #49

개요

설정 화면의 회원탈퇴 버튼을 백엔드 Member API와 연결했습니다.

기존 app/(tabs)/(home)/settings.tsx의 회원탈퇴 row는 destructive 스타일만 적용되어 있고 실제 동작은 없었습니다. 이번 작업에서는 DELETE /api/v1/members/me API를 호출하도록 연결하고, 성공 시 Clerk 로컬 세션을 정리한 뒤 로그인 화면으로 이동하도록 구현했습니다.

회원 탈퇴 API는 백엔드 dev 기준 Clerk 인증 토큰이 필요한 보호 API이므로, issue #41에서 정리한 공통 API 클라이언트 패턴을 따라 authenticatedApiRequest 기반으로 호출합니다.

또한 탈퇴 후 같은 Google 계정으로 다시 로그인할 때 Clerk 세션 토큰 갱신 타이밍 때문에 /auth/me 동기화가 일시적으로 401을 반환할 수 있어, 로그인 직후 member 동기화에서 새 토큰을 재요청하고 재시도하도록 보완했습니다.

주요 구현 내용

  • 회원 API 전용 파일 api/members.ts 추가
  • DELETE /api/v1/members/me 회원 탈퇴 API 연동
  • Clerk getToken() 기반 보호 API 호출 적용
  • 회원탈퇴 버튼 클릭 시 확인 Alert 표시
  • 사용자가 확인한 경우에만 회원 탈퇴 API 호출
  • 회원탈퇴 API 호출 중 중복 클릭 방지
  • 탈퇴 성공 시 Clerk signOut() 후 로그인 화면 이동
  • 탈퇴 성공 안내 Alert 표시
  • 탈퇴 실패 시 실패 Alert 표시 및 기존 세션 유지
  • 회원 탈퇴 요청에서 401 Unauthorized 발생 시 세션 무효 상태로 보고 signOut() 후 로그인 화면 이동
  • 탈퇴 완료 Alert 확인 후 로그인 화면이 다시 로드되는 느낌이 없도록, 로그인 화면 이동 후 Alert 표시 흐름으로 정리
  • 탈퇴 후 재로그인 시 stale token으로 인한 /auth/me 401이 로그인 실패 Alert로 노출되지 않도록 보완
  • SSO 직후 member 동기화에서 getToken({ skipCache: true }) 기반 재시도 처리
  • 공유 인텐트로 진입한 로그인 흐름의 기존 라우팅 유지

파일별 역할

  • api/members.ts: 회원 탈퇴 API 타입 및 호출 함수 추가
  • app/(tabs)/(home)/settings.tsx: 회원탈퇴 버튼 동작, 확인/성공/실패 Alert, 중복 호출 방지, 세션 정리 및 로그인 이동 처리
  • app/(auth)/login.tsx: 탈퇴 후 재로그인 시 새 세션 토큰 기반 member 동기화 재시도 및 stale token 401 대응

해결한 이슈 목록

  • 현재 app/(tabs)/(home)/settings.tsx의 회원탈퇴 버튼 구조 확인
  • api/members.ts를 추가하고 회원 탈퇴 API 호출 함수를 분리
  • DELETE /api/v1/members/me 요청 시 Clerk getToken() 기반 인증 헤더 포함
  • 회원 탈퇴 API 호출에 authenticatedApiRequest 사용
  • 기능별 API 파일에서 API base URL을 새로 만들지 않음
  • 기능별 API 파일에서 fetch() 직접 호출하지 않음
  • 기능별 API 파일에서 Authorization 헤더 직접 주입하지 않음
  • 204 No Content 응답을 공통 API 클라이언트 흐름에서 처리
  • 회원탈퇴 버튼 클릭 시 확인 Alert 표시
  • 사용자가 확인한 경우에만 회원 탈퇴 API 호출
  • API 호출 중 중복 클릭 방지
  • 탈퇴 성공 시 signOut()으로 Clerk 세션 정리 후 /login 이동
  • 탈퇴 실패 시 사용자에게 실패 안내 표시 및 세션 유지
  • 로그아웃 흐름과 회원탈퇴 흐름이 서로 영향 주지 않도록 분리
  • 네트워크 오류 등 탈퇴 처리 상태가 애매한 응답에서 세션 정리 여부와 사용자 안내 문구 검토
  • 회원 탈퇴 요청에서 401 Unauthorized 발생 시 세션 무효 상태로 보고 signOut() 후 로그인 화면 이동
  • 회원 탈퇴 API 성공 후 기존 Clerk 세션이 남지 않도록 signOut() 처리 보장

체크 사항

  • 커밋/코딩 컨벤션에 맞게 작성
  • 프론트 API 클라이언트 사용 규칙 준수
  • 회원 API 파일에서 직접 fetch() 호출 없음
  • 회원 API 파일에서 직접 API base URL 생성 없음
  • 회원 API 파일에서 직접 Authorization 헤더 주입 없음
  • 회원 API 파일에서 { data: ... } 응답 직접 unwrap 없음
  • Clerk 세션 토큰이 필요한 API는 authenticatedApiRequest 사용
  • 보호 API 호출이 /auth/me 수동 사전 호출에 의존하지 않음
  • 204 No Content 응답을 공통 API 클라이언트에서 처리

참고

백엔드 PR #35 기준 회원 탈퇴 API는 다음 계약을 사용합니다.

  • DELETE /api/v1/members/me
  • Clerk 인증 토큰 필요
  • 성공 응답: 204 No Content

백엔드의 회원 탈퇴는 member row를 즉시 물리 삭제하는 방식이 아니라 deleted_at을 채우는 soft delete 방식입니다. Clerk 계정 삭제가 성공하면 같은 Google 계정으로 재로그인 시 새 clerk_id가 발급되어 신규 회원처럼 생성됩니다.

이번 PR에서는 프론트에서 해당 API를 연결하고, 성공/실패/401 상태에 대한 사용자 흐름을 정리했습니다.

Screenshots or Video

  • 회원탈퇴 확인 Alert
image
  • 회원탈퇴 완료 Alert
image
  • 회원 DB에 정상적으로 들어오는 모습입니다.
image

@kbh0218 kbh0218 requested review from minsoo0506 and sunm2n May 22, 2026 11:17
@kbh0218 kbh0218 self-assigned this May 22, 2026
@kbh0218 kbh0218 added the feature 기능개발 label May 22, 2026
Comment thread app/(tabs)/(home)/settings.tsx Outdated
}

function goToLoginWithAlert(title: string, message: string) {
router.replace('/login' as any);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as any 타입 캐스트

Expo Router는 타입 안전한 라우팅을 지원합니다.
as any는 라우트 변경 시 컴파일 오류를 놓칠 위험이 있습니다.

개선 제안:

  router.replace('/(auth)/login');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 감사합니다!

Expo Router의 typed routes가 활성화되어 있는 상태에서 router.replace('/login' as any)처럼 as any를 사용하면, 라우트 경로가 변경되거나 오타가 생겨도 컴파일 단계에서 잡히지 않을 수 있다고 판단했습니다.

현재 로그인 화면은 app/(auth)/login.tsx에 위치해 있고, 생성된 typed route에서도 /(auth)/login이 유효한 경로로 포함되어 있어 해당 경로를 사용했습니다. /(auth)/login은 실제 라우트 그룹 구조를 명시적으로 드러내므로, 인증 플로우로 이동한다는 의도도 더 분명하다고 보았습니다.

따라서 settings.tsx의 로그인 이동 로직에서 as any를 제거하고 router.replace('/(auth)/login')으로 변경했습니다.
같은 이유로 동일한 로그인 이동 패턴이 있던 app/(tabs)/_layout.tsx의 세션 동기화 실패 처리도 함께 정리했습니다.

Comment thread app/(tabs)/(home)/settings.tsx Outdated
}
}

function goToLoginWithAlert(title: string, message: string) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setTimeout(..., 0) 기반 Alert 표시

현재 코드:

  function goToLoginWithAlert(title: string, message: string) {
    router.replace('/login' as any); 
    setTimeout(() => {
      Alert.alert(title, message);
    }, 0);
  } 

setTimeout(..., 0)은 navigation render 이후 Alert를 띄우기 위한 워크어라운드인데, 타이밍이 보장되지 않아 저사양 기기에서 Alert가 표시되지 않거나 순서가 어긋날 수 있습니다.
login 화면에 params로 alertMessage를 전달하는 방식이 더 안전할 거 같습니다

수정 제안:

  router.replace({ pathname: '/(auth)/login', params: { withdrawalAlert: 'true' } });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다!

말씀해주신 것처럼 setTimeout(..., 0)은 navigation 이후 Alert를 표시하기 위한 워크어라운드에 가깝고, 실제 화면 전환/렌더 완료 시점을 보장하지 못한다고 판단했습니다.

그래서 settings.tsx에서 Alert 문구를 직접 띄우지 않고, 로그인 화면으로 이동할 때 notice params를 전달하도록 변경했습니다.

} catch (error) {
console.error(error);

if (error instanceof ApiError && error.status === 401) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

재시도 로직에서 마지막 401도 성공 처리

현재 코드:

  // syncAuthenticatedMemberAfterSso가 401로 throw한 경우도 성공으로 처리
  if (error instanceof ApiError && error.status === 401) {
    navigateAfterSuccessfulLogin();
    return;
  } 

3번 재시도 후에도 401이면 member 동기화 실패 상태로 홈 화면 이동합니다.
탈퇴 후 재가입 케이스를 의도한 것으로 보이는데, 이 케이스가 정말 의도된 동작인지 확인 부탁드립니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다! 해당 분기는 제가 테스트 중 계정 탈퇴 후 같은 계정으로 바로 재로그인했을 때 /auth/me에서 401이 발생해, 사용자가 로그인을 두 번 시도해야 하는 불편함을 줄이기 위해 추가했던 처리였습니다.

다만 현재는 syncAuthenticatedMemberAfterSso() 내부에서 401에 대해 3번 재시도하도록 보완되어 있으므로, 재시도 이후에도 계속 401이 발생한다면 더 이상 성공 로그인으로 간주하기 어렵다고 판단했습니다.

따라서 최종 401을 성공처럼 처리하던 분기는 제거했습니다.
이제 401은 재시도 대상까지만 허용하고, 마지막까지 실패하면 기존 로그인 실패 흐름으로 처리됩니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

계정 탈퇴 이후 바로 재로그인을 시도 하면 애초에 막아야 한다고 생각이 됩니다.
물론 이부분은 회의를 통해서 30일 이전 복구 정책에 대해서 논의를 해봐야 하지만
탈퇴 이후 해당 계정으로 로그인을 하지 못하거나 복구 페이지로 가는 등의 결정이 필요해 보입니다.

Comment thread app/(auth)/login.tsx Outdated
throw lastError;
}

function delay(ms: number) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delay 함수 컴포넌트 내부 정의

컴포넌트 내부에 있을 이유가 없는 순수 유틸 함수입니다.
컴포넌트 외부 또는 공통 유틸로 분리를 제안 드립니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다!

delay는 컴포넌트의 state, props, hook 값에 의존하지 않는 순수 유틸 함수라 LoginScreen 내부에 둘 필요가 없다고 판단했습니다. 컴포넌트 내부에 있으면 렌더링과 관련 있는 로직처럼 보일 수 있고, 렌더마다 함수가 다시 생성되기 때문에 책임 범위도 다소 흐려진다고 보았습니다.

다만 현재 delay 사용처는 로그인 화면의 SSO member 동기화 재시도 로직 한 곳뿐이라, 별도의 공통 유틸 파일로 분리하는 것은 아직 범위가 크다고 판단했습니다. 공통 유틸로 빼면 재사용 전에는 파일/모듈이 불필요하게 늘어날 수 있어, 우선 login.tsx 내부의 컴포넌트 외부 함수로 이동했습니다.

이렇게 하면 delay가 렌더링과 무관한 파일 단위 helper라는 점은 명확해지고, 추후 다른 화면에서도 필요해질 때 공통 유틸로 승격하기도 쉽습니다.

@sunm2n

sunm2n commented May 23, 2026

Copy link
Copy Markdown
Contributor

수고하셨습니다 다만 지금 회원 탈퇴 이후 재로그인 시도에 대해서는 내부 회의를 통해 좀 더 구체적인 결정을 해야할 것 같습니다.
해당 부분은 추후 pr에서 반영해도 되고 지금까지는 머지해도 괜찮을 거 같습니다

@kbh0218

kbh0218 commented May 23, 2026

Copy link
Copy Markdown
Contributor Author

수고하셨습니다 다만 지금 회원 탈퇴 이후 재로그인 시도에 대해서는 내부 회의를 통해 좀 더 구체적인 결정을 해야할 것 같습니다. 해당 부분은 추후 pr에서 반영해도 되고 지금까지는 머지해도 괜찮을 거 같습니다

네 알겠습니다! 우선 머지하겠습니다

@kbh0218 kbh0218 merged commit dac5431 into dev May 23, 2026
@kbh0218 kbh0218 deleted the feat/#49-member-withdrawal-api branch May 23, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 회원 탈퇴 API 연동

2 participants