fix: Remove car_id property from adminAddVehicle call to fix TypeScript error
This commit is contained in:
997
temp_full_api.ts
Normal file
997
temp_full_api.ts
Normal file
@@ -0,0 +1,997 @@
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8001/api";
|
||||
|
||||
interface FetchOptions extends RequestInit {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
async function fetchApi<T>(endpoint: string, options: FetchOptions = {}): Promise<T> {
|
||||
const { token, ...fetchOptions } = options;
|
||||
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
};
|
||||
|
||||
if (token) {
|
||||
(headers as Record<string, string>)["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<Project[]>(`/projects/?${params}`);
|
||||
},
|
||||
|
||||
getOne: (id: number, lang: string = "ko") =>
|
||||
fetchApi<Project>(`/projects/${id}?lang=${lang}`),
|
||||
|
||||
// Admin
|
||||
adminGetAll: (token: string) =>
|
||||
fetchApi<ProjectAdmin[]>("/projects/admin/list", { token }),
|
||||
|
||||
adminGetOne: (id: number, token: string) =>
|
||||
fetchApi<ProjectAdmin>(`/projects/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: Partial<ProjectAdmin>, token: string) =>
|
||||
fetchApi<ProjectAdmin>("/projects/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<ProjectAdmin>, token: string) =>
|
||||
fetchApi<ProjectAdmin>(`/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<Solution[]>(`/solutions/?lang=${lang}`),
|
||||
|
||||
getOne: (id: number, lang: string = "ko") =>
|
||||
fetchApi<Solution>(`/solutions/${id}?lang=${lang}`),
|
||||
|
||||
// Admin
|
||||
adminGetAll: (token: string) =>
|
||||
fetchApi<SolutionAdmin[]>("/solutions/admin/list", { token }),
|
||||
|
||||
adminGetOne: (id: number, token: string) =>
|
||||
fetchApi<SolutionAdmin>(`/solutions/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: Partial<SolutionAdmin>, token: string) =>
|
||||
fetchApi<SolutionAdmin>("/solutions/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<SolutionAdmin>, token: string) =>
|
||||
fetchApi<SolutionAdmin>(`/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<Product[]>(`/products/?lang=${lang}`),
|
||||
|
||||
getOne: (id: number, lang: string = "ko") =>
|
||||
fetchApi<Product>(`/products/${id}?lang=${lang}`),
|
||||
|
||||
// Admin
|
||||
adminGetAll: (token: string) =>
|
||||
fetchApi<ProductAdmin[]>("/products/admin/list", { token }),
|
||||
|
||||
adminGetOne: (id: number, token: string) =>
|
||||
fetchApi<ProductAdmin>(`/products/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: Partial<ProductAdmin>, token: string) =>
|
||||
fetchApi<ProductAdmin>("/products/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<ProductAdmin>, token: string) =>
|
||||
fetchApi<ProductAdmin>(`/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<ContactSettings>(`/contact/settings?lang=${lang}`),
|
||||
|
||||
submitInquiry: (data: { name: string; email: string; phone?: string; company?: string; message: string }) =>
|
||||
fetchApi<ContactInquiry>("/contact/inquiry", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
|
||||
// Admin
|
||||
adminGetSettings: (token: string) =>
|
||||
fetchApi<ContactSettingsAdmin>("/contact/admin/settings", { token }),
|
||||
|
||||
adminUpdateSettings: (data: Partial<ContactSettingsAdmin>, token: string) =>
|
||||
fetchApi<ContactSettingsAdmin>("/contact/admin/settings", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminGetInquiries: (token: string, status?: string) => {
|
||||
const params = status ? `?status=${status}` : "";
|
||||
return fetchApi<ContactInquiry[]>(`/contact/admin/inquiries${params}`, { token });
|
||||
},
|
||||
|
||||
adminGetInquiry: (id: number, token: string) =>
|
||||
fetchApi<ContactInquiry>(`/contact/admin/inquiries/${id}`, { token }),
|
||||
|
||||
adminDeleteInquiry: (id: number, token: string) =>
|
||||
fetchApi<{ message: string }>(`/contact/admin/inquiries/${id}`, {
|
||||
method: "DELETE",
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdateInquiryStatus: (id: number, status: string, token: string) =>
|
||||
fetchApi<ContactInquiry>(`/contact/admin/inquiries/${id}/status`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ status }),
|
||||
token,
|
||||
}),
|
||||
};
|
||||
|
||||
// Banner API
|
||||
export const bannerApi = {
|
||||
// Public
|
||||
getAll: (lang: string = "ko") =>
|
||||
fetchApi<Banner[]>(`/banners/?lang=${lang}`),
|
||||
|
||||
adminGetSettings: (token: string) =>
|
||||
fetchApi<BannerSettings>("/banners/admin/settings", { token }),
|
||||
|
||||
getSettings: () =>
|
||||
fetchApi<BannerSettings>("/banners/settings"),
|
||||
|
||||
// Admin
|
||||
adminGetAll: (token: string) =>
|
||||
fetchApi<BannerAdmin[]>("/banners/admin/list", { token }),
|
||||
|
||||
adminGetOne: (id: number, token: string) =>
|
||||
fetchApi<BannerAdmin>(`/banners/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: Partial<BannerAdmin>, token: string) =>
|
||||
fetchApi<BannerAdmin>("/banners/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<BannerAdmin>, token: string) =>
|
||||
fetchApi<BannerAdmin>(`/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<BannerSettings>, token: string) =>
|
||||
fetchApi<BannerSettings>("/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;
|
||||
version?: string;
|
||||
file_size?: 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<Download[]>(`/downloads/?lang=${lang}`),
|
||||
|
||||
getOne: (id: number, lang: string = "ko") =>
|
||||
fetchApi<Download>(`/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<DownloadAdmin[]>("/downloads/admin/list", { token }),
|
||||
|
||||
adminGetOne: (id: number, token: string) =>
|
||||
fetchApi<DownloadAdmin>(`/downloads/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: Partial<DownloadAdmin>, token: string) =>
|
||||
fetchApi<DownloadAdmin>("/downloads/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<DownloadAdmin>, token: string) =>
|
||||
fetchApi<DownloadAdmin>(`/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<DownloadRequest[]>(`/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<ContentVideoPublic[]>(
|
||||
`/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<ContentVideo[]>(
|
||||
`/content-videos/admin/list${query ? "?" + query : ""}`,
|
||||
{ token }
|
||||
);
|
||||
},
|
||||
|
||||
adminGet: (id: number, token: string) =>
|
||||
fetchApi<ContentVideo>(`/content-videos/admin/${id}`, { token }),
|
||||
|
||||
adminCreate: (data: ContentVideoCreate, token: string) =>
|
||||
fetchApi<ContentVideo>("/content-videos/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<ContentVideoCreate>, token: string) =>
|
||||
fetchApi<ContentVideo>(`/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,
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
// App Ads API
|
||||
export interface AppAd {
|
||||
id: number;
|
||||
title: string;
|
||||
app_type: string;
|
||||
slot: number;
|
||||
image?: string;
|
||||
link_url?: string;
|
||||
is_active: boolean;
|
||||
click_count: number;
|
||||
impression_count: number;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
id: number;
|
||||
app_type: string;
|
||||
footer_text: string;
|
||||
footer_url: string;
|
||||
}
|
||||
|
||||
export const appAdsApi = {
|
||||
// Public
|
||||
getForApp: (appType: string, slot: number) =>
|
||||
fetchApi<AppAd | null>(`/app-ads/public/${appType}/${slot}`),
|
||||
|
||||
recordImpression: (adId: number) =>
|
||||
fetchApi<{ success: boolean }>(`/app-ads/public/${adId}/impression`, {
|
||||
method: "POST",
|
||||
}),
|
||||
|
||||
recordClick: (adId: number) =>
|
||||
fetchApi<{ success: boolean }>(`/app-ads/public/${adId}/click`, {
|
||||
method: "POST",
|
||||
}),
|
||||
|
||||
getSettings: (appType: string, token?: string) =>
|
||||
fetchApi<AppSettings>(`/app-ads/settings/${appType}`, { token }),
|
||||
|
||||
// Admin
|
||||
adminGetAll: (token: string) =>
|
||||
fetchApi<AppAd[]>("/app-ads/admin/list", { token }),
|
||||
|
||||
adminCreate: (data: Partial<AppAd>, token: string) =>
|
||||
fetchApi<AppAd>("/app-ads/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminUpdate: (id: number, data: Partial<AppAd>, token: string) =>
|
||||
fetchApi<AppAd>(`/app-ads/admin/${id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
adminDelete: (id: number, token: string) =>
|
||||
fetchApi<{ message: string }>(`/app-ads/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}/app-ads/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();
|
||||
},
|
||||
|
||||
updateSettings: (appType: string, data: Partial<AppSettings>, token: string) =>
|
||||
fetchApi<AppSettings>(`/app-ads/settings/${appType}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
};
|
||||
|
||||
// Visitors API
|
||||
export interface OverviewStats {
|
||||
total_visits: number;
|
||||
unique_visitors: number;
|
||||
pages_per_visit: number;
|
||||
today_visitors: number;
|
||||
growth_rate: number;
|
||||
mobile_percentage: number;
|
||||
}
|
||||
|
||||
export interface ChartData {
|
||||
labels: string[];
|
||||
data: number[];
|
||||
}
|
||||
|
||||
export interface BreakdownData {
|
||||
items: { name: string; count: number; percentage: number }[];
|
||||
}
|
||||
|
||||
export interface TopPagesData {
|
||||
pages: { path: string; count: number }[];
|
||||
}
|
||||
|
||||
export interface TopReferrersData {
|
||||
referrers: { domain: string; count: number }[];
|
||||
}
|
||||
|
||||
export interface RealtimeStats {
|
||||
active_visitors: number;
|
||||
recent_pages: { path: string; time: string }[];
|
||||
}
|
||||
|
||||
export interface CountryStats {
|
||||
country_code: string;
|
||||
country: string;
|
||||
visitor_count: number;
|
||||
}
|
||||
|
||||
export const visitorsApi = {
|
||||
// Public
|
||||
track: () =>
|
||||
fetchApi<{ success: boolean }>("/visitors/track", {
|
||||
method: "POST",
|
||||
}),
|
||||
|
||||
// Admin
|
||||
adminGetOverview: (token: string, days: number = 30) =>
|
||||
fetchApi<OverviewStats>(`/visitors/admin/overview?days=${days}`, { token }),
|
||||
|
||||
adminGetVisitsChart: (token: string, days: number = 30) =>
|
||||
fetchApi<ChartData>(`/visitors/admin/chart/visits?days=${days}`, { token }),
|
||||
|
||||
adminGetUniqueChart: (token: string, days: number = 30) =>
|
||||
fetchApi<ChartData>(`/visitors/admin/chart/unique?days=${days}`, { token }),
|
||||
|
||||
adminGetDeviceBreakdown: (token: string, days: number = 30) =>
|
||||
fetchApi<BreakdownData>(`/visitors/admin/breakdown/device?days=${days}`, { token }),
|
||||
|
||||
adminGetBrowserBreakdown: (token: string, days: number = 30) =>
|
||||
fetchApi<BreakdownData>(`/visitors/admin/breakdown/browser?days=${days}`, { token }),
|
||||
|
||||
adminGetOsBreakdown: (token: string, days: number = 30) =>
|
||||
fetchApi<BreakdownData>(`/visitors/admin/breakdown/os?days=${days}`, { token }),
|
||||
|
||||
adminGetTopPages: (token: string, days: number = 30) =>
|
||||
fetchApi<TopPagesData>(`/visitors/admin/top-pages?days=${days}`, { token }),
|
||||
|
||||
adminGetTopReferrers: (token: string, days: number = 30) =>
|
||||
fetchApi<TopReferrersData>(`/visitors/admin/top-referrers?days=${days}`, { token }),
|
||||
|
||||
adminGetRealtime: (token: string) =>
|
||||
fetchApi<RealtimeStats>("/visitors/admin/realtime", { token }),
|
||||
|
||||
adminGetCountryStats: (token: string) =>
|
||||
fetchApi<CountryStats[]>("/visitors/admin/countries", { token }),
|
||||
|
||||
adminGetStats: (token: string) =>
|
||||
fetchApi<VisitorStats>("/visitors/admin/stats", { token }),
|
||||
};
|
||||
|
||||
// Alias for bannersApi (some components use this name)
|
||||
export const bannersApi = bannerApi;
|
||||
|
||||
// Notifications API
|
||||
export interface NotificationSettings {
|
||||
id: number;
|
||||
email_enabled: boolean;
|
||||
smtp_server: string;
|
||||
smtp_port: number;
|
||||
smtp_username: string;
|
||||
smtp_password: string;
|
||||
admin_email: string;
|
||||
kakao_enabled: boolean;
|
||||
kakao_webhook_url: string;
|
||||
}
|
||||
|
||||
export const notificationApi = {
|
||||
getSettings: (token: string) =>
|
||||
fetchApi<NotificationSettings>("/notifications/settings", { token }),
|
||||
|
||||
updateSettings: (data: Partial<NotificationSettings>, token: string) =>
|
||||
fetchApi<NotificationSettings>("/notifications/settings", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(data),
|
||||
token,
|
||||
}),
|
||||
|
||||
testEmail: (token: string) =>
|
||||
fetchApi<{ success: boolean; message: string }>("/notifications/test/email", {
|
||||
method: "POST",
|
||||
token,
|
||||
}),
|
||||
|
||||
testKakao: (token: string) =>
|
||||
fetchApi<{ success: boolean; message: string }>("/notifications/test/kakao", {
|
||||
method: "POST",
|
||||
token,
|
||||
}),
|
||||
|
||||
testNotification: (type: string, token: string) =>
|
||||
fetchApi<{ success: boolean; message: string }>(`/notifications/test/${type}`, {
|
||||
method: "POST",
|
||||
token,
|
||||
}),
|
||||
};
|
||||
|
||||
// VisitorStats interface (used in admin page)
|
||||
export interface VisitorStats {
|
||||
total_visitors: number;
|
||||
today_visitors: number;
|
||||
}
|
||||
Reference in New Issue
Block a user