/** * LicencePage.tsx * * React component replacing settings-parts/settings-licence.php. * * PHP still wraps the section in the settings
, so form inputs * rendered here are naturally submitted with the existing form flow. * * Data injected by PHP via wp_add_inline_script('pdfib-licence-page', '...', 'before') * into window.pdfibLicenceData. * * window.pdfBuilderLicense (existing localisation) is still available for nonces. */ import React, { useState, useRef, useCallback, useEffect } from 'react'; // ─── Types ──────────────────────────────────────────────────────────────────── export interface LicenceData { isPremium: boolean; isActive: boolean; badgeClass: 'badge-premium' | 'badge-free'; badgeText: string; maskedKey: string; displayKey: string; licenseStatus: string; licenseCustomer: string; licenseEmail: string; licenseActivations: number | null; expiresLabel: string; daysLeftLabel: string; daysLeftClass: string; expired: boolean; expiringSoon: boolean; isLifetime: boolean; activateButtonText: string; activateTitle: string; activateHelp: string; inputPlaceholder: string; purchaseUrl: string; supportUrl: string; docsUrl: string; renewalUrl: string; unsubscribeUrl: string; emailRemindersEnabled: boolean; reminderEmail: string; adminEmail: string; siteUrl: string; licenseType?: string; siteCount?: number; activationLimit?: number | string; } declare global { interface Window { pdfibLicenceData?: LicenceData; pdfBuilderLicense?: { ajaxNonce: string; deactivateNonce: string; ajaxUrl: string; btnText: string; }; } } // ─── EDD key regex ──────────────────────────────────────────────────────────── const EDD_REGEX = /^[a-f0-9]{32}$/i; type NoticeType = 'error' | 'success' | 'loading' | 'idle'; // ─── Deactivation modal ─────────────────────────────────────────────────────── interface DeactivateModalProps { onClose: () => void; onConfirm: () => void; } const DeactivateModal: React.FC = ({ onClose, onConfirm }) => (
{ if ((e.target as HTMLElement).id === 'deactivate-modal-overlay') onClose(); }} >

⚠️ Confirmer la désactivation

⚠️

Êtes-vous sûr de vouloir désactiver la licence ?

