이벤트 목록 Mock 데이터 적용 및 Participation API 연동

- 이벤트 목록 페이지에 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>
This commit is contained in:
cherry2250
2025-10-30 20:17:09 +09:00
parent 86ae038a31
commit 974961e1bd
29 changed files with 2105 additions and 328 deletions
+1
View File
@@ -0,0 +1 @@
export { participationApi, default } from './participationApi';
@@ -0,0 +1,142 @@
import axios, { AxiosInstance } from 'axios';
import type {
ParticipationRequest,
ParticipationResponse,
ApiResponse,
PageResponse,
DrawWinnersRequest,
DrawWinnersResponse,
} from '../model/types';
// Use Next.js API proxy to bypass CORS issues
const PARTICIPATION_API_BASE = '/api/participations';
const participationApiClient: AxiosInstance = axios.create({
baseURL: PARTICIPATION_API_BASE,
timeout: 90000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
participationApiClient.interceptors.request.use(
(config) => {
console.log('🎫 Participation API Request:', {
method: config.method?.toUpperCase(),
url: config.url,
baseURL: config.baseURL,
});
const token = localStorage.getItem('accessToken');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
console.error('❌ Participation API Request Error:', error);
return Promise.reject(error);
}
);
// Response interceptor
participationApiClient.interceptors.response.use(
(response) => {
console.log('✅ Participation API Response:', {
status: response.status,
url: response.config.url,
});
return response;
},
(error) => {
console.error('❌ Participation API Error:', {
message: error.message,
status: error.response?.status,
url: error.config?.url,
});
return Promise.reject(error);
}
);
/**
* Participation API Service
* 이벤트 참여 관리 API
*/
export const participationApi = {
/**
* 이벤트 참여
*/
participate: async (
eventId: string,
data: ParticipationRequest
): Promise<ApiResponse<ParticipationResponse>> => {
const response = await participationApiClient.post<
ApiResponse<ParticipationResponse>
>(`/${eventId}/participate`, data);
return response.data;
},
/**
* 참여자 목록 조회
*/
getParticipants: async (
eventId: string,
params?: {
storeVisited?: boolean;
page?: number;
size?: number;
sort?: string[];
}
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
const response = await participationApiClient.get<
ApiResponse<PageResponse<ParticipationResponse>>
>(`/${eventId}/participants`, { params });
return response.data;
},
/**
* 특정 참여자 조회
*/
getParticipant: async (
eventId: string,
participantId: string
): Promise<ApiResponse<ParticipationResponse>> => {
const response = await participationApiClient.get<
ApiResponse<ParticipationResponse>
>(`/${eventId}/participants/${participantId}`);
return response.data;
},
/**
* 당첨자 추첨
*/
drawWinners: async (
eventId: string,
data: DrawWinnersRequest
): Promise<ApiResponse<DrawWinnersResponse>> => {
const response = await participationApiClient.post<
ApiResponse<DrawWinnersResponse>
>(`/${eventId}/draw-winners`, data);
return response.data;
},
/**
* 당첨자 목록 조회
*/
getWinners: async (
eventId: string,
params?: {
page?: number;
size?: number;
sort?: string[];
}
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
const response = await participationApiClient.get<
ApiResponse<PageResponse<ParticipationResponse>>
>(`/${eventId}/winners`, { params });
return response.data;
},
};
export default participationApi;
+10
View File
@@ -0,0 +1,10 @@
export { participationApi } from './api';
export type {
ParticipationRequest,
ParticipationResponse,
ApiResponse,
PageResponse,
DrawWinnersRequest,
DrawWinnersResponse,
WinnerSummary,
} from './model';
@@ -0,0 +1,9 @@
export type {
ParticipationRequest,
ParticipationResponse,
ApiResponse,
PageResponse,
DrawWinnersRequest,
DrawWinnersResponse,
WinnerSummary,
} from './types';
+114
View File
@@ -0,0 +1,114 @@
/**
* Participation API Types
* 이벤트 참여 관련 타입 정의
*/
/**
* 참여 요청
*/
export interface ParticipationRequest {
/** 이름 (2-50자, 필수) */
name: string;
/** 전화번호 (형식: "010-1234-5678", 필수) */
phoneNumber: string;
/** 이메일 (선택) */
email?: string;
/** 채널 (선택) */
channel?: string;
/** 마케팅 동의 (선택) */
agreeMarketing?: boolean;
/** 개인정보 동의 (필수) */
agreePrivacy: boolean;
/** 매장 방문 여부 (선택) */
storeVisited?: boolean;
}
/**
* 참여 응답
*/
export interface ParticipationResponse {
/** 참여자 ID (UUID) */
participantId: string;
/** 이벤트 ID */
eventId: string;
/** 이름 */
name: string;
/** 전화번호 */
phoneNumber: string;
/** 이메일 */
email?: string;
/** 채널 */
channel?: string;
/** 참여 일시 */
participatedAt: string;
/** 매장 방문 여부 */
storeVisited?: boolean;
/** 보너스 응모권 수 */
bonusEntries: number;
/** 당첨 여부 */
isWinner: boolean;
}
/**
* API 공통 응답
*/
export interface ApiResponse<T> {
success: boolean;
data: T;
errorCode?: string;
message?: string;
timestamp: string;
}
/**
* 페이지 응답
*/
export interface PageResponse<T> {
content: T[];
page: number;
size: number;
totalElements: number;
totalPages: number;
first: boolean;
last: boolean;
}
/**
* 당첨자 추첨 요청
*/
export interface DrawWinnersRequest {
/** 당첨자 수 (최소 1명, 필수) */
winnerCount: number;
/** 매장 방문 보너스 적용 여부 (선택) */
applyStoreVisitBonus?: boolean;
}
/**
* 당첨자 요약 정보
*/
export interface WinnerSummary {
/** 참여자 ID */
participantId: string;
/** 이름 */
name: string;
/** 전화번호 */
phoneNumber: string;
/** 등수 */
rank: number;
}
/**
* 당첨자 추첨 응답
*/
export interface DrawWinnersResponse {
/** 이벤트 ID */
eventId: string;
/** 총 참여자 수 */
totalParticipants: number;
/** 당첨자 수 */
winnerCount: number;
/** 추첨 일시 */
drawnAt: string;
/** 당첨자 목록 */
winners: WinnerSummary[];
}
+62 -20
View File
@@ -1,4 +1,4 @@
import { apiClient } from '@/shared/api';
import axios, { AxiosInstance } from 'axios';
import type {
LoginRequest,
LoginResponse,
@@ -10,8 +10,57 @@ import type {
ChangePasswordRequest,
} from '../model/types';
// Use Next.js API proxy to bypass CORS issues
const USER_API_BASE = '/api/v1/users';
const userApiClient: AxiosInstance = axios.create({
baseURL: USER_API_BASE,
timeout: 90000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor
userApiClient.interceptors.request.use(
(config) => {
console.log('👤 User API Request:', {
method: config.method?.toUpperCase(),
url: config.url,
baseURL: config.baseURL,
});
const token = localStorage.getItem('accessToken');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
console.error('❌ User API Request Error:', error);
return Promise.reject(error);
}
);
// Response interceptor
userApiClient.interceptors.response.use(
(response) => {
console.log('✅ User API Response:', {
status: response.status,
url: response.config.url,
});
return response;
},
(error) => {
console.error('❌ User API Error:', {
message: error.message,
status: error.response?.status,
url: error.config?.url,
});
return Promise.reject(error);
}
);
/**
* User API Service
* 사용자 인증 및 프로필 관리 API
@@ -21,8 +70,8 @@ export const userApi = {
* 로그인
*/
login: async (data: LoginRequest): Promise<LoginResponse> => {
const response = await apiClient.post<LoginResponse>(
`${USER_API_BASE}/login`,
const response = await userApiClient.post<LoginResponse>(
'/login',
data
);
return response.data;
@@ -33,15 +82,14 @@ export const userApi = {
*/
register: async (data: RegisterRequest): Promise<RegisterResponse> => {
console.log('📞 userApi.register 호출');
console.log('🎯 URL:', `${USER_API_BASE}/register`);
console.log('📦 요청 데이터:', {
...data,
password: '***'
});
try {
const response = await apiClient.post<RegisterResponse>(
`${USER_API_BASE}/register`,
const response = await userApiClient.post<RegisterResponse>(
'/register',
data
);
console.log('✅ userApi.register 성공:', response.data);
@@ -56,15 +104,9 @@ export const userApi = {
* 로그아웃
*/
logout: async (): Promise<LogoutResponse> => {
const token = localStorage.getItem('accessToken');
const response = await apiClient.post<LogoutResponse>(
`${USER_API_BASE}/logout`,
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
const response = await userApiClient.post<LogoutResponse>(
'/logout',
{}
);
return response.data;
},
@@ -73,8 +115,8 @@ export const userApi = {
* 프로필 조회
*/
getProfile: async (): Promise<ProfileResponse> => {
const response = await apiClient.get<ProfileResponse>(
`${USER_API_BASE}/profile`
const response = await userApiClient.get<ProfileResponse>(
'/profile'
);
return response.data;
},
@@ -85,8 +127,8 @@ export const userApi = {
updateProfile: async (
data: UpdateProfileRequest
): Promise<ProfileResponse> => {
const response = await apiClient.put<ProfileResponse>(
`${USER_API_BASE}/profile`,
const response = await userApiClient.put<ProfileResponse>(
'/profile',
data
);
return response.data;
@@ -96,7 +138,7 @@ export const userApi = {
* 비밀번호 변경
*/
changePassword: async (data: ChangePasswordRequest): Promise<void> => {
await apiClient.put(`${USER_API_BASE}/password`, data);
await userApiClient.put('/password', data);
},
};
+7 -7
View File
@@ -11,7 +11,7 @@ export interface LoginRequest {
export interface LoginResponse {
token: string;
userId: number;
userId: string; // UUID format
userName: string;
role: string;
email: string;
@@ -31,9 +31,9 @@ export interface RegisterRequest {
export interface RegisterResponse {
token: string;
userId: number;
userId: string; // UUID format
userName: string;
storeId: number;
storeId: string; // UUID format
storeName: string;
}
@@ -45,12 +45,12 @@ export interface LogoutResponse {
// 프로필 조회/수정
export interface ProfileResponse {
userId: number;
userId: string; // UUID format
userName: string;
phoneNumber: string;
email: string;
role: string;
storeId: number;
storeId: string; // UUID format
storeName: string;
industry: string;
address: string;
@@ -77,12 +77,12 @@ export interface ChangePasswordRequest {
// User 상태
export interface User {
userId: number;
userId: string; // UUID format
userName: string;
email: string;
role: string;
phoneNumber?: string;
storeId?: number;
storeId?: string; // UUID format
storeName?: string;
industry?: string;
address?: string;