const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001/api"; interface FetchOptions extends RequestInit { token?: string; } async function fetchApi(endpoint: string, options: FetchOptions = {}): Promise { const { token, ...fetchOptions } = options; const headers: HeadersInit = { "Content-Type": "application/json", ...options.headers, }; if (token) { (headers as Record)["Authorization"] = `Bearer ${token}`; } const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...fetchOptions, headers, }); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Unknown error" })); const errorMessage = typeof error.detail === 'string' ? error.detail : JSON.stringify(error.detail) || `HTTP error! status: ${response.status}`; throw new Error(errorMessage); } return response.json(); } // Auth API export const authApi = { login: (username: string, password: string) => fetchApi<{ access_token: string; token_type: string }>("/auth/login", { method: "POST", body: JSON.stringify({ username, password }), }), getMe: (token: string) => fetchApi<{ id: number; username: string }>("/auth/me", { token }), }; // Projects API (Public) export interface Project { id: number; title: string; subtitle?: string; description?: string; client?: string; period?: string; year?: number; category?: string; main_image?: string; is_featured: boolean; images: { id: number; image_url: string; caption?: string }[]; } export interface ProjectAdmin { id: number; title_ko: string; title_en?: string; title_ja?: string; title_zh?: string; subtitle_ko?: string; subtitle_en?: string; subtitle_ja?: string; subtitle_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; client?: string; period?: string; year?: number; category?: string; main_image?: string; is_featured: boolean; is_active: boolean; display_order: number; images: { id: number; image_url: string; caption_ko?: string; caption_en?: string; caption_ja?: string; caption_zh?: string }[]; } export const projectsApi = { // Public getAll: (lang: string = "ko", category?: string) => { const params = new URLSearchParams({ lang }); if (category) params.append("category", category); return fetchApi(`/projects/?${params}`); }, getOne: (id: number, lang: string = "ko") => fetchApi(`/projects/${id}?lang=${lang}`), // Admin adminGetAll: (token: string) => fetchApi("/projects/admin/list", { token }), adminGetOne: (id: number, token: string) => fetchApi(`/projects/admin/${id}`, { token }), adminCreate: (data: Partial, token: string) => fetchApi("/projects/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/projects/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/projects/admin/${id}`, { method: "DELETE", token, }), adminUploadImage: async (projectId: number, file: File, isMain: boolean, token: string) => { const formData = new FormData(); formData.append("file", file); const response = await fetch( `${API_BASE_URL}/projects/admin/${projectId}/upload-image?is_main=${isMain}`, { method: "POST", headers: { Authorization: `Bearer ${token}`, }, body: formData, } ); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Upload failed" })); throw new Error(error.detail); } return response.json(); }, adminDeleteImage: (imageId: number, token: string) => fetchApi<{ message: string }>(`/projects/admin/images/${imageId}`, { method: "DELETE", token, }), }; // Get upload URL export function getUploadUrl(path: string): string { if (!path) return ""; if (path.startsWith("http")) return path; const baseUrl = process.env.NEXT_PUBLIC_API_URL?.replace(/\/api$/, "") || "http://localhost:8001"; // Handle API endpoints (e.g., /api/downloads/1/file) if (path.startsWith("/api/")) { return `${baseUrl}${path}`; } // Remove leading slash from path to avoid double slashes const cleanPath = path.startsWith("/") ? path.slice(1) : path; return `${baseUrl}/${cleanPath}`; } // Banner API export interface Banner { id: number; title?: string; subtitle?: string; image_url: string; link_url?: string; } export interface BannerAdmin { id: number; title_ko?: string; title_en?: string; title_ja?: string; title_zh?: string; subtitle_ko?: string; subtitle_en?: string; subtitle_ja?: string; subtitle_zh?: string; image_url: string; link_url?: string; is_active: boolean; display_order: number; } export interface BannerSettings { id: number; transition_type: "fade" | "slide"; slide_interval: number; } // Solution API export interface Solution { id: number; title: string; subtitle?: string; description?: string; features: string[]; icon?: string; color?: string; main_image?: string; images: { id: number; image_url: string; caption?: string }[]; } export interface SolutionAdmin { id: number; title_ko: string; title_en?: string; title_ja?: string; title_zh?: string; subtitle_ko?: string; subtitle_en?: string; subtitle_ja?: string; subtitle_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; features_ko?: string; features_en?: string; features_ja?: string; features_zh?: string; icon?: string; color?: string; main_image?: string; is_active: boolean; display_order: number; images: { id: number; image_url: string; caption_ko?: string; caption_en?: string; caption_ja?: string; caption_zh?: string }[]; } export const solutionsApi = { // Public getAll: (lang: string = "ko") => fetchApi(`/solutions/?lang=${lang}`), getOne: (id: number, lang: string = "ko") => fetchApi(`/solutions/${id}?lang=${lang}`), // Admin adminGetAll: (token: string) => fetchApi("/solutions/admin/list", { token }), adminGetOne: (id: number, token: string) => fetchApi(`/solutions/admin/${id}`, { token }), adminCreate: (data: Partial, token: string) => fetchApi("/solutions/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/solutions/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/solutions/admin/${id}`, { method: "DELETE", token, }), adminUploadImage: async (solutionId: number, file: File, isMain: boolean, token: string) => { const formData = new FormData(); formData.append("file", file); const response = await fetch( `${API_BASE_URL}/solutions/admin/${solutionId}/upload-image?is_main=${isMain}`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData, } ); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Upload failed" })); throw new Error(error.detail); } return response.json(); }, adminDeleteImage: (imageId: number, token: string) => fetchApi<{ message: string }>(`/solutions/admin/images/${imageId}`, { method: "DELETE", token, }), }; // Product API export interface Product { id: number; name: string; category?: string; description?: string; detail?: string; specifications?: string; icon?: string; main_image?: string; images: { id: number; image_url: string; caption?: string }[]; } export interface ProductAdmin { id: number; name_ko: string; name_en?: string; name_ja?: string; name_zh?: string; category_ko?: string; category_en?: string; category_ja?: string; category_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; detail_ko?: string; detail_en?: string; detail_ja?: string; detail_zh?: string; specifications?: string; icon?: string; main_image?: string; is_active: boolean; display_order: number; images: { id: number; image_url: string; caption_ko?: string; caption_en?: string; caption_ja?: string; caption_zh?: string }[]; } export const productsApi = { // Public getAll: (lang: string = "ko") => fetchApi(`/products/?lang=${lang}`), getOne: (id: number, lang: string = "ko") => fetchApi(`/products/${id}?lang=${lang}`), // Admin adminGetAll: (token: string) => fetchApi("/products/admin/list", { token }), adminGetOne: (id: number, token: string) => fetchApi(`/products/admin/${id}`, { token }), adminCreate: (data: Partial, token: string) => fetchApi("/products/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/products/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/products/admin/${id}`, { method: "DELETE", token, }), adminUploadImage: async (productId: number, file: File, isMain: boolean, token: string) => { const formData = new FormData(); formData.append("file", file); const response = await fetch( `${API_BASE_URL}/products/admin/${productId}/upload-image?is_main=${isMain}`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData, } ); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Upload failed" })); throw new Error(error.detail); } return response.json(); }, adminDeleteImage: (imageId: number, token: string) => fetchApi<{ message: string }>(`/products/admin/images/${imageId}`, { method: "DELETE", token, }), }; // Contact API export interface ContactSettings { phone?: string; email?: string; address?: string; weekday_hours?: string; weekend_hours?: string; } export interface ContactSettingsAdmin { id: number; phone?: string; email?: string; address_ko?: string; address_en?: string; address_ja?: string; address_zh?: string; weekday_hours?: string; weekend_hours?: string; } export interface ContactInquiry { id: number; name: string; email: string; phone?: string; company?: string; message: string; status: string; created_at?: string; } export const contactApi = { // Public getSettings: (lang: string = "ko") => fetchApi(`/contact/settings?lang=${lang}`), submitInquiry: (data: { name: string; email: string; phone?: string; company?: string; message: string }) => fetchApi("/contact/inquiry", { method: "POST", body: JSON.stringify(data), }), // Admin adminGetSettings: (token: string) => fetchApi("/contact/admin/settings", { token }), adminUpdateSettings: (data: Partial, token: string) => fetchApi("/contact/admin/settings", { method: "PUT", body: JSON.stringify(data), token, }), adminGetInquiries: (token: string, status?: string) => { const params = status ? `?status=${status}` : ""; return fetchApi(`/contact/admin/inquiries${params}`, { token }); }, adminGetInquiry: (id: number, token: string) => fetchApi(`/contact/admin/inquiries/${id}`, { token }), adminUpdateInquiryStatus: (id: number, status: string, token: string) => fetchApi(`/contact/admin/inquiries/${id}/status`, { method: "PUT", body: JSON.stringify({ status }), token, }), }; // Banner API export const bannerApi = { // Public getAll: (lang: string = "ko") => fetchApi(`/banners/?lang=${lang}`), getSettings: () => fetchApi("/banners/settings"), // Admin adminGetAll: (token: string) => fetchApi("/banners/admin/list", { token }), adminGetOne: (id: number, token: string) => fetchApi(`/banners/admin/${id}`, { token }), adminCreate: (data: Partial, token: string) => fetchApi("/banners/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/banners/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/banners/admin/${id}`, { method: "DELETE", token, }), adminUploadImage: async (file: File, token: string) => { const formData = new FormData(); formData.append("file", file); const response = await fetch( `${API_BASE_URL}/banners/admin/upload-image`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData, } ); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Upload failed" })); throw new Error(error.detail); } return response.json(); }, adminUpdateSettings: (data: Partial, token: string) => fetchApi("/banners/admin/settings", { method: "PUT", body: JSON.stringify(data), token, }), }; // Downloads API export interface Download { id: number; title: string; description?: string; category?: string; file_url: string; thumbnail?: string; download_count: number; } export interface DownloadAdmin { id: number; title_ko: string; title_en?: string; title_ja?: string; title_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; category_ko?: string; category_en?: string; category_ja?: string; category_zh?: string; file_url: string; file_name?: string; file_size?: number; version?: string; thumbnail?: string; is_active: boolean; display_order: number; download_count: number; } export interface DownloadRequest { id: number; download_id: number; download_title?: string; email: string; newsletter_agreed: boolean; country?: string; country_code?: string; requested_at?: string; } export const downloadsApi = { // Public getAll: (lang: string = "ko") => fetchApi(`/downloads/?lang=${lang}`), getOne: (id: number, lang: string = "ko") => fetchApi(`/downloads/${id}?lang=${lang}`), requestDownload: (id: number, email?: string, newsletterAgreed?: boolean, skipEmail?: boolean) => fetchApi<{ download_url: string; file_name: string }>(`/downloads/${id}/request`, { method: "POST", body: JSON.stringify({ email: email || null, newsletter_agreed: newsletterAgreed || false, skip_email: skipEmail || false }), }), // Admin adminGetAll: (token: string) => fetchApi("/downloads/admin/list", { token }), adminGetOne: (id: number, token: string) => fetchApi(`/downloads/admin/${id}`, { token }), adminCreate: (data: Partial, token: string) => fetchApi("/downloads/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/downloads/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/downloads/admin/${id}`, { method: "DELETE", token, }), adminUpload: async (file: File, fileType: string = "file", token: string) => { const formData = new FormData(); formData.append("file", file); const response = await fetch( `${API_BASE_URL}/downloads/admin/upload?file_type=${fileType}`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body: formData, } ); if (!response.ok) { const error = await response.json().catch(() => ({ detail: "Upload failed" })); throw new Error(error.detail); } return response.json(); }, adminGetRequests: (token: string, newsletterOnly: boolean = false) => fetchApi(`/downloads/admin/requests?newsletter_only=${newsletterOnly}`, { token }), adminGetStats: (token: string) => fetchApi<{ total_requests: number; newsletter_subscribers: number; unique_emails: number }>( "/downloads/admin/requests/stats", { token } ), }; // Content Videos API (YouTube) export interface ContentVideo { id: number; youtube_id: string; title_ko: string; title_en?: string; title_ja?: string; title_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; entity_type: "project" | "solution" | "product"; entity_id: number; display_order: number; is_active: boolean; youtube_url: string; youtube_embed_url: string; thumbnail_url: string; created_at?: string; updated_at?: string; } export interface ContentVideoPublic { id: number; youtube_id: string; title: string; description?: string; youtube_url: string; youtube_embed_url: string; thumbnail_url: string; display_order: number; } export interface ContentVideoCreate { youtube_id: string; title_ko: string; title_en?: string; title_ja?: string; title_zh?: string; description_ko?: string; description_en?: string; description_ja?: string; description_zh?: string; entity_type: "project" | "solution" | "product"; entity_id: number; display_order?: number; is_active?: boolean; } export const contentVideosApi = { // Public getForEntity: (entityType: string, entityId: number, locale: string = "ko") => fetchApi( `/content-videos/entity/${entityType}/${entityId}?locale=${locale}` ), // Admin adminList: (token: string, entityType?: string, entityId?: number) => { const params = new URLSearchParams(); if (entityType) params.append("entity_type", entityType); if (entityId) params.append("entity_id", entityId.toString()); const query = params.toString(); return fetchApi( `/content-videos/admin/list${query ? "?" + query : ""}`, { token } ); }, adminGet: (id: number, token: string) => fetchApi(`/content-videos/admin/${id}`, { token }), adminCreate: (data: ContentVideoCreate, token: string) => fetchApi("/content-videos/admin", { method: "POST", body: JSON.stringify(data), token, }), adminUpdate: (id: number, data: Partial, token: string) => fetchApi(`/content-videos/admin/${id}`, { method: "PUT", body: JSON.stringify(data), token, }), adminDelete: (id: number, token: string) => fetchApi<{ message: string }>(`/content-videos/admin/${id}`, { method: "DELETE", token, }), adminReorder: (entityType: string, entityId: number, videoIds: number[], token: string) => fetchApi<{ message: string }>( `/content-videos/admin/reorder?entity_type=${entityType}&entity_id=${entityId}`, { method: "PUT", body: JSON.stringify(videoIds), token, } ), };