Skip to content

feat: 로그인 / 회원가입 API 연동#54

Open
dew102938 wants to merge 6 commits intodevelopfrom
feat/auth-api
Open

feat: 로그인 / 회원가입 API 연동#54
dew102938 wants to merge 6 commits intodevelopfrom
feat/auth-api

Conversation

@dew102938
Copy link
Contributor

@dew102938 dew102938 commented Feb 4, 2026

💡 개요

로그인 / 회원가입 API 연동

🔢 관련 이슈 링크

💻 작업내용

  • UI/UX

    • 회원가입 모달 내 Role(사장/고객) 선택 버튼 UI 추가
    • 로그인 상태에 따른 헤더 UI 분기 처리 (로그인/회원가입 ↔ 마이페이지/로그아웃)
  • API 연동

    • 이메일 로그인/회원가입 API 연동
    • 소셜(구글, 카카오) 로그인 API 연동
  • 상태 관리

    • Zustand 도입: 인증 정보(accessToken, isAuthenticated) 전역 관리
    • Persist Middleware 적용: 브라우저 새로고침 후에도 로그인 상태 유지
    • 로그아웃 로직 구현
  • 현재 배포 서버에 인증 API가 반영되지 않아 실제 로그인, 회원가입 시 오류가 발생하는 것이 정상입니다.
    (추후 서버 배포 완료 시 연동 테스트 예정)

  • 로그인 후의 UI를 확인하시려면, 개발자 도구(F12) 콘솔에 아래 코드를 한 번에 복붙 입력하여
    임시 로그인 상태로 전환하실 수 있습니다.

    localStorage.setItem('auth-storage', '{"state":{"accessToken":"fake-test-token","isAuthenticated":true},"version":0}'); location.reload();

  • 현재 API 명세서에 아이디/비밀번호 찾기 기능이 없습니다.
    이번 MVP 스펙에서 제외되는 것인지 팀장님께 먼저 확인받은 뒤에 작업 진행할 예정입니다!

📌 변경사항PR

  • FEAT: 새로운 기능 추가
  • FIX: 버그/오류 수정
  • CHORE: 코드/내부 파일/설정 수정
  • DOCS: 문서 수정(README 등)
  • REFACTOR: 코드 리팩토링 (기능 변경 없음)
  • TEST: 테스트 코드 추가/수정
  • STYLE: 스타일 변경(포맷, 세미콜론 등)

🤔 추가 논의하고 싶은 내용

  • 소셜 로그인 약관 처리
    이용약관 구현하는 것이 매우 복잡해질 것 같아 소셜 계정으로 가입 시 이용약관, 개인정보처리방침에 동의하는 것으로 간주합니다.
    라는 문구를 추가하는 방식으로 처리했습니다. 이대로 진행해도 괜찮을까요?

  • 개발 우선순위
    로그인한 사용자만 접근 가능한 경로 설정(Route Guard)은 별도 PR로 분리할 생각입니다.
    경로 설정을 먼저 마무리하는 것이 좋을지, 아니면 새 가게 등록 API 연동 작업을 진행하는 것이 좋을지 의견 부탁드립니다.
    경로 설정을 먼저 적용할 경우 마이페이지나 내가게관리 페이지 접근 시 반드시 로그인이 필요해져 다소 번거로울 수 있을 것 같습니다

  • 비로그인 시 헤더 메뉴 처리 방식
    로그인을 안 했을 때 로그인이 필수인 메뉴(ex:내 가게 관리)를 헤더에서 어떻게 처리하면 좋을까요?
    A안: 아예 버튼을 숨김
    B안: 버튼은 보여주되, 클릭 시 로그인 모달을 띄움
    어떤 방식이 UX적으로 더 적절할지 의견 부탁드립니다.
    이 부분은 경로 설정과 함께 하나의 PR로 올리겠습니다.

✅ 체크리스트

  • 브랜치는 잘 맞게 올렸는지
  • 관련 이슈를 맞게 연결했는지
  • 로컬에서 정상 동작을 확있했는지
  • 충돌이 없다(또는 브랜치에서 충돌 해결 후 PR 업데이트 완료)

Summary by CodeRabbit

릴리즈 노트

  • New Features

    • Google 및 Kakao 소셜 로그인 추가
    • 회원 역할(고객/사업자) 선택 기능 추가
    • 전화번호 형식 개선 (010-xxxx-xxxx)
    • 사용자 인증 상태에 따른 헤더 메뉴 동적 업데이트
  • Bug Fixes

    • 토큰 갱신 실패 시 자동 로그아웃 처리 개선

@dew102938 dew102938 self-assigned this Feb 4, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

로그인/회원가입 API를 실제로 연동하고, Google/Kakao 소셜 로그인을 지원합니다. 토큰 기반 인증, 자동 토큰 갱신, 상태 지속성, 그리고 통합된 에러 처리를 구현했습니다.

Changes

