diff --git a/src/components/dashboard/SubscriptionManager.jsx b/src/components/dashboard/WebServiceSubscriptionManager.jsx
similarity index 99%
rename from src/components/dashboard/SubscriptionManager.jsx
rename to src/components/dashboard/WebServiceSubscriptionManager.jsx
index a7a9cd0..7707399 100644
--- a/src/components/dashboard/SubscriptionManager.jsx
+++ b/src/components/dashboard/WebServiceSubscriptionManager.jsx
@@ -1,9 +1,9 @@
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';
+import { getProductList, getServiceDetails } from '../../services/api';
const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || '';
-export default function SubscriptionManager({ order, onClose, onRefresh, onAlert }) {
+export default function WebServiceSubscriptionManager({ order, onClose, onRefresh, onAlert }) {
const [loading, setLoading] = useState(false);
const [cancelLoading, setCancelLoading] = useState(false);
const [catalog, setCatalog] = useState([]);
@@ -20,7 +20,7 @@ export default function SubscriptionManager({ order, onClose, onRefresh, onAlert
const fetchDetails = async () => {
setIsDetailsLoading(true);
try {
- const data = await getHostingServiceDetails(order.id);
+ const data = await getServiceDetails(order.id);
setServiceDetails(data);
} catch (err) {
console.error("Erreur détails service:", err);
diff --git a/src/pages/app/Dashboard.jsx b/src/pages/app/Dashboard.jsx
index b78f41b..58b2d24 100644
--- a/src/pages/app/Dashboard.jsx
+++ b/src/pages/app/Dashboard.jsx
@@ -1,11 +1,13 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { getClientOrders } from '../../services/api';
-import { AlertCircle, Loader } from 'lucide-react';
+import { AlertCircle, Loader, FileText, X } from 'lucide-react';
-// Importation des composants isolés
+// Importation des composants
import DashboardServiceCard from '../../components/dashboard/DashboardServiceCard';
import NewServiceCard from '../../components/dashboard/NewServiceCard';
+import NotificationModal from '../../components/ui/NotificationModal';
+import WebServiceSubscriptionManager from '../../components/dashboard/WebServiceSubscriptionManager';
export default function Dashboard() {
const navigate = useNavigate();
@@ -13,54 +15,82 @@ export default function Dashboard() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
- // Chargement des données à l'ouverture du Sas
- useEffect(() => {
- const fetchInventory = async () => {
- try {
- const data = await getClientOrders();
-
- if (data.list) {
- // LE FILTRE CHIRURGICAL PAR PREFIXE
- const filteredOrders = data.list.filter(order => {
- const title = (order.title || '').toLowerCase();
- const type = (order.type || '').toLowerCase();
+ const [activeSubscriptionModal, setActiveSubscriptionModal] = useState(null);
+ const [customAlert, setCustomAlert] = useState(null);
- const isGhostProduct =
- type === 'domain' ||
- title.startsWith('domain ') ||
- title.startsWith('domaine ') ||
- title.startsWith('enregistrement ');
-
- return !isGhostProduct;
- });
+ const triggerAlert = (title, message, type = "info") => setCustomAlert({ title, message, type });
- setOrders(filteredOrders);
- } else {
- setOrders([]);
- }
- } catch (err) {
- setError(err.message || "Impossible de récupérer la télémétrie des services.");
- } finally {
- setIsLoading(false);
+ const fetchInventory = async () => {
+ setIsLoading(true);
+ try {
+ const data = await getClientOrders();
+
+ if (data.list) {
+ // LE FILTRE CHIRURGICAL PAR PREFIXE
+ const filteredOrders = data.list.filter(order => {
+ const title = (order.title || '').toLowerCase();
+ const type = (order.type || '').toLowerCase();
+
+ const isGhostProduct =
+ type === 'domain' ||
+ title.startsWith('domain ') ||
+ title.startsWith('domaine ') ||
+ title.startsWith('enregistrement ');
+
+ return !isGhostProduct;
+ });
+
+ setOrders(filteredOrders);
+ } else {
+ setOrders([]);
}
- };
+ } catch (err) {
+ setError(err.message || "Impossible de récupérer la télémétrie des services.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ useEffect(() => {
fetchInventory();
}, []);
+ // 🌟 Plus besoin de charger les IPs, on ouvre juste la modale comptable !
+ const handleManageSubscription = (order) => {
+ // 1. Détection du type de service
+ const titleLower = (order.title || '').toLowerCase();
+ const isVPS = titleLower.includes('vps') || titleLower.includes('compute');
+ const isCloud = titleLower.includes('cloud') || titleLower.includes('nextcloud');
+ const isDB = titleLower.includes('db') || titleLower.includes('base') || titleLower.includes('sql');
+
+ // C'est un hébergement web s'il ne correspond à aucun des cas ci-dessus
+ const isWeb = !isVPS && !isCloud && !isDB;
+
+ // 2. Logique de blocage
+ if (isWeb) {
+ // On ouvre la modale comptable uniquement pour le WEB
+ setActiveSubscriptionModal(order);
+ } else {
+ // On affiche une notification pour les autres types d'instances
+ triggerAlert(
+ "Action non disponible",
+ "La modification autonome d'abonnement est actuellement exclusive aux Hébergements Web. Pour restructurer cette instance, veuillez contacter les ingénieurs via le Centre de Support.",
+ "info"
+ );
+ }
+ };
+
return (
-
-
+
TABLEAU DE BORD
- Aperçu de vos accréditations réseau et infrastructures.
+ Gestion financière et accréditations de vos infrastructures.
- {/* GESTION DES ERREURS & CHARGEMENT */}
{isLoading && (
- Synchronisation avec l'orchestrateur en cours...
+ Synchronisation avec le registre comptable...
)}
@@ -71,24 +101,44 @@ export default function Dashboard() {
)}
- {/* GRILLE DES SERVICES */}
{!isLoading && !error && (
-
- {/* Boucle sur les composants isolés */}
{orders.map((order) => (
- navigate(`/services/${order.id}`)}
+ handleManageSubscription(order)}
/>
))}
-
- {/* Le composant carte d'ajout */}
navigate('/store')} />
-
)}
+
+ {/* 🌟 LA MODALE DE GESTION D'ABONNEMENT */}
+ {activeSubscriptionModal && (
+
+
+
+
+
+ GESTION DE L'ABONNEMENT
+
+
+
+
+
setActiveSubscriptionModal(null)}
+ onRefresh={fetchInventory}
+ onAlert={triggerAlert}
+ />
+
+
+ )}
+
+
setCustomAlert(null)} />
);
}
\ No newline at end of file
diff --git a/src/pages/app/Services.jsx b/src/pages/app/Services.jsx
index 75ab49b..e11a169 100644
--- a/src/pages/app/Services.jsx
+++ b/src/pages/app/Services.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
-import { getMyServices, getHostingServiceDetails } from '../../services/api';
+import { getClientOrders, getOrderDetails } from '../../services/api';
import { useVPC } from '../../services/useVPC';
import { Server, Globe, Folder, Trash2, Loader, Plus, AlertCircle } from 'lucide-react';
@@ -25,12 +25,12 @@ export default function Services() {
const fetchServices = useCallback(async () => {
try {
- const data = await getMyServices();
+ const data = await getClientOrders();
if (data.list && data.list.length > 0) {
const detailedServices = await Promise.all(data.list.map(async (order) => {
let hDetails = null;
if (order.status === 'active' && (order.plugin === 'hosting' || (order.title || '').toLowerCase().includes('vps'))) {
- try { hDetails = await getHostingServiceDetails(order.id); } catch (e) {}
+ try { hDetails = await getOrderDetails(order.id); } catch (e) {}
}
return { ...order, hostingDetails: hDetails };
}));
@@ -65,7 +65,7 @@ export default function Services() {
} else if (isDB) {
window.open('https://pma.gise.be/', '_blank');
} else if (isVPS || isWeb) {
- const freshDetails = await getHostingServiceDetails(service.id);
+ const freshDetails = await getOrderDetails(service.id);
setActiveServiceModal({ type: isVPS ? 'vps' : 'web', data: service, details: freshDetails });
}
} catch (err) { triggerAlert("Erreur", err.message, "error"); }
diff --git a/src/services/api.js b/src/services/api.js
index d546f75..07554f9 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -1,7 +1,7 @@
const BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
const CUSTOM_API_BASE_URL = import.meta.env.VITE_CUSTOM_API_BASE_URL || '';
-// Le moteur de requête unifié et intelligent
+// Le moteur de requête
const apiCall = async (endpoint, param2 = 'GET', param3 = null) => {
let method = 'GET';
let body = null;
@@ -64,24 +64,6 @@ const apiCall = async (endpoint, param2 = 'GET', param3 = null) => {
}
};
-// ==========================================
-// ROUTES FOSSBILLING NATIVES (BACKTICKS INTÉGRÉS)
-// ==========================================
-
-export const loginClient = (email, password) =>
- apiCall(`${BASE_URL}/api/guest/client/login`, { email, password });
-
-// Récupère la liste des services/commandes du client
-export const getClientOrders = () =>
- apiCall(`${BASE_URL}/api/client/order/get_list`);
-
-// Récupère les détails techniques du service rattaché à une commande
-export const getOrderService = (order_id) =>
- apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
-
-// Récupère le catalogue public des produits FOSSBilling
-export const getProductList = () =>
- apiCall(`${BASE_URL}/api/guest/product/get_list`);
// Vide le panier (Action PUBLIQUE : on passe par l'API Guest)
export const resetCart = async () => {
@@ -100,22 +82,6 @@ export const resetCart = async () => {
}
};
-// Ajoute un produit au panier avec ses options étalées à la racine
-export const addToCart = (productId, period, additionalData = {}) =>
- apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', {
- id: productId,
- period: period,
- ...additionalData // Les 3 petits points "étalent" le contenu de l'objet
- });
-
-// Valide le panier (Action PRIVÉE : on reste sur l'API Client pour générer la facture)
-export const checkoutCart = () =>
- apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST');
-
-// Récupère les informations du client connecté
-export const getClientProfile = () =>
- apiCall(`${BASE_URL}/api/client/profile/get`, 'GET');
-
// ==========================================
// ROUTES PERSONNALISÉES (CUSTOM API)
// ==========================================
@@ -144,18 +110,6 @@ export const registerUnifiedClient = async (email, username, password, firstName
}
}
-// Récupère la liste de toutes les commandes actives du client
-export const getMyServices = () =>
- apiCall(`${BASE_URL}/api/client/order/get_list`, 'GET');
-
-// Récupère les détails secrets d'un service (dont le mot de passe HestiaCP/VPS)
-export const getServiceDetails = (orderId) =>
- apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId });
-
-// Récupère les secrets spécifiques du service physique attaché à une commande
-export const getHostingServiceDetails = (orderId) =>
- apiCall(`${BASE_URL}/api/client/order/service`, 'POST', { id: orderId });
-
// Force la réinitialisation du mot de passe sur le serveur distant (HestiaCP)
export const resetHostingPassword = (orderId, newPassword) =>
apiCall(`${BASE_URL}/api/client/servicehosting/change_password`, 'POST', {
@@ -191,33 +145,169 @@ export const launchSSOGateway = (username, password) => {
document.body.removeChild(form);
};
+// #######################################################################################
+
// ==========================================
-// ROUTES SUPPORT FOSSBILLING (NATIVES)
+// GESTION DES ACCES
// ==========================================
-// Récupère la liste de tous les tickets du client
-export const getClientTickets = () =>
- apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET');
+// Connexion
+export const loginClient = (email, password) =>
+ apiCall(`${BASE_URL}/api/guest/client/login`, { email, password });
-// Récupère le contenu et les messages d'un ticket spécifique
-export const getTicketDetails = (ticketId) =>
- apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId });
+// Deconnexion
+export const logoutClient = () =>
+ apiCall(`${BASE_URL}/api/client/profile/logout`);
-// Crée un nouveau ticket de support
+
+// ==========================================
+// GESTION DU CLIENT
+// ==========================================
+
+// Recuperer les informations du client
+export const getClientProfile = () =>
+ apiCall(`${BASE_URL}/api/client/profile/get`, 'GET');
+
+// Update les informations du client
+export const updateClientProfile = (email, last_name, aid, gender, country, city, birthday, company, company_vat, company_number, type, address_1, address_2, postcode, state, phone, phone_cc, document_type, document_nr, notes, lang, custom_1, custom_2, custom_3, custom_4, custom_5, custom_6, custom_7, custom_8, custom_9, custom_10) =>
+ apiCall(`${BASE_URL}/api/client/profile/update`, {
+ email: email,
+ last_name: last_name,
+ aid: aid,
+ gender: gender,
+ country: country,
+ city: city,
+ birthday: birthday,
+ company: company,
+ company_vat: company_vat,
+ company_number: company_number,
+ type: type,
+ address_1: address_1,
+ address_2: address_2,
+ postcode: postcode,
+ state: state,
+ phone: phone,
+ phone_cc: phone_cc,
+ document_type: document_type,
+ document_nr: document_nr,
+ notes: notes,
+ lang: lang,
+ custom_1: custom_1,
+ custom_2: custom_2,
+ custom_3: custom_3,
+ custom_4: custom_4,
+ custom_5: custom_5,
+ custom_6: custom_6,
+ custom_7: custom_7,
+ custom_8: custom_8,
+ custom_9: custom_9,
+ custom_10: custom_10
+
+ });
+
+// Changer le mot de passe
+export const changeClientPassword = (current_password, new_password, confirm_password) =>
+ apiCall(`${BASE_URL}/api/client/profile/change_password`, {current_password:current_password, new_password:new_password, confirm_password:confirm_password});
+
+
+
+// ==========================================
+// GESTION DES ABONNEMENTS
+// ==========================================
+
+// Souscription
+
+// Resiliation
+
+// Suppression
+export const deleteWrongOrder = (order_id) =>
+ apiCall(`${BASE_URL}/api/client/order/delete`, { id: order_id });
+
+// Promotion (Surclassement)
+// Lister
+export const getUpgrades = (order_id) =>
+ apiCall(`${BASE_URL}/api/client/order/upgradables`, { id: order_id });
+
+// Demotion (Declassement)
+
+// Lister les abonnements du client
+export const getClientOrders = () =>
+ apiCall(`${BASE_URL}/api/client/order/get_list`);
+
+// Lister les produits disponibles
+export const getProductList = () =>
+ apiCall(`${BASE_URL}/api/guest/product/get_list`);
+
+// Recuperer les details d'un abonnement
+export const getOrderDetails = (order_id) =>
+ apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
+
+// Recuperer les details secrets d'un abonnement
+export const getServiceDetails = (orderId) =>
+ apiCall(`${BASE_URL}/api/client/order/get`, 'POST', { id: orderId });
+
+
+// ==========================================
+// GESTION DU PANIER
+// ==========================================
+
+// Ajouter au panier
+export const addToCart = (productId, period, additionalData = {}) =>
+ apiCall(`${BASE_URL}/api/guest/cart/add_item`, 'POST', {
+ id: productId,
+ period: period,
+ ...additionalData // Les 3 petits points "étalent" le contenu de l'objet
+ });
+
+// Checkout
+export const checkoutCart = () =>
+ apiCall(`${BASE_URL}/api/client/cart/checkout`, 'POST');
+
+
+// ==========================================
+// GESTION DES FACTURES
+// ==========================================
+
+// Creation
+// Suppression
+// Modification
+// Lister
+export const getInvoiceList = () =>
+ apiCall(`${BASE_URL}/api/client/invoice/get_list`, 'GET');
+
+// Lire
+export const getInvoiceDetails = (InvoiceHash) =>
+ apiCall(`${BASE_URL}/api/client/invoice/get`, 'GET');
+
+
+// ==========================================
+// GESTION DES TICKETS
+// ==========================================
+
+// Creation
export const createTicket = (subject, message, helpdesk_id) =>
apiCall(`${BASE_URL}/api/client/support/ticket_create`, 'POST', {
support_helpdesk_id: helpdesk_id,
subject: subject,
content: message
});
+// Suppression
-// Répond à un ticket existant
+// Lister les tickets
+export const getClientTickets = () =>
+ apiCall(`${BASE_URL}/api/client/support/ticket_get_list`, 'GET');
+
+// Lister les helpdesks
+export const getHelpdesks = () =>
+ apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET');
+
+// Lire
+export const getTicketDetails = (ticketId) =>
+ apiCall(`${BASE_URL}/api/client/support/ticket_get`, 'POST', { id: ticketId });
+
+// Repondre
export const replyTicket = (ticketId, message) =>
apiCall(`${BASE_URL}/api/client/support/ticket_reply`, 'POST', {
id: ticketId,
content: message
- });
-
-// Récupère la liste dynamique des départements (Helpdesks) configurés sur FOSSBilling
-export const getHelpdesks = () =>
- apiCall(`${BASE_URL}/api/client/support/helpdesk_get_pairs`, 'GET');
\ No newline at end of file
+ });
\ No newline at end of file