feat(composer.lock): Met à jour les dépendances Composer et ajoute php-whois.

🎨 style(templates/order/process.twig): Améliore l'affichage de la commande et la gestion des options.
🐛 fix(src/Controller/Order/HomeController.php): Ajoute une route pour vérifier la disponibilité d'un nom de domaine (WHOIS).
This commit is contained in:
Serreau Jovann
2025-11-04 09:51:35 +01:00
parent 3d89f1e1a8
commit c3c17b0149
4 changed files with 357 additions and 112 deletions

View File

@@ -22,6 +22,7 @@
"fpdf/fpdf": "*",
"google/cloud": "^0.296.0",
"imagine/imagine": "^1.5",
"io-developer/php-whois": "*",
"knplabs/knp-paginator-bundle": "^6.8",
"lasserafn/php-initial-avatar-generator": "^4.4",
"league/flysystem-aws-s3-v3": "^3.29",

64
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c30269ac9b27fa9af3a5958c1033684b",
"content-hash": "95e96b2eef6b5c2b6179fc5b8ba9ef6e",
"packages": [
{
"name": "async-aws/core",
@@ -3799,6 +3799,68 @@
],
"time": "2022-05-21T17:30:32+00:00"
},
{
"name": "io-developer/php-whois",
"version": "4.1.10",
"source": {
"type": "git",
"url": "https://github.com/io-developer/php-whois.git",
"reference": "ea4cf52832fe8ff56fc4937138467819aa2829c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/io-developer/php-whois/zipball/ea4cf52832fe8ff56fc4937138467819aa2829c1",
"reference": "ea4cf52832fe8ff56fc4937138467819aa2829c1",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": ">=7.2",
"symfony/polyfill-intl-idn": "^1.27"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Iodev\\": "src/Iodev/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sergey Sedyshev",
"email": "i.o.developer@gmail.com",
"homepage": "https://github.com/io-developer"
}
],
"description": "PHP WHOIS provides parsed and raw whois lookup of domains and ASN routes. PHP 5.4+ and 7+ compatible ",
"homepage": "https://github.com/io-developer/php-whois",
"keywords": [
"asn",
"domain",
"info",
"lookup",
"parser",
"php",
"query",
"routes",
"tld",
"whois",
"црщшы"
],
"support": {
"issues": "https://github.com/io-developer/php-whois/issues",
"source": "https://github.com/io-developer/php-whois/tree/4.1.10"
},
"time": "2023-01-25T14:42:45+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.1",

View File

@@ -4,7 +4,9 @@ declare(strict_types=1);
namespace App\Controller\Order;
use Iodev\Whois\Factory;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
@@ -467,4 +469,38 @@ class HomeController extends AbstractController
'formules' => self::formules,
]);
}
#[Route('/commande/check', name: 'commande_check')]
public function check(Request $request): JsonResponse
{
$ndd = $request->get('ndd');
if (empty($ndd)) {
return new JsonResponse([
'isAvailable' => false,
'message' => 'Nom de domaine manquant.',
], Response::HTTP_BAD_REQUEST);
}
// 2. Validation de format simple (côté serveur)
// C'est une vérification de sécurité et de propreté des données
if (!preg_match('/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i', $ndd)) {
return new JsonResponse([
'isAvailable' => false,
'message' => 'Format de domaine invalide.',
], Response::HTTP_OK); // HTTP 200 car c'est une réponse de l'API, pas une erreur serveur
}
$whois = Factory::get()->createWhois();
if($whois->isDomainAvailable($ndd)) {
return new JsonResponse([
'isAvailable' => true,
'message' => 'Félicitations ! Ce domaine est disponible.',
]);
} else {
return new JsonResponse([
'isAvailable' => false,
'message' => 'Désolé, ce domaine est déjà enregistré.',
]);
}
}
}

View File

