kt-event-marketing-fe/docs/user-api-integration.md
cherry2250 10c728dbaf User API 전체 연동 완료 및 로그아웃 에러 처리 개선
## 주요 변경사항

### 1. FSD 아키텍처 기반 API 레이어 구축
- entities/user: User 엔티티 (타입, API)
- features/auth: 인증 기능 (useAuth, AuthProvider)
- shared/api: 공통 API 클라이언트 (Axios, 인터셉터)

### 2. 전체 User API 화면 연동 완료
-  POST /api/v1/users/login → login/page.tsx
-  POST /api/v1/users/register → register/page.tsx
-  POST /api/v1/users/logout → profile/page.tsx
-  GET /api/v1/users/profile → profile/page.tsx
-  PUT /api/v1/users/profile → profile/page.tsx
-  PUT /api/v1/users/password → profile/page.tsx

### 3. 로그인 페이지 API 연동
- useAuthStore → useAuthContext 변경
- 실제 로그인 API 호출
- 비밀번호 검증 완화 (API 스펙에 맞춤)
- 상세 로깅 추가

### 4. 프로필 페이지 API 연동
- 프로필 자동 로드 (GET /profile)
- 프로필 수정 (PUT /profile)
- 비밀번호 변경 (PUT /password)
- 로그아웃 (POST /logout)
- 전화번호 형식 변환 (01012345678 ↔ 010-1234-5678)

### 5. 로그아웃 에러 처리 개선
- 백엔드 500 에러 발생해도 로컬 상태 정리 후 로그아웃 진행
- 사용자 경험 우선: 정상 로그아웃으로 처리
- 개발자용 상세 에러 로그 출력

### 6. 문서화
- docs/api-integration-complete.md: 전체 연동 완료 보고서
- docs/api-server-issue.md: 백엔드 이슈 상세 보고 (회원가입 타임아웃, 로그아웃 500 에러)
- docs/user-api-integration.md: User API 통합 가이드
- docs/register-api-guide.md: 회원가입 API 가이드

### 7. 에러 처리 강화
- 서버 응답 에러 / 네트워크 에러 / 요청 설정 에러 구분
- 사용자 친화적 에러 메시지
- 전체 프로세스 상세 로깅

## 기술 스택
- FSD Architecture
- React Context API (AuthProvider)
- Axios (인터셉터, 90초 타임아웃)
- Zod (폼 검증)
- TypeScript (엄격한 타입)

## 테스트
-  빌드 성공
-  백엔드 안정화 후 전체 플로우 테스트 필요

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:18:23 +09:00

7.8 KiB

User API 연동 가이드

개요

FSD(Feature-Sliced Design) 아키텍처를 기반으로 User Service API 연동을 구현했습니다.

디렉토리 구조

src/
├── shared/
│   └── api/
│       ├── client.ts          # Axios 클라이언트 설정
│       ├── types.ts           # 공통 API 타입
│       └── index.ts
├── entities/
│   └── user/
│       ├── model/
│       │   └── types.ts       # User 엔티티 타입
│       ├── api/
│       │   └── userApi.ts     # User API 함수
│       └── index.ts
└── features/
    ├── auth/
    │   ├── model/
    │   │   ├── useAuth.ts     # 인증 훅
    │   │   └── AuthProvider.tsx  # 인증 Context
    │   └── index.ts
    └── profile/
        ├── model/
        │   └── useProfile.ts  # 프로필 훅
        └── index.ts

API 명세

  • Base URL: http://20.196.65.160:8081
  • Endpoints:
    • POST /api/v1/users/login - 로그인
    • POST /api/v1/users/register - 회원가입
    • POST /api/v1/users/logout - 로그아웃
    • GET /api/v1/users/profile - 프로필 조회
    • PUT /api/v1/users/profile - 프로필 수정
    • PUT /api/v1/users/password - 비밀번호 변경

사용 방법

1. AuthProvider 설정

루트 레이아웃에 AuthProvider를 추가합니다:

// app/layout.tsx
import { AuthProvider } from '@/features/auth';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

2. 로그인 구현 예제

'use client';

import { useAuthContext } from '@/features/auth';
import { useState } from 'react';

export default function LoginPage() {
  const { login, isLoading } = useAuthContext();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();

    const result = await login({ email, password });

    if (result.success) {
      // 로그인 성공
      console.log('로그인 성공:', result.user);
      // 페이지 이동 등
    } else {
      // 로그인 실패
      console.error('로그인 실패:', result.error);
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="이메일"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="비밀번호"
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? '로그인 중...' : '로그인'}
      </button>
    </form>
  );
}

3. 회원가입 구현 예제

'use client';

import { useAuthContext } from '@/features/auth';
import { useState } from 'react';
import type { RegisterRequest } from '@/entities/user';