Cohort / File(s) Summary
인증 타입 정의
src/types/auth.ts, src/vite-env.d.ts
회원가입, 로그인, 소셜 로그인, 토큰 갱신 DTO 추가. Kakao 글로벌 타입 정의.
API 계층
src/api/auth.ts, src/api/axios.ts
인증 엔드포인트(회원가입, 로그인, 소셜 로그인, 로그아웃, 토큰 갱신) 구현. 401 응답 시 자동 토큰 갱신 후 재시도하는 응답 인터셉터 로직 추가.
상태 관리 & 커스텀 훅
src/stores/useAuthStore.ts, src/hooks/queries/useAuth.ts
Zustand 기반 인증 상태 저장소(localStorage 지속성). React Query 뮤테이션 훅 3개(이메일 회원가입/로그인, 소셜 로그인) 추가.
인증 UI 컴포넌트
src/components/auth/LoginDialog.tsx, src/components/auth/SignupDialog.tsx, src/components/auth/signup.schema.ts
이메일/소셜 로그인 플로우 구현. Google/Kakao 로그인 통합. 회원가입 역할 선택(고객/사업주) 추가. 전화번호 검증 패턴 변경(010-XXXX-XXXX → XX(X)-XXX(X)-XXXX).
레이아웃 & 헤더
src/layouts/PublicLayout.tsx, src/components/main/Header.tsx
PublicLayout Props 제거, 내부 로그아웃 상태 관리로 변경. Header에 인증 상태 기반 UI 조건부 렌더링(마이페이지, 로그아웃 버튼).
앱 초기화
src/App.tsx, src/main.tsx
Kakao SDK 초기화 추가(useEffect). GoogleOAuthProvider로 앱 감싸기.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EmailForm as Email Login<br/>Component
    participant Query as React Query<br/>(useEmailLogin)
    participant API as Auth API<br/>(postLogin)
    participant Axios as Axios + Interceptor
    participant Store as Auth Store<br/>(useAuthActions)

    User->>EmailForm: 이메일/비밀번호 입력
    EmailForm->>Query: emailLoginMutation.mutate(data)
    Query->>API: postLogin(email, password)
    API->>Axios: POST /auth/login
    Axios->>Axios: Request: 액세스 토큰 헤더 추가
    Axios-->>API: Response: {accessToken, user}
    API-->>Query: ApiResponse 반환
    Query->>Store: login(accessToken)
    Store->>Store: localStorage에 토큰 저장
    Query-->>EmailForm: onSuccess 콜백
    EmailForm->>User: 로그인 완료, 다이얼로그 닫기
Loading
sequenceDiagram
    participant User
    participant SocialForm as Social Login<br/>Component
    participant SDK as Google/Kakao<br/>SDK
    participant Query as React Query<br/>(useSocialLogin)
    participant API as Auth API<br/>(postSocialLogin)
    participant Store as Auth Store

    User->>SocialForm: Google/Kakao 버튼 클릭
    SocialForm->>SDK: useGoogleLogin() 또는<br/>Kakao.Auth.login()
    SDK->>User: 로그인 팝업 표시
    User->>SDK: 인증 완료
    SDK-->>SocialForm: accessToken 반환
    SocialForm->>Query: socialLoginMutation.mutate({provider, accessToken})
    Query->>API: postSocialLogin(provider, {accessToken})
    API->>API: POST /auth/social/{provider}
    API-->>Query: ApiResponse 반환
    Query->>Store: login(accessToken)
    Store->>Store: localStorage에 토큰 저장
    Query-->>SocialForm: onSuccess
    SocialForm->>User: 로그인 완료, 다이얼로그 닫기
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • chore: global 스타일 적용 #33: axios 인스턴스 및 인터셉터 기초를 구현했고, 이 PR이 그 위에 토큰 갱신 재시도 로직과 인증 헬퍼 통합을 추가하므로 직접 연관됩니다.
  • feat:내가게관리 UI 구현 #37: 이 PR은 Header 컴포넌트에서 로그인/로그아웃 버튼 상태 관리를 추가하는데, 이전 PR과 같은 파일을 수정합니다.

Suggested labels

feat

Suggested reviewers

  • yooseolhee

Poem

🔑 토큰을 손에 들고, 소셜은 품에 안고
갱신의 춤을 추며 401을 넘어
localStorage에 남기고, 상태는 흘러가고
로그인의 여행, 이제 시작이야 ✨


📝 리뷰 포인트 (친절한 조언)

안정성 & 에러 처리

1. Axios 인터셉터의 토큰 갱신 로직 - 엣지 케이스 주의 ⚠️

// src/api/axios.ts 라인 ~ 
// 현재 구조 검토 사항:
if (!(_retry in originalRequest)) {  // _retry 플래그로 무한 루프 방지
  // ✓ 좋은 패턴입니다! 다만...
  
  // 🔍 확인 사항:
  // 1. postRefresh 실패 후에도 clearAuth가 호출되나요? 
  //    → 검토 권장: finally 블록 필요할 수 있음
  // 2. 동시 다중 401 요청 시, 여러 refresh 호출이 동시에 발생할 수 있음
  //    → 개선 제안: 토큰 갱신 Promise를 캐싱하여 중복 호출 방지
  
  const refreshPromise = postRefresh()
    .then(res => res.data.accessToken)
    .catch(() => { clearAuth(); throw new Error('토큰 갱신 실패'); });
  // 여러 요청이 같은 refreshPromise를 기다리도록 개선 가능
}

2. Social Login 에러 메시지

// src/components/auth/LoginDialog.tsx, SignupDialog.tsx
// 현재: onError: (error) => setErrorMessage(error.message)

// 🔍 고려 사항:
// - error.message가 사용자 친화적인 메시지인가요?
// - 네트워크 에러 vs 인증 실패 vs 사업 로직 에러를 구분하나요?
// → 개선 예시:
const handleSocialLoginError = (error: ApiError) => {
  if (error.code === 'INVALID_TOKEN') {
    setErrorMessage('소셜 로그인이 만료되었습니다. 다시 시도해주세요.');
  } else if (error.code === 'ACCOUNT_NOT_FOUND') {
    setErrorMessage('등록되지 않은 계정입니다. 회원가입을 먼저 진행해주세요.');
  } else {
    setErrorMessage('로그인에 실패했습니다. 잠시 후 다시 시도해주세요.');
  }
};

접근성 & UX

3. Kakao SDK 초기화

// src/App.tsx
useEffect(() => {
  if (window.Kakao && !window.Kakao.isInitialized()) {
    window.Kakao.init(import.meta.env.VITE_KAKAO_JS_KEY);
  }
}, []); // ✓ 좋습니다!

// 🔍 다만, 다음을 확인하세요:
// - VITE_KAKAO_JS_KEY가 없을 경우의 처리?
// → 개선:
if (!import.meta.env.VITE_KAKAO_JS_KEY) {
  console.warn('Kakao SDK 초기화: 환경변수 미설정');
  return;
}

4. 회원가입 전화번호 검증 패턴