Cette action va :

  • Supprimer votre clé de licence
  • Repasser en mode gratuit
  • Perdre l'accès aux options PRO
); // ─── Feature comparison data ─────────────────────────────────────────────────── // [label, free, premium, alwaysVisible] // Source vérifiée : canvas, hooks premium et renderer de comparaison partagé. const FEATURE_ROWS: Array<[string, string, string, boolean]> = [ ['Éditeur visuel drag & drop', '✓', '✓', true], ['Génération PDF', 'PDF', 'PDF', true], ['Éléments standards', 'Texte, image, ligne, rectangle', 'Texte, image, ligne, rectangle', true], ['Types de document', 'Facture, devis', 'Facture, devis, bon de commande, avoir, relevé, contrat', true], ['Modèles de texte dynamique', '3 modèles', '36 modèles', true], ['Modèles de mentions', '3 modèles', '21 modèles', true], ['Intégration WooCommerce', '✓', '✓', true], ['Qualité d\'export max', '150 DPI', '300 & 600 DPI', false], ['Formats d\'export', 'PDF', 'PDF, PNG, JPG', false], ['Formats de page', 'A4 portrait', 'A3, Letter, Legal + paysage', false], ['Galerie de modèles avancés', '—', '✓', false], ['Navigation grille, snap & guides', '—', '✓', false], ['Sélection multiple & mode groupe', '—', '✓', false], ['Raccourcis clavier', '—', '✓', false], ['Couleurs canvas avancées', '—', '✓', false], ['Thèmes prédéfinis de l’éditeur', '—', '✓', false], ]; // ─── Main component ─────────────────────────────────────────────────────────── const LicencePage: React.FC = () => { const d = window.pdfibLicenceData!; const lic = window.pdfBuilderLicense; // ── Local state ────────────────────────────────────────────────────────── const [keyInput, setKeyInput] = useState(''); const [noticeMsg, setNoticeMsg] = useState(''); const [noticeType, setNoticeType] = useState('idle'); const [activating, setActivating] = useState(false); const [showDeactivateModal, setShowDeactivateModal] = useState(false); const [copyLabel, setCopyLabel] = useState('Copier'); const [detailsOpen, setDetailsOpen] = useState(false); const [compareOpen, setCompareOpen] = useState(false); const [globalNotice, setGlobalNotice] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); const inputRef = useRef(null); // ── Key validation ─────────────────────────────────────────────────────── const keyValid = EDD_REGEX.test(keyInput.trim()); const keyTouched = keyInput.trim().length > 0; // ── Activate ───────────────────────────────────────────────────────────── const handleActivate = useCallback(async () => { const key = keyInput.trim(); if (!key) { setNoticeMsg('⚠ Veuillez saisir votre clé de licence.'); setNoticeType('error'); inputRef.current?.focus(); return; } if (!EDD_REGEX.test(key)) { setNoticeMsg('⚠ Format invalide — une clé EDD comporte 32 caractères hexadécimaux (0-9, a-f).'); setNoticeType('error'); inputRef.current?.focus(); return; } setActivating(true); setNoticeMsg('⏳ Vérification auprès du serveur de licences…'); setNoticeType('loading'); const formData = new FormData(); formData.append('action', 'pdfib_activate_license'); formData.append('nonce', lic?.ajaxNonce ?? ''); formData.append('license_key', key); try { const res = await fetch(lic?.ajaxUrl ?? '/wp-admin/admin-ajax.php', { method: 'POST', body: formData, }); const data = await res.json(); if (data.success) { setNoticeMsg('✓ ' + data.data.message); setNoticeType('success'); setTimeout(() => window.location.reload(), 1500); } else { setNoticeMsg('✗ ' + (data.data?.message ?? 'Erreur inconnue')); setNoticeType('error'); setActivating(false); } } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); setNoticeMsg('✗ Erreur réseau : ' + msg); setNoticeType('error'); setActivating(false); } }, [keyInput, lic]); // ── Deactivate ──────────────────────────────────────────────────────────── const handleConfirmDeactivate = useCallback(async () => { setShowDeactivateModal(false); const body = 'action=pdfib_deactivate_license&nonce=' + encodeURIComponent(lic?.deactivateNonce ?? ''); try { const xhr = new XMLHttpRequest(); xhr.open('POST', lic?.ajaxUrl ?? '/wp-admin/admin-ajax.php', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = () => { if (xhr.status !== 200) { setGlobalNotice({ type: 'error', msg: '✗ Erreur serveur (statut: ' + xhr.status + ')' }); return; } try { const resp = JSON.parse(xhr.responseText); if (resp.success) { setGlobalNotice({ type: 'success', msg: '✓ Licence désactivée avec succès. Rafraîchissement…' }); setTimeout(() => window.location.reload(), 1500); } else { setGlobalNotice({ type: 'error', msg: '✗ ' + (resp.message ?? 'Erreur lors de la désactivation') }); } } catch { setGlobalNotice({ type: 'error', msg: '✗ Réponse invalide du serveur.' }); } }; xhr.onerror = () => setGlobalNotice({ type: 'error', msg: '✗ Erreur de connexion' }); xhr.send(body); } catch { setGlobalNotice({ type: 'error', msg: '✗ Erreur inattendue.' }); } }, [lic]); // ── Copy key ────────────────────────────────────────────────────────────── const handleCopyKey = useCallback(() => { if (!navigator.clipboard || !d.displayKey) { setCopyLabel('Copie impossible'); return; } navigator.clipboard.writeText(d.displayKey) .then(() => { setCopyLabel('Clé copiée'); setTimeout(() => setCopyLabel('Copier'), 2000); }) .catch(() => setCopyLabel('Copie impossible')); }, [d.displayKey]); // ── Focus input ─────────────────────────────────────────────────────────── const handleFocusInput = useCallback(() => { document.getElementById('activate-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); setTimeout(() => inputRef.current?.focus(), 250); }, []); // ── Animation CSS injection ─────────────────────────────────────────────── useEffect(() => { const id = 'pdfib-licence-anim'; if (document.getElementById(id)) return; const s = document.createElement('style'); s.id = id; s.textContent = [ '@keyframes pdfib-shimmer{0%{background-position:-200% center}100%{background-position:200% center}}', '@keyframes pdfib-glow{0%,100%{box-shadow:0 0 8px rgba(246,178,14,.35),0 2px 15px rgba(0,0,0,.08)}50%{box-shadow:0 0 22px rgba(246,178,14,.7),0 2px 15px rgba(0,0,0,.08)}}', '@keyframes pdfib-glow-orange{0%,100%{box-shadow:0 0 8px rgba(255,107,0,.25),0 2px 15px rgba(0,0,0,.06)}50%{box-shadow:0 0 20px rgba(255,107,0,.55),0 2px 15px rgba(0,0,0,.06)}}', '@keyframes pdfib-pulse{0%,100%{opacity:1}50%{opacity:.82}}', ].join(''); document.head.appendChild(s); return () => { document.getElementById(id)?.remove(); }; }, []); // ── Notice colour ───────────────────────────────────────────────────────── const noticeColor = noticeType === 'error' ? '#cc1818' : noticeType === 'success' ? '#1a7e2e' : '#888'; // ─── Render ─────────────────────────────────────────────────────────────── return (
{/* Global notice (deactivation result) */} {globalNotice && (

{globalNotice.msg}

)} {/* Header */}

Gestion de la licence

{d.badgeText}

Activez votre clé, consultez son état et gérez les rappels avant expiration.

{!d.isActive ? ( ) : ( )}
{/* Expiry notices */} {d.expired && (

Licence expirée. {d.daysLeftLabel}.

)} {d.expiringSoon && !d.isLifetime && !d.expired && (

Expiration imminente. {d.daysLeftLabel}.

)} {/* Actions section */}
{/* Activate card */}

