diff --git a/ansible/playbook-docker.yml b/ansible/playbook-docker.yml index 081b3d1c..e47faeb0 100644 --- a/ansible/playbook-docker.yml +++ b/ansible/playbook-docker.yml @@ -196,33 +196,3 @@ group: "{{ docker_group }}" mode: "0640" ignore_errors: yes - - - name: Build Docker project - ansible.builtin.command: - cmd: docker compose -f docker-compose-prod.yml build - chdir: "{{ docker_dir }}/docker" - become: yes - become_user: "{{ docker_user }}" - tags: build - - - name: Start Docker project - ansible.builtin.command: - cmd: docker compose -f docker-compose-prod.yml up -d - chdir: "{{ docker_dir }}/docker" - become: yes - become_user: "{{ docker_user }}" - tags: start - - - name: Show Docker containers - ansible.builtin.command: - cmd: docker compose ps - chdir: "{{ docker_dir }}/docker" - become: yes - become_user: "{{ docker_user }}" - register: docker_ps - tags: start - - - name: Display Docker containers - debug: - var: docker_ps.stdout_lines - tags: start diff --git a/docker/frontend/custom-rules.conf b/docker/frontend/custom-rules.conf index 1700913c..c9ffc63d 100644 --- a/docker/frontend/custom-rules.conf +++ b/docker/frontend/custom-rules.conf @@ -1,3 +1,11 @@ +SecRuleRemoveById 932235 +SecRuleRemoveById 911100 + +SecRule REQUEST_URI "@streq /api/v2/admin/protected/products" \ + "id:399002,phase:2,nolog,pass,\ + ctl:ruleRemoveById=920120,\ + ctl:ruleRemoveById=920121" + SecRule IP:BANNED "@eq 1" \ "id:100000,phase:1,deny,status:403,log,\ msg:'IP is banned'" diff --git a/frontend-prep/src/api/api.ts b/frontend-prep/src/api/api.ts index 964ef90f..3681e2ca 100644 --- a/frontend-prep/src/api/api.ts +++ b/frontend-prep/src/api/api.ts @@ -5,7 +5,7 @@ // ✅ loginUser et registerUser retournent AuthResponse // ✅ sessionStorage (pas localStorage) -const API_URL = "http://localhost:8080/api/v1"; +const API_URL = "/api/v1"; import type { ConfirmReceptionResponse, CheckoutCartResponse, diff --git a/frontend-prep/src/api/api_admin.ts b/frontend-prep/src/api/api_admin.ts index c9058a56..a30bd2f2 100644 --- a/frontend-prep/src/api/api_admin.ts +++ b/frontend-prep/src/api/api_admin.ts @@ -15,7 +15,7 @@ import type { DeliveryPersonDetails, DeliveryPersonStats, } from "./api_admin_types"; -const API_URL = "http://localhost:8080/api/v2"; +const API_URL = "/api/v2"; // ============================================ // 🔐 TYPES - ADMIN @@ -825,59 +825,6 @@ export const getCommandByID = async (commandId: number) => { } }; -/** - * ✅ Récupérer les items d'une commande - */ -export const getCommandItems = async (commandId: number) => { - const token = sessionStorage.getItem("admin_token"); - - if (!token) { - throw new Error("Token admin non trouvé"); - } - - try { - console.log("🔍 [GET_COMMAND_ITEMS] Appel:", commandId); - - // Utiliser l'endpoint cabine qui est accessible avec le token admin - const response = await fetch( - `http://localhost:8080/api/v1/cabine/commands/${commandId}/items`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }, - ); - - const data = await response.json(); - - if (!response.ok) { - console.error("❌ [GET_COMMAND_ITEMS] Erreur API:", data); - return { - success: false, - items: [], - }; - } - - console.log("✅ [GET_COMMAND_ITEMS] Réponse:", data); - - return { - success: true, - items: data.items || [], - count: data.count || 0, - command_info: data.command_info, - client_info: data.client_info, - }; - } catch (error) { - console.error("❌ [GET_COMMAND_ITEMS] Erreur fetch:", error); - return { - success: false, - items: [], - }; - } -}; - /** * ✅ Mettre à jour le statut d'une commande */ @@ -2653,7 +2600,6 @@ export default { // Commands getAllCommands, getCommandByID, - getCommandItems, getCommandCount, getCommandCountCompleted, getCommandCountInRoute, diff --git a/frontend-prep/src/api/api_cabine.ts b/frontend-prep/src/api/api_cabine.ts index dec754b5..4b97222b 100644 --- a/frontend-prep/src/api/api_cabine.ts +++ b/frontend-prep/src/api/api_cabine.ts @@ -5,7 +5,7 @@ // ✅ Utilise /api/v1/cabine/* endpoints uniquement // ✅ sessionStorage pour la persistance -const API_URL = "http://localhost:8080/api/v1/cabine"; +const API_URL = "/api/v1/cabine"; import type { DeliveryPerson, DeliveryPersonsStats, diff --git a/frontend-prep/src/api/api_delivery.ts b/frontend-prep/src/api/api_delivery.ts index c1eba814..2f233d2f 100644 --- a/frontend-prep/src/api/api_delivery.ts +++ b/frontend-prep/src/api/api_delivery.ts @@ -4,7 +4,7 @@ // ✅ Fonctions helper pour le dashboard livreur // ✅ Utilise /api/v1/livreur/* endpoints -const API_URL = "http://localhost:8080/api/v1/livreur"; +const API_URL = "/api/v1/livreur"; // ============================================ // 🔐 TYPES - LIVREUR diff --git a/frontend-prep/src/pages/AdminAlerts/AdminAlerts.tsx b/frontend-prep/src/pages/AdminAlerts/AdminAlerts.tsx index fbfdc8f6..f6c4229c 100644 --- a/frontend-prep/src/pages/AdminAlerts/AdminAlerts.tsx +++ b/frontend-prep/src/pages/AdminAlerts/AdminAlerts.tsx @@ -17,6 +17,7 @@ import "./AdminAlerts.css"; import Sidebar from "../../components/Sidebar"; import { isAdminAuthenticated, deleteAlertAdmin } from "../../api/api_admin"; import { getAllAlerts, getAlertDetails } from "../../api/api_cabine"; +import Toast from "../../components/Toast"; interface Alert { id: number; @@ -32,6 +33,12 @@ interface AlertStats { resolved: number; } +interface ToastState { + show: boolean; + message: string; + type: "success" | "error" | "warning" | "info"; +} + function AdminAlerts() { const navigate = useNavigate(); @@ -54,6 +61,26 @@ function AdminAlerts() { const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [alertToDelete, setAlertToDelete] = useState(null); + // État pour le Toast + const [toast, setToast] = useState({ + show: false, + message: "", + type: "success", + }); + + // Fonction pour afficher un toast + const showToast = ( + message: string, + type: "success" | "error" | "warning" | "info" = "success", + ) => { + setToast({ show: true, message, type }); + }; + + // Fonction pour fermer le toast + const handleCloseToast = () => { + setToast({ ...toast, show: false }); + }; + // ============================================ // 🔐 VÉRIFICATION AUTHENTIFICATION // ============================================ @@ -249,11 +276,14 @@ function AdminAlerts() { setSelectedAlert(result.alert as Alert); setShowDetailsModal(true); } else { - alert("Impossible de récupérer les détails"); + showToast( + "Impossible de récupérer les détails de l'alerte", + "error", + ); } } catch (error) { console.error("❌ Erreur récupération détails:", error); - alert("Erreur lors de la récupération des détails"); + showToast("Erreur lors de la récupération des détails", "error"); } }; @@ -281,15 +311,21 @@ function AdminAlerts() { setShowDeleteConfirm(false); setAlertToDelete(null); - // Message de succès - alert("Alerte supprimée avec succès"); + // Afficher le toast de succès + showToast( + `Alerte #${alertToDelete} supprimée avec succès`, + "success", + ); } else { console.error("❌ [DELETE_ALERT] Erreur:", result.error); - alert(result.error || "Erreur lors de la suppression"); + showToast( + result.error || "Erreur lors de la suppression", + "error", + ); } } catch (error) { console.error("❌ [DELETE_ALERT] Erreur:", error); - alert("Erreur lors de la suppression de l'alerte"); + showToast("Erreur lors de la suppression de l'alerte", "error"); } }; @@ -673,6 +709,16 @@ function AdminAlerts() { )} + + {/* Toast Notifications */} + {toast.show && ( + + )} ); diff --git a/frontend-prep/src/pages/AdminOrders/AdminOrders.tsx b/frontend-prep/src/pages/AdminOrders/AdminOrders.tsx index 22abeca9..2654c0b9 100644 --- a/frontend-prep/src/pages/AdminOrders/AdminOrders.tsx +++ b/frontend-prep/src/pages/AdminOrders/AdminOrders.tsx @@ -20,11 +20,10 @@ import AdminLayout from "../../components/AdminLayout"; import "./AdminOrders.css"; import { getAllCommands, - getCommandItems, getDeliverymanLocationForCommand, isAdminAuthenticated, } from "../../api/api_admin"; - +import { getCommandItems } from "../../api/api_cabine"; import type { DeliverymanLocationResponse } from "../../api/api_admin_types"; // Interface correspondant au backend diff --git a/frontend-prep/src/pages/CabineOrders/CabineOrders.tsx b/frontend-prep/src/pages/CabineOrders/CabineOrders.tsx index a134b7f7..b0c6eea0 100644 --- a/frontend-prep/src/pages/CabineOrders/CabineOrders.tsx +++ b/frontend-prep/src/pages/CabineOrders/CabineOrders.tsx @@ -24,13 +24,13 @@ import SidebarCabine from "../../components/SidebarCabine"; import "./CabineOrders.css"; import { getAllCommands, - getCommandItems, getDeliverymanLocationForCommand, } from "../../api/api_admin"; import { deleteCommand, - isCabineAuthenticated, // ⭐ AJOUT + isCabineAuthenticated, + getCommandItems, } from "../../api/api_cabine"; import type { DeliverymanLocationResponse } from "../../api/api_admin_types"; diff --git a/frontend-prep/src/pages/CabineUserManager/UserManagement.css b/frontend-prep/src/pages/CabineUserManager/UserManagement.css index 7f9962a2..6acac431 100644 --- a/frontend-prep/src/pages/CabineUserManager/UserManagement.css +++ b/frontend-prep/src/pages/CabineUserManager/UserManagement.css @@ -3,19 +3,19 @@ /* ============================================ */ .users-management-container { - width: 100%; - min-height: 100vh; - padding: clamp(1rem, 3vw, 2rem); - max-width: 1600px; - margin: 0 auto; - background: #0a0a0a; - transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1); + width: 100%; + min-height: 100vh; + padding: clamp(1rem, 3vw, 2rem); + max-width: 1600px; + margin: 0 auto; + background: #0a0a0a; + transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @media (min-width: 769px) { - .users-management-container { - padding-top: 1rem; - } + .users-management-container { + padding-top: 1rem; + } } /* ============================================ */ @@ -23,88 +23,100 @@ /* ============================================ */ .filters-section { - display: flex; - gap: 1rem; - margin-bottom: clamp(2rem, 5vw, 3rem); - flex-wrap: wrap; + display: flex; + gap: 1rem; + margin-bottom: clamp(2rem, 5vw, 3rem); + flex-wrap: wrap; } .search-bar { - flex: 1; - min-width: 280px; - display: flex; - align-items: center; - gap: 1rem; - padding: 1rem 1.5rem; - background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 12px; - backdrop-filter: blur(8px); - transition: all 0.3s ease; + flex: 1; + min-width: 280px; + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem 1.5rem; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.01) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 12px; + backdrop-filter: blur(8px); + transition: all 0.3s ease; } .search-bar:focus-within { - border-color: rgba(59, 130, 246, 0.3); - box-shadow: 0 0 20px rgba(59, 130, 246, 0.2); + border-color: rgba(59, 130, 246, 0.3); + box-shadow: 0 0 20px rgba(59, 130, 246, 0.2); } .search-bar svg { - color: #3b82f6; - flex-shrink: 0; + color: #3b82f6; + flex-shrink: 0; } .search-bar input { - flex: 1; - background: transparent; - border: none; - outline: none; - color: white; - font-size: 1rem; - font-weight: 500; + flex: 1; + background: transparent; + border: none; + outline: none; + color: white; + font-size: 1rem; + font-weight: 500; } .search-bar input::placeholder { - color: #888; + color: #888; } .role-filters { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; + display: flex; + gap: 0.75rem; + flex-wrap: wrap; } .filter-btn { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 10px; - color: #888; - font-weight: 600; - font-size: 0.95rem; - cursor: pointer; - transition: all 0.3s ease; - white-space: nowrap; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.01) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + color: #888; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; } .filter-btn:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%); - border-color: rgba(59, 130, 246, 0.3); - color: white; - transform: translateY(-2px); + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.05) 0%, + rgba(255, 255, 255, 0.02) 100% + ); + border-color: rgba(59, 130, 246, 0.3); + color: white; + transform: translateY(-2px); } .filter-btn.active { - background: #5b21b6; - border-color: #4c1d95; - color: white; - box-shadow: 0 4px 16px #7c3aed; + background: #5b21b6; + border-color: #4c1d95; + color: white; + box-shadow: 0 4px 16px #7c3aed; } .filter-btn svg { - flex-shrink: 0; + flex-shrink: 0; } /* ============================================ */ @@ -112,27 +124,27 @@ /* ============================================ */ .users-section { - margin-bottom: clamp(2rem, 5vw, 3rem); + margin-bottom: clamp(2rem, 5vw, 3rem); } .users-section h2 { - color: white; - font-size: clamp(1.5rem, 4vw, 2rem); - margin: 0 0 clamp(1rem, 3vw, 1.5rem) 0; - font-weight: bold; - display: flex; - align-items: center; - gap: 0.75rem; + color: white; + font-size: clamp(1.5rem, 4vw, 2rem); + margin: 0 0 clamp(1rem, 3vw, 1.5rem) 0; + font-weight: bold; + display: flex; + align-items: center; + gap: 0.75rem; } .users-section h2 svg { - color: #3b82f6; + color: #3b82f6; } .users-list { - display: flex; - flex-direction: column; - gap: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; } /* ============================================ */ @@ -140,124 +152,141 @@ /* ============================================ */ .user-card { - position: relative; - background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 16px; - padding: 1.5rem; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - cursor: pointer; - overflow: hidden; - backdrop-filter: blur(8px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + position: relative; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.01) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 16px; + padding: 1.5rem; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + overflow: hidden; + backdrop-filter: blur(8px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); } .user-card::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.1), transparent); - transition: left 0.6s cubic-bezier(0.4, 0, 0.2, 1); - pointer-events: none; + content: ""; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(59, 130, 246, 0.1), + transparent + ); + transition: left 0.6s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; } .user-card:hover { - background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%); - border-color: rgba(59, 130, 246, 0.3); - box-shadow: 0 12px 32px rgba(59, 130, 246, 0.2); - transform: translateY(-2px); + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.05) 0%, + rgba(255, 255, 255, 0.02) 100% + ); + border-color: rgba(59, 130, 246, 0.3); + box-shadow: 0 12px 32px rgba(59, 130, 246, 0.2); + transform: translateY(-2px); } .user-card:hover::before { - left: 100%; + left: 100%; } .user-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; - gap: 1rem; - flex-wrap: wrap; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + gap: 1rem; + flex-wrap: wrap; } .user-info-main { - display: flex; - align-items: center; - gap: 1rem; + display: flex; + align-items: center; + gap: 1rem; } .user-avatar { - width: 56px; - height: 56px; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(37, 99, 235, 0.1)); - color: #3b82f6; - box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3); - transition: transform 0.3s ease; + width: 56px; + height: 56px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient( + 135deg, + rgba(59, 130, 246, 0.2), + rgba(37, 99, 235, 0.1) + ); + color: #3b82f6; + box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3); + transition: transform 0.3s ease; } .user-card:hover .user-avatar { - transform: scale(1.1) rotate(5deg); + transform: scale(1.1) rotate(5deg); } .user-details { - display: flex; - flex-direction: column; - gap: 0.25rem; + display: flex; + flex-direction: column; + gap: 0.25rem; } .user-username { - color: white; - font-size: 1.1rem; - font-weight: 700; + color: white; + font-size: 1.1rem; + font-weight: 700; } .user-id { - color: #888; - font-size: 0.85rem; - font-family: monospace; + color: #888; + font-size: 0.85rem; + font-family: monospace; } .user-role-badge { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - border-radius: 8px; - font-size: 0.9rem; - font-weight: 600; - white-space: nowrap; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + border-radius: 8px; + font-size: 0.9rem; + font-weight: 600; + white-space: nowrap; } .user-role-badge.red { - background: rgba(239, 68, 68, 0.15); - color: #ef4444; - border: 1px solid rgba(239, 68, 68, 0.3); + background: rgba(239, 68, 68, 0.15); + color: #ef4444; + border: 1px solid rgba(239, 68, 68, 0.3); } .user-role-badge.purple { - background: rgba(124, 58, 237, 0.15); - color: #7c3aed; - border: 1px solid rgba(124, 58, 237, 0.3); + background: rgba(124, 58, 237, 0.15); + color: #7c3aed; + border: 1px solid rgba(124, 58, 237, 0.3); } .user-role-badge.blue { - background: rgba(59, 130, 246, 0.15); - color: #3b82f6; - border: 1px solid rgba(59, 130, 246, 0.3); + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; + border: 1px solid rgba(59, 130, 246, 0.3); } .user-role-badge.gray { - background: rgba(156, 163, 175, 0.15); - color: #9ca3af; - border: 1px solid rgba(156, 163, 175, 0.3); + background: rgba(156, 163, 175, 0.15); + color: #9ca3af; + border: 1px solid rgba(156, 163, 175, 0.3); } /* ============================================ */ @@ -265,50 +294,50 @@ /* ============================================ */ .user-actions { - display: flex; - gap: 0.5rem; - flex-wrap: wrap; + display: flex; + gap: 0.5rem; + flex-wrap: wrap; } .btn-edit, .btn-delete { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - border: none; - border-radius: 10px; - font-weight: 600; - font-size: 0.95rem; - cursor: pointer; - transition: all 0.3s ease; - min-width: fit-content; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 10px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.3s ease; + min-width: fit-content; } .btn-edit { - background: linear-gradient(135deg, #3b82f6, #2563eb); - color: white; - box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3); + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: white; + box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3); } .btn-edit:hover { - background: linear-gradient(135deg, #2563eb, #1d4ed8); - box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4); - transform: translateY(-2px); + background: linear-gradient(135deg, #2563eb, #1d4ed8); + box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4); + transform: translateY(-2px); } .btn-delete { - background: linear-gradient(135deg, #ef4444, #dc2626); - color: white; - box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3); + background: linear-gradient(135deg, #ef4444, #dc2626); + color: white; + box-shadow: 0 4px 16px rgba(239, 68, 68, 0.3); } .btn-delete:hover { - background: linear-gradient(135deg, #dc2626, #b91c1c); - box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4); - transform: translateY(-2px); + background: linear-gradient(135deg, #dc2626, #b91c1c); + box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4); + transform: translateY(-2px); } /* ============================================ */ @@ -316,69 +345,79 @@ /* ============================================ */ .edit-modal-content { - background: linear-gradient(135deg, rgba(20, 20, 20, 0.98) 0%, rgba(15, 15, 15, 0.95) 100%); - border: 1px solid rgba(59, 130, 246, 0.3); - border-radius: 24px; - width: 100%; - max-width: 500px; - max-height: 90vh; - overflow-y: auto; - box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(59, 130, 246, 0.2); - animation: slideUp 0.3s ease; + background: linear-gradient( + 135deg, + rgba(20, 20, 20, 0.98) 0%, + rgba(15, 15, 15, 0.95) 100% + ); + border: 1px solid rgba(59, 130, 246, 0.3); + border-radius: 24px; + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: + 0 24px 48px rgba(0, 0, 0, 0.5), + 0 0 0 1px rgba(59, 130, 246, 0.2); + animation: slideUp 0.3s ease; } .edit-modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 2rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, transparent 100%); + display: flex; + justify-content: space-between; + align-items: center; + padding: 2rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + background: linear-gradient( + 135deg, + rgba(59, 130, 246, 0.1) 0%, + transparent 100% + ); } .modal-header-title { - display: flex; - align-items: center; - gap: 1rem; + display: flex; + align-items: center; + gap: 1rem; } .modal-header-title svg { - color: #3b82f6; + color: #3b82f6; } .modal-header-title h3 { - color: white; - font-size: 1.5rem; - margin: 0; - font-weight: 700; + color: white; + font-size: 1.5rem; + margin: 0; + font-weight: 700; } .modal-close-btn { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - color: #888; - cursor: pointer; - transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + color: #888; + cursor: pointer; + transition: all 0.3s ease; } .modal-close-btn:hover { - background: rgba(239, 68, 68, 0.2); - border-color: rgba(239, 68, 68, 0.3); - color: #ef4444; - transform: scale(1.1); + background: rgba(239, 68, 68, 0.2); + border-color: rgba(239, 68, 68, 0.3); + color: #ef4444; + transform: scale(1.1); } .edit-modal-body { - padding: 2rem; - display: flex; - flex-direction: column; - gap: 1.5rem; + padding: 2rem; + display: flex; + flex-direction: column; + gap: 1.5rem; } /* ============================================ */ @@ -386,87 +425,87 @@ /* ============================================ */ .form-group2 { - display: flex; - flex-direction: column; - gap: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.75rem; } .form-group2 label { - display: flex; - align-items: center; - gap: 0.5rem; - color: white; - font-weight: 600; - font-size: 0.95rem; + display: flex; + align-items: center; + gap: 0.5rem; + color: white; + font-weight: 600; + font-size: 0.95rem; } .form-group2 label svg { - color: #3b82f6; + color: #3b82f6; } .form-group2 input, .form-group2 select { - width: 100%; - padding: 0.875rem 1.25rem; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - color: white; - font-size: 1rem; - font-weight: 500; - transition: all 0.3s ease; + width: 100%; + padding: 0.875rem 1.25rem; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + color: white; + font-size: 1rem; + font-weight: 500; + transition: all 0.3s ease; } .form-group2 input:focus, .form-group2 select:focus { - outline: none; - border-color: rgba(59, 130, 246, 0.5); - box-shadow: 0 0 20px rgba(59, 130, 246, 0.2); - background: rgba(255, 255, 255, 0.05); + outline: none; + border-color: rgba(59, 130, 246, 0.5); + box-shadow: 0 0 20px rgba(59, 130, 246, 0.2); + background: rgba(255, 255, 255, 0.05); } .form-group2 input::placeholder { - color: #666; + color: #666; } .form-group2 select { - cursor: pointer; + cursor: pointer; } .form-group2 select option { - background: #1a1a1a; - color: white; + background: #1a1a1a; + color: white; } .password-input-group { - position: relative; - display: flex; - align-items: center; + position: relative; + display: flex; + align-items: center; } .password-input-group input { - flex: 1; - padding-right: 3rem; + flex: 1; + padding-right: 3rem; } .password-toggle { - position: absolute; - right: 0.75rem; - display: flex; - align-items: center; - justify-content: center; - padding: 0.5rem; - background: transparent; - border: none; - color: #888; - cursor: pointer; - transition: all 0.3s ease; - border-radius: 6px; + position: absolute; + right: 0.75rem; + display: flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + background: transparent; + border: none; + color: #888; + cursor: pointer; + transition: all 0.3s ease; + border-radius: 6px; } .password-toggle:hover { - background: rgba(59, 130, 246, 0.1); - color: #3b82f6; + background: rgba(59, 130, 246, 0.1); + color: #3b82f6; } /* ============================================ */ @@ -474,51 +513,51 @@ /* ============================================ */ .modal-actions { - display: flex; - gap: 1rem; - padding-top: 1rem; - border-top: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + gap: 1rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); } .btn-cancel, .btn-save { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 1rem 1.5rem; - border: none; - border-radius: 12px; - font-weight: 600; - font-size: 1rem; - cursor: pointer; - transition: all 0.3s ease; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 1rem 1.5rem; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; } .btn-cancel { - background: rgba(239, 68, 68, 0.15); - border: 1px solid rgba(239, 68, 68, 0.3); - color: #ef4444; - margin-left: -2px; + background: rgba(239, 68, 68, 0.15); + border: 1px solid rgba(239, 68, 68, 0.3); + color: #ef4444; + margin-left: -2px; } .btn-cancel:hover { - background: rgba(239, 68, 68, 0.25); - border-color: rgba(239, 68, 68, 0.5); - transform: translateY(-2px); + background: rgba(239, 68, 68, 0.25); + border-color: rgba(239, 68, 68, 0.5); + transform: translateY(-2px); } .btn-save { - background: linear-gradient(135deg, #10b981, #059669); - color: white; - box-shadow: 0 8px 24px rgba(16, 185, 129, 0.3); + background: linear-gradient(135deg, #10b981, #059669); + color: white; + box-shadow: 0 8px 24px rgba(16, 185, 129, 0.3); } .btn-save:hover { - background: linear-gradient(135deg, #059669, #047857); - box-shadow: 0 12px 32px rgba(16, 185, 129, 0.4); - transform: translateY(-2px); + background: linear-gradient(135deg, #059669, #047857); + box-shadow: 0 12px 32px rgba(16, 185, 129, 0.4); + transform: translateY(-2px); } /* ============================================ */ @@ -526,25 +565,25 @@ /* ============================================ */ .empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 4rem 2rem; - text-align: center; - color: #888; - gap: 1rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem 2rem; + text-align: center; + color: #888; + gap: 1rem; } .empty-state h3 { - color: white; - font-size: 1.5rem; - margin: 0; + color: white; + font-size: 1.5rem; + margin: 0; } .empty-state p { - margin: 0; - font-size: 1rem; + margin: 0; + font-size: 1rem; } /* ============================================ */ @@ -552,23 +591,24 @@ /* ============================================ */ .loading-users { - text-align: center; - padding: 4rem 2rem; + text-align: center; + padding: 4rem 2rem; } .loading-users p { - font-size: 1.2rem; - color: #666; - animation: pulse 1.5s ease-in-out infinite; + font-size: 1.2rem; + color: #666; + animation: pulse 1.5s ease-in-out infinite; } @keyframes pulse { - 0%, 100% { - opacity: 0.6; - } - 50% { - opacity: 1; - } + 0%, + 100% { + opacity: 0.6; + } + 50% { + opacity: 1; + } } /* ============================================ */ @@ -576,313 +616,324 @@ /* ============================================ */ @media (max-width: 1024px) { - .filters-section { - flex-direction: column; - } + .filters-section { + flex-direction: column; + } - .search-bar { - min-width: 100%; - } + .search-bar { + min-width: 100%; + } - .role-filters { - flex-wrap: wrap; - } + .role-filters { + flex-wrap: wrap; + } } @media (max-width: 768px) { - .users-management-container { - padding: 1rem; - } + .users-management-container { + padding: 1rem; + } - .user-header { - flex-direction: column; - align-items: flex-start; - } + .user-header { + flex-direction: column; + align-items: flex-start; + } - .user-role-badge { - padding: 0.4rem 0.8rem; - font-size: 0.85rem; - } + .user-role-badge { + padding: 0.4rem 0.8rem; + font-size: 0.85rem; + } - .user-actions { - flex-direction: column; - width: 100%; - } + .user-actions { + flex-direction: column; + width: 100%; + } - .btn-edit, - .btn-delete { - width: 100%; - } + .btn-edit, + .btn-delete { + width: 100%; + } - .edit-modal-content { - max-width: 95%; - } + .edit-modal-content { + max-width: 95%; + } - .edit-modal-header, - .edit-modal-body { - padding: 1.5rem; - } + .edit-modal-header, + .edit-modal-body { + padding: 1.5rem; + } - .modal-header-title h3 { - font-size: 1.25rem; - } + .modal-header-title h3 { + font-size: 1.25rem; + } - .modal-actions { - flex-direction: column; - } + .modal-actions { + flex-direction: column; + } - .btn-cancel, - .btn-save { - width: 100%; - } + .btn-cancel, + .btn-save { + width: 100%; + } } @media (max-width: 480px) { - .filter-btn { - padding: 0.6rem 1rem; - font-size: 0.85rem; - } + .filter-btn { + padding: 10px 13px; + font-size: 0.85rem; + } - .user-avatar { - width: 48px; - height: 48px; - } + .user-avatar { + width: 48px; + height: 48px; + } - .user-username { - font-size: 1rem; - } + .user-username { + font-size: 1rem; + } - .btn-edit, - .btn-delete { - padding: 0.65rem 1rem; - font-size: 0.9rem; - } + .btn-edit, + .btn-delete { + padding: 0.65rem 1rem; + font-size: 0.9rem; + } - .edit-modal-header, - .edit-modal-body { - padding: 1rem; - } + .edit-modal-header, + .edit-modal-body { + padding: 1rem; + } - .modal-header-title h3 { - font-size: 1.1rem; - } + .modal-header-title h3 { + font-size: 1.1rem; + } - .form-group2 input, - .form-group2 select { - padding: 0.75rem 1rem; - font-size: 0.95rem; - } + .form-group2 input, + .form-group2 select { + padding: 0.75rem 1rem; + font-size: 0.95rem; + } } @media (hover: none) { - .user-card:hover { - transform: none; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); - } + .user-card:hover { + transform: none; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + } - .btn-edit:hover, - .btn-delete:hover, - .btn-cancel:hover, - .btn-save:hover { - transform: none; - } + .btn-edit:hover, + .btn-delete:hover, + .btn-cancel:hover, + .btn-save:hover { + transform: none; + } } /* Styles supplémentaires pour les clients */ .clients-section { - margin-top: 3rem; + margin-top: 3rem; } .section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; - flex-wrap: wrap; - gap: 1rem; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; } .search-bar.compact { - max-width: 400px; - margin: 0; + max-width: 400px; + margin: 0; } .client-card { - background: linear-gradient(135deg, rgba(16, 185, 129, 0.05), rgba(16, 185, 129, 0.02)); - border-left: 3px solid #10b981; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.03) 0%, + rgba(255, 255, 255, 0.01) 100% + ); } .user-avatar.client { - background: linear-gradient(135deg, #10b981, #059669); + background: linear-gradient( + 135deg, + rgba(59, 130, 246, 0.2), + rgba(37, 99, 235, 0.1) + ); } .user-role-badge.green { - background: linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(16, 185, 129, 0.1)); - color: #10b981; - border: 1px solid rgba(16, 185, 129, 0.3); + background: linear-gradient( + 135deg, + rgba(16, 185, 129, 0.2), + rgba(16, 185, 129, 0.1) + ); + color: #10b981; + border: 1px solid rgba(16, 185, 129, 0.3); } .client-info-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - padding: 1.5rem; - background: rgba(0, 0, 0, 0.2); - border-radius: 12px; - margin: 1rem 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + padding: 1.5rem; + background: rgba(0, 0, 0, 0.2); + border-radius: 12px; + margin: 1rem 0; } .info-item { - display: flex; - align-items: center; - gap: 0.75rem; - color: rgba(255, 255, 255, 0.9); - font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.75rem; + color: rgba(255, 255, 255, 0.9); + font-size: 0.9rem; } .info-item.full-width { - grid-column: 1 / -1; + grid-column: 1 / -1; } .info-item svg { - color: #10b981; - flex-shrink: 0; + color: #10b981; + flex-shrink: 0; } .info-item span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .btn-view { - padding: 0.75rem 1.5rem; - background: linear-gradient(135deg, #10b981, #059669); - color: white; - border: none; - border-radius: 8px; - font-weight: 600; - cursor: pointer; - display: flex; - align-items: center; - gap: 0.5rem; - transition: all 0.3s ease; - box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, #10b981, #059669); + color: white; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); } .btn-view:hover { - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4); - background: linear-gradient(135deg, #059669, #047857); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4); + background: linear-gradient(135deg, #059669, #047857); } .btn-view:active { - transform: translateY(0); + transform: translateY(0); } /* Client Details Modal */ .client-modal { - max-width: 700px; + max-width: 700px; } .client-detail-section { - margin-bottom: 2rem; + margin-bottom: 2rem; } .client-detail-section h4 { - color: #10b981; - font-size: 1.1rem; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid rgba(16, 185, 129, 0.3); + color: #10b981; + font-size: 1.1rem; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid rgba(16, 185, 129, 0.3); } .detail-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1.5rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; } .detail-item { - display: flex; - flex-direction: column; - gap: 0.5rem; + display: flex; + flex-direction: column; + gap: 0.5rem; } .detail-item.full { - grid-column: 1 / -1; + grid-column: 1 / -1; } .detail-item label { - display: flex; - align-items: center; - gap: 0.5rem; - color: rgba(255, 255, 255, 0.6); - font-size: 0.85rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 0.5rem; + color: rgba(255, 255, 255, 0.6); + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; } .detail-item label svg { - color: #10b981; + color: #10b981; } .detail-item p { - color: rgba(255, 255, 255, 0.95); - font-size: 1rem; - padding: 0.75rem; - background: rgba(0, 0, 0, 0.3); - border-radius: 8px; - border-left: 3px solid #10b981; - margin: 0; - word-break: break-word; + color: rgba(255, 255, 255, 0.95); + font-size: 1rem; + padding: 0.75rem; + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; + border-left: 3px solid #10b981; + margin: 0; + word-break: break-word; } /* Stat card green variant */ .stat-icon.green { - background: linear-gradient(135deg, #10b981, #059669); + background: linear-gradient(135deg, #10b981, #059669); } /* Responsive */ @media (max-width: 768px) { - .section-header { - flex-direction: column; - align-items: stretch; - } + .section-header { + flex-direction: column; + align-items: stretch; + } - .search-bar.compact { - max-width: 100%; - } + .search-bar.compact { + max-width: 100%; + } - .client-info-grid { - grid-template-columns: 1fr; - } + .client-info-grid { + grid-template-columns: 1fr; + } - .detail-grid { - grid-template-columns: 1fr; - } + .detail-grid { + grid-template-columns: 1fr; + } } /* Animation pour les cartes clients */ @keyframes slideInClient { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } .client-card { - animation: slideInClient 0.4s ease-out; + animation: slideInClient 0.4s ease-out; } /* Hover effect pour les info items */ .info-item:hover svg { - transform: scale(1.2); - transition: transform 0.2s ease; -} \ No newline at end of file + transform: scale(1.2); + transition: transform 0.2s ease; +} diff --git a/s2.sh b/s2.sh new file mode 100755 index 00000000..a5eadf7b --- /dev/null +++ b/s2.sh @@ -0,0 +1,239 @@ +#!/bin/bash + +# ============================================ +# SCRIPT DE SIMULATION RÉALISTE - E-COMMERCE +# Version avec validation GPS + Simulation déplacement livreur +# ============================================ + +BASE_URL="https://d437671454fb1f66-90-50-148-138.serveousercontent.com" +ADMIN_TOKEN="" +CABINE_TOKEN="" +LIVREUR_TOKEN="" +NUM_PRODUCTS=13 + +# Couleurs pour l'affichage +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +declare -A PRODUCT_DETAILS +declare -a PRODUCT_IDS + +# Compteurs de tests +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 + +# Fonctions d'affichage +print_section() { + echo "" + echo -e "${BLUE}================================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================================${NC}" +} + +print_test() { + echo "" + echo -e "${YELLOW}▶ $1${NC}" + ((TOTAL_TESTS++)) +} + +print_success() { + echo -e "${GREEN}✓ $1${NC}" + ((PASSED_TESTS++)) +} + +print_error() { + echo -e "${RED}✗ $1${NC}" + ((FAILED_TESTS++)) +} + +print_info() { + echo -e "${MAGENTA}ℹ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠ $1${NC}" +} + +# Fonction pour extraire le token +extract_token() { + local response="$1" + local token=$(echo "$response" | jq -r '.token // .access_token // empty' 2>/dev/null) + echo "$token" +} + +# Fonction pour extraire un ID +extract_id() { + local response="$1" + local field="${2:-id}" + local id=$(echo "$response" | jq -r ".${field} // .product.id // .product_id // empty" 2>/dev/null) + echo "$id" +} + +# Fonction pour vérifier les erreurs +check_error() { + local response="$1" + if echo "$response" | jq -e '.error' >/dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +# Fonction pour vérifier le succès +check_success() { + local response="$1" + if echo "$response" | jq -e '.id // .token // .success // .command_id' >/dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +# ============================================ +# 0. VÉRIFICATION PRÉALABLE +# ============================================ +print_section "0. VÉRIFICATION PRÉALABLE" + +print_test "Vérification du serveur" +HEALTH_CHECK=$(curl -s -X GET "$BASE_URL/api/v1/health") +if [ -n "$HEALTH_CHECK" ]; then + print_success "Serveur accessible" +else + print_error "Serveur non accessible" + exit 1 +fi + +# ============================================ +# 1. AUTHENTIFICATION ADMIN +# ============================================ +print_section "0. AUTHENTIFICATION CLIENT" + +CLIENT_TOKEN=$(extract_token "$CLIENT_LOGIN") + +print_test "Création client" +CLIENT_REGISTER=$(curl -s -X POST "$BASE_URL/api/v1/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"salut\",\"password\":\"salut1234_\",\"role\":\"client\",\"nom\": \"Dupont\",\"prenom\": \"Jean\",\"telephone\": \"+33612345678\"}") +CLIENT_TOKEN=$(extract_token "$CLIENT_REGISTER") +if [ -n "$CLIENT_TOKEN" ]; then + print_success "Client créé et authentifié" +else + print_error "Échec authentification client" + exit 1 +fi + +# ============================================ +# 1. AUTHENTIFICATION ADMIN +# ============================================ +print_section "1. AUTHENTIFICATION ADMIN" + +ADMIN_TOKEN=$(extract_token "$ADMIN_LOGIN") + +print_test "Création admin" +UNIQUE_ADMIN="admin_$(date +%s)" +ADMIN_REGISTER=$(curl -s -X POST "$BASE_URL/api/v2/admin/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$UNIQUE_ADMIN\",\"password\":\"AdminPass123!\",\"role\":\"admin\"}") +ADMIN_TOKEN=$(extract_token "$ADMIN_REGISTER") +if [ -n "$ADMIN_TOKEN" ]; then + print_success "Admin créé et authentifié" +else + print_error "Échec authentification admin" + exit 1 +fi + +print_test "Création livreur" +UNIQUE_LIVREUR="livreur_$(date +%s)" +ADMIN_REGISTER=$(curl -s -X POST "$BASE_URL/api/v2/admin/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$UNIQUE_LIVREUR\",\"password\":\"AdminPass123!\",\"role\":\"livreur\"}") +LIVREUR_TOKEN=$(extract_token "$ADMIN_REGISTER") +if [ -n "$LIVREUR_TOKEN" ]; then + print_success "Admin créé et authentifié" +else + print_error "Échec authentification admin" + exit 1 +fi + +print_test "Création cabine" +UNIQUE_CABINE="cabine_$(date +%s)" +ADMIN_REGISTER=$(curl -s -X POST "$BASE_URL/api/v2/admin/auth/register" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$UNIQUE_CABINE\",\"password\":\"AdminPass123!\",\"role\":\"cabine\"}") +CABINE_TOKEN=$(extract_token "$ADMIN_REGISTER") +if [ -n "$CABINE_TOKEN" ]; then + print_success "Admin créé et authentifié" +else + print_error "Échec authentification admin" + exit 1 +fi + + +# ============================================ +# 2. CRÉATION MASSIVE DE PRODUITS +# ============================================ +print_section "2. CRÉATION MASSIVE DE PRODUITS" + +print_info "Création de $NUM_PRODUCTS produits..." + +PREDEFINED_PRODUCTS=( + '{"name":"Zipette Borealis 1", "category":"zipette&co", "description":"Plante luminescente aux teintes bleutées inspirée des aurores boréales", "price":24.99, "stock":35.00}' + '{"name":"Zipette Titan 1", "category":"zipette&co", "description":"Plante géante fictive atteignant jusqu'\''à 2 mètres en intérieur", "price":19.99, "stock":50.00}' + '{"name":"Zipette Borealis 2", "category":"zipette&co", "description":"Plante luminescente aux teintes bleutées inspirée des aurores boréales", "price":24.99, "stock":35.00}' + '{"name":"Zipette Titan 2", "category":"zipette&co", "description":"Plante géante fictive atteignant jusqu'\''à 2 mètres en intérieur", "price":19.99, "stock":50.00}' + '{"name":"Zipette Borealis 3", "category":"zipette&co", "description":"Plante luminescente aux teintes bleutées inspirée des aurores boréales", "price":24.99, "stock":35.00}' + '{"name":"Zipette Titan 3", "category":"zipette&co", "description":"Plante géante fictive atteignant jusqu'\''à 2 mètres en intérieur", "price":19.99, "stock":50.00}' + '{"name":"Titan 4", "category":"gros&semi", "description":"Plante géante fictive atteignant jusqu'\''à 2 mètres en intérieur", "price":19.99, "stock":50.00}' + '{"name":"Borealis 4", "category":"gros&semi", "description":"Plante luminescente aux teintes bleutées inspirée des aurores boréales", "price":24.99, "stock":35.00}' + '{"name":"Titan 5", "category":"gros&semi", "description":"Plante géante fictive atteignant jusqu'\''à 2 mètres en intérieur", "price":19.99, "stock":50.00}' + '{"name":"Kush 1", "category":"weed&hash", "description":"Variété fictive aux arômes terreux et notes citronnées, culture indoor facile", "price":14.99, "stock":80.00}' + '{"name":"Haze 1", "category":"weed&hash", "description":"Plante fictive à dominance sativa, réputée pour son parfum épicé", "price":16.99, "stock":60.00}' + '{"name":"Hash Gold 1", "category":"weed&hash", "description":"Résine fictive premium à la texture souple et aux notes florales", "price":9.99, "stock":120.00}' + '{"name":"Hash Black 1", "category":"weed&hash", "description":"Résine fictive sombre, goût intense et épicé", "price":11.99, "stock":90.00}' + '{"name":"Kush 2", "category":"weed&hash", "description":"Variété fictive compacte, floraison rapide et parfum boisé", "price":14.99, "stock":75.00}' + +) + +for ((i=0; i