// src/components/auth/signup.schema.ts
// 변경: /^\d{2,3}-\d{3,4}-\d{4}$/

// 현재 패턴 테스트:
// ✓ "010-1234-5678" → 맞음
// ✓ "02-123-4567" → 맞음
// ✗ "031-1234-567" → 틀림 (라인이 4자리 필요)
// 🔍 조건 확인:
// - 국내 지역번호 형식(010, 02~063)만 허용? 국제번호 고려?
// - 사용자가 입력 형식을 정확히 알고 있나요?
// → 개선 제안: 입력 중 자동 포맷팅 추가
import { phoneNumber } from '@/utils'; // 이미 사용 중이면 좋습니다!

성능 & 구조

5. 상태 관리 - localStorage 직접 접근

// src/api/auth.ts - clearAuth()
localStorage.removeItem('auth-storage'); // 매직 스트링

// 🔍 리스크:
// - 하드코딩된 스토어 이름이 stores/useAuthStore.ts와 일치해야 함
// → 개선 제안:
const AUTH_STORAGE_KEY = 'auth-storage'; // 상수화하거나
// store에서 export const getStorageName = () => 'auth-storage';

6. 토큰 갱신 실패 후 자동 리다이렉트

// src/api/auth.ts - clearAuth()
// window.location.href = '/'; // 하드 리다이렉트

// 🔍 문제점:
// - SPA 네비게이션이 아닌 풀 페이지 새로고침
// - 진행 중인 작업 손실 가능
// → 개선 제안:
export const clearAuth = (navigate?: ReturnType<typeof useNavigate>) => {
  localStorage.removeItem('auth-storage');
  if (navigate) {
    navigate('/', { replace: true });
  } else {
    window.location.href = '/'; // 폴백
  }
};
// 또는 별도의 이벤트 버스로 로그아웃 이벤트 발생

테스트 고려 사항

  • 토큰 갱신 중 다중 API 호출 시나리오
  • Kakao/Google SDK 로드 실패 시 폴백 처리
  • 소셜 로그인 후 일반 로그인 계정 충돌 시나리오
  • 오프라인 → 온라인 전환 시 토큰 자동 갱신

좋은 작업입니다! 🚀 위 포인트들만 검토하면 더욱 견고한 인증 시스템이 될 것 같습니다.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 로그인/회원가입 API 연동이라는 변경사항의 핵심을 명확하게 전달하고 있습니다.
Description check ✅ Passed PR 설명이 개요, 이슈 링크, 상세 작업내용, 변경사항, 추가 논의 사항, 체크리스트를 포함하여 템플릿을 충실히 따르고 있습니다.
Linked Issues check ✅ Passed API 연동(postSignup, postLogin, postSocialLogin, postLogout, postRefresh) [#47], 상태 관리(Zustand + persist) [#47], 로그인 UI 통합(Header, dialogs) [#47]의 핵심 요구사항이 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #47의 로그인/회원가입 API 연동 범위 내에 있으며, 불필요한 외부 작업은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auth-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@dew102938 dew102938 added API API 연동, 통신관련 feat 기능 and removed API API 연동, 통신관련 labels Feb 4, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/auth/LoginDialog.tsx (1)

126-152: ⚠️ Potential issue | 🟡 Minor

소셜 로그인 버튼 pending 상태 처리 누락

이메일 로그인 버튼은 emailLoginMutation.isPending으로 비활성화되지만, 소셜 로그인 버튼들은 socialLoginMutation.isPending 상태를 반영하지 않고 있어요. 중복 클릭으로 인한 다중 요청을 방지하려면 소셜 버튼도 비활성화해주는 게 좋겠습니다.

🛡️ 수정 제안
 <Button
   type="button"
   variant="outline"
   className="w-full h-12 text-base cursor-pointer"
   onClick={() => handleGoogleLogin()}
+  disabled={socialLoginMutation.isPending}
 >
src/components/auth/SignupDialog.tsx (1)

168-202: ⚠️ Potential issue | 🟡 Minor

소셜 가입 버튼 pending 상태 처리 누락

LoginDialog와 마찬가지로 소셜 로그인 버튼들이 socialLoginMutation.isPending 상태를 반영하지 않고 있어요. 중복 클릭 방지를 위해 비활성화 처리가 필요합니다.

🤖 Fix all issues with AI agents
In `@src/api/auth.ts`:
- Around line 50-53: clearAuth currently removes "accessToken" but the app
stores auth under "auth-storage", so update clearAuth to clear the actual
storage entry or, better, call the Zustand logout action to ensure state and
storage are cleared: use useAuthStore.getState().actions.logout() (or remove the
"auth-storage" key and any nested {accessToken,isAuthenticated} values) and then
navigate to "/"; keep the change focused in clearAuth and ensure the logout call
or manual removal preserves response/type safety and any existing error
handling/timeouts in the logout flow.
- Around line 55-63: The logout API currently always clears client auth state
even when postLogout() fails; update the export const logout function to call
await postLogout() and only call clearAuth() on success, log the error with
context (e.g., "로그아웃 실패:"), then rethrow the error so callers can show a
user-facing notification/retry UI; also change callers (e.g., PublicLayout.tsx)
to invoke this API logout instead of the store-only logout to keep client/server
state consistent and consider adding optional timeout/retry logic around
postLogout().

In `@src/api/axios.ts`:
- Line 4: Multiple concurrent 401s cause duplicate token refreshes and race
conditions; change the logic around postRefresh/clearAuth so only one refresh
runs at a time by introducing a shared in-flight promise (e.g.,
refreshingTokenPromise) that other requests await instead of calling postRefresh
again, have the single refresh handler check postRefresh() result and throw if
response.success === false so callers see the failure, and ensure clearAuth() is
only called after the refresh promise finally rejects (or when a non-recoverable
error occurs) so other waiting requests don’t lose tokens mid-refresh; update
the axios response interceptor (where postRefresh and clearAuth are used) to use
this shared promise and propagate errors to callers.

In `@src/App.tsx`:
- Line 22: Add a retry/backoff initialization inside the App component's
useEffect so the Kakao SDK init is attempted multiple times if window.Kakao
isn't ready yet: inside useEffect in App, poll or retry (e.g., exponential
backoff or fixed intervals, max attempts) to check for window.Kakao and call
window.Kakao.init(...) once available, and ensure you clear timers/intervals on
unmount; keep existing defensive checks in LoginDialog and SignupDialog but move
SDK-ready guarantees to App's useEffect to reduce missed initializations.

