Files
AutonetSellCar/frontend/src/lib/api.ts
AutonetSellCar Deploy 0b6bdf44e3 fix: Add board_enabled to SystemSettings interface
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 01:39:20 +09:00

1940 lines
53 KiB
TypeScript

import axios from 'axios';
import { Car, CarListResponse, CarMaker, CarModel, User, HeroBanner, HeroBannerSettings, CarView } from '@/types';
// When NEXT_PUBLIC_API_URL is empty, use relative path (for HTTPS proxy setup)
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const BASE_URL = API_URL ? `${API_URL}/api` : '/api';
const api = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests
api.interceptors.request.use((config) => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
});
// Cars API
export const carsApi = {
getList: async (params: {
page?: number;
page_size?: number;
maker_id?: number;
model_id?: number;
year_min?: number;
year_max?: number;
price_min?: number;
price_max?: number;
mileage_max?: number;
fuel?: string;
admin?: boolean;
is_displayed?: boolean;
}): Promise<CarListResponse> => {
const { data } = await api.get('/cars', { params });
return data;
},
getById: async (id: number, admin?: boolean): Promise<Car> => {
const { data } = await api.get(`/cars/${id}`, { params: { admin } });
return data;
},
update: async (id: number, updateData: {
margin_krw?: number;
is_displayed?: boolean;
car_name?: string;
status?: string;
}): Promise<Car> => {
const { data } = await api.put(`/cars/${id}`, updateData);
return data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/cars/${id}`);
},
getMakers: async (): Promise<CarMaker[]> => {
const { data } = await api.get('/cars/makers/');
return data;
},
getModels: async (makerId?: number): Promise<CarModel[]> => {
const params = makerId ? { maker_id: makerId } : {};
const { data } = await api.get('/cars/models/', { params });
return data;
},
// Soldout APIs
markSoldout: async (carId: number): Promise<{ car_id: number; soldout: boolean; message: string }> => {
const { data } = await api.post(`/cars/${carId}/soldout`);
return data;
},
markAvailable: async (carId: number): Promise<{ car_id: number; soldout: boolean; message: string }> => {
const { data } = await api.delete(`/cars/${carId}/soldout`);
return data;
},
getSoldoutStats: async (): Promise<{ total_active: number; soldout: number; available: number; soldout_percentage: number }> => {
const { data } = await api.get('/cars/admin/soldout-stats');
return data;
},
};
// Auth API
export const authApi = {
login: async (email: string, password: string): Promise<{ access_token: string }> => {
const params = new URLSearchParams();
params.append('username', email);
params.append('password', password);
const { data } = await api.post('/auth/login', params, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
return data;
},
register: async (userData: {
email: string;
password: string;
name?: string;
phone?: string;
country?: string;
}): Promise<User> => {
const { data } = await api.post('/auth/register', userData);
return data;
},
getMe: async (): Promise<User> => {
const { data } = await api.get('/auth/me');
return data;
},
// 사용자 탈퇴 요청
requestWithdrawal: async (password: string, reason?: string): Promise<{
message: string;
withdrawal_requested_at: string;
}> => {
const { data } = await api.post('/auth/withdraw', { password, reason });
return data;
},
// 탈퇴 요청 취소
cancelWithdrawal: async (): Promise<{ message: string }> => {
const { data } = await api.post('/auth/withdraw/cancel');
return data;
},
};
// Inquiries API
export const inquiriesApi = {
create: async (carId: number, message: string) => {
const { data } = await api.post('/inquiries', { car_id: carId, message });
return data;
},
getList: async () => {
const { data } = await api.get('/inquiries');
return data;
},
};
// Hero Banners API
export const heroBannersApi = {
// Public APIs
getList: async (lang: string = 'en'): Promise<HeroBanner[]> => {
const { data } = await api.get('/hero-banners/', { params: { lang } });
return data;
},
getSettings: async (): Promise<HeroBannerSettings> => {
const { data } = await api.get('/hero-banners/settings');
return data;
},
// 차량이 Banner에 연결되어 있는지 확인 (샘플 차량 여부)
checkBannerCar: async (carId: number): Promise<{ car_id: number; is_banner_car: boolean; banner_id: number | null }> => {
const { data } = await api.get(`/hero-banners/check-car/${carId}`);
return data;
},
// Admin APIs
adminGetList: async (): Promise<HeroBanner[]> => {
const { data } = await api.get('/hero-banners/admin/list');
return data;
},
adminGetById: async (id: number): Promise<HeroBanner> => {
const { data } = await api.get(`/hero-banners/admin/${id}`);
return data;
},
adminCreate: async (bannerData: Partial<HeroBanner>): Promise<HeroBanner> => {
const { data } = await api.post('/hero-banners/admin', bannerData);
return data;
},
adminUpdate: async (id: number, bannerData: Partial<HeroBanner>): Promise<HeroBanner> => {
const { data } = await api.put(`/hero-banners/admin/${id}`, bannerData);
return data;
},
adminDelete: async (id: number): Promise<void> => {
await api.delete(`/hero-banners/admin/${id}`);
},
adminUploadImage: async (file: File): Promise<{ image_url: string }> => {
const formData = new FormData();
formData.append('file', file);
const { data } = await api.post('/hero-banners/admin/upload-image', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return data;
},
adminUpdateSettings: async (settings: Partial<HeroBannerSettings>): Promise<HeroBannerSettings> => {
const { data } = await api.put('/hero-banners/admin/settings', settings);
return data;
},
// Banner Toggle & Ordering
adminToggleBanner: async (carId: number): Promise<{ car_id: number; is_banner: boolean; banner_id?: number; message: string }> => {
const { data } = await api.post(`/hero-banners/admin/toggle/${carId}`);
return data;
},
adminReorderBanners: async (carIds: number[]): Promise<{ message: string; count: number }> => {
const { data } = await api.put('/hero-banners/admin/reorder', { car_ids: carIds });
return data;
},
adminGetBannerCars: async (): Promise<{ car_ids: number[]; count: number }> => {
const { data } = await api.get('/hero-banners/admin/banner-cars');
return data;
},
};
// Translations API
export interface Translation {
id: number;
source_text: string;
category: string;
text_en?: string;
text_mn?: string;
text_ru?: string;
created_at: string;
updated_at: string;
}
export interface TranslationListResponse {
total: number;
page: number;
page_size: number;
translations: Translation[];
}
export const translationsApi = {
getCategories: async (): Promise<string[]> => {
const { data } = await api.get('/translations/categories');
return data;
},
getList: async (params: {
page?: number;
page_size?: number;
category?: string;
search?: string;
}): Promise<TranslationListResponse> => {
const { data } = await api.get('/translations', { params });
return data;
},
getById: async (id: number): Promise<Translation> => {
const { data } = await api.get(`/translations/${id}`);
return data;
},
create: async (translationData: {
source_text: string;
category: string;
text_en?: string;
text_mn?: string;
text_ru?: string;
}): Promise<Translation> => {
const { data } = await api.post('/translations', translationData);
return data;
},
update: async (id: number, translationData: {
source_text?: string;
category?: string;
text_en?: string;
text_mn?: string;
text_ru?: string;
}): Promise<Translation> => {
const { data } = await api.put(`/translations/${id}`, translationData);
return data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/translations/${id}`);
},
autoExtract: async (): Promise<{ message: string }> => {
const { data } = await api.post('/translations/auto-extract');
return data;
},
seedAllDefaults: async (): Promise<{ message: string; added: number; skipped: number; categories: string[] }> => {
const { data } = await api.post('/translations/seed-all-defaults');
return data;
},
bulkLookup: async (texts: string[], lang: string, category?: string): Promise<{ translations: Record<string, string> }> => {
const { data } = await api.post('/translations/bulk-lookup', {
texts,
lang,
category,
});
return data;
},
// Auto-translation endpoints
autoTranslate: async (translationId: number, targetLangs?: string[]): Promise<{
id: number;
source_text: string;
translations: Record<string, string>;
message: string;
}> => {
const { data } = await api.post(`/translations/auto-translate/${translationId}`, {
target_langs: targetLangs || ['en', 'mn', 'ru']
});
return data;
},
autoTranslateBatch: async (targetLangs?: string[], category?: string, overwriteExisting?: boolean): Promise<{
total_processed: number;
successful: number;
failed: number;
results: Array<{ id: number; source_text: string; success: boolean; error?: string }>;
}> => {
const { data } = await api.post('/translations/auto-translate-batch', {
target_langs: targetLangs || ['en', 'mn', 'ru'],
category,
overwrite_existing: overwriteExisting || false
});
return data;
},
translateOnDemand: async (text: string, sourceLang: string, targetLang: string): Promise<{
source_text: string;
translated_text: string;
source_lang: string;
target_lang: string;
}> => {
const { data } = await api.post('/translations/translate-on-demand', {
text,
source_lang: sourceLang,
target_lang: targetLang
});
return data;
},
getStats: async (): Promise<{
total_entries: number;
by_category: Record<string, number>;
translation_coverage: {
english: { translated: number; total: number; percentage: number };
mongolian: { translated: number; total: number; percentage: number };
russian: { translated: number; total: number; percentage: number };
};
}> => {
const { data } = await api.get('/translations/stats');
return data;
},
};
// Carmodoo API
export interface CarmodooMaker {
code: string;
name: string;
}
export interface CarmodooModel {
code: string;
name: string;
type?: string;
}
export interface CarmodooSearchResult {
id: string;
car_name: string;
maker_name?: string;
model_name?: string;
year?: number;
mileage?: number;
original_price?: number;
korea_margin?: number;
mongolia_margin?: number;
final_price?: number;
fuel?: string;
transmission?: string;
color?: string;
displacement?: number;
main_image?: string;
image_count: number;
check_num?: string; // 성능점검번호
car_key?: string; // 암호화된 차량 키 (딜러 설명 조회용)
}
export interface VehicleRequestSearchResponse {
total: number;
cars: CarmodooSearchResult[];
}
export const carmodooApi = {
getMakers: async (): Promise<CarmodooMaker[]> => {
const { data } = await api.get('/carmodoo/makers');
return data;
},
getModels: async (makerCode: string): Promise<CarmodooModel[]> => {
const { data } = await api.get(`/carmodoo/models/${makerCode}`);
return data;
},
requestSearch: async (params: {
maker_code?: string;
model_code?: string;
grade?: string;
year_min?: number;
year_max?: number;
mileage_min?: number;
mileage_max?: number;
price_min?: number;
price_max?: number;
fuel?: string;
transmission?: string;
displacement_min?: number;
displacement_max?: number;
page?: number;
page_size?: number;
}): Promise<VehicleRequestSearchResponse> => {
const { data } = await api.get('/carmodoo/request-search', { params });
return data;
},
getGrades: async (makerCode: string, modelCode: string): Promise<{ code: string; name: string }[]> => {
const { data } = await api.get(`/carmodoo/grades/${makerCode}/${modelCode}`);
return data;
},
// 차량을 로컬 DB에 저장 (이미지 다운로드 포함)
importCars: async (cars: {
car_no: string;
car_name: string;
maker_name?: string;
model_name?: string;
year?: number;
mileage?: number;
price?: number;
fuel?: string;
transmission?: string;
color?: string;
displacement?: number;
main_image?: string;
check_num?: string; // 성능점검번호
car_key?: string; // 암호화된 차량 키 (딜러 설명 조회용)
}[]): Promise<{
imported: {
car_no: string;
car_id: number;
car_name: string;
images_downloaded: number;
pdf_status?: {
success: boolean;
attempts: number;
error?: string;
path?: string;
check_num?: string;
};
}[];
skipped: { car_no: string; car_id: number; reason: string }[];
errors: { car_no: string; error: string }[];
summary: {
imported_count: number;
skipped_count: number;
error_count: number;
pdf_success_count: number;
pdf_failed_count: number;
};
}> => {
const { data } = await api.post('/carmodoo/import', { cars });
return data;
},
// Translation Management (Admin)
getCarTranslations: async (carId: number): Promise<{
car_id: number;
car_name: string;
dealer_description: string | null;
translations: {
en: string | null;
mn: string | null;
ru: string | null;
};
has_translations: boolean;
papago_configured: boolean;
}> => {
const { data } = await api.get(`/carmodoo/car/${carId}/translations`);
return data;
},
updateCarTranslations: async (carId: number, translations: {
dealer_description?: string; // 한국어 원문
dealer_description_en?: string;
dealer_description_mn?: string;
dealer_description_ru?: string;
}): Promise<{
message: string;
car_id: number;
dealer_description?: string;
translations: { en: string | null; mn: string | null; ru: string | null };
}> => {
const { data } = await api.put(`/carmodoo/car/${carId}/translations`, translations);
return data;
},
regenerateTranslations: async (carId: number): Promise<{
message: string;
car_id: number;
translations: { en: string | null; mn: string | null; ru: string | null };
}> => {
const { data } = await api.post(`/carmodoo/car/${carId}/translations/regenerate`);
return data;
},
getUntranslatedCars: async (limit?: number): Promise<{
count: number;
cars: {
id: number;
car_name: string;
dealer_description: string;
has_en: boolean;
has_mn: boolean;
has_ru: boolean;
}[];
}> => {
const { data } = await api.get('/carmodoo/admin/untranslated-cars', { params: { limit } });
return data;
},
translateAllPending: async (): Promise<{
message: string;
total: number;
success: number;
failed: number;
results: { car_id: number; status: string; error?: string }[];
}> => {
const { data } = await api.post('/carmodoo/admin/translate-all-pending');
return data;
},
};
// Settings API
export interface SystemSettings {
id: number;
search_page_size: number;
korea_margin_percent: number;
mongolia_margin_percent: number;
cc_per_usdc: number;
cc_per_view: number;
cc_signup_bonus: number;
cache_ttl_hours: number;
board_enabled?: boolean;
}
export const settingsApi = {
getSettings: async (): Promise<SystemSettings> => {
const { data } = await api.get('/settings/');
return data;
},
getSearchPageSize: async (): Promise<{ search_page_size: number }> => {
const { data } = await api.get('/settings/search-page-size');
return data;
},
updateSettings: async (settings: Partial<SystemSettings>): Promise<SystemSettings> => {
const { data } = await api.put('/settings/', settings);
return data;
},
};
// Vehicle Request API
export interface VehicleRequest {
id: number;
user_id: number;
user_email?: string; // 관리자용
user_name?: string; // 관리자용
maker_code?: string;
maker_name?: string;
model_code?: string;
model_name?: string;
grade_code?: string;
grade_name?: string;
year_from?: number;
year_to?: number;
mileage_min?: number;
mileage_max?: number;
fuel?: string;
displacement_min?: number;
displacement_max?: number;
status: string;
admin_reviewed_at?: string;
created_at: string;
}
export interface RequestVehicle {
id: number;
request_id: number;
car_id?: number; // Reference to cars table
car_data: Record<string, any>;
is_approved: boolean;
approved_at?: string;
created_at: string;
}
export interface PurchasedVehicle {
id: number;
user_id: number;
car_name?: string;
car_data?: Record<string, any>;
car_image?: string;
vehicle_price_krw?: number;
domestic_cost_krw?: number;
shipping_cost_usd?: number;
total_cost_krw?: number;
car_type?: string;
shipping_status: number;
status_updated_at?: string;
current_location?: string;
estimated_arrival?: string;
purchased_at: string;
delivered_at?: string;
}
export interface VehicleRequestWithVehicles {
request: VehicleRequest;
approved_vehicles: RequestVehicle[];
}
// Directly purchased car (from banner with 1CC)
export interface DirectPurchasedCar {
id: number;
car_id: number;
car_data: Record<string, any>;
cc_paid: number;
purchased_at: string;
}
// Full response including both recommended and directly purchased cars
export interface MyVehiclesResponse {
vehicle_requests: VehicleRequestWithVehicles[];
direct_purchases: DirectPurchasedCar[];
}
export const vehicleRequestsApi = {
// User endpoints
createRequest: async (requestData: {
maker_code: string;
maker_name?: string;
model_code: string;
model_name?: string;
grade_code?: string;
grade_name?: string;
year_from?: number;
year_to?: number;
mileage_min?: number;
mileage_max?: number;
fuel?: string;
displacement_min?: number;
displacement_max?: number;
}): Promise<VehicleRequest> => {
const { data } = await api.post('/vehicle-requests/', requestData);
return data;
},
getMyRequests: async (): Promise<VehicleRequestWithVehicles[]> => {
const { data } = await api.get('/vehicle-requests/my-requests');
return data;
},
// Get all vehicles: recommended + directly purchased from banners
getMyVehicles: async (): Promise<MyVehiclesResponse> => {
const { data } = await api.get('/vehicle-requests/my-vehicles');
return data;
},
getPurchasedVehicles: async (): Promise<PurchasedVehicle[]> => {
const { data } = await api.get('/vehicle-requests/purchased');
return data;
},
// Admin endpoints
adminGetAllRequests: async (status?: string): Promise<VehicleRequest[]> => {
const params = status ? { status } : {};
const { data } = await api.get('/vehicle-requests/admin/list', { params });
return data;
},
adminGetRequestDetail: async (requestId: number): Promise<VehicleRequestWithVehicles> => {
const { data } = await api.get(`/vehicle-requests/admin/${requestId}`);
return data;
},
adminAddVehicle: async (requestId: number, vehicleData: {
request_id: number;
car_data: Record<string, any>;
is_approved?: boolean;
}): Promise<RequestVehicle> => {
const { data } = await api.post(`/vehicle-requests/admin/${requestId}/vehicles`, vehicleData);
return data;
},
adminApproveVehicles: async (requestId: number, vehicleIds: number[]): Promise<{ message: string }> => {
const { data } = await api.post(`/vehicle-requests/admin/${requestId}/approve-vehicles`, { vehicle_ids: vehicleIds });
return data;
},
adminUpdateRequestStatus: async (requestId: number, newStatus: string): Promise<{ message: string }> => {
const { data } = await api.put(`/vehicle-requests/admin/${requestId}/status`, null, { params: { new_status: newStatus } });
return data;
},
adminDeleteVehicle: async (requestId: number, vehicleId: number): Promise<{ message: string }> => {
const { data } = await api.delete(`/vehicle-requests/admin/${requestId}/vehicles/${vehicleId}`);
return data;
},
adminCreatePurchased: async (userId: number, vehicleData: {
car_name: string;
car_data?: Record<string, any>;
car_image?: string;
vehicle_price_krw: number;
domestic_cost_krw: number;
shipping_cost_usd: number;
total_cost_krw: number;
car_type: string;
}): Promise<PurchasedVehicle> => {
const { data } = await api.post('/vehicle-requests/admin/purchased', vehicleData, { params: { user_id: userId } });
return data;
},
adminGetAllPurchased: async (): Promise<PurchasedVehicle[]> => {
const { data } = await api.get('/vehicle-requests/admin/purchased/all');
return data;
},
adminUpdateShippingStatus: async (vehicleId: number, statusUpdate: {
shipping_status: number;
current_location?: string;
estimated_arrival?: string;
}): Promise<PurchasedVehicle> => {
const { data } = await api.put(`/vehicle-requests/admin/purchased/${vehicleId}/status`, statusUpdate);
return data;
},
};
// CC (Coin) API
export interface PaymentInfo {
usdc_wallet_address: string;
usdc_network: string;
min_charge_usd: number;
max_charge_usd: number;
supported_currencies: string[];
supported_methods: string[];
rate: string;
}
export interface ChargeHistory {
id: number;
amount: number;
amount_usd?: number;
currency: string;
cc_amount: number;
payment_method: string;
transaction_id?: string;
status: string;
created_at?: string;
}
export interface AdminPayment extends ChargeHistory {
user_id: number;
user_email?: string;
user_name?: string;
wallet_address?: string;
admin_note?: string;
verified_at?: string;
}
export interface AdminPaymentListResponse {
payments: AdminPayment[];
total: number;
page: number;
page_size: number;
}
export const ccApi = {
getBalance: async (): Promise<{ cc_balance: number }> => {
const { data } = await api.get('/cc/balance');
return data;
},
getPurchasedViews: async (): Promise<CarView[]> => {
const { data } = await api.get('/cc/views');
return data;
},
getPurchasedCarIds: async (): Promise<{ car_ids: number[] }> => {
const { data } = await api.get('/cc/views/car-ids');
return data;
},
purchaseView: async (carId: number): Promise<{ message: string; cc_balance: number; car_id: number }> => {
const { data } = await api.post('/cc/purchase-view', { car_id: carId });
return data;
},
checkView: async (carId: number): Promise<{ has_access: boolean; cc_balance: number }> => {
const { data } = await api.get(`/cc/check-view/${carId}`);
return data;
},
getPaymentInfo: async (): Promise<PaymentInfo> => {
const { data } = await api.get('/cc/payment-info');
return data;
},
chargeCC: async (chargeData: {
amount: number;
currency?: string;
payment_method?: string;
transaction_id?: string;
wallet_address?: string;
}): Promise<{
message: string;
charge_id: number;
amount: number;
currency: string;
cc_amount: number;
status: string;
payment_info?: { usdc_wallet?: string; network?: string };
}> => {
const { data } = await api.post('/cc/charge', chargeData);
return data;
},
chargeUSDC: async (chargeData: {
amount_usdc: number;
transaction_hash: string;
wallet_address: string;
network?: string;
}): Promise<{
message: string;
charge_id: number;
amount_usdc: number;
cc_amount: number;
status: string;
transaction_hash: string;
}> => {
const { data } = await api.post('/cc/charge/usdc', chargeData);
return data;
},
getChargeHistory: async (): Promise<ChargeHistory[]> => {
const { data } = await api.get('/cc/charge-history');
return data;
},
// Admin endpoints
adminGetPendingPayments: async (): Promise<AdminPayment[]> => {
const { data } = await api.get('/cc/admin/pending');
return data;
},
adminGetAllPayments: async (params: {
status?: string;
page?: number;
page_size?: number;
}): Promise<AdminPaymentListResponse> => {
const { data } = await api.get('/cc/admin/all', { params });
return data;
},
adminVerifyPayment: async (chargeId: number, approved: boolean, adminNote?: string): Promise<{
message: string;
charge_id: number;
new_status: string;
}> => {
const { data } = await api.put(`/cc/admin/${chargeId}/verify`, null, {
params: { approved, admin_note: adminNote }
});
return data;
},
// Performance Check APIs (0.1 CC)
checkPerformanceCheckAccess: async (carId: number): Promise<{
has_access: boolean;
has_performance_check: boolean;
cc_balance: number;
cost: number;
}> => {
const { data } = await api.get(`/cc/check-performance-check/${carId}`);
return data;
},
purchasePerformanceCheck: async (carId: number): Promise<{
message: string;
cc_balance: number;
car_id: number;
}> => {
const { data } = await api.post('/cc/purchase-performance-check', { car_id: carId });
return data;
},
// Get performance check data from carmodoo API
getPerformanceCheck: async (carId: number): Promise<{
car_id: number;
found: boolean;
has_access: boolean;
preview?: {
check_number: string;
check_date: string | null;
mileage: number | null;
};
data: any | null;
}> => {
const { data } = await api.get(`/carmodoo/car/${carId}/performance-check`);
return data;
},
// Download performance check PDF
downloadPerformanceCheckPdf: async (carId: number): Promise<Blob> => {
const response = await api.get(`/carmodoo/car/${carId}/performance-check/pdf`, {
responseType: 'blob'
});
return response.data;
},
// Get PDF URL for iframe/embed viewing
getPerformanceCheckPdfUrl: (carId: number): string => {
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null;
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
return `${baseUrl}/api/carmodoo/car/${carId}/performance-check/pdf`;
},
};
// Withdrawal API
export interface WithdrawalBalance {
total_earned: number;
total_withdrawn: number;
pending_withdrawal: number;
available_balance: number;
}
export interface WithdrawalRequest {
id: number;
user_id: number;
amount: number;
tax_withheld: number;
net_amount: number;
bank_name: string;
bank_account: string;
account_holder: string;
status: string;
admin_note?: string;
requested_at: string;
processed_at?: string;
}
export const withdrawalApi = {
getBalance: async (): Promise<WithdrawalBalance> => {
const { data } = await api.get('/withdrawal/balance');
return data;
},
createRequest: async (requestData: {
amount: number;
bank_name: string;
bank_account: string;
account_holder: string;
}): Promise<WithdrawalRequest> => {
const { data } = await api.post('/withdrawal/request', requestData);
return data;
},
getMyRequests: async (): Promise<WithdrawalRequest[]> => {
const { data } = await api.get('/withdrawal/my-requests');
return data;
},
// Admin endpoints
adminGetAllRequests: async (status?: string): Promise<WithdrawalRequest[]> => {
const params = status ? { status_filter: status } : {};
const { data } = await api.get('/withdrawal/admin/list', { params });
return data;
},
adminProcessRequest: async (requestId: number, processData: {
status: string;
admin_note?: string;
}): Promise<WithdrawalRequest> => {
const { data } = await api.put(`/withdrawal/admin/${requestId}/process`, processData);
return data;
},
};
// Referral API
export interface ReferralReward {
id: number;
referrer_id: number;
referred_user_id: number;
payment_amount: number;
reward_amount: number;
status: string;
created_at: string;
credited_at?: string;
}
export interface ReferralStats {
total_referrals: number;
total_rewards_earned: number;
total_rewards_credited: number;
total_rewards_pending: number;
available_for_withdrawal: number;
}
export interface ReferralSettings {
referral_reward_enabled: boolean;
referral_reward_percent: number;
referral_reward_type: string;
}
export const referralApi = {
getMyLink: async (): Promise<{ referral_code: string; referral_link: string }> => {
const { data } = await api.get('/referral/my-link');
return data;
},
getMyRewards: async (): Promise<ReferralReward[]> => {
const { data } = await api.get('/referral/my-rewards');
return data;
},
getStats: async (): Promise<ReferralStats> => {
const { data } = await api.get('/referral/stats');
return data;
},
getSettings: async (): Promise<ReferralSettings> => {
const { data } = await api.get('/referral/settings');
return data;
},
// Admin endpoints
adminUpdateSettings: async (settings: Partial<ReferralSettings>): Promise<ReferralSettings> => {
const { data } = await api.put('/referral/admin/settings', settings);
return data;
},
adminGetAllRewards: async (): Promise<ReferralReward[]> => {
const { data } = await api.get('/referral/admin/all-rewards');
return data;
},
};
// Notification API
export interface Notification {
id: number;
user_id: number;
notification_type: string;
title: string;
message: string;
link?: string;
related_id?: number;
related_type?: string;
is_read: boolean;
read_at?: string;
created_at: string;
}
export interface NotificationListResponse {
notifications: Notification[];
unread_count: number;
total: number;
}
export const notificationApi = {
getNotifications: async (page: number = 1, pageSize: number = 20, unreadOnly: boolean = false): Promise<NotificationListResponse> => {
const { data } = await api.get('/notifications/', {
params: { page, page_size: pageSize, unread_only: unreadOnly }
});
return data;
},
getUnreadCount: async (): Promise<{ unread_count: number }> => {
const { data } = await api.get('/notifications/unread-count');
return data;
},
markAsRead: async (notificationIds: number[]): Promise<{ message: string }> => {
const { data } = await api.post('/notifications/mark-read', { notification_ids: notificationIds });
return data;
},
markAllAsRead: async (): Promise<{ message: string }> => {
const { data } = await api.post('/notifications/mark-all-read');
return data;
},
deleteNotification: async (notificationId: number): Promise<{ message: string }> => {
const { data } = await api.delete(`/notifications/${notificationId}`);
return data;
},
// Admin endpoints
adminSendNotification: async (notificationData: {
user_id: number;
notification_type: string;
title: string;
message: string;
link?: string;
}): Promise<Notification> => {
const { data } = await api.post('/notifications/admin/send', notificationData);
return data;
},
adminSendToAll: async (title: string, message: string, link?: string): Promise<{ message: string }> => {
const { data } = await api.post('/notifications/admin/send-all', null, {
params: { title, message, link }
});
return data;
},
};
// Inquiry API
export interface Inquiry {
id: number;
user_id: number;
car_id?: number;
category: string;
subject?: string;
message: string;
contact_email?: string;
contact_phone?: string;
status: string;
admin_response?: string;
responded_at?: string;
responded_by?: number;
created_at: string;
updated_at?: string;
}
export interface InquiryMessage {
id: number;
inquiry_id: number;
user_id: number;
message: string;
is_admin: boolean;
created_at: string;
}
export interface InquiryListResponse {
inquiries: Inquiry[];
total: number;
total_pages?: number;
page?: number;
page_size?: number;
}
export interface InquiryWithMessages {
inquiry: Inquiry;
messages: InquiryMessage[];
}
export interface InquiryStats {
total: number;
pending: number;
in_progress: number;
resolved: number;
closed: number;
}
// User Management API (Admin)
export interface AdminUser {
id: number;
email: string;
name?: string;
phone?: string;
country?: string;
cc_balance: number;
is_dealer: boolean;
referral_code?: string;
referred_by?: string;
created_at?: string;
}
export interface AdminUserListResponse {
users: AdminUser[];
total: number;
page: number;
page_size: number;
}
export const adminUserApi = {
getUsers: async (params: {
page?: number;
page_size?: number;
search?: string;
is_dealer?: boolean;
}): Promise<AdminUserListResponse> => {
const { data } = await api.get('/auth/admin/users', { params });
return data;
},
getUser: async (userId: number): Promise<AdminUser> => {
const { data } = await api.get(`/auth/admin/users/${userId}`);
return data;
},
updateUser: async (userId: number, userData: {
name?: string;
phone?: string;
country?: string;
}): Promise<AdminUser> => {
const { data } = await api.put(`/auth/admin/users/${userId}`, userData);
return data;
},
adjustCC: async (userId: number, amount: number, reason?: string): Promise<{ message: string; new_balance: number }> => {
const { data } = await api.put(`/auth/admin/users/${userId}/cc`, null, {
params: { amount, reason }
});
return data;
},
// 사용자 삭제 (관리자)
deleteUser: async (userId: number, hardDelete: boolean = false): Promise<{
message: string;
deleted_user_id: number;
hard_delete: boolean;
deleted_at?: string;
}> => {
const { data } = await api.delete(`/auth/admin/users/${userId}`, {
params: { hard_delete: hardDelete }
});
return data;
},
// 탈퇴 요청 사용자 목록
getWithdrawnUsers: async (page: number = 1, pageSize: number = 20): Promise<AdminUserListResponse & {
users: (AdminUser & { withdrawal_requested_at?: string; withdrawal_reason?: string })[];
}> => {
const { data } = await api.get('/auth/admin/users/withdrawn', {
params: { page, page_size: pageSize }
});
return data;
},
// 삭제된 사용자 목록
getDeletedUsers: async (params: {
page?: number;
page_size?: number;
search?: string;
}): Promise<AdminUserListResponse & {
users: (AdminUser & { deleted_at?: string; withdrawal_reason?: string })[];
}> => {
const { data } = await api.get('/auth/admin/users/deleted', { params });
return data;
},
// 삭제된 사용자 복원
restoreUser: async (userId: number): Promise<{ message: string; user_id: number }> => {
const { data } = await api.post(`/auth/admin/users/${userId}/restore`);
return data;
},
};
// Admin PDF API (성능점검표 PDF 관리)
export interface PdfFailure {
car_id: number;
check_num: string;
error: string;
timestamp: string;
retried: boolean;
}
export interface PdfRetryResult {
car_id: number;
check_number: string;
status: 'success' | 'failed' | 'error';
pdf_path?: string;
error?: string;
}
export const adminPdfApi = {
// PDF 생성 실패 목록 조회
getFailures: async (): Promise<{ failures: PdfFailure[]; count: number }> => {
const { data } = await api.get('/carmodoo/admin/pdf-failures');
return data;
},
// 모든 실패한 PDF 재시도
retryAllFailed: async (): Promise<{
total: number;
success: number;
failed: number;
results: PdfRetryResult[];
}> => {
const { data } = await api.post('/carmodoo/admin/retry-all-failed-pdfs');
return data;
},
// 단일 차량 PDF 재생성
regenerateSingle: async (carId: number): Promise<{ message: string; pdf_path?: string }> => {
const { data } = await api.post(`/carmodoo/car/${carId}/regenerate-pdf`);
return data;
},
};
// Dashboard API
export interface DashboardStats {
total_users: number;
new_users_today: number;
new_users_this_week: number;
total_dealers: number;
pending_dealer_applications: number;
total_cars: number;
total_vehicle_requests: number;
pending_requests: number;
total_purchased_vehicles: number;
total_inquiries: number;
pending_inquiries: number;
total_shares: number;
purchased_shares: number;
total_withdrawals: number;
pending_withdrawals: number;
total_cc_charged: number;
total_withdrawal_amount: number;
}
export interface RevenueStats {
total_revenue: number;
revenue_this_month: number;
revenue_last_month: number;
platform_commission: number;
dealer_commission: number;
}
export interface ChartData {
labels: string[];
values: number[];
}
export interface RecentActivity {
type: string;
title: string;
description: string;
time: string;
icon: string;
}
export interface TopDealer {
id: number;
name: string;
dealer_code: string;
total_sales: number;
total_commission: number;
}
export interface PendingActions {
pending_requests: number;
pending_inquiries: number;
pending_dealer_applications: number;
pending_withdrawals: number;
total_pending: number;
}
export const dashboardApi = {
getStats: async (): Promise<DashboardStats> => {
const { data } = await api.get('/dashboard/stats');
return data;
},
getRevenue: async (): Promise<RevenueStats> => {
const { data } = await api.get('/dashboard/revenue');
return data;
},
getUserChart: async (days: number = 30): Promise<ChartData> => {
const { data } = await api.get('/dashboard/chart/users', { params: { days } });
return data;
},
getRequestChart: async (days: number = 30): Promise<ChartData> => {
const { data } = await api.get('/dashboard/chart/requests', { params: { days } });
return data;
},
getRevenueChart: async (days: number = 30): Promise<ChartData> => {
const { data } = await api.get('/dashboard/chart/revenue', { params: { days } });
return data;
},
getRecentActivities: async (limit: number = 10): Promise<RecentActivity[]> => {
const { data } = await api.get('/dashboard/recent-activities', { params: { limit } });
return data;
},
getTopDealers: async (limit: number = 5): Promise<TopDealer[]> => {
const { data } = await api.get('/dashboard/top-dealers', { params: { limit } });
return data;
},
getPendingActions: async (): Promise<PendingActions> => {
const { data } = await api.get('/dashboard/pending-actions');
return data;
},
};
// Push Notification API
export interface PushSubscriptionData {
endpoint: string;
p256dh_key: string;
auth_key: string;
device_info?: string;
}
export interface NotificationPreferences {
vehicle_recommended: boolean;
shipping_update: boolean;
payment_confirmed: boolean;
withdrawal_processed: boolean;
dealer_status: boolean;
share_purchased: boolean;
referral_reward: boolean;
inquiry_reply: boolean;
system_announcements: boolean;
push_enabled: boolean;
email_enabled: boolean;
}
export const pushApi = {
getVapidKey: async (): Promise<{ public_key: string }> => {
const { data } = await api.get('/push/vapid-key');
return data;
},
subscribe: async (subscription: PushSubscriptionData): Promise<{ message: string }> => {
const { data } = await api.post('/push/subscribe', subscription);
return data;
},
unsubscribe: async (endpoint: string): Promise<{ message: string }> => {
const { data } = await api.delete('/push/unsubscribe', { params: { endpoint } });
return data;
},
getSubscriptions: async (): Promise<{ id: number; endpoint: string; device_info?: string; created_at?: string }[]> => {
const { data } = await api.get('/push/subscriptions');
return data;
},
getPreferences: async (): Promise<NotificationPreferences> => {
const { data } = await api.get('/push/preferences');
return data;
},
updatePreferences: async (preferences: Partial<NotificationPreferences>): Promise<{ message: string }> => {
const { data } = await api.put('/push/preferences', preferences);
return data;
},
// Admin
adminGetStats: async (): Promise<{ total_subscriptions: number; users_with_push: number }> => {
const { data } = await api.get('/push/admin/stats');
return data;
},
};
export const inquiryApi = {
// User endpoints
getInquiries: async (): Promise<Inquiry[]> => {
const { data } = await api.get('/inquiries');
return data;
},
createInquiry: async (inquiryData: {
category?: string;
subject?: string;
message: string;
contact_email?: string;
contact_phone?: string;
car_id?: number;
}): Promise<Inquiry> => {
const { data } = await api.post('/inquiries', inquiryData);
return data;
},
getMyInquiries: async (page: number = 1, pageSize: number = 10, status?: string): Promise<InquiryListResponse> => {
const params: Record<string, any> = { page, page_size: pageSize };
if (status) params.status = status;
const { data } = await api.get('/inquiries/my-inquiries', { params });
return data;
},
getInquiryDetail: async (inquiryId: number): Promise<InquiryWithMessages> => {
const { data } = await api.get(`/inquiries/my-inquiries/${inquiryId}`);
return data;
},
addMessage: async (inquiryId: number, message: string): Promise<InquiryMessage> => {
const { data } = await api.post(`/inquiries/my-inquiries/${inquiryId}/message`, { message });
return data;
},
// Admin endpoints
adminGetInquiries: async (page: number = 1, pageSize: number = 20, status?: string, category?: string): Promise<InquiryListResponse> => {
const params: Record<string, any> = { page, page_size: pageSize };
if (status) params.status = status;
if (category) params.category = category;
const { data } = await api.get('/inquiries/admin/list', { params });
return data;
},
adminGetInquiryDetail: async (inquiryId: number): Promise<InquiryWithMessages> => {
const { data } = await api.get(`/inquiries/admin/${inquiryId}`);
return data;
},
adminRespond: async (inquiryId: number, responseData: { message: string; status?: string }): Promise<InquiryMessage> => {
const { data } = await api.post(`/inquiries/admin/${inquiryId}/respond`, responseData);
return data;
},
adminUpdateStatus: async (inquiryId: number, status: string): Promise<Inquiry> => {
const { data } = await api.put(`/inquiries/admin/${inquiryId}/status`, { status });
return data;
},
adminGetStats: async (): Promise<InquiryStats> => {
const { data } = await api.get('/inquiries/admin/stats');
return data;
},
};
// Exchange Rate API
export interface ExchangeRateSimple {
[currencyCode: string]: {
rate: number; // KRW per 1 unit (e.g., 1 USD = 1450 KRW)
symbol: string;
name: string;
};
}
export const exchangeRateApi = {
getSimpleRates: async (): Promise<ExchangeRateSimple> => {
const { data } = await api.get('/exchange-rate/simple');
return data;
},
refreshRates: async (): Promise<any> => {
const { data } = await api.post('/exchange-rate/refresh');
return data;
},
};
// Verification API
export interface VerificationResponse {
success: boolean;
message: string;
}
export interface VerificationStatus {
email_verified: boolean;
phone_verified: boolean;
email?: string;
phone?: string;
}
export const verificationApi = {
// Email verification for pre-registration
sendEmailCodePreregister: async (email: string, language: string = 'en'): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/email/send-preregister', { email, language });
return data;
},
verifyEmailCodePreregister: async (email: string, code: string): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/email/verify-preregister', { email, code });
return data;
},
// Email verification for logged-in users
sendEmailCode: async (email: string, language: string = 'en'): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/email/send', { email, language });
return data;
},
verifyEmailCode: async (code: string, email?: string): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/email/verify', { code, email });
return data;
},
// Phone verification (requires login)
sendPhoneCode: async (phone: string, language: string = 'en'): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/phone/send', { phone, language });
return data;
},
verifyPhoneCode: async (phone: string, code: string): Promise<VerificationResponse> => {
const { data } = await api.post('/verification/phone/verify', { phone, code });
return data;
},
// Get verification status
getStatus: async (): Promise<VerificationStatus> => {
const { data } = await api.get('/verification/status');
return data;
},
};
// Visitor Analytics API
export interface VisitorStatsOverview {
total_visits: number;
unique_visitors: number;
device_breakdown: Record<string, number>;
browser_breakdown: Record<string, number>;
country_breakdown: Record<string, number>;
}
export interface TopPage {
path: string;
views: number;
title?: string;
}
export interface TopReferrer {
domain: string;
visits: number;
}
export interface RealtimeStats {
active_visitors: number;
minutes: number;
recent_pages: { path: string; views: number }[];
}
export const visitorApi = {
getOverview: async (days: number = 30): Promise<VisitorStatsOverview> => {
const { data } = await api.get('/visitor/admin/overview', { params: { days } });
return data;
},
getVisitsChart: async (days: number = 30): Promise<ChartData> => {
const { data } = await api.get('/visitor/admin/chart/visits', { params: { days } });
return data;
},
getUniqueVisitorsChart: async (days: number = 30): Promise<ChartData> => {
const { data } = await api.get('/visitor/admin/chart/unique-visitors', { params: { days } });
return data;
},
getTopPages: async (days: number = 30, limit: number = 20): Promise<TopPage[]> => {
const { data } = await api.get('/visitor/admin/top-pages', { params: { days, limit } });
return data;
},
getTopReferrers: async (days: number = 30, limit: number = 10): Promise<TopReferrer[]> => {
const { data } = await api.get('/visitor/admin/top-referrers', { params: { days, limit } });
return data;
},
getRealtime: async (minutes: number = 5): Promise<RealtimeStats> => {
const { data } = await api.get('/visitor/admin/realtime', { params: { minutes } });
return data;
},
};
// SNS Share API
export interface SnsShareSubmission {
id: number;
user_id: number;
car_id: number;
platform: string;
sns_url: string;
status: string;
rejected_reason?: string;
reward_cc: number;
rewarded_at?: string;
submitted_at: string;
verified_at?: string;
verified_by?: number;
car_name?: string;
car_image?: string;
}
export interface SnsShareListResponse {
submissions: SnsShareSubmission[];
total: number;
pending_count: number;
approved_count: number;
rejected_count: number;
}
export interface SnsShareStats {
total_submissions: number;
pending_submissions: number;
approved_submissions: number;
rejected_submissions: number;
total_rewarded_cc: number;
}
export interface CampaignStatus {
enabled: boolean;
start_date?: string;
end_date?: string;
sns_share_reward_cc?: number;
referral_signup_reward_cc?: number;
message?: string;
}
export const snsShareApi = {
// Public: 캠페인 상태 조회
getCampaignStatus: async (): Promise<CampaignStatus> => {
const { data } = await api.get('/sns-share/campaign-status');
return data;
},
// User: SNS 공유 URL 제출
submit: async (carId: number, platform: string, snsUrl: string): Promise<SnsShareSubmission> => {
const { data } = await api.post('/sns-share/submit', {
car_id: carId,
platform,
sns_url: snsUrl,
});
return data;
},
// User: 내 제출 목록 조회
getMySubmissions: async (): Promise<SnsShareListResponse> => {
const { data } = await api.get('/sns-share/my-submissions');
return data;
},
// Admin: 전체 제출 목록 조회
getAdminList: async (statusFilter?: string, skip: number = 0, limit: number = 50): Promise<SnsShareListResponse> => {
const params: any = { skip, limit };
if (statusFilter) params.status_filter = statusFilter;
const { data } = await api.get('/sns-share/admin/list', { params });
return data;
},
// Admin: 통계 조회
getAdminStats: async (): Promise<SnsShareStats> => {
const { data } = await api.get('/sns-share/admin/stats');
return data;
},
// Admin: 검증 (승인/거부)
verify: async (submissionId: number, approved: boolean, rejectedReason?: string): Promise<SnsShareSubmission> => {
const { data } = await api.put(`/sns-share/admin/${submissionId}/verify`, {
approved,
rejected_reason: rejectedReason,
});
return data;
},
};
// Board (Bulletin) API Types
export interface BoardCategory {
id: number;
name: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug: string;
description?: string;
sort_order: number;
is_active: boolean;
created_at: string;
updated_at?: string;
post_count: number;
}
export interface BoardPostListItem {
id: number;
title: string;
category_id: number;
category_name?: string;
author_id: number;
author_name?: string;
is_notice: boolean;
is_pinned: boolean;
view_count: number;
created_at: string;
}
export interface BoardPost {
id: number;
title: string;
content: string;
category_id: number;
category?: BoardCategory;
author_id: number;
author?: {
id: number;
name?: string;
email: string;
is_admin: boolean;
};
is_notice: boolean;
is_pinned: boolean;
is_published: boolean;
view_count: number;
created_at: string;
updated_at?: string;
}
export interface BoardPostListResponse {
posts: BoardPostListItem[];
notices: BoardPostListItem[];
total: number;
page: number;
page_size: number;
total_pages: number;
}
export interface BoardCategoryListResponse {
categories: BoardCategory[];
total: number;
}
// Board API
export const boardApi = {
// Categories
getCategories: async (includeInactive: boolean = false): Promise<BoardCategoryListResponse> => {
const { data } = await api.get('/board/categories', { params: { include_inactive: includeInactive } });
return data;
},
createCategory: async (category: {
name: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug: string;
description?: string;
sort_order?: number;
is_active?: boolean;
}): Promise<BoardCategory> => {
const { data } = await api.post('/board/categories', category);
return data;
},
updateCategory: async (categoryId: number, category: {
name?: string;
name_en?: string;
name_mn?: string;
name_ru?: string;
slug?: string;
description?: string;
sort_order?: number;
is_active?: boolean;
}): Promise<BoardCategory> => {
const { data } = await api.put(`/board/categories/${categoryId}`, category);
return data;
},
deleteCategory: async (categoryId: number): Promise<void> => {
await api.delete(`/board/categories/${categoryId}`);
},
// Posts
getPosts: async (params: {
page?: number;
page_size?: number;
category_id?: number;
search?: string;
}): Promise<BoardPostListResponse> => {
const { data } = await api.get('/board/posts', { params });
return data;
},
getPost: async (postId: number): Promise<BoardPost> => {
const { data } = await api.get(`/board/posts/${postId}`);
return data;
},
createPost: async (post: {
title: string;
content: string;
category_id: number;
is_notice?: boolean;
}): Promise<BoardPost> => {
const { data } = await api.post('/board/posts', post);
return data;
},
updatePost: async (postId: number, post: {
title?: string;
content?: string;
category_id?: number;
is_notice?: boolean;
is_pinned?: boolean;
is_published?: boolean;
}): Promise<BoardPost> => {
const { data } = await api.put(`/board/posts/${postId}`, post);
return data;
},
deletePost: async (postId: number): Promise<void> => {
await api.delete(`/board/posts/${postId}`);
},
// Admin
getAdminPosts: async (params: {
page?: number;
page_size?: number;
category_id?: number;
is_notice?: boolean;
is_published?: boolean;
search?: string;
}): Promise<BoardPostListResponse> => {
const { data } = await api.get('/board/admin/posts', { params });
return data;
},
toggleNotice: async (postId: number): Promise<{ is_notice: boolean; is_pinned: boolean }> => {
const { data } = await api.put(`/board/admin/posts/${postId}/toggle-notice`);
return data;
},
togglePin: async (postId: number): Promise<{ is_pinned: boolean }> => {
const { data } = await api.put(`/board/admin/posts/${postId}/toggle-pin`);
return data;
},
};
export default api;