Files
projet_gestion_commande/mobile/src/api/api.ts
T
Xor290 22a8d5026c
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Backend - Build & Lint / build (push) Failing after 25m18s
Frontend Web - Build & Lint / build (push) Failing after 9m58s
chore: build
2026-08-06 12:06:05 +02:00

1088 lines
32 KiB
TypeScript

import apiClient from "./client";
import { API_BASE_URL } 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 = `${API_BASE_URL}/api/v1`;
export const getJwtUsername = async (): Promise<string | null> => {
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;
requires_2fa?: boolean;
session_token?: string;
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<AuthResponse> => {
try {
const { data } = await apiClient.post(`${V1}/auth/login`, {
username,
password,
});
if (data.requires_2fa) {
return {
success: false,
requires_2fa: true,
session_token: data.session_token,
};
}
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 verifyClient2FA = async (
sessionToken: string,
code: string,
): Promise<AuthResponse> => {
try {
const { data } = await apiClient.post(`${V1}/auth/2fa/verify`, {
session_token: sessionToken,
code,
});
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 ||
"Code invalide";
return { success: false, message: msg };
}
};
export const registerUser = async (
username: string,
password: string,
nom: string,
prenom: string,
telephone: string,
): Promise<AuthResponse> => {
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<void> => {
try {
await apiClient.post(`${V1}/auth/logout`);
} catch {
/* ignore */
}
};
export interface Category {
id: number;
name: string;
color: string;
is_coming_soon: boolean;
created_at: string;
}
export const getCategories = async (): Promise<Category[]> => {
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 };
}
};
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",
};
}
};
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,
payment_method?: string,
pay_currency?: string,
): Promise<CheckoutCartResponse> => {
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<string, unknown> = {
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;
if (payment_method) payload.payment_method = payment_method;
if (pay_currency) payload.pay_currency = pay_currency;
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,
payment_method: data.payment_method,
payment_status: data.payment_status,
pay_address: data.pay_address,
pay_amount: data.pay_amount,
pay_currency: data.pay_currency,
price_amount: data.price_amount,
price_currency: data.price_currency,
};
} catch (error: any) {
const data = error.response?.data;
if (data?.corrected_address) {
return {
success: false,
invalid_address: true,
suggested_address: data.corrected_address,
message: data.error || "Adresse non reconnue",
};
}
return {
success: false,
message: data?.error || "Erreur serveur",
};
}
};
export const getReferralBalance =
async (): Promise<ReferralBalanceResponse> => {
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<ConfirmReceptionResponse> => {
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",
};
}
};
export const getOrderTracking = async (
commandId: number,
): Promise<TrackingResponse> => {
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<ETAResponse> => {
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<HistoryResponse> => {
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",
};
}
};
export const cancelCommand = async (
commandId: number,
reason?: string,
force = false,
): Promise<CancelCommandResponse> => {
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<PenaltiesResponse> => {
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<NotificationsResponse> => {
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[];
crypto_payment_enabled: boolean;
crypto_only: boolean;
nowpayments_currencies: string[];
telegram_notifications_enabled: boolean;
two_fa_enabled: boolean;
contact_telegram: string;
client_color_primary: string;
client_color_secondary: string;
client_color_success: string;
client_color_danger: string;
client_color_warning: string;
}
export const getPublicSettings = async (): Promise<PublicSettings> => {
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"],
crypto_payment_enabled: false,
crypto_only: false,
nowpayments_currencies: [],
telegram_notifications_enabled: false,
two_fa_enabled: false,
contact_telegram: "",
client_color_primary: "#7c3aed",
client_color_secondary: "#22d3ee",
client_color_success: "#4ade80",
client_color_danger: "#ef4444",
client_color_warning: "#f59e0b",
};
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,
crypto_payment_enabled: data.crypto_payment_enabled ?? false,
crypto_only: data.crypto_only ?? false,
nowpayments_currencies: Array.isArray(data.nowpayments_currencies)
? data.nowpayments_currencies
: [],
telegram_notifications_enabled:
data.telegram_notifications_enabled ?? false,
two_fa_enabled: data.two_fa_enabled ?? false,
contact_telegram: data.contact_telegram || "",
client_color_primary: data.client_color_primary || "#7c3aed",
client_color_secondary: data.client_color_secondary || "#22d3ee",
client_color_success: data.client_color_success || "#4ade80",
client_color_danger: data.client_color_danger || "#ef4444",
client_color_warning: data.client_color_warning || "#f59e0b",
};
} catch {
return defaults;
}
};
export interface CryptoPaymentStatus {
command_id: number;
client_order_number?: number;
payment_status: string;
pay_address: string;
pay_amount: number;
pay_currency: string;
price_amount: number;
price_currency: string;
}
export const getCryptoPaymentStatus = async (
commandId: number,
): Promise<CryptoPaymentStatus | null> => {
try {
const { data } = await apiClient.get(
`${V1}/commands/${commandId}/payment-status`,
);
return data;
} catch {
return null;
}
};
// ============================================
// TELEGRAM
// ============================================
export const getTelegramStatus = async (): Promise<{
linked: boolean;
enabled: boolean;
}> => {
try {
const { data } = await apiClient.get(`${V1}/telegram/status`);
return data;
} catch {
return { linked: false, enabled: false };
}
};
export const generateTelegramLinkToken = async (): Promise<{
token?: string;
link_url?: string;
expires_in?: number;
error?: string;
}> => {
try {
const { data } = await apiClient.post(`${V1}/telegram/link-token`);
return data;
} catch (e: any) {
return { error: e?.response?.data?.error || "Erreur" };
}
};
export const unlinkTelegram = async (): Promise<void> => {
try {
await apiClient.delete(`${V1}/telegram/unlink`);
} catch {
// silencieux
}
};
export const get2FAStatus = async (): Promise<{
two_fa_enabled: boolean;
telegram_linked: boolean;
admin_2fa_enabled: boolean;
}> => {
try {
const { data } = await apiClient.get(`${V1}/two-fa/status`);
return data;
} catch {
return {
two_fa_enabled: false,
telegram_linked: false,
admin_2fa_enabled: false,
};
}
};
export const toggle2FA = async (
enabled: boolean,
): Promise<{ success: boolean; error?: string }> => {
try {
const { data } = await apiClient.post(`${V1}/two-fa/toggle`, {
enabled,
});
return { success: data.success ?? true };
} catch (e: any) {
return { success: false, error: e?.response?.data?.error || "Erreur" };
}
};
// ============================================
// 🏆 POINTS — RÉCOMPENSES
// ============================================
export type RewardCategoryConfig = {
category: string;
all_products: boolean;
product_ids: number[];
product_names: string[];
amount: number;
};
export type RewardItemConfig = {
product_id: number;
product_name: string;
quantity: number;
price: number;
};
export type PointsPoolInfo = {
key: string;
name: string;
points: number;
rewards_earned: number;
rewards_claimed: number;
rewards_available: number;
eligible_configs: RewardCategoryConfig[];
eligible_reward_items: RewardItemConfig[];
};
export type PointsRewardConfig = {
threshold: number;
type: string;
description: string;
reward_items: RewardItemConfig[];
};
export const getMyPointsRewards = async (): Promise<{
success: boolean;
enabled: boolean;
pools: PointsPoolInfo[];
reward: PointsRewardConfig | null;
}> => {
try {
const { data } = await apiClient.get(`${V1}/points/rewards`);
return {
success: true,
enabled: data.enabled ?? false,
pools: data.pools ?? [],
reward: data.reward ?? null,
};
} catch {
return { success: false, enabled: false, pools: [], reward: null };
}
};
export const claimMyReward = async (
poolKey: string,
productId?: number,
): Promise<{
success: boolean;
description?: string;
remaining_rewards?: number;
product_added?: boolean;
product_name?: string;
error?: string;
}> => {
try {
const body: { pool_key: string; product_id?: number } = {
pool_key: poolKey,
};
if (productId !== undefined) body.product_id = productId;
const { data } = await apiClient.post(`${V1}/points/claim`, body);
return {
success: true,
description: data.description,
remaining_rewards: data.remaining_rewards,
product_added: data.product_added,
product_name: data.product_name,
};
} catch (e: any) {
return { success: false, error: e?.response?.data?.error || "Erreur" };
}
};
export const submitLivreurRating = async (
orderId: number,
rating: number,
comment: string,
): Promise<{ success: boolean; error?: string }> => {
try {
await apiClient.post(`${V1}/orders/${orderId}/rate`, {
rating,
comment,
});
return { success: true };
} catch (e: any) {
return { success: false, error: e?.response?.data?.error || "Erreur" };
}
};
export const getOrderRatingStatus = async (
orderId: number,
): Promise<{ rated: boolean; rating?: number; comment?: string }> => {
try {
const { data } = await apiClient.get(`${V1}/orders/${orderId}/rating`);
return data;
} catch {
return { rated: false };
}
};
export const calculateOrderTotal = (order: any): number => {
if (typeof order.total_prix === "number" && order.total_prix > 0)
return order.total_prix;
if (typeof order.total === "number" && order.total > 0) return order.total;
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;
};