@@ -18,20 +18,8 @@
</div>
{% set options = {
'domaine': {'price': 15.00, 'period': '/ an', 'description': 'Enregistrement et gestion de votre nom de domaine (.fr, .com, .net, etc.)', 'is_annual': true},
'email': {'price': 5.00, 'period': '/ mois', 'description': 'Ajout d\'une boîte email professionnelle personnalisée (par compte)'},
'esycreator': {'price': 50.00, 'period': '/ mois', 'description': 'Accompagnement et coaching dans la conception et l\'évolution de votre site.'},
'esyseo_pro': {'price': 75.00, 'period': '/ mois', 'description': 'Optimisation avancée de votre référencement naturel par nos experts.'},
'esydefender_pro': {'price': 30.00, 'period': '/ mois', 'description': 'Protection et surveillance avancées (niveau Pro) contre les menaces.'},
'esytranslate': {'price': 25.00, 'period': '/ mois', 'description': 'Traduction automatisée de votre site en plus de 100 langues.'},
'esymailer': {'price': 40.00, 'period': '/ mois', 'description': 'Plateforme pour l\'envoi de campagnes marketing et newsletters professionnelles.'},
'esyvisio': {'price': 15.00, 'period': '/ mois', 'description': 'Solution de visioconférence gratuite et illimitée intégrée.'},
'esymeet': {'price': 20.00, 'period': '/ mois', 'description': 'Module de prise de rendez-vous en ligne et gestion de calendrier.'},
'esytchat': {'price': 15.00, 'period': '/ mois', 'description': 'Ajout d\'un module de chat en direct pour interagir instantanément avec vos clients.'},
'esysignature': {'price': 25.00, 'period': '/ mois', 'description': 'Outil de signature électronique à valeur juridique intégré.'}
} %}
'domaine': {'price': 50.00, 'period': '/ an', 'description': 'Enregistrement et gestion de votre nom de domaine (.fr, .com, .net, etc.)', 'is_annual': true},
} %}
{# Vérification pour s'assurer qu'une formule est bien présente #}
{% if currentFormule is defined and currentFormule is not empty %}
@@ -44,23 +32,54 @@
{# Colonne de gauche (Formulaire et Détails) - 8/12 = 2/3 #}
<div class="lg:col-span-8">
<div class="card flex flex-col p-8 md:p-10 border-4 border-{{ currentFormule.color }}-600 shadow-2xl bg-white rounded-xl mb-12">
<div class="mb-8 border-b pb-4 border-gray-200">
<h2 class="text-4xl font-extrabold text-gray-900 mb-2">{{ currentFormule.name }}</h2>
<p class="text-lg font-semibold text-{{ currentFormule.color }}-600">{{ currentFormule.audience }}</p>
{# 💡 BLOC AMÉLIORÉ : FORMULE SÉLECTIONNÉE #}
<div class="bg-white p-8 md:p-10 border-4 border-{{ currentFormule.color }}-600 shadow-2xl rounded-xl mb-12">
{# En-tête de la formule #}
<div class="border-b pb-4 mb-6 border-gray-200">
<h2 class="text-4xl font-extrabold text-gray-900 mb-1">
{{ currentFormule.name }}
</h2>
<p class="text-lg font-semibold text-{{ currentFormule.color }}-600">
Pour les **{{ currentFormule.audience }}**
</p>
</div>
{# Les détails du prix ici sont statiques, le prix dynamique est dans le panneau flottant #}
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-end mb-8">
<div>
<p class="text-sm font-medium text-gray-500 mb-1">Prix de la formule</p>
<p class="text-6xl font-extrabold text-gray-900">
<span class="text-7xl font-extrabold text-{{ currentFormule.color }}-600">{{ currentFormule.price | number_format(2, ',', ' ') }}</span>
<span class="text-2xl font-medium text-gray-500">{{ currentFormule.period }}</span>
{# Détails de la tarification et engagement #}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-center">
{# Prix de la formule #}
<div class="border-r border-gray-100 md:pr-6">
<p class="text-base font-medium text-gray-500 mb-1">Tarif mensuel</p>
<p class="text-5xl font-extrabold text-gray-900">
<span class="text-6xl font-extrabold text-{{ currentFormule.color }}-600">
{{ currentFormule.price | number_format(2, ',', ' ') }}
</span>
<span class="text-xl font-medium text-gray-500">{{ currentFormule.period }}</span>
</p>
</div>
<p class="text-base text-gray-700 mt-4 sm:mt-0 sm:text-right font-semibold">
{{ currentFormule.commitment }}
</p>
{# Engagement #}
<div class="md:pr-6 md:pl-6 border-r border-gray-100">
<h5 class="text-base font-medium text-gray-500 mb-1">Engagement</h5>
<p class="text-xl text-gray-700 font-semibold mt-2">
{{ currentFormule.commitment }}
</p>
</div>
{# Frais de mise en service #}
<div class="md:pl-6">
<h5 class="text-base font-medium text-gray-500 mb-1">Frais de mise en service</h5>
{% if currentFormule.priceUp is defined and currentFormule.priceUp > 0 %}
<p class="text-xl text-gray-700 font-semibold mt-2">
{{ currentFormule.priceUp | number_format(2, ',', ' ') }} € (HT)
</p>
{% else %}
<p class="text-xl text-green-600 font-bold mt-2">
OFFERTS
</p>
{% endif %}
</div>
</div>
</div>
@@ -88,57 +107,10 @@
<div id="domaine-input" class="pl-10 hidden">
<label for="domaine_name" class="block text-sm font-medium text-gray-700 mb-2">Nom de domaine souhaité</label>
<input type="text" id="domaine_name" name="options[domaine][name]" placeholder="Entrez le nom de domaine"
<input type="text" id="domaine_name" name="options[domaine][name]" placeholder="Entrez le nom de domaine (ex:monsite.fr)"
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
<div class="space-y-4 border-b pb-4 mb-4" data-option-id="email" data-option-price="{{ options.email.price }}" data-option-period="monthly">
<div class="flex items-center justify-between">
<label for="option_email" class="flex items-center cursor-pointer w-full">
<input type="checkbox" id="option_email" name="options[email][activate]" value="1" class="option-checkbox h-6 w-6 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded mr-4" data-option-input-target="#email-input" data-qty-target="#email_count">
<div>
<span class="text-lg font-bold text-gray-900">Boîtes Email Professionnelles</span>
<p class="text-sm text-gray-500">{{ options.email.description }}</p>
</div>
</label>
<span class="text-lg font-semibold text-gray-700 whitespace-nowrap">
+{{ options.email.price | number_format(2, ',', ' ') }}{{ options.email.period }} / boîte
</span>
</div>
<div id="email-input" class="pl-10 hidden">
<label for="email_count" class="block text-sm font-medium text-gray-700 mb-2">Nombre de boîtes email souhaitées</label>
<input type="number" id="email_count" name="options[email][count]" min="1" value="1"
class="mt-1 block w-32 border border-gray-300 rounded-md shadow-sm p-2 focus:ring-indigo-500 focus:border-indigo-500 option-qty-input">
</div>
</div>
<h3 class="text-xl font-bold text-gray-800 mt-8 mb-4">Modules Communication & Sécurité</h3>
{% for key, option in options %}
{% if key not in ['domaine', 'email'] %}
<div class="flex items-center justify-between py-3 border-b {% if loop.last %}border-b-0 pb-0{% endif %}"
data-option-id="{{ key }}" data-option-price="{{ option.price }}" data-option-period="monthly">
<label for="option_{{ key }}" class="flex items-center cursor-pointer w-full">
<input type="checkbox" id="option_{{ key }}" name="options[{{ key }}][activate]" value="1" class="option-checkbox h-6 w-6 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded mr-4">
<div>
<span class="text-lg font-bold text-gray-900">
{% if key == 'esyseo_pro' %}ESY-SEO Pro
{% elseif key == 'esydefender_pro' %}ESY-Defender Pro
{% else %}ESY-{{ key | capitalize | replace({'Esy': ''}) }}
{% endif %}
</span>
<p class="text-sm text-gray-500">{{ option.description }}</p>
</div>
</label>
<span class="text-lg font-semibold text-gray-700 whitespace-nowrap">
+{{ option.price | number_format(2, ',', ' ') }}{{ option.period }}
</span>
</div>
{% endif %}
{% endfor %}
</div>
{# BLOC : FONCTIONNALITÉS (Incluses) #}
@@ -172,45 +144,88 @@
</div>
{# Colonne de droite (Sous-total flottant) - 4/12 = 1/3 #}
{# Colonne de droite (Récapitulatif - Sticky) #}
<div class="lg:col-span-4 mt-10 lg:mt-0">
<div class="sticky top-8 bg-white p-6 shadow-2xl rounded-xl border border-gray-200">
<h3 class="text-2xl font-bold text-gray-900 border-b pb-3 mb-4">
<div class="sticky top-8 bg-white p-6 shadow-2xl rounded-xl border border-gray-200 divide-y divide-gray-200">
<h3 class="text-2xl font-bold text-gray-900 pb-3 mb-4">
Votre Commande
</h3>
<div class="mb-4 flex justify-between items-center text-gray-800">
<span class="font-semibold text-lg">{{ currentFormule.name }}</span>
<span class="font-bold text-lg text-{{ currentFormule.color }}-600">{{ currentFormule.price | number_format(2, ',', ' ') }} € / mois</span>
{# SECTION 1: DÉTAILS DE LA FORMULE DE BASE #}
<div class="py-4">
<h4 class="text-lg font-bold text-gray-800 mb-3">Formule sélectionnée :</h4>
{# Nom et Prix Mensuel de la Formule #}
<div class="flex justify-between items-center mb-2">
<span class="font-semibold text-lg text-gray-900">{{ currentFormule.name }}</span>
<span class="font-bold text-lg text-{{ currentFormule.color }}-600">
{{ currentFormule.price | number_format(2, ',', ' ') }} € / mois
</span>
</div>
{# Frais de mise en service (Si applicables) #}
{% if currentFormule.priceUp is defined and currentFormule.priceUp > 0 %}
<div class="flex justify-between items-center text-base">
<span class="font-medium text-gray-600">Frais de mise en service (Unique)</span>
<span class="font-bold text-gray-800">
{{ currentFormule.priceUp | number_format(2, ',', ' ') }}
</span>
</div>
{% endif %}
</div>
<div class="border-t pt-4 mt-4">
<h4 class="font-bold text-gray-700 mb-3">Options sélectionnées :</h4>
<ul id="selected-options-list" class="space-y-2 text-sm text-gray-600 min-h-[50px]">
{# SECTION 2: OPTIONS SÉLECTIONNÉES (Dynamique via JS) #}
<div class="py-4">
<h4 class="font-bold text-gray-800 mb-3">Options & Services :</h4>
<ul id="selected-options-list" class="space-y-2 text-sm text-gray-600 min-h-[40px]">
<li id="no-options-message" class="italic">Aucune option supplémentaire choisie.</li>
</ul>
</div>
<div class="border-t border-gray-300 pt-4 mt-4">
{# SECTION 3: RÉCAPITULATIF DES TOTAUX #}
<div class="pt-4">
<h4 class="font-bold text-gray-700 mb-3 text-xl">Total Mensuel Estimé :</h4>
<div class="flex justify-between items-center mb-4">
<span class="text-3xl font-extrabold text-gray-900">TOTAL HT/mois</span>
<span id="monthly-total" class="text-3xl font-extrabold text-indigo-600">
{{ currentFormule.price | number_format(2, ',', ' ') }}
</span>
{% set initialUpPrice = currentFormule.priceUp is defined ? currentFormule.priceUp : 0.00 %}
{% set firstMonthPrice = currentFormule.price is defined ? currentFormule.price : 0.00 %}
{# BLOC TOTAL À PAYER AUJOURD'HUI (Mise en avant) #}
<div class="bg-indigo-50 p-3 rounded-lg border-2 border-indigo-200 mb-4">
<h4 class="font-bold text-indigo-800 mb-2 text-xl">Total à payer aujourd'hui (HT) :</h4>
<div class="flex justify-between items-center">
<span class="text-2xl font-extrabold text-indigo-900">Total Initial</span>
<span id="initial-total-static" class="text-3xl font-extrabold text-indigo-600">
{{ (firstMonthPrice + initialUpPrice) | number_format(2, ',', ' ') }}
</span>
</div>
<p class="text-xs text-indigo-600 text-right mt-1">
(1er mois + Frais de mise en service + Options Annuelles)
</p>
</div>
<h4 class="font-bold text-gray-700 mb-3 text-xl">Frais Annuels (Domaine) :</h4>
<div class="flex justify-between items-center mb-4">
<span class="text-xl font-bold text-gray-700">TOTAL HT/an</span>
<span id="annual-total" class="text-xl font-bold text-indigo-600">
0,00 €
</span>
<h4 class="font-bold text-gray-700 mb-3 text-lg">Synthèse des frais récurrents :</h4>
{# Total Mensuel Estimé #}
<div class="flex justify-between items-center mb-2">
<span class="text-lg font-bold text-gray-700">TOTAL HT / mois</span>
<span id="monthly-total" class="text-2xl font-bold text-gray-900">
{{ currentFormule.price | number_format(2, ',', ' ') }}
</span>
</div>
{# Frais Annuels (Options) #}
<div class="flex justify-between items-center mb-4">
<span class="text-base font-medium text-gray-600">TOTAL HT / an (Options)</span>
<span id="annual-total" class="text-lg font-semibold text-gray-800">
0,00 €
</span>
</div>
{# Bouton de Soumission #}
<button type="submit"
class="w-full py-3 mt-4 rounded-lg text-white font-bold text-lg shadow-md
bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-300 transition duration-150 hidden lg:block">
class="w-full py-3 mt-2 rounded-lg text-white font-bold text-lg shadow-md
bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-4 focus:ring-indigo-300 transition duration-150 hidden lg:block">
Passer à l'étape suivante
</button>
</div>
@@ -235,25 +250,34 @@
const form = document.getElementById('order-form');
const checkboxes = form.querySelectorAll('.option-checkbox');
const qtyInputs = form.querySelectorAll('.option-qty-input');
// Cibles pour l'affichage des totaux
const monthlyTotalEl = document.getElementById('monthly-total');
const annualTotalEl = document.getElementById('annual-total');
const initialTotalEl = document.getElementById('initial-total-static'); // NOUVEAU ID pour le total initial
const selectedOptionsList = document.getElementById('selected-options-list');
const noOptionsMessage = document.getElementById('no-options-message');
// Prix de base de la formule (mensuel)
// Prix de base
const baseMonthlyPrice = {{ currentFormule.price | json_encode() | raw }};
// Frais fixes initiaux (Mise en service), s'ils existent
const initialFixedUpPrice = parseFloat({{ currentFormule.priceUp is defined ? currentFormule.priceUp : 0.00 | json_encode() | raw }});
// Fonction utilitaire pour formater le prix
// Fonction utilitaire pour formater le prix
const formatPrice = (price) => {
price = parseFloat(price);
// On vérifie si le prix est un nombre valide avant de formater
if (isNaN(price)) return '0,00 €';
return price.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' €';
}; // <--- L'erreur se produit ici si 'price' n'est pas un nombre.
};
// Fonction de calcul du total
const calculateTotal = () => {
let currentMonthlyTotal = baseMonthlyPrice;
let currentAnnualTotal = 0;
// Démarrer le total initial avec le prix du 1er mois + frais de mise en service
let currentInitialTotal = parseFloat(baseMonthlyPrice) + parseFloat(initialFixedUpPrice);
let selectedOptionsHtml = '';
let optionsCount = 0;
@@ -265,6 +289,7 @@
const price = parseFloat(parentDiv.dataset.optionPrice);
const period = parentDiv.dataset.optionPeriod || 'monthly';
// Gestion de la quantité (bien que non utilisé pour 'domaine' ici, on le garde)
let quantity = 1;
const qtyTarget = parentDiv.dataset.qtyTarget;
if (qtyTarget) {
@@ -274,7 +299,7 @@
}
}
// Affiche/Masque les champs de saisie pour domaine/email
// Affiche/Masque les champs de saisie
const inputTarget = checkbox.dataset.optionInputTarget;
if (inputTarget) {
const inputDiv = document.querySelector(inputTarget);
@@ -287,7 +312,6 @@
}
}
if (checkbox.checked) {
optionsCount++;
@@ -299,6 +323,7 @@
if (period === 'annual') {
currentAnnualTotal += optionPriceTotal;
currentInitialTotal += parseFloat(optionPriceTotal); // AJOUT : L'option annuelle est facturée immédiatement
displayPeriod = '/ an';
priceText = formatPrice(optionPriceTotal) + displayPeriod;
} else {
@@ -313,16 +338,16 @@
}
selectedOptionsHtml += `
<li class="flex justify-between items-start">
<span class="flex-1">${optionTitle}</span>
<span class="font-medium text-right text-gray-800 whitespace-nowrap">+${priceText}</span>
</li>
`;
<li class="flex justify-between items-start">
<span class="flex-1">${optionTitle}</span>
<span class="font-medium text-right text-gray-800 whitespace-nowrap">+${priceText}</span>
</li>
`;
}
});
// Mise à jour de l'affichage
initialTotalEl.textContent = formatPrice(currentInitialTotal); // MISE À JOUR DU TOTAL INITIAL
monthlyTotalEl.textContent = formatPrice(currentMonthlyTotal);
annualTotalEl.textContent = formatPrice(currentAnnualTotal);
selectedOptionsList.innerHTML = selectedOptionsHtml;
@@ -330,7 +355,9 @@
if (optionsCount === 0) {
selectedOptionsList.innerHTML = `<li id="no-options-message" class="italic">Aucune option supplémentaire choisie.</li>`;
} else {
noOptionsMessage && noOptionsMessage.remove(); // Supprime le message s'il est là
// S'assurer que le message "Aucune option" est retiré si des options sont sélectionnées
const existingNoMessage = document.getElementById('no-options-message');
existingNoMessage && existingNoMessage.remove();
}
};
@@ -345,6 +372,125 @@
// Lancement du calcul initial
calculateTotal();
document.querySelector('#domaine_name').addEventListener('keyup', async (e) => {
const inputElement = e.target;
let ndd = inputElement.value.trim().toLowerCase();
// Retirer tout message précédent avant de commencer
removeErrorMessage(inputElement);
removeLoadingMessage(inputElement);
// Validation de format côté client
const nddRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i;
if (ndd === '') {
displayErrorMessage(inputElement, 'Veuillez entrer le nom de domaine.');
return;
} else if (!nddRegex.test(ndd)) {
displayErrorMessage(inputElement, 'Format invalide. Utilisez uniquement lettres, chiffres et tirets, avec une extension valide (.fr, .com, etc.).');
return;
}
// Si le format est valide, lancer la vérification de disponibilité asynchrone
await checkDomainAvailability(ndd, inputElement);
});
function displayLoadingMessage(inputElement) {
let loadingEl = inputElement.nextElementSibling;
if (!loadingEl || !loadingEl.classList.contains('ndd-loading-message')) {
loadingEl = document.createElement('p');
loadingEl.classList.add('ndd-loading-message', 'text-sm', 'text-indigo-600', 'mt-1', 'flex', 'items-center');
inputElement.parentNode.insertBefore(loadingEl, inputElement.nextSibling);
}
loadingEl.innerHTML = `<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-indigo-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg><span>Vérification de la disponibilité...</span>`;
inputElement.setAttribute('disabled', 'true');
inputElement.classList.add('opacity-50');
inputElement.classList.remove('border-red-500', 'border-green-500');
}
function removeLoadingMessage(inputElement) {
const loadingEl = inputElement.nextElementSibling;
if (loadingEl && loadingEl.classList.contains('ndd-loading-message')) {
loadingEl.remove();
}
inputElement.removeAttribute('disabled');
inputElement.classList.remove('opacity-50');
}
function displaySuccessMessage(inputElement) {
let successEl = inputElement.nextElementSibling;
if (!successEl || !successEl.classList.contains('ndd-success-message')) {
successEl = document.createElement('p');
successEl.classList.add('ndd-success-message', 'text-sm', 'text-green-600', 'mt-1', 'font-semibold', 'flex', 'items-center');
inputElement.parentNode.insertBefore(successEl, inputElement.nextSibling);
}
successEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg><span>Félicitations ! Ce nom de domaine est DISPONIBLE.</span>`;
inputElement.classList.add('border-green-500', 'ring-green-500');
inputElement.classList.remove('border-red-500', 'ring-red-500');
}
const checkDomainAvailability = async (ndd, inputElement) => {
// 1. Initialiser l'état de chargement
removeErrorMessage(inputElement);
displayLoadingMessage(inputElement);
// Endpoint où le serveur vérifiera le domaine
try {
// SIMULATION : Remplacez ce bloc par votre appel réel à l'API
const response = await fetch("/commande/check?ndd="+ndd, {
});
const data = await response.json();
// 2. Traitement de la réponse
if (data.isAvailable) { // data.isAvailable
removeLoadingMessage(inputElement)
displaySuccessMessage(inputElement);
return true;
} else {
removeLoadingMessage(inputElement)
displayErrorMessage(inputElement, `Ce nom de domaine (${ndd}) n'est PAS disponible.`);
}
} catch (error) {
console.error('Erreur lors de la vérification du domaine:', error);
displayErrorMessage(inputElement, 'Erreur de connexion. Veuillez réessayer.');
return false;
} finally {
// 3. Retirer l'état de chargement
removeLoadingMessage(inputElement);
// Si la simulation a créé un message de succès, le laisser
if (!inputElement.classList.contains('border-green-500') && !inputElement.classList.contains('border-red-500')) {
// Si aucune bordure n'a été ajoutée, cela signifie une erreur sans message
removeErrorMessage(inputElement);
}
}
};
function displayErrorMessage(inputElement, message) {
// Créer ou mettre à jour un élément d'erreur juste après l'input
let errorEl = inputElement.nextElementSibling
if (!errorEl || !errorEl.classList.contains('ndd-error-message')) {
errorEl = document.createElement('p');
errorEl.classList.add('ndd-error-message', 'text-sm', 'text-red-600', 'mt-1');
inputElement.parentNode.insertBefore(errorEl, inputElement.nextSibling);
}
errorEl.textContent = '❌ ' + message;
inputElement.classList.add('border-green-500', 'ring-green-500');
inputElement.classList.remove('border-red-500', 'ring-red-500');
}
function removeErrorMessage(inputElement) {
const errorEl = inputElement.nextElementSibling;
if (errorEl && errorEl.classList.contains('ndd-error-message')) {
errorEl.remove();
}
}
});
</script>
{% endblock %}