{d.activateTitle}

{d.activateHelp}

{/* Steps — uniquement si pas premium */} {!d.isPremium && (
{[ { n: '1', label: 'Choisissez votre formule', desc: 'Mensuel · Annuel · À vie' }, { n: '2', label: 'Obtenez votre clé', desc: '32 caractères — reçue par email' }, { n: '3', label: 'Activez votre clé', desc: 'Accès à toutes les options PRO' }, ].map(({ n, label, desc }) => (
{n}
{label}
{desc}
))}
)}
setKeyInput(e.target.value)} />
{/* Inline validation notice */} {keyTouched && (

{keyValid ? '✓ Format valide.' : '⚠ Format invalide — une clé EDD comporte 32 caractères hexadécimaux (0-9, a-f).'}

)} {/* AJAX notice */} {noticeType !== 'idle' && (

{noticeMsg}

)}

{!d.isPremium ? ( <> Vous n'avez pas encore de clé ?{' '} Découvrir l'extension PRO ) : ( 'Une nouvelle clé remplacera la licence actuellement active.' )}

{d.isPremium && d.displayKey && (

Renouveler la licence {' | '} Gérer le renouvellement

)}
{/* Infos licence active — uniquement si premium */} {d.isPremium && (
{/* Statut détaillé */}
{[ { icon: '🔑', label: 'Type de licence', value: d.licenseType || 'PRO' }, { icon: '📍', label: 'Site activé', value: d.siteCount ? `${d.siteCount} / ${d.activationLimit || '∞'}` : '1 site' }, { icon: '📅', label: 'Expiration', value: d.expiresLabel || 'À vie' }, { icon: '⚡', label: 'Statut', value: d.badgeText || 'Actif' }, ].map(({ icon, label, value }) => (
{icon} {label}
{value}
))}
{/* Note sécurité */}
⚠️ Ne remplacez votre clé que si vous avez reçu une nouvelle clé valide. L'ancienne sera désactivée sur ce site.
)} {/* Garanties — uniquement si pas premium */} {!d.isPremium && (
{[ { icon: '🔒', text: 'Achat séparé' }, { icon: '⚡', text: 'Activation par clé' }, { icon: '💬', text: 'Support du vendeur' }, { icon: '♾️', text: 'Licences à vie disponibles' }, ].map(({ icon, text }) => (
{icon} {text}
))}
)}
{/* Support / upgrade card — conditional style free vs premium */} {d.isActive ? ( /* ── Premium: carte or avec ruban animé ────────────────────── */
{/* Ruban animé en haut */}
{/* Badge Premium actif */}
★ PRO actif

🏆 Options PRO actives

Toutes les options PRO sont actives sur votre installation.

{/* Avantages liste */}
{[ { icon: '⚡', text: 'Génération PDF, PNG & JPG' }, { icon: '🖨️', text: 'Export 300 & 600 DPI' }, { icon: '🖼️', text: 'Formats de page étendus' }, { icon: '🗂️', text: 'Galerie de modèles avancés' }, { icon: '🧲', text: 'Grille, guides & aimantation' }, { icon: '⌨️', text: 'Sélection multiple et raccourcis clavier' }, ].map(({ icon, text }) => (
{icon} {text}
))}
{/* Ressources */}
Ressources & gestion
) : ( /* ── No active license card ─────────────────────────── */

Aucune licence active

Saisissez votre clé de licence ci-dessus pour activer les fonctionnalités avancées.

)}
{/* Details section */}
{detailsOpen && (

Statut

{d.badgeText}

État distant

{d.licenseStatus || 'Inconnu'}

Site actuel

{d.siteUrl}

{d.maskedKey && (

Clé active

{d.maskedKey} {d.displayKey && ( )}

)} {d.isActive && ( <>

Expiration

{d.expiresLabel}

Délai restant

{d.daysLeftLabel}

)} {d.licenseCustomer && (

Titulaire

{d.licenseCustomer}

)} {d.licenseEmail && (

Email

{d.licenseEmail}

)} {d.licenseActivations !== null && (

Activations restantes

{Math.abs(d.licenseActivations)}

)}
)}
{/* Free vs Premium comparison */}

Gratuit vs PRO

{FEATURE_ROWS.filter((row) => row[3] || compareOpen).map(([label, free, premium]) => { const isFreeCheck = free === '✓'; const isFreeMinus = free === '—'; const isPremGreen = premium.startsWith('✓'); const isPremMinus = premium === '—'; return ( ); })}
Fonctionnalité Gratuit ★ PRO
{label} {free} {premium}
{/* Email reminders — fields stay in the PHP form for native form submission */}

Rappels par email

Recevez une alerte avant l'expiration pour éviter une coupure de service.

Laissez vide pour réutiliser l'email administrateur du site.

Cette adresse est utilisée uniquement pour les rappels de licence.

{/* Deactivation confirmation modal */} {showDeactivateModal && ( setShowDeactivateModal(false)} onConfirm={handleConfirmDeactivate} /> )}
); }; export default LicencePage;