import { useState, useEffect } from 'react'; import { CreditCard, Check, Globe, Loader, ToggleLeft, ShieldAlert, Calendar, TrendingUp, TrendingDown, RefreshCcw } from 'lucide-react'; import { getProductList, getHostingServiceDetails } from '../../services/api'; const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || ''; export default function SubscriptionManager({ order, onClose, onRefresh, onAlert }) { const [loading, setLoading] = useState(false); const [cancelLoading, setCancelLoading] = useState(false); const [catalog, setCatalog] = useState([]); const [selectedPlan, setSelectedPlan] = useState(null); const [isCatalogLoading, setIsCatalogLoading] = useState(true); const [serviceDetails, setServiceDetails] = useState(null); const [isDetailsLoading, setIsDetailsLoading] = useState(true); const [billingPeriod, setBillingPeriod] = useState(order.period || '1M'); // 1. Récupération des détails techniques (Domaine/IP) useEffect(() => { const fetchDetails = async () => { setIsDetailsLoading(true); try { const data = await getHostingServiceDetails(order.id); setServiceDetails(data); } catch (err) { console.error("Erreur détails service:", err); } finally { setIsDetailsLoading(false); } }; fetchDetails(); }, [order.id]); const rawOrderTitle = order.title || ""; const titleParts = rawOrderTitle.split(' pour '); const currentPlanName = titleParts[0].trim(); const currentPlanId = order.product_id; const currentBillingPeriod = order.period; // 2. Extraction robuste du domaine let extractedDomain = "Aucun domaine lié"; if (serviceDetails?.domain || serviceDetails?.config?.domain) { extractedDomain = serviceDetails.domain || serviceDetails.config.domain; } const renderMarkdownFeatures = (text) => { if (!text) return Aucune spécification disponible.; return text.split('\n').map((line, index) => { const cleanLine = line.trim(); if (cleanLine.startsWith('* ') || cleanLine.startsWith('- ')) { return (
{parseBold(cleanLine)}
; }); }; const parseBold = (text) => text.split('**').map((part, i) => i % 2 === 1 ? {part} : part); const getProductCardMetrics = (pricing, period) => { if (!pricing || !pricing.recurrent) { return { displayPrice: "0.00", billingPrice: "0.00", oldDisplayPrice: "0.00", saving: 0, isAvailable: false }; } const recurrent = pricing.recurrent; const priceW = recurrent['1W']?.price ? parseFloat(recurrent['1W'].price) : 0; const priceM = recurrent['1M']?.price ? parseFloat(recurrent['1M'].price) : 0; const priceY = recurrent['1Y']?.price ? parseFloat(recurrent['1Y'].price) : 0; let billingPrice = 0, oldBillingPrice = 0, savingPercent = 0, isAvailable = true; let displayPrice = 0, oldDisplayPrice = 0; if (period === '1W') { billingPrice = priceW; oldBillingPrice = priceW; displayPrice = priceW * 4.333; oldDisplayPrice = displayPrice; if (!priceW) isAvailable = false; } else if (period === '1M') { billingPrice = priceM; oldBillingPrice = priceW > 0 ? priceW * 4.333 : priceM; displayPrice = priceM; oldDisplayPrice = oldBillingPrice; if (!priceM) isAvailable = false; } else if (period === '1Y') { billingPrice = priceY; oldBillingPrice = priceW > 0 ? priceW * 52 : (priceM > 0 ? priceM * 12 : priceY); displayPrice = priceY / 12; oldDisplayPrice = oldBillingPrice / 12; if (!priceY) isAvailable = false; } savingPercent = (oldBillingPrice > billingPrice && billingPrice > 0) ? Math.round(((oldBillingPrice - billingPrice) / oldBillingPrice) * 100) : 0; return { displayPrice: displayPrice.toFixed(2), billingPrice: billingPrice.toFixed(2), oldDisplayPrice: oldDisplayPrice.toFixed(2), saving: savingPercent, isAvailable }; }; const getPlanBadge = (index) => { if (index === 0) return { label: "ESSENTIEL", color: "text-gray-400 bg-gray-900 border-gray-700" }; if (index === 1) return { label: "PLUS POPULAIRE", color: "text-cyan-300 bg-cyan-950 border-cyan-800 shadow-[0_0_10px_rgba(6,182,212,0.3)]" }; return { label: "PRO", color: "text-amber-400 bg-amber-950 border-amber-800 shadow-[0_0_10px_rgba(245,158,11,0.2)]" }; }; useEffect(() => { const fetchCatalog = async () => { setIsCatalogLoading(true); try { const data = await getProductList(); const rawProducts = data.list || data.catalog || (Array.isArray(data) ? data : []); const webProducts = rawProducts.filter(p => p.product_category_id === 1); if (webProducts.length > 0) { setCatalog(webProducts); const current = webProducts.find(p => currentPlanName.toLowerCase() === p.title.toLowerCase()); if (current) setSelectedPlan(current); } else { console.warn("Aucun produit de type 'Web Service' n'a été trouvé."); setCatalog([]); } } catch (err) { console.error("Erreur API Catalogue:", err); } finally { setIsCatalogLoading(false); } }; fetchCatalog(); }, [currentPlanName]); const isNoChange = selectedPlan && (currentPlanId === selectedPlan.id) && (currentBillingPeriod === billingPeriod); // ========================================== // 🧠 LOGIQUE UPGRADE / DOWNGRADE & PRORATA // ========================================== let migrationLabel = "Valider la modification"; let buttonColor = "bg-cyan-500 hover:bg-cyan-400 shadow-[0_0_20px_rgba(6,182,212,0.2)]"; let ActionIcon = null; let finalInvoicePrice = 0; let isProrataApplied = false; let isRefund = false; if (selectedPlan && !isNoChange) { const currentPrice = parseFloat(order.price) || 0; let currentMonthly = currentPrice; if (currentBillingPeriod === '1W') currentMonthly = currentPrice * 4.333; if (currentBillingPeriod === '1M') currentMonthly = currentPrice; if (currentBillingPeriod === '1Y') currentMonthly = currentPrice / 12; const selectedMetrics = getProductCardMetrics(selectedPlan.pricing, billingPeriod); const selectedMonthly = parseFloat(selectedMetrics.displayPrice); const p1 = parseFloat(order.price) || 0; const p2 = parseFloat(selectedMetrics.billingPrice) || 0; let m = 30; if (currentBillingPeriod === '1Y') m = 365; if (currentBillingPeriod === '1M') m = 30; if (currentBillingPeriod === '1W') m = 7; const expiresAt = new Date(order.expires_at); const now = new Date(); let remainingDays = 0; if (!isNaN(expiresAt)) { remainingDays = Math.max(0, Math.ceil((expiresAt - now) / (1000 * 60 * 60 * 24))); } if (remainingDays > 0 && remainingDays <= m) { const n = m - remainingDays; // Jours écoulés finalInvoicePrice = p2 - p1 - (n * (p1 - p2) / m); isProrataApplied = true; } else { finalInvoicePrice = p2; } isRefund = finalInvoicePrice < -0.01; // --- 3. Style du bouton (Triggers : Upgrade / Downgrade / Cycle) --- // On récupère le forfait actuel depuis le catalogue pour comparer la "puissance" brute des forfaits const currentPlanFromCatalog = catalog.find(p => p.id === currentPlanId); // Pour définir la hiérarchie absolue, on compare les prix de base sur 1 Mois const baseCurrentPrice = currentPlanFromCatalog?.pricing?.recurrent?.['1M']?.price || 0; const baseSelectedPrice = selectedPlan?.pricing?.recurrent?.['1M']?.price || 0; const isSamePlan = currentPlanId === selectedPlan.id; const isSamePeriod = currentBillingPeriod === billingPeriod; if (isSamePlan && !isSamePeriod) { // Le pack est identique, seule la période change migrationLabel = "Changer de cycle"; buttonColor = "bg-blue-500 hover:bg-blue-400 text-black shadow-[0_0_20px_rgba(59,130,246,0.2)]"; ActionIcon =Abonnement Actuel
{currentPlanName}
Espace Domaine
{extractedDomain}
Prochain Renouvellement
{order.expires_at ? order.expires_at : "Date non définie"} | {order.price || '0.00'} {order.currency || '€'} / {order.period === '1W' ? 'semaine' : order.period === '1Y' ? 'an' : 'mois'}
Synchronisation des tarifs...
Au lieu de {metrics.oldDisplayPrice} € / mois
)}Pour ce cycle
{isRefund ? "Remboursement estimé" : finalInvoicePrice > 0.01 ? "Montant à régler aujourd'hui" : "Aucun frais immédiat"}
{isProrataApplied &&Calculé au prorata des jours restants
}