In `@src/components/auth/signup.schema.ts`:
- Around line 5-6: The phone validation in signup.schema.ts currently rejects
inputs with or without hyphens; update the schema's phone (phoneNumber) field to
either accept optional hyphens via a more permissive regex or, preferably,
normalize input with z.preprocess to strip non-digit characters before running
the numeric/length check; apply the same change to the other occurrence noted
(lines 17-18) so both validations match and the role:
z.enum([...]).default("customer") entry remains unchanged.

In `@src/components/auth/SignupDialog.tsx`:
- Around line 137-166: The role-selection buttons inside the Controller for
name="role" lack aria state for assistive tech; update the two buttons rendered
in SignupDialog (inside the Controller render using field and field.onChange) to
include aria-pressed={field.value === "customer"} for the 고객 button and
aria-pressed={field.value === "owner"} for the 점주 button so the current
selection is exposed to screen readers (keep existing onClick/field.onChange
logic and styling).

In `@src/hooks/queries/useAuth.ts`:
- Around line 11-18: The mutationFn in useAuth.ts currently uses a non-null
assertion for data.role (role: data.role!) which is unsafe because
SignupFormValues (z.input<typeof signupSchema>) can have role undefined; remove
the `!` and either provide an explicit safe fallback (e.g., role: data.role ??
'<appropriate-default>' ) or run runtime validation with
signupSchema.parse/parseAsync to derive a guaranteed role value before building
requestBody; reference mutationFn, SignupFormValues, and signupSchema when
making the change.

In `@src/layouts/PublicLayout.tsx`:
- Around line 9-15: handleLogout currently only calls the client-side store
logout and skips the server API call; update it to call the existing API logout
flow (src/api/auth.ts's logout which calls postLogout() then clearAuth()) and
handle errors. Specifically, in PublicLayout.tsx update the handleLogout handler
to be async, prompt confirmation, await the api logout() from src/api/auth.ts
(or dispatch an async store.logout that wraps that API), then show success alert
and navigate, and on catch log the error and show a failure alert; apply the
same pattern to Header.tsx's handleLogout so both use the API-backed logout
instead of only the store action.

In `@src/main.tsx`:
- Around line 7-16: Check import.meta.env.VITE_GOOGLE_CLIENT_ID before rendering
and guard the GoogleOAuthProvider initialization: ensure the value used for
GoogleOAuthProvider's clientId (import.meta.env.VITE_GOOGLE_CLIENT_ID) is
validated and throw or log a clear error and avoid rendering when it's missing;
update the bootstrap around createRoot(...).render(...) to perform the check and
fail fast with a clear message that includes which env var is missing so
developers can fix their environment quickly.
🧹 Nitpick comments (5)
src/components/main/Header.tsx (1)

108-114: 브랜드 홈 이동은 Link가 더 웹/접근성 친화적입니다

button으로 이동하면 새 탭 열기/주소 복사 같은 기본 링크 동작이 사라집니다. Link로 바꾸고 필요한 부가 동작만 onClick에 두는 쪽을 권장합니다.

♻️ 제안 수정
-<button
-  type="button"
-  onClick={() => go("/")}
-  className={`text-[24px] tracking-tight ${brandClass}`}
->
-  잇츠파인
-</button>
+<Link
+  to="/"
+  onClick={() => setMobileOpen(false)}
+  className={`text-[24px] tracking-tight ${brandClass}`}
+>
+  잇츠파인
+</Link>

As per coding guidelines, src/components/**: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.

src/vite-env.d.ts (1)

14-17: window.Kakao를 any로 두면 타입 안정성이 깨집니다

최소한 사용 중인 메서드만이라도 인터페이스로 정의하면 오타/호출 오류를 컴파일 타임에 잡을 수 있어요.

♻️ 제안 수정
+interface KakaoSDK {
+  isInitialized(): boolean;
+  init(appKey: string): void;
+}
+
 interface Window {
-  // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
-  Kakao: any;
+  Kakao?: KakaoSDK;
 }
src/components/auth/LoginDialog.tsx (2)

26-28: KakaoAuthSuccessResponse 타입 중복

이 타입이 SignupDialog.tsx에도 동일하게 정의되어 있어요. 공통 타입으로 분리하면 유지보수가 편해질 것 같습니다.

♻️ 공통 타입 분리 제안

src/types/auth.ts에 추가:

export type KakaoAuthSuccessResponse = {
  access_token: string;
};

61-98: 소셜 로그인 핸들러 로직 중복

handleSocialLogin, handleGoogleLogin, handleKakaoLogin 로직이 SignupDialog.tsx와 거의 동일하게 구현되어 있어요. 커스텀 훅으로 추출하면 두 컴포넌트에서 재사용할 수 있고, Kakao SDK 초기화 로직도 한 곳에서 관리할 수 있습니다.

♻️ 커스텀 훅 추출 예시
// src/hooks/useSocialAuth.ts
export const useSocialAuth = (onSuccess: () => void) => {
  const socialLoginMutation = useSocialLogin();

  const handleSocialLogin = (provider: "google" | "kakao", token: string) => {
    socialLoginMutation.mutate(
      { provider, token },
      {
        onSuccess,
        onError: (error) =>
          alert((error as ApiError).message || "로그인 중 문제가 발생했습니다."),
      }
    );
  };

  const handleKakaoLogin = () => { /* ... */ };
  const googleLogin = useGoogleLogin({ /* ... */ });

  return { handleKakaoLogin, googleLogin, isPending: socialLoginMutation.isPending };
};
src/stores/useAuthStore.ts (1)

