import { ref } from 'vue'; import { useWallet } from './useWallet'; import { refreshMyPerks } from './useMessages'; import { parseMeta, type ProductMeta } from './useMeta'; /** Marketplace client: catalogue, my entitlements, purchase flow. */ export interface Product { id: string; category: string; name: string; subtitle?: string | null; kind: string; basePrice: number; // centi-credits promoPrice?: number | null; badge?: string | null; stockLimit?: number | null; stockSold: number; sortOrder: number; metaJson?: string | null; } export interface Entitlement { id: string; ip: string; kind: string; active: boolean; expiresAt?: string | null; metaJson?: string | null; createdAt: string; } export interface PurchaseOptions { plan?: 'monthly' | 'annual'; durationDays?: number; format?: 'static' | 'gif'; url?: string; petDesign?: string; petChar?: string; petPosition?: 'left' | 'right' | 'both'; } const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000'; export function useShop() { const products = ref([]); const entitlements = ref([]); const loading = ref(false); const buying = ref(null); // productId currently being purchased const lastError = ref(null); const lastSuccess = ref(null); const { fetchWallet } = useWallet(); async function fetchProducts(): Promise { loading.value = true; try { const res = await fetch(`${API_URL}/api/shop/products`); if (res.ok) products.value = (await res.json()) as Product[]; } finally { loading.value = false; } } async function fetchMe(): Promise { try { const res = await fetch(`${API_URL}/api/shop/me`); if (res.ok) { const data = await res.json(); entitlements.value = data.entitlements ?? []; } } catch { /* ignore */ } } function owns(kind: string): boolean { return entitlements.value.some((e) => e.kind === kind && e.active); } function petCount(): number { return entitlements.value.filter((e) => e.kind === 'pet' && e.active).length; } function ownedPetChars(): string[] { return entitlements.value .filter((e) => e.kind === 'pet' && e.active) .map((e) => parseMeta(e.metaJson).char ?? '') .filter(Boolean); } async function purchase(productId: string, options: PurchaseOptions = {}): Promise { buying.value = productId; lastError.value = null; lastSuccess.value = null; try { const res = await fetch(`${API_URL}/api/shop/purchase`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId, options }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { lastError.value = data.error || 'Achat impossible'; return false; } lastSuccess.value = `Acheté : ${productId}`; // Refresh wallet + my entitlements + myPerks (WS also pushes wallet, this is belt-and-braces). await Promise.all([fetchWallet(), fetchMe(), fetchProducts(), refreshMyPerks()]); return true; } catch { lastError.value = 'Réseau indisponible'; return false; } finally { buying.value = null; } } return { products, entitlements, loading, buying, lastError, lastSuccess, fetchProducts, fetchMe, owns, petCount, ownedPetChars, purchase, }; }