export default function RegisterPage() {
  const { register, isLoading } = useAuthContext();
  const [formData, setFormData] = useState<RegisterRequest>({
    name: '',
    phoneNumber: '',
    email: '',
    password: '',
    storeName: '',
    industry: '',
    address: '',
    businessHours: '',
  });

  const handleRegister = async (e: React.FormEvent) => {
    e.preventDefault();

    const result = await register(formData);

    if (result.success) {
      console.log('회원가입 성공:', result.user);
    } else {
      console.error('회원가입 실패:', result.error);
    }
  };

  return (
    <form onSubmit={handleRegister}>
      {/* 폼 필드들... */}
      <button type="submit" disabled={isLoading}>
        {isLoading ? '가입 중...' : '회원가입'}
      </button>
    </form>
  );
}

4. 프로필 조회 및 수정 예제

'use client';

import { useProfile } from '@/features/profile';
import { useEffect } from 'react';

export default function ProfilePage() {
  const {
    profile,
    isLoading,
    error,
    fetchProfile,
    updateProfile
  } = useProfile();

  useEffect(() => {
    fetchProfile();
  }, [fetchProfile]);

  const handleUpdate = async () => {
    const result = await updateProfile({
      name: '새로운 이름',
      storeName: '새로운 가게명',
    });

    if (result.success) {
      console.log('프로필 수정 성공:', result.data);
    }
  };

  if (isLoading) return <div>로딩 ...</div>;
  if (error) return <div>에러: {error}</div>;
  if (!profile) return <div>프로필 없음</div>;

  return (
    <div>
      <h1>{profile.userName}</h1>
      <p>이메일: {profile.email}</p>
      <p>가게명: {profile.storeName}</p>
      <button onClick={handleUpdate}>프로필 수정</button>
    </div>
  );
}

5. 인증 상태 확인 예제

'use client';

import { useAuthContext } from '@/features/auth';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function ProtectedPage() {
  const { user, isAuthenticated, isLoading } = useAuthContext();
  const router = useRouter();

  useEffect(() => {
    if (!isLoading && !isAuthenticated) {
      router.push('/login');
    }
  }, [isAuthenticated, isLoading, router]);

  if (isLoading) return <div>로딩 ...</div>;
  if (!isAuthenticated) return null;

  return (
    <div>
      <h1>환영합니다, {user?.userName}!</h1>
    </div>
  );
}

6. 로그아웃 구현 예제

'use client';

import { useAuthContext } from '@/features/auth';
import { useRouter } from 'next/navigation';

export default function Header() {
  const { user, isAuthenticated, logout } = useAuthContext();
  const router = useRouter();

  const handleLogout = async () => {
    await logout();
    router.push('/login');
  };

  if (!isAuthenticated) return null;

  return (
    <header>
      <span>{user?.userName}</span>
      <button onClick={handleLogout}>로그아웃</button>
    </header>
  );
}

타입 정의

User 타입

interface User {
  userId: number;
  userName: string;
  email: string;
  role: string;
  phoneNumber?: string;
  storeId?: number;
  storeName?: string;
  industry?: string;
  address?: string;
  businessHours?: string;
}

LoginRequest 타입

interface LoginRequest {
  email: string;
  password: string;
}

RegisterRequest 타입

interface RegisterRequest {
  name: string;
  phoneNumber: string;      // 패턴: ^010\d{8}$
  email: string;
  password: string;          // 최소 8자
  storeName: string;
  industry?: string;
  address: string;
  businessHours?: string;
}

API Client 설정

API 클라이언트는 다음 기능을 자동으로 처리합니다:

  1. JWT 토큰 자동 추가: localStorage의 accessToken을 자동으로 헤더에 포함
  2. 401 인증 오류 처리: 인증 실패 시 자동으로 토큰 삭제 및 로그인 페이지로 리다이렉트
  3. Base URL 설정: 환경 변수로 API 서버 URL 관리

환경 변수

.env.local 파일에 다음 환경 변수를 설정하세요:

NEXT_PUBLIC_API_BASE_URL=http://20.196.65.160:8081

주의사항

  1. 토큰 관리: 토큰은 localStorage에 저장되며, 로그아웃 시 자동으로 삭제됩니다.
  2. 인증 상태: AuthProvider로 감싼 컴포넌트에서만 useAuthContext 사용 가능합니다.
  3. 에러 처리: 모든 API 함수는 try-catch로 에러를 처리하며, 결과 객체에 success와 error를 포함합니다.
  4. 비밀번호 검증: 회원가입 시 비밀번호는 최소 8자 이상이어야 합니다.
  5. 전화번호 형식: 010으로 시작하는 11자리 숫자만 허용됩니다.

빌드 및 실행

# 빌드
npm run build

# 개발 서버 실행 (사용자가 직접 수행)
npm run dev