5-13: isAuthenticated 파생 상태 고려

현재 isAuthenticatedaccessToken과 별도로 저장하고 있는데, 이 둘이 동기화되지 않을 가능성이 있어요. isAuthenticatedaccessToken !== null로 파생시키면 상태 불일치를 원천 차단할 수 있습니다.

현재 구현도 명시적이라 동작에는 문제없지만, 참고해주세요.

♻️ 파생 상태로 변경 예시
 interface AuthState {
   accessToken: string | null;
-  isAuthenticated: boolean;
   actions: {
     login: (token: string) => void;
     logout: () => void;
   };
 }

+// selector에서 파생
+export const useIsAuthenticated = () =>
+  useAuthStore((state) => state.accessToken !== null);

Comment on lines +50 to +53
export const clearAuth = () => {
localStorage.removeItem("accessToken");
window.location.href = "/";
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

localStorage 키 불일치 버그

clearAuth()"accessToken" 키를 삭제하지만, useAuthStore"auth-storage" 키에 {accessToken, isAuthenticated}를 저장합니다. 실제로 인증 상태가 클리어되지 않는 버그예요.

🐛 수정 제안
 export const clearAuth = () => {
-  localStorage.removeItem("accessToken");
+  localStorage.removeItem("auth-storage");
   window.location.href = "/";
 };

또는 Zustand store의 logout 액션을 직접 호출하는 방식도 고려해볼 수 있어요:

import { useAuthStore } from "@/stores/useAuthStore";

export const clearAuth = () => {
  useAuthStore.getState().actions.logout();
  window.location.href = "/";
};

As per coding guidelines: 에러 처리/타임아웃/리트라이 전략 확인, 응답 타입 안전성 유지.

🤖 Prompt for AI Agents
In `@src/api/auth.ts` around lines 50 - 53, clearAuth currently removes
"accessToken" but the app stores auth under "auth-storage", so update clearAuth
to clear the actual storage entry or, better, call the Zustand logout action to
ensure state and storage are cleared: use
useAuthStore.getState().actions.logout() (or remove the "auth-storage" key and
any nested {accessToken,isAuthenticated} values) and then navigate to "/"; keep
the change focused in clearAuth and ensure the logout call or manual removal
preserves response/type safety and any existing error handling/timeouts in the
logout flow.

Comment on lines +55 to +63
export const logout = async () => {
try {
await postLogout();
} catch (e) {
console.error(e);
} finally {
clearAuth();
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check logout function usage across the codebase
rg -n "logout\(" --type=ts --type=tsx -B2 -A2 | head -60

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Check imports of auth functions
rg -n "from.*auth|import.*logout" --type=ts --type=tsx | head -30

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Check the actual auth.ts file to see all logout-related functions
fd auth.ts -t f --exec cat -n {} \;

Repository: Eatsfine/FE

Length of output: 10070


🏁 Script executed:

# Look for PublicLayout and its logout usage
fd PublicLayout.tsx -t f --exec cat -n {} \;

Repository: Eatsfine/FE

Length of output: 1965


API logout() 함수의 에러 처리 전략을 명확히 해야 합니다

현재 코드를 보니 실제로 두 가지 로그아웃 로직이 섞여 있네요:

  • PublicLayout.tsx: 스토어의 logout() 호출 (클라이언트 상태만 클리어)
  • src/api/auth.ts: logout() 함수 (서버 호출 + 클라이언트 클리어)

더 큰 문제는 API 함수의 에러 처리인데, 현재는 서버 호출 실패 시에도 clearAuth()가 무조건 실행돼서 클라이언트와 서버 상태가 불일치할 수 있어요.

export const logout = async () => {
  try {
    await postLogout();
  } catch (e) {
    console.error(e);  // ← 실패 후에도 토큰을 지워버림
  } finally {
    clearAuth();  // ← 항상 실행
  }
};

제안:

  1. 로그아웃 사용처 통일 → PublicLayout에서도 API logout()을 호출하도록 변경
  2. 에러 발생 시 사용자에게 알리고, 재시도 기회 제공
  3. 필요하면 타임아웃/재시도 로직 추가
export const logout = async () => {
  try {
    await postLogout();
    clearAuth();  // 성공 시에만 클리어
  } catch (error) {
    console.error("로그아웃 실패:", error);
    throw error;  // 호출처에서 처리하도록
  }
};
🤖 Prompt for AI Agents
In `@src/api/auth.ts` around lines 55 - 63, The logout API currently always clears
client auth state even when postLogout() fails; update the export const logout
function to call await postLogout() and only call clearAuth() on success, log
the error with context (e.g., "로그아웃 실패:"), then rethrow the error so callers can
show a user-facing notification/retry UI; also change callers (e.g.,
PublicLayout.tsx) to invoke this API logout instead of the store-only logout to
keep client/server state consistent and consider adding optional timeout/retry
logic around postLogout().

import axios, { type AxiosError, type InternalAxiosRequestConfig } from "axios";
import { isApiResponse, normalizeApiError } from "./api.error";
import type { ApiError } from "@/types/api";
import { clearAuth, postRefresh } from "./auth";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find src/api -type f -name "*.ts" | head -20

Repository: Eatsfine/FE

Length of output: 167


🏁 Script executed:

cat -n src/api/axios.ts

Repository: Eatsfine/FE

Length of output: 3117


🏁 Script executed:

cat -n src/api/auth.ts | head -50

Repository: Eatsfine/FE

Length of output: 1582


동시 401 요청에서 토큰 재발급이 중복 호출되고 있어요

현재 코드에서 여러 요청이 동시에 401을 받으면 각각 postRefresh()를 독립적으로 호출하게 되어 경쟁 상태가 발생합니다. 예를 들어, 요청 A와 B가 동시에 401을 받으면 둘 다 새로운 토큰 재발급을 시작하는데, 이때 한 요청의 실패가 clearAuth()를 호출하면서 다른 요청은 토큰을 잃게 되는 문제가 생겨요.

또한 postRefresh()가 성공했지만 response.success === false를 반환하는 경우에 대한 명시적 처리가 없어서, 에러 상황이 제대로 전파되지 않을 수 있습니다.

🛠️ 해결 방안
+let refreshPromise: ReturnType<typeof postRefresh> | null = null;
+
 api.interceptors.response.use(
   (res) => {
     const data = res.data;
@@
       try {
-        //토큰 재발급 요청
-        const response = await postRefresh();
+        // 이미 진행 중인 재발급이 있으면 그 결과 재사용
+        if (!refreshPromise) {
+          refreshPromise = postRefresh().finally(() => {
+            refreshPromise = null;
+          });
+        }
+        const response = await refreshPromise;
+
+        // 재발급은 성공했지만 서버에서 실패 응답을 준 경우
+        if (!response.success) {
+          clearAuth();
+          return Promise.reject(apiError);
+        }
 
         if (response.success) {
           const newAccessToken = response.data.accessToken;
🤖 Prompt for AI Agents
In `@src/api/axios.ts` at line 4, Multiple concurrent 401s cause duplicate token
refreshes and race conditions; change the logic around postRefresh/clearAuth so
only one refresh runs at a time by introducing a shared in-flight promise (e.g.,
refreshingTokenPromise) that other requests await instead of calling postRefresh
again, have the single refresh handler check postRefresh() result and throw if
response.success === false so callers see the failure, and ensure clearAuth() is
only called after the refresh promise finally rejects (or when a non-recoverable
error occurs) so other waiting requests don’t lose tokens mid-refresh; update
the axios response interceptor (where postRefresh and clearAuth are used) to use
this shared promise and propagate errors to callers.

import ReservationPage from "./pages/myPage/reservationPage";
import StorePage from "./pages/myPage/storePage";
import OwnerPage from "./pages/ownerPage";
import { useEffect } from "react";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/App.tsx | head -100

Repository: Eatsfine/FE

Length of output: 3175


🏁 Script executed:

# Search for Kakao SDK script loading
rg -i "kakao" --type html --type js --type ts --type jsx --type tsx -A 3 -B 3 | head -100

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Search for Kakao SDK script loading without type restrictions
rg -i "kakao" -A 3 -B 3 --max-count 50

Repository: Eatsfine/FE

Length of output: 19669


🏁 Script executed:

cat -n index.html

Repository: Eatsfine/FE

Length of output: 945


카카오 SDK 초기화 시 재시도 로직 추가 권장

현재 코드는 useEffect가 한 번만 실행되므로, 외부 CDN에서 SDK가 로드되는 시간이 지연되는 경우(네트워크 지연 등) 초기화가 건너뛰어질 수 있어요. 특히 로그인 기능이 필요한 순간에 SDK를 사용할 수 없을 수 있습니다.

LoginDialog와 SignupDialog에서 이미 방어 로직이 있지만, App 레벨에서도 재시도 로직을 추가하면 더 안정적이에요.

💡 개선 예시
 useEffect(() => {
-  if (window.Kakao && !window.Kakao.isInitialized()) {
-    window.Kakao.init(import.meta.env.VITE_KAKAO_JS_KEY);
-  }
+  const tryInit = () => {
+    if (window.Kakao && !window.Kakao.isInitialized()) {
+      window.Kakao.init(import.meta.env.VITE_KAKAO_JS_KEY);
+      return true;
+    }
+    return false;
+  };
+
+  if (tryInit()) return;
+  const interval = setInterval(() => {
+    if (tryInit()) clearInterval(interval);
+  }, 200);
+  return () => clearInterval(interval);
 }, []);
🤖 Prompt for AI Agents
In `@src/App.tsx` at line 22, Add a retry/backoff initialization inside the App
component's useEffect so the Kakao SDK init is attempted multiple times if
window.Kakao isn't ready yet: inside useEffect in App, poll or retry (e.g.,
exponential backoff or fixed intervals, max attempts) to check for window.Kakao
and call window.Kakao.init(...) once available, and ensure you clear
timers/intervals on unmount; keep existing defensive checks in LoginDialog and
SignupDialog but move SDK-ready guarantees to App's useEffect to reduce missed
initializations.

Comment on lines +5 to +6
role: z.enum(["customer", "owner"]).default("customer"),

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

휴대폰 포맷 변경으로 숫자만 입력하면 전부 실패할 수 있어요

UI에서 하이픈을 강제하지 않으면 검증이 깨질 수 있습니다. 하이픈을 옵션으로 허용하거나 입력을 정규화해 주세요.

🛠️ 제안 수정
-      .regex(/^\d{2,3}-\d{3,4}-\d{4}$/, {
+      .regex(/^\d{2,3}-?\d{3,4}-?\d{4}$/, {
         message: "올바른 휴대폰 번호 형식이 아닙니다.",
       }),

Also applies to: 17-18

🤖 Prompt for AI Agents
In `@src/components/auth/signup.schema.ts` around lines 5 - 6, The phone
validation in signup.schema.ts currently rejects inputs with or without hyphens;
update the schema's phone (phoneNumber) field to either accept optional hyphens
via a more permissive regex or, preferably, normalize input with z.preprocess to
strip non-digit characters before running the numeric/length check; apply the
same change to the other occurrence noted (lines 17-18) so both validations
match and the role: z.enum([...]).default("customer") entry remains unchanged.

Comment on lines +137 to +166
<Controller
control={control}
name="role"
render={({ field }) => (
<div className="flex justify-center gap-2">
<button
type="button"
onClick={() => field.onChange("customer")}
className={
field.value === "customer"
? "bg-blue-600 text-white h-12 w-full rounded-lg transition-colors"
: "bg-gray-200 text-gray-600 h-12 w-full rounded-lg hover:bg-gray-300 transition-colors cursor-pointer"
}
>
고객
</button>
<button
type="button"
onClick={() => field.onChange("owner")}
className={
field.value === "owner"
? "bg-blue-600 text-white h-12 w-full rounded-lg transition-colors"
: "bg-gray-200 text-gray-600 h-12 w-full rounded-lg hover:bg-gray-300 transition-colors cursor-pointer"
}
>
점주
</button>
</div>
)}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

역할 선택 버튼 접근성 개선 필요

토글 버튼에 aria-pressed 속성이 없어서 스크린 리더 사용자가 현재 선택 상태를 알기 어려워요. 접근성을 위해 추가해주시면 좋겠습니다.

♿ 접근성 개선 제안
 <button
   type="button"
   onClick={() => field.onChange("customer")}
+  aria-pressed={field.value === "customer"}
   className={
     field.value === "customer"
       ? "bg-blue-600 text-white h-12 w-full rounded-lg transition-colors"
       : "bg-gray-200 text-gray-600 h-12 w-full rounded-lg hover:bg-gray-300 transition-colors cursor-pointer"
   }
 >
   고객
 </button>
 <button
   type="button"
   onClick={() => field.onChange("owner")}
+  aria-pressed={field.value === "owner"}
   className={

As per coding guidelines: 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.

🤖 Prompt for AI Agents
In `@src/components/auth/SignupDialog.tsx` around lines 137 - 166, The
role-selection buttons inside the Controller for name="role" lack aria state for
assistive tech; update the two buttons rendered in SignupDialog (inside the
Controller render using field and field.onChange) to include
aria-pressed={field.value === "customer"} for the 고객 button and
aria-pressed={field.value === "owner"} for the 점주 button so the current
selection is exposed to screen readers (keep existing onClick/field.onChange
logic and styling).

Comment on lines +11 to +18
mutationFn: (data: SignupFormValues) => {
const requestBody = {
role: data.role!,
name: data.name,
email: data.email,
phone: data.phone,
password: data.password,
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "useAuth.ts" -o -name "*useAuth*" | head -20

Repository: Eatsfine/FE

Length of output: 114


🏁 Script executed:

# Find the hooks directory and examine the structure
fd "useAuth" --type f

Repository: Eatsfine/FE

Length of output: 110


🏁 Script executed:

# Search for SignupFormValues definition
rg "SignupFormValues" --type ts --type tsx -A 3 -B 1

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

rg "SignupFormValues" --type ts -A 3 -B 1

Repository: Eatsfine/FE

Length of output: 2317


🏁 Script executed:

# Read the useAuth.ts file to see the context
head -40 src/hooks/queries/useAuth.ts

Repository: Eatsfine/FE

Length of output: 1201


🏁 Script executed:

cat src/components/auth/signup.schema.ts

Repository: Eatsfine/FE

Length of output: 1135


role 필드는 타입상 undefined가 될 수 있어서 non-null 단언이 안전하지 않아요

SignupFormValues = z.input<typeof signupSchema>로 정의되어 있는데, z.input은 Zod의 기본값(.default())을 포함하지 않습니다. 따라서 TypeScript는 roleundefined 가능성이 있는 것으로 봅니다. 실제로는 폼에서 기본값으로 초기화되지만, 타입 안정성을 위해 더 안전한 처리가 필요합니다.

🛠️ 제안 수정
-        role: data.role!,
+        role: data.role ?? "customer",
🤖 Prompt for AI Agents
In `@src/hooks/queries/useAuth.ts` around lines 11 - 18, The mutationFn in
useAuth.ts currently uses a non-null assertion for data.role (role: data.role!)
which is unsafe because SignupFormValues (z.input<typeof signupSchema>) can have
role undefined; remove the `!` and either provide an explicit safe fallback
(e.g., role: data.role ?? '<appropriate-default>' ) or run runtime validation
with signupSchema.parse/parseAsync to derive a guaranteed role value before
building requestBody; reference mutationFn, SignupFormValues, and signupSchema
when making the change.

Comment on lines +9 to +15
const handleLogout = () => {
if (!confirm("로그아웃 하시겠습니까?")) return;

logout();
alert("로그아웃 되었습니다.");
nav("/", { replace: true });
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find logout usage and related auth implementations
rg -n "logout|postLogout" --type=ts --type=tsx -C 3

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Find logout usage and related auth implementations
rg -n "logout|postLogout" --type=ts -C 3

Repository: Eatsfine/FE

Length of output: 2928


🏁 Script executed:

# Read src/api/auth.ts to see full logout and clearAuth implementations
cat -n src/api/auth.ts | head -65

Repository: Eatsfine/FE

Length of output: 1949


로그아웃 시 서버 API 호출 필요

현재 handleLogout은 Zustand store의 logout() 액션만 호출해요. 이건 클라이언트 상태(토큰)만 클리어하고, 서버 세션을 무효화하는 API 호출이 빠져있어요.

src/api/auth.ts에 정의된 logout() 함수가 있는데, 이게 postLogout()으로 서버에 로그아웃 요청을 보낸 후 clearAuth()로 로컬 스토리지를 정리합니다. 현재는 이 함수를 사용하지 않고 있어요.

제안: store의 logout 액션을 async로 변경하거나, 아니면 다음처럼 handleLogout에서 API를 직접 호출하기

const handleLogout = async () => {
  if (!confirm("로그아웃 하시겠습니까?")) return;

  try {
    await logout(); // src/api/auth.ts의 logout() 사용
    alert("로그아웃 되었습니다.");
  } catch (error) {
    console.error("로그아웃 실패:", error);
    alert("로그아웃에 실패했습니다.");
  }
};

참고로 Header.tsx의 handleLogout도 같은 패턴이라 함께 수정하면 좋을 것 같아요.

🤖 Prompt for AI Agents
In `@src/layouts/PublicLayout.tsx` around lines 9 - 15, handleLogout currently
only calls the client-side store logout and skips the server API call; update it
to call the existing API logout flow (src/api/auth.ts's logout which calls
postLogout() then clearAuth()) and handle errors. Specifically, in
PublicLayout.tsx update the handleLogout handler to be async, prompt
confirmation, await the api logout() from src/api/auth.ts (or dispatch an async
store.logout that wraps that API), then show success alert and navigate, and on
catch log the error and show a failure alert; apply the same pattern to
Header.tsx's handleLogout so both use the API-backed logout instead of only the
store action.

Comment on lines +7 to +16
import { GoogleOAuthProvider } from "@react-oauth/google";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>,
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
</GoogleOAuthProvider>,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the actual content of src/main.tsx
cat -n src/main.tsx

Repository: Eatsfine/FE

Length of output: 782


🏁 Script executed:

# Search for VITE_GOOGLE_CLIENT_ID usage across the codebase
rg "VITE_GOOGLE_CLIENT_ID" --type ts --type tsx -B 2 -A 2

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Check if there's any environment variable validation/setup
rg "import\.meta\.env" --type ts --type tsx -B 1 -A 1

Repository: Eatsfine/FE

Length of output: 82


🏁 Script executed:

# Look for vite config to understand env variable setup
fd "vite.config" --type f

Repository: Eatsfine/FE

Length of output: 69


🏁 Script executed:

# Check for .env or .env.example files
fd "\\.env" --type f

Repository: Eatsfine/FE

Length of output: 67


🏁 Script executed:

# Read .env.example to see expected env variables
cat .env.example

Repository: Eatsfine/FE

Length of output: 136


🏁 Script executed:

# Check vite.config.ts for environment configuration
cat vite.config.ts

Repository: Eatsfine/FE

Length of output: 424


🏁 Script executed:

# Search for VITE_GOOGLE_CLIENT_ID without file type restriction
rg "VITE_GOOGLE_CLIENT_ID" -B 2 -A 2

Repository: Eatsfine/FE

Length of output: 415


🏁 Script executed:

# Check if there's any initialization or validation code in the project
rg "clientId" --type-add "source:!{node_modules,dist,.git}" -B 2 -A 2 | head -50

Repository: Eatsfine/FE

Length of output: 316


🏁 Script executed:

# Look for any error boundaries or initialization logic
fd "main\." --type f

Repository: Eatsfine/FE

Length of output: 67


🏁 Script executed:

# Check if there's any env validation or setup code in utils/config
fd "config\|env\|setup" --type f src/

Repository: Eatsfine/FE

Length of output: 37


🏁 Script executed:

# Look for any build or setup documentation
fd "README\|SETUP\|\.md" --type f

Repository: Eatsfine/FE

Length of output: 37


🏁 Script executed:

# Check package.json to understand the project better
cat package.json | head -50

Repository: Eatsfine/FE

Length of output: 1565


🏁 Script executed:

# Search for any error handling or initialization patterns
rg "throw new Error\|console\.error\|process\.exit" -B 1 -A 1 | head -40

Repository: Eatsfine/FE

Length of output: 37


GoogleOAuthProvider에 clientId가 없으면 앱 초기화가 깨질 수 있어요

VITE_GOOGLE_CLIENT_ID 환경 변수가 설정되지 않으면 provider가 undefined를 받게 되어 런타임 오류로 이어질 수 있습니다. 초기화 전에 필수 환경 변수를 검증하는 가드를 추가해 주세요. 개발할 때 환경 설정을 잘못했을 경우 빠르게 문제를 파악할 수 있습니다.

🛠️ 제안 수정
+const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
+if (!googleClientId) {
+  throw new Error("VITE_GOOGLE_CLIENT_ID is required");
+}
 
 createRoot(document.getElementById("root")!).render(
-  <GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
+  <GoogleOAuthProvider clientId={googleClientId}>
     <StrictMode>
       <QueryClientProvider client={queryClient}>
         <App />
       </QueryClientProvider>
     </StrictMode>
   </GoogleOAuthProvider>,
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { GoogleOAuthProvider } from "@react-oauth/google";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>,
<GoogleOAuthProvider clientId={import.meta.env.VITE_GOOGLE_CLIENT_ID}>
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
</GoogleOAuthProvider>,
import { GoogleOAuthProvider } from "@react-oauth/google";
const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
if (!googleClientId) {
throw new Error("VITE_GOOGLE_CLIENT_ID is required");
}
createRoot(document.getElementById("root")!).render(
<GoogleOAuthProvider clientId={googleClientId}>
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
</GoogleOAuthProvider>,
🤖 Prompt for AI Agents
In `@src/main.tsx` around lines 7 - 16, Check
import.meta.env.VITE_GOOGLE_CLIENT_ID before rendering and guard the
GoogleOAuthProvider initialization: ensure the value used for
GoogleOAuthProvider's clientId (import.meta.env.VITE_GOOGLE_CLIENT_ID) is
validated and throw or log a clear error and avoid rendering when it's missing;
update the bootstrap around createRoot(...).render(...) to perform the check and
fail fast with a clear message that includes which env var is missing so
developers can fix their environment quickly.

>
회원가입
</Button>
{isAuthenticated ? (
Copy link
Contributor

Choose a reason for hiding this comment

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

지금 isAuthenticated에 따라 마이페이지-로그아웃 또는 로그인-회원가입으로 헤더가 바뀌지 않는게 배포서버가 완료되지않아서 일까요?

Copy link
Contributor Author

@dew102938 dew102938 Feb 4, 2026

Choose a reason for hiding this comment

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

넵 api 요청이 수행되지 않으므로 상태가 false로 유지되어 안바뀌는 것 같습니다
헤더가 변경되는 UI 동작 확인을 위해
브라우저에서 개발자 도구 콘솔 창에
localStorage.setItem('auth-storage', '{"state":{"accessToken":"fake-test-token","isAuthenticated":true},"version":0}');
location.reload();
이 코드를 입력해 엔터를 쳐보면
강제로 로그인 상태를 만들어 UI 확인 가능합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API API 연동, 통신관련 feat 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 로그인 / 회원가입 API 연동

2 participants