mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 11:36:24 +00:00
- 이벤트 목록 페이지에 Mock 데이터 적용 (evt_2025012301 등 4개 이벤트) - 이벤트 상세 페이지 Analytics API 임시 주석처리 (서버 이슈) - Participation API 프록시 라우트 URL 구조 수정 (/events/ 제거) - EventID localStorage 저장 기능 추가 - 상세한 console.log 추가 (생성된 eventId, objective, timestamp) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
598 lines
14 KiB
Markdown
598 lines
14 KiB
Markdown
# User API 연동 현황
|
|
|
|
## 📋 연동 완료 요약
|
|
|
|
User API는 **완전히 구현되어 있으며**, 로그인 및 회원가입 기능이 정상적으로 작동합니다.
|
|
|
|
### ✅ 구현 완료 항목
|
|
|
|
1. **API 클라이언트 설정**
|
|
- Gateway를 통한 백엔드 직접 연동
|
|
- 토큰 기반 인증 시스템
|
|
- Request/Response 인터셉터
|
|
|
|
2. **타입 정의**
|
|
- LoginRequest/Response
|
|
- RegisterRequest/Response
|
|
- ProfileResponse
|
|
- User 및 AuthState 인터페이스
|
|
|
|
3. **인증 로직**
|
|
- useAuth 커스텀 훅
|
|
- AuthProvider Context
|
|
- localStorage 기반 세션 관리
|
|
|
|
4. **UI 페이지**
|
|
- 로그인 페이지 (/login)
|
|
- 회원가입 페이지 (/register)
|
|
- 3단계 회원가입 플로우
|
|
|
|
---
|
|
|
|
## 🏗️ 아키텍처 구조
|
|
|
|
```
|
|
프론트엔드 Gateway 백엔드
|
|
┌─────────────┐ ┌────────┐ ┌──────────┐
|
|
│ │ HTTP │ │ HTTP │ │
|
|
│ Browser ├────────────>│Gateway ├─────────────>│ User API │
|
|
│ │<────────────┤ │<─────────────┤ │
|
|
└─────────────┘ JSON+JWT └────────┘ JSON+JWT └──────────┘
|
|
```
|
|
|
|
### API 클라이언트 설정
|
|
|
|
```typescript
|
|
// src/shared/api/client.ts
|
|
const GATEWAY_HOST = 'http://kt-event-marketing-api.20.214.196.128.nip.io';
|
|
const API_HOSTS = {
|
|
user: GATEWAY_HOST,
|
|
event: GATEWAY_HOST,
|
|
content: GATEWAY_HOST,
|
|
ai: GATEWAY_HOST,
|
|
participation: GATEWAY_HOST,
|
|
distribution: GATEWAY_HOST,
|
|
analytics: GATEWAY_HOST,
|
|
};
|
|
|
|
// User API는 apiClient를 통해 직접 Gateway에 연결
|
|
export const apiClient: AxiosInstance = axios.create({
|
|
baseURL: API_HOSTS.user,
|
|
timeout: 90000,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
```
|
|
|
|
**💡 프록시 라우트 불필요**: User API는 Next.js 프록시를 거치지 않고 브라우저에서 Gateway로 직접 요청합니다.
|
|
|
|
---
|
|
|
|
## 📡 User API 엔드포인트
|
|
|
|
### 1. 로그인
|
|
```http
|
|
POST /api/v1/users/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"userId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"userName": "홍길동",
|
|
"role": "USER",
|
|
"email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
### 2. 회원가입
|
|
```http
|
|
POST /api/v1/users/register
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "홍길동",
|
|
"phoneNumber": "01012345678",
|
|
"email": "user@example.com",
|
|
"password": "password123",
|
|
"storeName": "홍길동 고깃집",
|
|
"industry": "restaurant",
|
|
"address": "서울특별시 강남구",
|
|
"businessHours": "09:00-18:00"
|
|
}
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
|
"userId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"userName": "홍길동",
|
|
"storeId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
"storeName": "홍길동 고깃집"
|
|
}
|
|
```
|
|
|
|
### 3. 로그아웃
|
|
```http
|
|
POST /api/v1/users/logout
|
|
Authorization: Bearer {token}
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "로그아웃되었습니다"
|
|
}
|
|
```
|
|
|
|
### 4. 프로필 조회
|
|
```http
|
|
GET /api/v1/users/profile
|
|
Authorization: Bearer {token}
|
|
```
|
|
|
|
**응답:**
|
|
```json
|
|
{
|
|
"userId": "550e8400-e29b-41d4-a716-446655440000",
|
|
"userName": "홍길동",
|
|
"phoneNumber": "01012345678",
|
|
"email": "user@example.com",
|
|
"role": "USER",
|
|
"storeId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
"storeName": "홍길동 고깃집",
|
|
"industry": "restaurant",
|
|
"address": "서울특별시 강남구",
|
|
"businessHours": "09:00-18:00",
|
|
"createdAt": "2025-01-01T00:00:00",
|
|
"lastLoginAt": "2025-01-10T12:00:00"
|
|
}
|
|
```
|
|
|
|
### 5. 프로필 수정
|
|
```http
|
|
PUT /api/v1/users/profile
|
|
Authorization: Bearer {token}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "홍길동",
|
|
"phoneNumber": "01012345678",
|
|
"storeName": "홍길동 고깃집",
|
|
"industry": "restaurant",
|
|
"address": "서울특별시 강남구",
|
|
"businessHours": "09:00-18:00"
|
|
}
|
|
```
|
|
|
|
### 6. 비밀번호 변경
|
|
```http
|
|
PUT /api/v1/users/password
|
|
Authorization: Bearer {token}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"currentPassword": "oldpassword",
|
|
"newPassword": "newpassword123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 인증 플로우
|
|
|
|
### 로그인 플로우
|
|
```
|
|
1. 사용자가 이메일/비밀번호 입력
|
|
2. userApi.login() 호출
|
|
3. 서버에서 JWT 토큰 발급
|
|
4. localStorage에 토큰 저장
|
|
5. userApi.getProfile() 호출 (storeId 포함된 전체 정보 획득)
|
|
6. localStorage에 User 정보 저장
|
|
7. AuthContext 상태 업데이트
|
|
8. 메인 페이지로 리디렉션
|
|
```
|
|
|
|
### 회원가입 플로우
|
|
```
|
|
1. 3단계 폼 작성
|
|
- Step 1: 계정 정보 (이메일, 비밀번호)
|
|
- Step 2: 개인 정보 (이름, 전화번호)
|
|
- Step 3: 사업장 정보 (상호명, 업종, 주소 등)
|
|
2. userApi.register() 호출
|
|
3. 서버에서 사용자 생성 및 JWT 토큰 발급
|
|
4. localStorage에 토큰 및 User 정보 저장
|
|
5. AuthContext 상태 업데이트
|
|
6. 회원가입 완료 다이얼로그 표시
|
|
7. 메인 페이지로 리디렉션
|
|
```
|
|
|
|
### 로그아웃 플로우
|
|
```
|
|
1. userApi.logout() 호출
|
|
2. 서버에서 세션 무효화 (실패해도 계속 진행)
|
|
3. localStorage에서 토큰 및 User 정보 삭제
|
|
4. AuthContext 상태 초기화
|
|
5. 로그인 페이지로 리디렉션
|
|
```
|
|
|
|
---
|
|
|
|
## 📂 파일 구조
|
|
|
|
### API Layer
|
|
```
|
|
src/entities/user/
|
|
├── api/
|
|
│ └── userApi.ts # User API 서비스 함수
|
|
├── model/
|
|
│ └── types.ts # TypeScript 타입 정의
|
|
└── index.ts # Public exports
|
|
```
|
|
|
|
### Features Layer
|
|
```
|
|
src/features/auth/
|
|
├── model/
|
|
│ ├── useAuth.ts # 인증 커스텀 훅
|
|
│ └── AuthProvider.tsx # Context Provider
|
|
└── index.ts # Public exports
|
|
```
|
|
|
|
### Pages
|
|
```
|
|
src/app/(auth)/
|
|
├── login/
|
|
│ └── page.tsx # 로그인 페이지
|
|
└── register/
|
|
└── page.tsx # 회원가입 페이지 (3단계 플로우)
|
|
```
|
|
|
|
### Shared
|
|
```
|
|
src/shared/api/
|
|
├── client.ts # Axios 클라이언트 설정
|
|
└── index.ts # Public exports
|
|
|
|
src/stores/
|
|
└── authStore.ts # Zustand 인증 스토어 (참고용)
|
|
```
|
|
|
|
---
|
|
|
|
## 🔑 주요 코드 구조
|
|
|
|
### 1. User API Service
|
|
|
|
**src/entities/user/api/userApi.ts:**
|
|
```typescript
|
|
const USER_API_BASE = '/api/v1/users';
|
|
|
|
export const userApi = {
|
|
login: async (data: LoginRequest): Promise<LoginResponse> => {...},
|
|
register: async (data: RegisterRequest): Promise<RegisterResponse> => {...},
|
|
logout: async (): Promise<LogoutResponse> => {...},
|
|
getProfile: async (): Promise<ProfileResponse> => {...},
|
|
updateProfile: async (data: UpdateProfileRequest): Promise<ProfileResponse> => {...},
|
|
changePassword: async (data: ChangePasswordRequest): Promise<void> => {...},
|
|
};
|
|
```
|
|
|
|
### 2. useAuth Hook
|
|
|
|
**src/features/auth/model/useAuth.ts:**
|
|
```typescript
|
|
export const useAuth = () => {
|
|
const [authState, setAuthState] = useState<AuthState>({
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
isLoading: true,
|
|
});
|
|
|
|
// 초기 인증 상태 확인 (localStorage 기반)
|
|
useEffect(() => {
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
const userStr = localStorage.getItem(USER_KEY);
|
|
if (token && userStr) {
|
|
const user = JSON.parse(userStr) as User;
|
|
setAuthState({
|
|
user,
|
|
token,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
});
|
|
}
|
|
}, []);
|
|
|
|
// 로그인 함수
|
|
const login = async (credentials: LoginRequest) => {
|
|
const response = await userApi.login(credentials);
|
|
localStorage.setItem(TOKEN_KEY, response.token);
|
|
const profile = await userApi.getProfile();
|
|
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
|
setAuthState({...});
|
|
return { success: true, user };
|
|
};
|
|
|
|
// 회원가입, 로그아웃, 프로필 새로고침 함수들...
|
|
|
|
return {
|
|
...authState,
|
|
login,
|
|
register,
|
|
logout,
|
|
refreshProfile,
|
|
};
|
|
};
|
|
```
|
|
|
|
### 3. AuthProvider Context
|
|
|
|
**src/features/auth/model/AuthProvider.tsx:**
|
|
```typescript
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|
const auth = useAuth();
|
|
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
|
|
};
|
|
|
|
export const useAuthContext = () => {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuthContext must be used within AuthProvider');
|
|
}
|
|
return context;
|
|
};
|
|
```
|
|
|
|
### 4. RootLayout 적용
|
|
|
|
**src/app/layout.tsx:**
|
|
```typescript
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html lang="ko">
|
|
<body>
|
|
<MUIThemeProvider>
|
|
<ReactQueryProvider>
|
|
<AuthProvider>
|
|
{children}
|
|
</AuthProvider>
|
|
</ReactQueryProvider>
|
|
</MUIThemeProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 테스트 방법
|
|
|
|
### 1. 회원가입 테스트
|
|
|
|
1. 개발 서버 실행
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
2. 브라우저에서 `/register` 접속
|
|
|
|
3. 3단계 폼 작성:
|
|
- **Step 1**: 이메일 및 비밀번호 입력
|
|
- **Step 2**: 이름 및 전화번호 입력 (010-1234-5678 형식)
|
|
- **Step 3**: 사업장 정보 입력 및 약관 동의
|
|
|
|
4. "가입완료" 버튼 클릭
|
|
|
|
5. 성공 시:
|
|
- 회원가입 완료 다이얼로그 표시
|
|
- localStorage에 토큰 및 사용자 정보 저장
|
|
- 메인 페이지로 리디렉션
|
|
|
|
### 2. 로그인 테스트
|
|
|
|
1. 브라우저에서 `/login` 접속
|
|
|
|
2. 이메일 및 비밀번호 입력
|
|
|
|
3. "로그인" 버튼 클릭
|
|
|
|
4. 성공 시:
|
|
- localStorage에 토큰 및 사용자 정보 저장
|
|
- 메인 페이지로 리디렉션
|
|
- 헤더에 사용자 정보 표시
|
|
|
|
### 3. 로그아웃 테스트
|
|
|
|
1. 로그인된 상태에서 프로필 페이지 또는 헤더 메뉴 접근
|
|
|
|
2. "로그아웃" 버튼 클릭
|
|
|
|
3. 성공 시:
|
|
- localStorage에서 토큰 및 사용자 정보 삭제
|
|
- 로그인 페이지로 리디렉션
|
|
|
|
### 4. 디버깅
|
|
|
|
브라우저 개발자 도구 Console에서 다음 로그 확인:
|
|
|
|
```
|
|
📞 전화번호 변환: 010-1234-5678 -> 01012345678
|
|
📦 회원가입 요청 데이터: {...}
|
|
🚀 registerUser 함수 호출
|
|
📥 registerUser 결과: {...}
|
|
✅ 회원가입 성공: {...}
|
|
💾 localStorage에 토큰과 사용자 정보 저장 완료
|
|
```
|
|
|
|
---
|
|
|
|
## 🚨 주의사항
|
|
|
|
### 1. 전화번호 형식 변환
|
|
- **UI 입력**: `010-1234-5678` (하이픈 포함)
|
|
- **API 전송**: `01012345678` (하이픈 제거)
|
|
- 회원가입 페이지에서 자동 변환 처리됨
|
|
|
|
### 2. 토큰 관리
|
|
- Access Token은 localStorage에 `accessToken` 키로 저장
|
|
- User 정보는 localStorage에 `user` 키로 저장
|
|
- 401 응답 시 자동으로 로그인 페이지로 리디렉션
|
|
|
|
### 3. 프록시 라우트 없음
|
|
- User API는 Next.js 프록시를 사용하지 않음
|
|
- 브라우저에서 Gateway로 직접 요청
|
|
- CORS 설정이 Gateway에서 처리되어야 함
|
|
|
|
### 4. 로그아웃 에러 처리
|
|
- 로그아웃 API 실패해도 로컬 상태는 정리됨
|
|
- 서버 에러 발생 시에도 사용자는 정상적으로 로그아웃됨
|
|
|
|
---
|
|
|
|
## 📝 타입 정의 요약
|
|
|
|
```typescript
|
|
// 로그인
|
|
interface LoginRequest {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
interface LoginResponse {
|
|
token: string;
|
|
userId: string; // UUID format
|
|
userName: string;
|
|
role: string;
|
|
email: string;
|
|
}
|
|
|
|
// 회원가입
|
|
interface RegisterRequest {
|
|
name: string;
|
|
phoneNumber: string; // "01012345678" 형식
|
|
email: string;
|
|
password: string;
|
|
storeName: string;
|
|
industry?: string;
|
|
address: string;
|
|
businessHours?: string;
|
|
}
|
|
|
|
interface RegisterResponse {
|
|
token: string;
|
|
userId: string; // UUID format
|
|
userName: string;
|
|
storeId: string; // UUID format
|
|
storeName: string;
|
|
}
|
|
|
|
// 프로필
|
|
interface ProfileResponse {
|
|
userId: string; // UUID format
|
|
userName: string;
|
|
phoneNumber: string;
|
|
email: string;
|
|
role: string;
|
|
storeId: string; // UUID format
|
|
storeName: string;
|
|
industry: string;
|
|
address: string;
|
|
businessHours: string;
|
|
createdAt: string;
|
|
lastLoginAt: string;
|
|
}
|
|
|
|
// User 상태
|
|
interface User {
|
|
userId: string; // UUID format
|
|
userName: string;
|
|
email: string;
|
|
role: string;
|
|
phoneNumber?: string;
|
|
storeId?: string; // UUID format
|
|
storeName?: string;
|
|
industry?: string;
|
|
address?: string;
|
|
businessHours?: string;
|
|
}
|
|
|
|
// 인증 상태
|
|
interface AuthState {
|
|
user: User | null;
|
|
token: string | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ 체크리스트
|
|
|
|
- [x] API 클라이언트 설정 완료
|
|
- [x] TypeScript 타입 정의 완료
|
|
- [x] useAuth Hook 구현 완료
|
|
- [x] AuthProvider Context 구현 완료
|
|
- [x] 로그인 페이지 구현 완료
|
|
- [x] 회원가입 페이지 (3단계) 구현 완료
|
|
- [x] localStorage 세션 관리 완료
|
|
- [x] Request/Response 인터셉터 설정 완료
|
|
- [x] 401 에러 핸들링 완료
|
|
- [x] RootLayout에 AuthProvider 적용 완료
|
|
- [x] 빌드 테스트 통과 ✅
|
|
- [ ] 개발 서버 실행 및 실제 API 테스트 (사용자가 수행)
|
|
|
|
---
|
|
|
|
## 📚 관련 파일
|
|
|
|
### 핵심 파일
|
|
- `src/entities/user/api/userApi.ts` - User API 서비스
|
|
- `src/entities/user/model/types.ts` - TypeScript 타입 정의
|
|
- `src/features/auth/model/useAuth.ts` - 인증 Hook
|
|
- `src/features/auth/model/AuthProvider.tsx` - Context Provider
|
|
- `src/app/(auth)/login/page.tsx` - 로그인 페이지
|
|
- `src/app/(auth)/register/page.tsx` - 회원가입 페이지
|
|
- `src/shared/api/client.ts` - Axios 클라이언트 설정
|
|
|
|
### 참고 파일
|
|
- `src/stores/authStore.ts` - Zustand 인증 스토어 (참고용, 현재 미사용)
|
|
- `src/app/layout.tsx` - RootLayout with AuthProvider
|
|
|
|
---
|
|
|
|
## 🎯 다음 단계
|
|
|
|
User API 연동은 완료되었으므로 다음 작업을 진행할 수 있습니다:
|
|
|
|
1. **개발 서버 테스트**: `npm run dev` 실행 후 실제 회원가입/로그인 테스트
|
|
2. **프로필 페이지 개선**: 사용자 정보 수정 기능 강화
|
|
3. **비밀번호 찾기**: 비밀번호 재설정 플로우 구현 (현재 미구현)
|
|
4. **소셜 로그인**: 카카오톡, 네이버 소셜 로그인 구현 (현재 준비 중)
|
|
5. **권한 관리**: Role 기반 접근 제어 (ADMIN, USER) 구현
|
|
6. **세션 갱신**: Refresh Token 로직 추가 (필요시)
|
|
|
|
---
|
|
|
|
## 📞 문의
|
|
|
|
User API 관련 문제나 개선사항은 프로젝트 팀에 문의하세요.
|
|
|
|
**문서 작성일**: 2025-01-30
|
|
**마지막 업데이트**: 2025-01-30
|