import apiClient from "./client"; import type { ConfirmReceptionResponse, CheckoutCartResponse, HistoryResponse, ETAResponse, TrackingResponse, CancelCommandResponse, PenaltiesResponse, ReferralBalanceResponse, } from "./api_types"; import { getToken } from "../auth/tokenStorage"; import { extractUsernameFromToken } from "../auth/jwtUtils"; const V1 = "http://5.181.0.112/api/v1"; export const getJwtUsername = async (): Promise => { const token = await getToken(); if (!token) return null; return extractUsernameFromToken(token); }; export interface AuthResponse { success: boolean; message?: string; access_token?: string; token_type?: string; expires_in?: number; user?: { id: number; username: string; nom?: string; prenom?: string; telephone?: string; role?: string; session_id?: string; must_change_password?: boolean; }; } export const loginUser = async ( username: string, password: string, ): Promise => { try { const { data } = await apiClient.post(`${V1}/auth/login`, { username, password, }); if (!data.access_token) { return { success: false, message: "Token non reçu du serveur" }; } return { success: true, message: "Connexion réussie", access_token: data.access_token, token_type: data.token_type, expires_in: data.expires_in, user: data.user, }; } catch (error: any) { const msg = error.response?.data?.error || error.message || "Erreur de connexion"; return { success: false, message: msg }; } }; export const registerUser = async ( username: string, password: string, nom: string, prenom: string, telephone: string, ): Promise => { try { const { data } = await apiClient.post(`${V1}/auth/register`, { username, password, nom, prenom, telephone, }); if (!data.access_token) { return { success: false, message: "Token non reçu du serveur" }; } return { success: true, message: "Inscription réussie", access_token: data.access_token, token_type: data.token_type, expires_in: data.expires_in, user: data.user, }; } catch (error: any) { const msg = error.response?.data?.error || error.message || "Erreur d'inscription"; return { success: false, message: msg }; } }; export const logoutUser = async (): Promise => { try { await apiClient.post(`${V1}/auth/logout`); } catch { /* ignore */ } }; // ============================================ // PRODUCTS // ============================================ export interface Category { id: number; name: string; color: string; is_coming_soon: boolean; created_at: string; } export const getCategories = async (): Promise => { try { const { data } = await apiClient.get(`${V1}/categories`); return data.categories || []; } catch { return []; } }; export const getAllProducts = async () => { try { const { data } = await apiClient.get(`${V1}/products`); return data; } catch { return { success: false, data: [] }; } }; export const getProductsByCategory = async (category: string) => { try { const { data } = await apiClient.get( `${V1}/products/category/${category}`, ); return data; } catch { return { success: false, data: [] }; } }; export const getProductById = async (id: number) => { try { const { data } = await apiClient.get(`${V1}/products/${id}`); return data; } catch { return { success: false, data: null }; } }; // ============================================ // CART // ============================================ export const getCart = async (username: string) => { const jwtUsername = await getJwtUsername(); if (!jwtUsername || jwtUsername !== username) { return { success: false, message: "Accès refusé", panier: [] }; } try { const { data } = await apiClient.get(`${V1}/panier/${jwtUsername}`); return { success: true, panier: data.panier || [], message: data.message, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur récupération", panier: [], }; } }; export const addToCart = async (cartItem: { username?: string; product_id?: number; name_product: string; category: string; quantity: number; price: number; }) => { const jwtUsername = await getJwtUsername(); if (!jwtUsername) return { success: false, message: "Session invalide" }; cartItem.username = jwtUsername; try { const { data } = await apiClient.post(`${V1}/panier/add`, cartItem); return { success: true, message: "Produit ajouté", panier: data.panier, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur ajout", }; } }; export const removeFromCart = async (id: number, username: string) => { const jwtUsername = await getJwtUsername(); if (!jwtUsername || jwtUsername !== username) return { success: false, message: "Accès refusé" }; try { const { data } = await apiClient.delete(`${V1}/panier/remove`, { data: { id, username: jwtUsername }, }); return { success: true, message: "Produit supprimé" }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur suppression", }; } }; export const clearCart = async (username: string) => { const jwtUsername = await getJwtUsername(); if (!jwtUsername || jwtUsername !== username) return { success: false, message: "Accès refusé" }; try { const { data } = await apiClient.delete(`${V1}/panier/clear`); return { success: true, message: data.message || "Panier vidé", stock_released: data.stock_released, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur vidage", }; } }; // ============================================ // ORDERS // ============================================ export const getMyOrders = async () => { try { const { data } = await apiClient.get(`${V1}/my-commands`); return { success: true, commands: data.commands || [] }; } catch { return { success: false, message: "Erreur récupération", commands: [] }; } }; export const createCheckout = async (checkoutData: { username?: string; delivery_address: string; }) => { const jwtUsername = await getJwtUsername(); if (!jwtUsername) return { success: false, message: "Session invalide" }; checkoutData.username = jwtUsername; try { const { data } = await apiClient.post(`${V1}/checkout`, checkoutData); return { success: true, message: "Commande créée", command_id: data.command_id, command: data.command, delivery_address: data.delivery_address, assigned_to: data.assigned_to, queue_info: data.queue_info, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur création", }; } }; export const checkoutCart = async ( delivery_address: string, nom?: string, prenom?: string, telephone?: string, use_referral_balance?: boolean, ): Promise => { const jwtUsername = await getJwtUsername(); if (!jwtUsername) return { success: false, message: "Session invalide" }; if (!delivery_address.trim()) return { success: false, message: "Veuillez saisir une adresse de livraison", }; try { const payload: Record = { username: jwtUsername, delivery_address, use_referral_balance: use_referral_balance ?? false, }; if (nom) payload.nom = nom; if (prenom) payload.prenom = prenom; if (telephone) payload.telephone = telephone; const { data } = await apiClient.post(`${V1}/checkout`, payload); return { success: true, message: data.message || "Commande validée avec succès", command_id: data.command_id, delivery_address: data.delivery_address, command: data.command, assigned_to: data.assigned_to, queue_info: data.queue_info, referral_used: data.referral_used, referral_balance: data.referral_balance, }; } catch (error: any) { const errMsg: string = error.response?.data?.error || "Erreur serveur"; if (errMsg.startsWith("Adresse invalide ")) { const suggested = errMsg.replace("Adresse invalide ", "").trim(); return { success: false, invalid_address: true, suggested_address: suggested, message: errMsg, }; } return { success: false, message: errMsg, }; } }; export const getReferralBalance = async (): Promise => { try { const { data } = await apiClient.get(`${V1}/referral/balance`); return { success: true, balance: data.balance ?? 0 }; } catch { return { success: false, balance: 0, message: "Erreur récupération solde" }; } }; export const approveDelivery = async ( commandId: number, reqData?: { rating?: number; comment?: string }, ) => { try { const { data } = await apiClient.post( `${V1}/commands/${commandId}/approve`, reqData || {}, ); return { success: true, message: "Commande approuvée", points_earned: data.points_earned, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur approbation", }; } }; export const confirmReception = async ( commandId: number, ): Promise => { try { const { data } = await apiClient.post( `${V1}/commands/${commandId}/approve`, {}, ); return { success: true, message: "Commande confirmée avec succès", points_earned: data.points_earned || 0, category: data.category, data, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur lors de la confirmation", }; } }; export const respondToAddressProposal = async ( commandId: number, accepted: boolean, ): Promise<{ success: boolean; message: string }> => { try { const { data } = await apiClient.post( `${V1}/commands/${commandId}/address/respond`, { accepted }, ); return { success: true, message: data.message || "Réponse enregistrée", }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur lors de la réponse", }; } }; // ============================================ // TRACKING // ============================================ export const getOrderTracking = async ( commandId: number, ): Promise => { try { const { data } = await apiClient.get( `${V1}/commands/${commandId}/track`, ); return { success: true, command_id: data.id || commandId, status: data.status || "pending", current_step: data.current_step || "En attente", livreur_username: data.livreur_username || data.livreur, livreur_distance: data.livreur_distance, eta_minutes: data.eta_minutes, estimated_arrival: data.estimated_arrival, delivery_address: data.delivery_address || data.adresse, location: data.location, incidents_detected: data.incidents_detected || 0, updated_at: data.updated_at, created_at: data.created_at, message: "Suivi récupéré", }; } catch (error: any) { return { success: false, command_id: commandId, status: "error", current_step: "Erreur", message: error.response?.data?.error || "Erreur récupération suivi", }; } }; export const getOrderETA = async (commandId: number): Promise => { try { const { data } = await apiClient.get(`${V1}/commands/${commandId}/eta`); return { success: true, command_id: data.id || commandId, eta_minutes: data.eta_minutes || 0, estimated_arrival: data.estimated_arrival || "N/A", status: data.status || "pending", livreur_distance: data.livreur_distance, message: "ETA récupéré", }; } catch { return { success: false, command_id: commandId, eta_minutes: 0, estimated_arrival: "N/A", status: "error", }; } }; export const getOrdersWithTracking = async () => { try { const { data } = await apiClient.get(`${V1}/my-commands`); return { success: true, commands: data.commands || [], count: data.count || 0, }; } catch { return { success: false, commands: [], message: "Erreur récupération" }; } }; // ============================================ // HISTORY // ============================================ export const getMyCompletedOrders = async (): Promise => { try { const { data } = await apiClient.get(`${V1}/my-commands/history`); return { success: true, commands: data.commands || [], count: data.count || 0, client_stats: data.client_stats, }; } catch { return { success: false, commands: [], count: 0, message: "Erreur récupération", }; } }; // ============================================ // CANCEL // ============================================ export const cancelCommand = async ( commandId: number, reason?: string, force = false, ): Promise => { try { const { data } = await apiClient.post( `${V1}/commands/${commandId}/cancel`, { reason: reason || "Annulation par le client", force, }, ); return { success: true, message: data.message || "Commande annulée avec succès", command_id: data.command_id, new_status: data.new_status, cancelled_by: data.cancelled_by, reason: data.reason, penalty: data.penalty, info: data.info, }; } catch (error: any) { const resp = error.response; if (resp?.status === 409 && resp?.data?.warning) { return { success: false, warning: true, message: resp.data.message, details: resp.data.details, penalty_warning: resp.data.penalty_warning, action_required: resp.data.action_required, }; } return { success: false, message: resp?.data?.error || "Erreur lors de l'annulation", }; } }; // ============================================ // PENALTIES // ============================================ export const changePassword = async ( currentPassword: string, newPassword: string, ): Promise<{ success: boolean; message?: string }> => { try { await apiClient.put(`${V1}/auth/change-password`, { current_password: currentPassword, new_password: newPassword, }); return { success: true }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur lors du changement de mot de passe", }; } }; export const getMyPenalties = async (): Promise => { try { const { data } = await apiClient.get(`${V1}/penalties`); return { success: true, data: data.data }; } catch { return { success: false, message: "Erreur récupération" }; } }; // ============================================ // 👤 PROFIL CLIENT // ============================================ export const getMyProfile = async (): Promise<{ success: boolean; client?: any; message?: string }> => { try { const { data } = await apiClient.get(`${V1}/profile`); return { success: true, client: data.client }; } catch { return { success: false, message: "Erreur récupération profil" }; } }; export const updateMyProfile = async (fields: { nom?: string; prenom?: string; telephone?: string }): Promise<{ success: boolean; message?: string; client?: any }> => { try { const { data } = await apiClient.put(`${V1}/profile/update`, fields); return { success: true, client: data.client, message: data.message }; } catch (error: any) { return { success: false, message: error.response?.data?.error || error.response?.data?.message || "Erreur mise à jour profil", }; } }; // ============================================ // ORDER DETAILS // ============================================ export const getOrderDetails = async (commandId: number) => { try { const { data } = await apiClient.get(`${V1}/commands/${commandId}`); return { success: true, order: data.command || data, message: "Commande récupérée", }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur de connexion", order: null, }; } }; export const getCommandItemsWithDetails = async (commandId: number) => { try { const { data } = await apiClient.get( `${V1}/commands/${commandId}/items`, ); return { success: true, data, message: "Items récupérés" }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur de connexion", data: null, }; } }; // ============================================ // UTILS // ============================================ export const formatOrderDate = (dateString: string): string => { try { const date = new Date(dateString); if (isNaN(date.getTime())) return ""; return date.toLocaleDateString("fr-FR", { day: "2-digit", month: "long", year: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return ""; } }; export const getOrderAge = (dateString: string): string => { try { const diffMs = Date.now() - new Date(dateString).getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays === 0) return "Aujourd'hui"; if (diffDays === 1) return "Hier"; if (diffDays < 7) return `Il y a ${diffDays} jours`; if (diffDays < 30) { const w = Math.floor(diffDays / 7); return `Il y a ${w} semaine${w > 1 ? "s" : ""}`; } const m = Math.floor(diffDays / 30); return `Il y a ${m} mois`; } catch { return "Date inconnue"; } }; export const formatPrice = (price: number): string => `${price.toFixed(2)} €`; // ============================================ // NOTIFICATIONS // ============================================ export interface ClientNotification { command_id: number; type: string; message: string; created_at: string; read: boolean; } export interface NotificationsResponse { success: boolean; notifications: ClientNotification[]; unread_count: number; total: number; message?: string; } export const getClientNotifications = async (): Promise => { try { const { data } = await apiClient.get(`${V1}/notifications`); return { success: true, notifications: data.notifications || [], unread_count: data.unread_count || 0, total: data.total || 0, }; } catch { return { success: false, notifications: [], unread_count: 0, total: 0, message: "Erreur récupération notifications", }; } }; export const markNotificationsRead = async (): Promise<{ success: boolean; marked_count?: number; }> => { try { const { data } = await apiClient.post(`${V1}/notifications/read`); return { success: true, marked_count: data.marked_count }; } catch { return { success: false }; } }; // ============================================ // PUBLIC SETTINGS // ============================================ export interface PublicSettings { penalties_enabled: boolean; show_amende_score: boolean; points_enabled: boolean; points_separated: boolean; referral_enabled: boolean; pool_names: string[]; } export const getPublicSettings = async (): Promise => { const defaults: PublicSettings = { penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: true, pool_names: ['Pool 1', 'Pool 2'] }; try { const { data } = await apiClient.get(`${V1}/app-settings`); return { penalties_enabled: data.penalties_enabled ?? true, show_amende_score: data.show_amende_score ?? true, points_enabled: data.points_enabled ?? true, points_separated: data.points_separated ?? true, referral_enabled: data.referral_enabled ?? true, pool_names: Array.isArray(data.pool_names) && data.pool_names.length > 0 ? data.pool_names : defaults.pool_names, }; } catch { return defaults; } }; export const registerPushToken = async (token: string): Promise => { try { await apiClient.post(`${V1}/push-token`, { token }); } catch { // silencieux } }; export const unregisterPushToken = async (): Promise => { try { await apiClient.delete(`${V1}/push-token`); } catch { // silencieux } }; export const calculateOrderTotal = (order: any): number => { if (typeof order.total === "number" && order.total > 0) return order.total; if (typeof order.total_prix === "number" && order.total_prix > 0) return order.total_prix; if (Array.isArray(order.items) && order.items.length > 0) { return order.items.reduce((sum: number, item: any) => { return ( sum + (item.prix || item.price || 0) * (item.quantite || item.quantity || 1) ); }, 0); } return 0; };