PluginProbe
AI Builder – Generate pages, blocks, images & translate with AI / 2.1.2
AI Builder – Generate pages, blocks, images & translate with AI v2.1.2
2.7.10 2.7.9 2.7.8 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.3.10 All 122 releases
ai-builder / assets / js / credits.js

credits.js in AI Builder – Generate pages, blocks, images & translate with AI 2.1.2, at assets/js/credits.js

746 lines 24.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 let userData = null;
2 let hasSubscription = false;
3 let currentPlan = null;
4
5 // Charger les informations utilisateur au chargement
6 document.addEventListener("DOMContentLoaded", function () {
7 // Vérifier les paramètres de succès dans l'URL
8 checkSuccessParameters();
9
10 // Attacher les événements aux boutons de plan (une seule fois au chargement)
11 attachPlanButtonEvents();
12
13 // Charger les informations utilisateur
14 loadUserInfo();
15 });
16
17 // Fonction pour vérifier les paramètres de succès dans l'URL
18 function checkSuccessParameters() {
19 const urlParams = new URLSearchParams(window.location.search);
20 const type = urlParams.get("type");
21 const sessionId = urlParams.get("session_id");
22
23 if (type && sessionId) {
24 if (type === "credits_success") {
25 showMessage(
26 "Credit purchase completed successfully! Your credits will be updated shortly.",
27 "success"
28 );
29 // Recharger les données après un délai pour s'assurer que les crédits sont mis à jour
30 setTimeout(() => {
31 loadUserInfo();
32 }, 3000);
33 // Nettoyer l'URL après affichage du message
34 setTimeout(() => {
35 const newUrl =
36 window.location.pathname +
37 window.location.search
38 .replace(/[?&]type=[^&]*&session_id=[^&]*/, "")
39 .replace(/^&/, "?");
40 window.history.replaceState({}, document.title, newUrl);
41 }, 5000);
42 } else if (type === "subscription_success") {
43 showMessage(
44 "Subscription activated successfully! Your subscription is now active.",
45 "success"
46 );
47 // Recharger les données après un délai pour s'assurer que l'abonnement est mis à jour
48 setTimeout(() => {
49 loadUserInfo();
50 }, 3000);
51 // Nettoyer l'URL après affichage du message
52 setTimeout(() => {
53 const newUrl =
54 window.location.pathname +
55 window.location.search
56 .replace(/[?&]type=[^&]*&session_id=[^&]*/, "")
57 .replace(/^&/, "?");
58 window.history.replaceState({}, document.title, newUrl);
59 }, 5000);
60 }
61 }
62 }
63
64 // Fonction pour charger les informations utilisateur
65 async function loadUserInfo() {
66 try {
67 const tokenResponse = await fetch(ajaxurl, {
68 method: "POST",
69 headers: { "Content-Type": "application/x-www-form-urlencoded" },
70 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
71 });
72
73 const tokenData = await tokenResponse.json();
74
75 if (!tokenData.success || !tokenData.data.token) {
76 showMessage("Authentication failed", "error");
77 return;
78 }
79
80 const jwtToken = tokenData.data.token;
81
82 const response = await fetch(window.config.apiUrl + "/user/profile", {
83 method: "GET",
84 headers: {
85 Authorization: `Bearer ${jwtToken}`,
86 "Content-Type": "application/json",
87 },
88 });
89
90 if (response.ok) {
91 userData = await response.json();
92 displayUserInfo(userData?.user);
93 // updateSubscriptionButton() n'est plus nécessaire car updatePlansDisplay() gère tout
94 } else {
95 showMessage("Failed to load user information", "error");
96 }
97 } catch (error) {
98 console.error("Error loading user info:", error);
99 showMessage("Network error while loading user information", "error");
100 }
101 }
102
103 // Afficher les informations utilisateur
104 function displayUserInfo(data) {
105 // Afficher les crédits
106 const creditsDisplay = document.getElementById("current-credits");
107 const creditsBreakdown = data.aiCredits || {
108 onAccountCreation: 0,
109 monthlySubscription: 0,
110 paid: 0,
111 };
112
113 const totalCredits =
114 creditsBreakdown.onAccountCreation +
115 creditsBreakdown.monthlySubscription +
116 creditsBreakdown.paid;
117
118 if (creditsDisplay) {
119 creditsDisplay.innerHTML = `
120 <span class="credits-number">${totalCredits}</span>
121 <span class="credits-label">credits</span>
122 `;
123 }
124
125 // Afficher le plan
126 const planDisplay = document.getElementById("current-plan");
127 if (planDisplay) {
128 const plan = data.plan || "basic";
129 let resiliateText = "";
130 if (data.resiliateAt) {
131 const resiliateDate = new Date(data.resiliateAt);
132 const now = new Date();
133 const isEnded = resiliateDate < now.setHours(0, 0, 0, 0);
134 if (isEnded) {
135 resiliateText = `<span class="resiliate-badge">Subscription has ended on <strong>${resiliateDate.toLocaleDateString()}</strong></span>`;
136 } else {
137 resiliateText = `<span class="resiliate-badge">Subscription will end on <strong>${resiliateDate.toLocaleDateString()}</strong></span>`;
138 }
139 }
140 planDisplay.innerHTML = `
141 <span class="plan-badge ${plan}">${plan.charAt(0).toUpperCase() + plan.slice(1)
142 }</span>
143 ${resiliateText}
144 `;
145 }
146
147 // Afficher le message dans la carte Subscription (section management)
148 const subscriptionCard = document.querySelector(
149 ".ai-subscription-actions .ai-action-content"
150 );
151 if (subscriptionCard) {
152 let resiliateMsg = subscriptionCard.querySelector(".resiliate-badge");
153 if (data.resiliateAt) {
154 if (!resiliateMsg) {
155 resiliateMsg = document.createElement("div");
156 resiliateMsg.className = "resiliate-badge";
157 resiliateMsg.innerHTML = `Subscription will end on <strong>${new Date(
158 data.resiliateAt
159 ).toLocaleDateString()}</strong>`;
160 subscriptionCard.appendChild(resiliateMsg);
161 } else {
162 resiliateMsg.innerHTML = `Subscription will end on <strong>${new Date(
163 data.resiliateAt
164 ).toLocaleDateString()}</strong>`;
165 }
166 } else if (resiliateMsg) {
167 resiliateMsg.remove();
168 }
169 }
170
171 // Vérifier si l'utilisateur a un abonnement
172 hasSubscription = data.plan && data.plan !== "basic";
173 currentPlan = data.plan || "basic";
174 window.hasResiliate = !!data.resiliateAt;
175
176 // Mettre à jour l'affichage de la section Buy Credits
177 updateCreditPurchaseSection();
178
179 // Mettre à jour l'affichage des plans
180 updatePlansDisplay();
181 }
182
183 // Attacher les événements aux boutons de plan
184 function attachPlanButtonEvents() {
185 // Utiliser la délégation d'événements pour éviter les problèmes de duplication
186 const plansContainer = document.getElementById("subscription-plans-container");
187 if (plansContainer) {
188 // Retirer l'ancien listener s'il existe
189 plansContainer.removeEventListener("click", handlePlanButtonClick);
190 // Ajouter le nouveau listener
191 plansContainer.addEventListener("click", handlePlanButtonClick);
192 }
193
194 // Bouton d'annulation
195 const cancelBtn = document.getElementById("subscription-btn");
196 if (cancelBtn) {
197 cancelBtn.onclick = showCancelModal;
198 }
199 }
200
201 // Gestionnaire de clic pour les boutons de plan (délégation d'événements)
202 function handlePlanButtonClick(event) {
203 const button = event.target.closest(".ai-plan-btn");
204 if (!button || button.disabled) return;
205
206 const subscriptionType = button.getAttribute("data-subscription-type");
207 if (subscriptionType) {
208 // Vérifier dynamiquement l'état de l'abonnement
209 if (hasSubscription && !window.hasResiliate) {
210 // Utilisateur avec abonnement actif -> changer de plan
211 handleChangeSubscription(subscriptionType, button);
212 } else {
213 // Utilisateur sans abonnement ou abonnement annulé -> nouveau checkout
214 handleSubscription(subscriptionType, button);
215 }
216 }
217 }
218
219 // Mettre à jour l'affichage des plans
220 function updatePlansDisplay() {
221 const planWrappers = document.querySelectorAll(".ai-plan-card-wrapper");
222 const cancelCard = document.getElementById("cancel-subscription-card");
223
224 if (!planWrappers || planWrappers.length === 0) {
225 return; // Les plans ne sont pas encore chargés
226 }
227
228 planWrappers.forEach((wrapper) => {
229 const planType = wrapper.getAttribute("data-plan");
230 const button = wrapper.querySelector(".ai-plan-btn");
231 if (!button) return;
232
233 const btnText = button.querySelector(".btn-text");
234 if (!btnText) return;
235
236 // Réinitialiser l'état
237 wrapper.setAttribute("data-current-plan", "false");
238 button.classList.remove("current-plan");
239 button.disabled = false;
240
241 if (hasSubscription && !window.hasResiliate) {
242 // Utilisateur avec abonnement actif
243 if (planType === currentPlan) {
244 // Plan actuel
245 wrapper.setAttribute("data-current-plan", "true");
246 button.classList.add("current-plan");
247 btnText.textContent = "Current Plan";
248 button.disabled = true;
249 } else {
250 // Autre plan -> option de changement
251 btnText.textContent = `Switch to ${planType.charAt(0).toUpperCase() + planType.slice(1)}`;
252 button.disabled = false;
253 }
254 } else if (hasSubscription && window.hasResiliate) {
255 // Abonnement annulé mais pas encore terminé
256 btnText.textContent = `Choose ${planType.charAt(0).toUpperCase() + planType.slice(1)}`;
257 button.disabled = false;
258 } else {
259 // Pas d'abonnement
260 btnText.textContent = `Choose ${planType.charAt(0).toUpperCase() + planType.slice(1)}`;
261 button.disabled = false;
262 }
263 });
264
265 // Afficher/masquer la carte d'annulation
266 if (cancelCard) {
267 if (hasSubscription && !window.hasResiliate) {
268 cancelCard.style.display = "block";
269 } else {
270 cancelCard.style.display = "none";
271 }
272 }
273 }
274
275 // Mettre à jour le bouton d'abonnement (fonction conservée pour compatibilité mais non utilisée)
276 // L'affichage des plans est maintenant géré par updatePlansDisplay()
277 // Cette fonction peut être utilisée pour le bouton d'annulation si nécessaire
278 function updateSubscriptionButton() {
279 const btn = document.getElementById("subscription-btn");
280 if (!btn) return; // Sécurité : le bouton n'existe pas
281
282 // Reconstruit le HTML interne du bouton à chaque fois
283 btn.innerHTML = `
284 <span class="ai-loading" style="display: none;"></span>
285 <span class="btn-text"></span>
286 `;
287 const btnText = btn.querySelector(".btn-text");
288 const loading = btn.querySelector(".ai-loading");
289 if (!btnText || !loading) return; // Sécurité : structure inattendue
290
291 if (hasSubscription && window.hasResiliate) {
292 btnText.textContent = "Reactivate Subscription";
293 btn.className = "ai-primary-btn ai-reactivate-btn";
294 btn.onclick = handleReactivateSubscription;
295 } else if (hasSubscription) {
296 btnText.textContent = "Cancel Subscription";
297 btn.className = "ai-danger-btn";
298 btn.onclick = showCancelModal;
299 } else {
300 // Ce cas ne devrait plus se produire car nous avons maintenant les boutons de plan individuels
301 btnText.textContent = "Get Subscription";
302 btn.className = "ai-primary-btn";
303 btn.onclick = null; // Ne pas assigner handleSubscription sans paramètre
304 }
305 }
306
307 // Fonction pour réactiver l'abonnement
308 async function handleReactivateSubscription() {
309 setLoading("subscription-btn", true);
310 try {
311 const tokenResponse = await fetch(ajaxurl, {
312 method: "POST",
313 headers: { "Content-Type": "application/x-www-form-urlencoded" },
314 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
315 });
316 const tokenData = await tokenResponse.json();
317 const jwtToken = tokenData.data.token;
318 const response = await fetch(
319 window.config.apiUrl + "/payments/resume-subscription",
320 {
321 method: "POST",
322 headers: {
323 Authorization: `Bearer ${jwtToken}`,
324 "Content-Type": "application/json",
325 },
326 }
327 );
328 const data = await response.json();
329 if (response.ok) {
330 showMessage("Subscription reactivated successfully!", "success");
331 setTimeout(() => {
332 loadUserInfo();
333 }, 1000);
334 } else {
335 showMessage(data.message || "Failed to reactivate subscription", "error");
336 }
337 } catch (error) {
338 console.error("Error reactivating subscription:", error);
339 showMessage("Network error while reactivating subscription", "error");
340 } finally {
341 setLoading("subscription-btn", false);
342 }
343 }
344
345 // Mettre à jour l'affichage de la section Buy Credits
346 function updateCreditPurchaseSection() {
347 const creditCard = document.getElementById("credit-purchase-card");
348 const buyCreditsBtn = document.getElementById("buy-credits-btn");
349 const subscriptionMessage = document.getElementById(
350 "subscription-required-message"
351 );
352 const sectionHeader = document.querySelector(
353 "#credit-purchase-section .ai-section-header p"
354 );
355
356 if (hasSubscription) {
357 // Utilisateur avec abonnement - afficher normalement
358 creditCard.classList.remove("ai-disabled");
359 buyCreditsBtn.disabled = false;
360 buyCreditsBtn.onclick = handleCreditPurchase;
361 subscriptionMessage.style.display = "none";
362 sectionHeader.textContent =
363 "Purchase additional credits when you need them";
364 } else {
365 // Utilisateur sans abonnement - griser et afficher message
366 creditCard.classList.add("ai-disabled");
367 buyCreditsBtn.disabled = true;
368 buyCreditsBtn.onclick = null;
369 subscriptionMessage.style.display = "flex";
370 // sectionHeader.textContent =
371 // "Active subscription required to purchase credits";
372 }
373 }
374
375 // Gérer l'abonnement (nouveau checkout)
376 async function handleSubscription(subscriptionType, buttonElement) {
377 if (!subscriptionType) {
378 showMessage("Please select a subscription plan", "error");
379 return;
380 }
381
382 // Désactiver tous les boutons pendant le traitement
383 const planButtons = document.querySelectorAll(".ai-plan-btn");
384 planButtons.forEach((btn) => {
385 btn.disabled = true;
386 });
387
388 // Afficher le loading sur le bouton cliqué
389 if (buttonElement) {
390 const loading = buttonElement.querySelector(".ai-loading");
391 const btnText = buttonElement.querySelector(".btn-text");
392 if (loading) loading.style.display = "inline-block";
393 if (btnText) {
394 const originalText = btnText.textContent;
395 btnText.textContent = "Processing...";
396 }
397 }
398
399 try {
400 const tokenResponse = await fetch(ajaxurl, {
401 method: "POST",
402 headers: { "Content-Type": "application/x-www-form-urlencoded" },
403 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
404 });
405
406 const tokenData = await tokenResponse.json();
407 if (!tokenData.success || !tokenData.data.token) {
408 showMessage("Authentication failed", "error");
409 return;
410 }
411
412 const jwtToken = tokenData.data.token;
413
414 const response = await fetch(
415 window.config.apiUrl + "/payments/create-checkout-session-abo",
416 {
417 method: "POST",
418 headers: {
419 Authorization: `Bearer ${jwtToken}`,
420 "Content-Type": "application/json",
421 },
422 body: JSON.stringify({
423 subscriptionType: subscriptionType,
424 urlFrom: window.location.href,
425 }),
426 }
427 );
428
429 const data = await response.json();
430
431 if (response.ok && data.url) {
432 window.location.href = data.url;
433 } else {
434 showMessage(
435 data.message || "Failed to create subscription checkout",
436 "error"
437 );
438 // Réactiver les boutons en cas d'erreur
439 planButtons.forEach((btn) => {
440 btn.disabled = false;
441 });
442 if (buttonElement) {
443 const loading = buttonElement.querySelector(".ai-loading");
444 const btnText = buttonElement.querySelector(".btn-text");
445 if (loading) loading.style.display = "none";
446 if (btnText) {
447 btnText.textContent = buttonElement.getAttribute("data-subscription-type")
448 ? `Choose ${buttonElement.getAttribute("data-subscription-type").charAt(0).toUpperCase() + buttonElement.getAttribute("data-subscription-type").slice(1)}`
449 : "Choose Plan";
450 }
451 }
452 }
453 } catch (error) {
454 console.error("Error creating subscription:", error);
455 showMessage("Network error while creating subscription", "error");
456 // Réactiver les boutons en cas d'erreur
457 const planButtons = document.querySelectorAll(".ai-plan-btn");
458 planButtons.forEach((btn) => {
459 btn.disabled = false;
460 });
461 if (buttonElement) {
462 const loading = buttonElement.querySelector(".ai-loading");
463 const btnText = buttonElement.querySelector(".btn-text");
464 if (loading) loading.style.display = "none";
465 if (btnText) {
466 btnText.textContent = buttonElement.getAttribute("data-subscription-type")
467 ? `Choose ${buttonElement.getAttribute("data-subscription-type").charAt(0).toUpperCase() + buttonElement.getAttribute("data-subscription-type").slice(1)}`
468 : "Choose Plan";
469 }
470 }
471 }
472 }
473
474 // Gérer le changement d'abonnement
475 async function handleChangeSubscription(subscriptionType, buttonElement) {
476 if (!subscriptionType) {
477 showMessage("Please select a subscription plan", "error");
478 return;
479 }
480
481 // Vérifier si c'est le même plan
482 if (subscriptionType === currentPlan) {
483 showMessage("You are already on this plan", "info");
484 return;
485 }
486
487 // Désactiver tous les boutons pendant le traitement
488 const planButtons = document.querySelectorAll(".ai-plan-btn");
489 planButtons.forEach((btn) => {
490 btn.disabled = true;
491 });
492
493 // Afficher le loading sur le bouton cliqué
494 if (buttonElement) {
495 const loading = buttonElement.querySelector(".ai-loading");
496 const btnText = buttonElement.querySelector(".btn-text");
497 if (loading) loading.style.display = "inline-block";
498 if (btnText) {
499 const originalText = btnText.textContent;
500 btnText.textContent = "Switching...";
501 }
502 }
503
504 try {
505 const tokenResponse = await fetch(ajaxurl, {
506 method: "POST",
507 headers: { "Content-Type": "application/x-www-form-urlencoded" },
508 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
509 });
510
511 const tokenData = await tokenResponse.json();
512 if (!tokenData.success || !tokenData.data.token) {
513 showMessage("Authentication failed", "error");
514 return;
515 }
516
517 const jwtToken = tokenData.data.token;
518
519 const response = await fetch(
520 window.config.apiUrl + "/payments/change-subscription",
521 {
522 method: "POST",
523 headers: {
524 Authorization: `Bearer ${jwtToken}`,
525 "Content-Type": "application/json",
526 },
527 body: JSON.stringify({
528 subscriptionType: subscriptionType,
529 }),
530 }
531 );
532
533 const data = await response.json();
534
535 if (response.ok) {
536 showMessage("Subscription changed successfully!", "success");
537 // Recharger les informations utilisateur
538 setTimeout(() => {
539 loadUserInfo();
540 }, 1000);
541 } else {
542 showMessage(
543 data.message || "Failed to change subscription",
544 "error"
545 );
546 // Réactiver les boutons en cas d'erreur
547 planButtons.forEach((btn) => {
548 btn.disabled = false;
549 });
550 if (buttonElement) {
551 const loading = buttonElement.querySelector(".ai-loading");
552 const btnText = buttonElement.querySelector(".btn-text");
553 if (loading) loading.style.display = "none";
554 if (btnText) {
555 btnText.textContent = `Switch to ${subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1)}`;
556 }
557 }
558 }
559 } catch (error) {
560 console.error("Error changing subscription:", error);
561 showMessage("Network error while changing subscription", "error");
562 // Réactiver les boutons en cas d'erreur
563 const planButtons = document.querySelectorAll(".ai-plan-btn");
564 planButtons.forEach((btn) => {
565 btn.disabled = false;
566 });
567 if (buttonElement) {
568 const loading = buttonElement.querySelector(".ai-loading");
569 const btnText = buttonElement.querySelector(".btn-text");
570 if (loading) loading.style.display = "none";
571 if (btnText) {
572 btnText.textContent = `Switch to ${subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1)}`;
573 }
574 }
575 }
576 }
577
578 // Acheter des crédits
579 async function handleCreditPurchase() {
580 try {
581 const tokenResponse = await fetch(ajaxurl, {
582 method: "POST",
583 headers: { "Content-Type": "application/x-www-form-urlencoded" },
584 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
585 });
586
587 const tokenData = await tokenResponse.json();
588 const jwtToken = tokenData.data.token;
589
590 const urlFrom = window.location.href;
591 const response = await fetch(
592 window.config.apiUrl + "/payments/create-checkout-session-credits",
593 {
594 method: "POST",
595 headers: {
596 Authorization: `Bearer ${jwtToken}`,
597 "Content-Type": "application/json",
598 },
599 body: JSON.stringify({
600 urlFrom,
601 }),
602 }
603 );
604
605 const data = await response.json();
606
607 if (response.ok && data.url) {
608 window.location.href = data.url;
609 } else {
610 showMessage(data.message || "Failed to create credit checkout", "error");
611 }
612 } catch (error) {
613 console.error("Error creating credit checkout:", error);
614 showMessage("Network error while creating credit checkout", "error");
615 }
616 }
617
618 // Accéder au compte Stripe
619 async function handleStripeAccount() {
620 try {
621 const tokenResponse = await fetch(ajaxurl, {
622 method: "POST",
623 headers: { "Content-Type": "application/x-www-form-urlencoded" },
624 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
625 });
626
627 const tokenData = await tokenResponse.json();
628 const jwtToken = tokenData.data.token;
629
630 const urlFrom = window.location.href;
631 const response = await fetch(
632 window.config.apiUrl + "/payments/stripe-customer-portal",
633 {
634 method: "POST",
635 headers: {
636 Authorization: `Bearer ${jwtToken}`,
637 "Content-Type": "application/json",
638 },
639 body: JSON.stringify({
640 urlFrom,
641 }),
642 }
643 );
644
645 const data = await response.json();
646
647 if (response.ok && data.url) {
648 window.open(data.url, "_blank");
649 } else {
650 showMessage(data.message || "Failed to access Stripe account", "error");
651 }
652 } catch (error) {
653 console.error("Error accessing Stripe account:", error);
654 showMessage("Network error while accessing Stripe account", "error");
655 }
656 }
657
658 // Afficher la modal de confirmation
659 function showCancelModal() {
660 document.getElementById("confirmation-modal").style.display = "flex";
661 }
662
663 // Fermer la modal
664 function closeModal() {
665 document.getElementById("confirmation-modal").style.display = "none";
666 }
667
668 // Confirmer l'annulation de l'abonnement
669 async function confirmCancelSubscription() {
670 const btn = document.querySelector(".ai-danger-btn");
671 const loading = btn.querySelector(".ai-loading");
672 const text = btn.textContent;
673
674 loading.style.display = "inline-block";
675 btn.textContent = "";
676 btn.appendChild(loading);
677 btn.appendChild(document.createTextNode(text));
678 btn.disabled = true;
679
680 try {
681 const tokenResponse = await fetch(ajaxurl, {
682 method: "POST",
683 headers: { "Content-Type": "application/x-www-form-urlencoded" },
684 body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce,
685 });
686
687 const tokenData = await tokenResponse.json();
688 const jwtToken = tokenData.data.token;
689
690 const response = await fetch(
691 window.config.apiUrl + "/payments/cancel-subscription",
692 {
693 method: "POST",
694 headers: {
695 Authorization: `Bearer ${jwtToken}`,
696 "Content-Type": "application/json",
697 },
698 }
699 );
700
701 const data = await response.json();
702
703 if (response.ok) {
704 showMessage("Subscription cancelled successfully", "success");
705 closeModal();
706 // Forcer l'état local pour l'UI
707 window.hasResiliate = true;
708 hasSubscription = false; // Mettre à jour l'état local
709 // Recharger les informations utilisateur (qui va mettre à jour l'affichage via updatePlansDisplay)
710 setTimeout(() => {
711 loadUserInfo();
712 }, 1000);
713 } else {
714 showMessage(data.message || "Failed to cancel subscription", "error");
715 }
716 } catch (error) {
717 console.error("Error cancelling subscription:", error);
718 showMessage("Network error while cancelling subscription", "error");
719 } finally {
720 loading.style.display = "none";
721 btn.textContent = text;
722 btn.disabled = false;
723 }
724 }
725
726 // Fonctions utilitaires
727 function showMessage(message, type = "info") {
728 const container = document.getElementById("message-container");
729 container.innerHTML = `<div class="ai-message ${type}">${message}</div>`;
730 container.scrollIntoView({ behavior: "smooth" });
731 }
732
733 function setLoading(buttonId, isLoading) {
734 const button = document.getElementById(buttonId);
735 const loading = button.querySelector(".ai-loading");
736 const text = button.querySelector(".btn-text");
737
738 if (isLoading) {
739 loading.style.display = "inline-block";
740 button.disabled = true;
741 } else {
742 loading.style.display = "none";
743 button.disabled = false;
744 }
745 }
746