chore: build
This commit is contained in:
+198
-23
@@ -1,9 +1,3 @@
|
||||
// ============================================
|
||||
// api/api.ts - VERSION CORRIGÉE
|
||||
// ============================================
|
||||
// ✅ AuthResponse inclut access_token
|
||||
// ✅ loginUser et registerUser retournent AuthResponse
|
||||
// ✅ sessionStorage (pas localStorage)
|
||||
const API_URL = "/api/v1";
|
||||
const BACKEND_URL = "";
|
||||
export function getMediaUrl(url: string): string {
|
||||
@@ -28,19 +22,15 @@ import type {
|
||||
CancelCommandResponse,
|
||||
PenaltiesResponse,
|
||||
} from "./api_types";
|
||||
// ============================================
|
||||
// 🔐 TYPES - AUTHRESPONSE COMPLETE
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* ✅ TYPE CORRECT - Inclut access_token!
|
||||
*/
|
||||
export interface AuthResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
access_token?: string; // ✅ CRITICAL!
|
||||
access_token?: string;
|
||||
token_type?: string;
|
||||
expires_in?: number;
|
||||
requires_2fa?: boolean;
|
||||
session_token?: string;
|
||||
user?: {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -53,17 +43,8 @@ export interface AuthResponse {
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 🔐 GESTION CENTRALISÉE DU JWT
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* ✅ Extraire username du JWT
|
||||
* Source de vérité UNIQUE pour le username
|
||||
*/
|
||||
export const extractUsernameFromToken = (): string | null => {
|
||||
try {
|
||||
// ✅ CRITICAL: sessionStorage (pas localStorage!)
|
||||
const token = sessionStorage.getItem("token");
|
||||
|
||||
if (!token) {
|
||||
@@ -210,6 +191,15 @@ export const loginUser = async (
|
||||
const data = await safeJson(response);
|
||||
console.log("📋 [LOGIN] Réponse:", data);
|
||||
|
||||
// 2FA requis — retourner sans token
|
||||
if (data.requires_2fa) {
|
||||
return {
|
||||
success: true,
|
||||
requires_2fa: true,
|
||||
session_token: data.session_token,
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Vérifier access_token
|
||||
if (!data.access_token) {
|
||||
console.error("❌ [LOGIN] Pas de access_token");
|
||||
@@ -728,6 +718,9 @@ export const createCheckout = async (checkoutData: CheckoutData) => {
|
||||
pay_currency: data.pay_currency as string | undefined,
|
||||
price_amount: data.price_amount as number | undefined,
|
||||
price_currency: data.price_currency as string | undefined,
|
||||
referral_used: data.referral_used as number | undefined,
|
||||
referral_balance: data.referral_balance as number | undefined,
|
||||
client_order_number: data.client_order_number as number | undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("❌ [CHECKOUT] Erreur:", error);
|
||||
@@ -804,8 +797,9 @@ export interface Product {
|
||||
category: string;
|
||||
unit?: string;
|
||||
stock: number;
|
||||
prices?: Array<{ quantity: number; price: number }>;
|
||||
prices?: Array<{ quantity: number; price: number; active_price?: boolean }>;
|
||||
media?: MediaItem[]; // ✅ CHANGÉ: string[] → MediaItem[]
|
||||
coming_soon?: boolean;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
@@ -1866,6 +1860,9 @@ export interface PublicSettings {
|
||||
crypto_payment_enabled: boolean;
|
||||
crypto_only: boolean;
|
||||
nowpayments_currencies: string[];
|
||||
shop_name: string;
|
||||
two_fa_enabled: boolean;
|
||||
contact_telegram: string;
|
||||
}
|
||||
|
||||
export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
@@ -1880,6 +1877,9 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
crypto_payment_enabled: false,
|
||||
crypto_only: false,
|
||||
nowpayments_currencies: [],
|
||||
shop_name: "Milieu-Nantais",
|
||||
two_fa_enabled: false,
|
||||
contact_telegram: "",
|
||||
};
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/app-settings`);
|
||||
@@ -1901,12 +1901,93 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
nowpayments_currencies: Array.isArray(data.nowpayments_currencies)
|
||||
? data.nowpayments_currencies
|
||||
: [],
|
||||
shop_name: data.shop_name || "Milieu-Nantais",
|
||||
two_fa_enabled: data.two_fa_enabled ?? false,
|
||||
contact_telegram: data.contact_telegram || "",
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
}
|
||||
};
|
||||
|
||||
export const verify2FA = async (
|
||||
sessionToken: string,
|
||||
code: string,
|
||||
): Promise<AuthResponse> => {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/2fa/verify`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ session_token: sessionToken, code }),
|
||||
});
|
||||
const data = await safeJson(response);
|
||||
if (!response.ok) {
|
||||
return { success: false, message: data.error || "Code invalide" };
|
||||
}
|
||||
sessionStorage.setItem("token", data.access_token);
|
||||
syncUsernameFromJWT();
|
||||
return {
|
||||
success: true,
|
||||
access_token: data.access_token,
|
||||
token_type: data.token_type,
|
||||
expires_in: data.expires_in,
|
||||
user: data.user,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : "Erreur",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const get2FAStatus = async (): Promise<{
|
||||
two_fa_enabled: boolean;
|
||||
telegram_linked: boolean;
|
||||
admin_2fa_enabled: boolean;
|
||||
}> => {
|
||||
const token = sessionStorage.getItem("token");
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/two-fa/status`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!response.ok)
|
||||
return {
|
||||
two_fa_enabled: false,
|
||||
telegram_linked: false,
|
||||
admin_2fa_enabled: false,
|
||||
};
|
||||
return await safeJson(response);
|
||||
} catch {
|
||||
return {
|
||||
two_fa_enabled: false,
|
||||
telegram_linked: false,
|
||||
admin_2fa_enabled: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const toggle2FA = async (
|
||||
enabled: boolean,
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
const token = sessionStorage.getItem("token");
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/two-fa/toggle`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ enabled }),
|
||||
});
|
||||
const data = await safeJson(response);
|
||||
if (!response.ok) return { success: false, error: data.error };
|
||||
return { success: true };
|
||||
} catch {
|
||||
return { success: false, error: "Erreur réseau" };
|
||||
}
|
||||
};
|
||||
|
||||
export interface CryptoPaymentStatus {
|
||||
command_id: number;
|
||||
client_order_number?: number;
|
||||
@@ -2058,3 +2139,97 @@ export const unlinkTelegram = async (): Promise<void> => {
|
||||
/* silencieux */
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// 🏆 POINTS — RÉCOMPENSES
|
||||
// ============================================
|
||||
|
||||
export type RewardCategoryConfig = {
|
||||
category: string;
|
||||
all_products: boolean;
|
||||
product_ids: number[];
|
||||
product_names: string[];
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type PointsPoolInfo = {
|
||||
key: string;
|
||||
name: string;
|
||||
points: number;
|
||||
rewards_earned: number;
|
||||
rewards_claimed: number;
|
||||
rewards_available: number;
|
||||
eligible_configs: RewardCategoryConfig[];
|
||||
};
|
||||
|
||||
export type RewardItemConfig = {
|
||||
product_id: number;
|
||||
product_name: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
};
|
||||
|
||||
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;
|
||||
}> => {
|
||||
const token = getAuthToken();
|
||||
if (!token) return { success: false, enabled: false, pools: [], reward: null };
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/points/rewards`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!response.ok) return { success: false, enabled: false, pools: [], reward: null };
|
||||
const data = await safeJson(response);
|
||||
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): Promise<{
|
||||
success: boolean;
|
||||
description?: string;
|
||||
remaining_rewards?: number;
|
||||
product_added?: boolean;
|
||||
product_name?: string;
|
||||
error?: string;
|
||||
}> => {
|
||||
const token = getAuthToken();
|
||||
if (!token) return { success: false, error: "Non authentifié" };
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/points/claim`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ pool_key: poolKey }),
|
||||
});
|
||||
const data = await safeJson(response);
|
||||
if (!response.ok) return { success: false, error: data.error || "Erreur" };
|
||||
return {
|
||||
success: true,
|
||||
description: data.description,
|
||||
remaining_rewards: data.remaining_rewards,
|
||||
product_added: data.product_added,
|
||||
product_name: data.product_name,
|
||||
};
|
||||
} catch {
|
||||
return { success: false, error: "Erreur de connexion" };
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user