| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605 |
- import i18n from "../i18n";
- export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
- const getErrorMessage = async (response: Response, defaultMsg: string) => {
- try {
- const errorData = await response.json();
- if (errorData && errorData.detail) {
- if (Array.isArray(errorData.detail)) {
- // FastAPI/Pydantic validation error
- const firstError = errorData.detail[0];
- const errorType = firstError.type;
- const fieldName = firstError.loc[firstError.loc.length - 1];
-
- // Try to translate the error type using i18n
- // Pydantic types look like 'string_too_short', 'value_error.email'
- const translationKey = `errors.${errorType}`;
- const translatedMsg = i18n.global.t(translationKey, {
- ...firstError.ctx,
- defaultValue: firstError.msg
- });
-
- return `${fieldName}: ${translatedMsg}`;
- }
- return errorData.detail;
- }
- } catch (e) {}
- return defaultMsg;
- };
- export const getMaterials = async () => {
- const response = await fetch(`${API_BASE_URL}/materials?lang=${i18n.global.locale.value}`);
- if (!response.ok) {
- throw new Error('Failed to fetch materials');
- }
- return response.json();
- };
- export const getServices = async () => {
- const response = await fetch(`${API_BASE_URL}/services?lang=${i18n.global.locale.value}`);
- if (!response.ok) {
- throw new Error('Failed to fetch services');
- }
- return response.json();
- };
- export const uploadFilesToServer = async (data: FormData) => {
- const response = await fetch(`${API_BASE_URL}/files/upload`, {
- method: "POST",
- body: data,
- });
- if (!response.ok) throw new Error("Failed to upload files");
- return response.json();
- };
- export const submitOrder = async (orderData: FormData) => {
- const token = localStorage.getItem("token");
- const headers: Record<string, string> = {};
- if (token) {
- headers['Authorization'] = `Bearer ${token}`;
- }
- const response = await fetch(`${API_BASE_URL}/orders?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- body: orderData,
- headers: headers,
- // Note: Do not set Content-Type header manually when using FormData
- // The browser will automatically set it with the correct boundary
- });
-
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Failed to submit order'));
- }
-
- return response.json();
- };
- export const getMyOrders = async (page = 1, size = 10) => {
- const token = localStorage.getItem("token");
- if (!token) return { orders: [], total: 0 };
-
- const query = new URLSearchParams({ page: page.toString(), size: size.toString() });
- const response = await fetch(`${API_BASE_URL}/orders/my?${query.toString()}&lang=${i18n.global.locale.value}`, {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!response.ok) {
- throw new Error('Failed to fetch orders');
- }
-
- return response.json();
- };
- export const registerUser = async (userData: any) => {
- const response = await fetch(`${API_BASE_URL}/auth/register?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(userData),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Failed to register'));
- }
- return response.json();
- };
- export const loginUser = async (userData: any) => {
- const response = await fetch(`${API_BASE_URL}/auth/login?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(userData),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Incorrect credentials'));
- }
- return response.json();
- };
- export const logoutUser = async () => {
- const token = localStorage.getItem("token");
- if (!token) return;
- await fetch(`${API_BASE_URL}/auth/logout`, {
- method: 'POST',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- };
- export const socialLogin = async (socialData: any) => {
- const response = await fetch(`${API_BASE_URL}/auth/social-login?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(socialData),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Social login failed'));
- }
- return response.json();
- };
- export const getCurrentUser = async () => {
- const token = localStorage.getItem("token");
- if (!token) return null;
- const response = await fetch(`${API_BASE_URL}/auth/me?lang=${i18n.global.locale.value}`, {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
-
- if (!response.ok) {
- if (response.status === 401) {
- localStorage.removeItem("token");
- }
- return null;
- }
-
- return response.json();
- };
- export const updateProfile = async (userData: any) => {
- const token = localStorage.getItem("token");
- if (!token) throw new Error("No token found");
- const response = await fetch(`${API_BASE_URL}/auth/me?lang=${i18n.global.locale.value}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(userData),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Failed to update profile'));
- }
- return response.json();
- };
- export const forgotPassword = async (email: string) => {
- const response = await fetch(`${API_BASE_URL}/auth/forgot-password?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ email }),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Failed to request reset'));
- }
- return response.json();
- };
- export const resetPassword = async (data: any) => {
- const response = await fetch(`${API_BASE_URL}/auth/reset-password?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data),
- });
- if (!response.ok) {
- throw new Error(await getErrorMessage(response, 'Failed to reset password'));
- }
- return response.json();
- };
- export const adminGetOrders = async (filters: { search?: string, status?: string, date_from?: string, date_to?: string } = {}) => {
- const token = localStorage.getItem("token");
- const query = new URLSearchParams();
- if (filters.search) query.append("search", filters.search);
- if (filters.status) query.append("status", filters.status);
- if (filters.date_from) query.append("date_from", filters.date_from);
- if (filters.date_to) query.append("date_to", filters.date_to);
-
- const response = await fetch(`${API_BASE_URL}/orders/admin/list?${query.toString()}&lang=${i18n.global.locale.value}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch admin orders");
- return response.json();
- };
- export const adminUpdateOrder = async (orderId: number, data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}?lang=${i18n.global.locale.value}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update order");
- return response.json();
- };
- export const adminDeleteOrder = async (orderId: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/admin?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to delete order");
- return response.json();
- };
- export const getPriceEstimate = async (data: any) => {
- const response = await fetch(`${API_BASE_URL}/orders/estimate?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to get estimate");
- return response.json();
- };
- export const adminGetMaterials = async () => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/materials?lang=${i18n.global.locale.value}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch admin materials");
- return response.json();
- };
- export const adminCreateMaterial = async (data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/materials?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to create material");
- return response.json();
- };
- export const adminUpdateMaterial = async (id: number, data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/materials/${id}?lang=${i18n.global.locale.value}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update material");
- return response.json();
- };
- export const adminGetServices = async () => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/services?lang=${i18n.global.locale.value}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch admin services");
- return response.json();
- };
- export const adminCreateService = async (data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/services?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to create service");
- return response.json();
- };
- export const adminUpdateService = async (id: number, data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/services/${id}?lang=${i18n.global.locale.value}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update service");
- return response.json();
- };
- export const adminDeleteMaterial = async (id: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/materials/${id}?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to delete material");
- return response.json();
- };
- export const adminDeleteService = async (id: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/services/${id}?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to delete service");
- return response.json();
- };
- export const adminUploadOrderPhoto = async (orderId: number, formData: FormData) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/orders/${orderId}/photos?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`
- },
- body: formData
- });
- if (!response.ok) throw new Error("Failed to upload photo");
- return response.json();
- };
- export const adminDeletePhoto = async (photoId: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/photos/${photoId}?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to delete photo");
- return response.json();
- };
- export const adminGetAllPhotos = async () => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/all-photos?lang=${i18n.global.locale.value}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch all photos");
- return response.json();
- };
- export const adminAttachFile = async (orderId: number, formData: FormData) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/attach-file?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`
- },
- body: formData
- });
- if (!response.ok) throw new Error("Failed to attach file");
- return response.json();
- };
- export const adminDeleteFile = async (orderId: number, fileId: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/files/${fileId}?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
- if (!response.ok) throw new Error("Failed to delete file");
- return response.json();
- };
- export const adminUpdatePhotoStatus = async (photoId: number, data: { is_public: boolean }) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/admin/photos/${photoId}?lang=${i18n.global.locale.value}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update photo status");
- return response.json();
- };
- export const getPortfolio = async () => {
- const response = await fetch(`${API_BASE_URL}/portfolio?lang=${i18n.global.locale.value}`);
- if (!response.ok) throw new Error("Failed to fetch portfolio");
- return response.json();
- };
- export const getOrderDetails = async (orderId: number) => {
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}?lang=${i18n.global.locale.value}`);
- if (!response.ok) throw new Error("Failed to fetch order details");
- return response.json();
- };
- export const getOrderMessages = async (orderId: number, lang: string = i18n.global.locale.value) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/messages?lang=${lang}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch messages");
- return response.json();
- };
- export const sendOrderMessage = async (orderId: number, message: string, lang: string = i18n.global.locale.value) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/messages?lang=${lang}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify({ message })
- });
- if (response.status === 429) {
- const err = await response.json();
- throw new Error(err.detail || "Rate limit exceeded");
- }
- if (!response.ok) throw new Error("Failed to send message");
- return response.json();
- };
- export const authPing = async () => {
- const token = localStorage.getItem("token");
- if (!token) return null;
- try {
- const response = await fetch(`${API_BASE_URL}/auth/ping?lang=${i18n.global.locale.value}`, {
- method: "POST",
- headers: { "Authorization": `Bearer ${token}` }
- });
- if (response.ok) {
- return response.json();
- }
- } catch (e) {
- // silently fail
- }
- return null;
- };
- // Blog API
- export const getBlogPosts = async (publishedOnly: boolean = true) => {
- const response = await fetch(`${API_BASE_URL}/blog?published_only=${publishedOnly}&lang=${i18n.global.locale.value}`);
- if (!response.ok) throw new Error("Failed to fetch blog posts");
- return response.json();
- };
- export const getBlogPost = async (idOrSlug: string) => {
- const response = await fetch(`${API_BASE_URL}/blog/${idOrSlug}?lang=${i18n.global.locale.value}`);
- if (!response.ok) throw new Error("Failed to fetch blog post");
- return response.json();
- };
- export const adminCreatePost = async (data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/blog?lang=${i18n.global.locale.value}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to create blog post");
- return response.json();
- };
- export const adminUpdatePost = async (id: number, data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/blog/${id}?lang=${i18n.global.locale.value}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update blog post");
- return response.json();
- };
- export const adminDeletePost = async (id: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/blog/${id}?lang=${i18n.global.locale.value}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to delete blog post");
- return response.json();
- };
- export const adminGetUsers = async (page = 1, size = 50, search = "") => {
- const token = localStorage.getItem("token");
- const query = new URLSearchParams({ page: page.toString(), size: size.toString(), search });
- const response = await fetch(`${API_BASE_URL}/auth/admin/users?${query.toString()}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch users");
- return response.json();
- };
- export const adminCreateUser = async (data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/auth/admin/users`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) {
- const err = await response.json();
- throw new Error(err.detail || "Failed to create user");
- }
- return response.json();
- };
- export const adminUpdateUser = async (userId: number, data: any) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/auth/users/${userId}/admin?lang=${i18n.global.locale.value}`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(data)
- });
- if (!response.ok) throw new Error("Failed to update user");
- return response.json();
- };
- export const adminGetAuditLogs = async (page = 1, size = 50, action = "") => {
- const token = localStorage.getItem("token");
- const query = new URLSearchParams({ page: page.toString(), size: size.toString() });
- if (action) query.append("action", action);
-
- const response = await fetch(`${API_BASE_URL}/admin/audit-logs?${query.toString()}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch audit logs");
- return response.json();
- };
- export const adminGetOrderItems = async (orderId: number) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/items?lang=${i18n.global.locale.value}`, {
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (!response.ok) throw new Error("Failed to fetch order items");
- return response.json();
- };
- export const adminUpdateOrderItems = async (orderId: number, items: any[]) => {
- const token = localStorage.getItem("token");
- const response = await fetch(`${API_BASE_URL}/orders/${orderId}/items?lang=${i18n.global.locale.value}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify(items)
- });
- if (!response.ok) throw new Error("Failed to update order items");
- return response.json();
- };
|