mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 14:16:24 +00:00
## 주요 변경사항 ### 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>
345 lines
7.8 KiB
Markdown
345 lines
7.8 KiB
Markdown
# 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를 추가합니다:
|
|
|
|
```tsx
|
|
// app/layout.tsx
|
|
import { AuthProvider } from '@/features/auth';
|
|
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html>
|
|
<body>
|
|
<AuthProvider>
|
|
{children}
|
|
</AuthProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 2. 로그인 구현 예제
|
|
|
|
```tsx
|
|
'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. 회원가입 구현 예제
|
|
|
|
```tsx
|
|
'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. 프로필 조회 및 수정 예제
|
|
|
|
```tsx
|
|
'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. 인증 상태 확인 예제
|
|
|
|
```tsx
|
|
'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. 로그아웃 구현 예제
|
|
|
|
```tsx
|
|
'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 타입
|
|
|
|
```typescript
|
|
interface User {
|
|
userId: number;
|
|
userName: string;
|
|
email: string;
|
|
role: string;
|
|
phoneNumber?: string;
|
|
storeId?: number;
|
|
storeName?: string;
|
|
industry?: string;
|
|
address?: string;
|
|
businessHours?: string;
|
|
}
|
|
```
|
|
|
|
### LoginRequest 타입
|
|
|
|
```typescript
|
|
interface LoginRequest {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
```
|
|
|
|
### RegisterRequest 타입
|
|
|
|
```typescript
|
|
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` 파일에 다음 환경 변수를 설정하세요:
|
|
|
|
```env
|
|
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자리 숫자만 허용됩니다.
|
|
|
|
## 빌드 및 실행
|
|
|
|
```bash
|
|
# 빌드
|
|
npm run build
|
|
|
|
# 개발 서버 실행 (사용자가 직접 수행)
|
|
npm run dev
|
|
```
|