1088 lines
32 KiB
TypeScript
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;
|
|
};
|