/** * ========================================================================= * PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0 * Composant : Paramètres du compte client (Onglet "Mon compte") * ========================================================================= */ const { useState, useEffect, useRef } = React; window.ClientAccountTab = ({ myClientData, data, isCollaborator, uiMode, showToast, refreshData, setArchiveConfig, openTeamManage }) => { const { Building, Lock, ShieldCheck, Mail, Users, Bell, Power, AlertTriangle, Download, Trash2, CheckCircle, Loader, Zap, X, FileText, RefreshCw } = window; const [isSaving, setIsSaving] = useState(false); // États locaux des formulaires const [profileData, setProfileData] = useState({ type_client: 'entreprise', employees_gt_5: false, name: '', address: '', siret: '', tva: '', monthlyReport: 0, two_factor_method: 'none' }); const [hasTva, setHasTva] = useState(true); const [passwordData, setPasswordData] = useState({ oldPassword: '', newPassword: '' }); const [emailChangeData, setEmailChangeData] = useState({ step: 'input', email: '', code: '' }); const [totpSetupData, setTotpSetupData] = useState(null); const [pushEnabled, setPushEnabled] = useState(localStorage.getItem('eco_push_enabled') === 'true' && window.Notification?.permission === 'granted'); // États des modales locales const [showPushTutorial, setShowPushTutorial] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [promptDialog, setPromptDialog] = useState(null); const [legalModal, setLegalModal] = useState(null); const profileLoadedRef = useRef(false); useEffect(() => { if (myClientData.id && !profileLoadedRef.current) { const isTvaExempt = myClientData.tva === 'NON_ASSUJETTI'; setHasTva(!isTvaExempt); setProfileData({ type_client: myClientData.type_client || 'entreprise', employees_gt_5: myClientData.employees_gt_5 == 1 || myClientData.employees_gt_5 === true, name: myClientData.name || '', address: myClientData.address || '', siret: myClientData.siret || '', tva: isTvaExempt ? '' : (myClientData.tva || ''), monthlyReport: myClientData.monthlyReport || 0, two_factor_method: myClientData.two_factor_method || 'none' }); profileLoadedRef.current = true; } }, [myClientData]); const allSimpOptionsActive = data.settings?.simp_opt_description === '1' && data.settings?.simp_opt_image === '1' && data.settings?.simp_opt_theme === '1' && data.settings?.simp_opt_link === '1' && data.settings?.simp_opt_emergency === '1' && data.settings?.simp_opt_schedule === '1'; const isAdvancedMode = uiMode === 'professionnel' || allSimpOptionsActive; // --- LOGIQUE MÉTIER --- const handleAutoFill = () => { setPromptDialog({ title: "Saisie automatique", message: "Saisissez votre numéro SIRET (14 chiffres) ou SIREN (9 chiffres) :", placeholder: "N° SIREN ou SIRET", confirmText: "Rechercher", onConfirm: async (num) => { const cleanNum = num.replace(/\D/g, ''); if (cleanNum.length !== 9 && cleanNum.length !== 14) return showToast("Le numéro doit contenir exactement 9 ou 14 chiffres.", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'system/sirene', { method: 'POST', body: JSON.stringify({ q: cleanNum }) }); const responseData = await res.json(); if (responseData.status !== 'success') throw new Error(responseData.message); const apiData = responseData.data; if (apiData.results && apiData.results.length > 0) { const company = apiData.results[0]; const expectedSiren = cleanNum.substring(0, 9); if (company.siren !== expectedSiren) { setIsSaving(false); return showToast("Numéro invalide.", "error"); } const siege = company.siege || {}; let foundSiret = siege.siret || company.siren + "00010"; if (cleanNum.length === 14) { if (siege.siret === cleanNum) foundSiret = cleanNum; else if (company.matching_etablissements) { const exactEtab = company.matching_etablissements.find(e => e.siret === cleanNum); if (exactEtab) foundSiret = cleanNum; } } const addressParts = [siege.numero_voie, siege.indice_repetition, siege.type_voie, siege.libelle_voie, siege.code_postal, siege.libelle_commune]; const address = addressParts.filter(Boolean).join(' ').replace(/\s+/g, ' ').trim() || "Adresse non renseignée"; const tvaKey = (12 + 3 * (parseInt(company.siren, 10) % 97)) % 97; const calculatedTva = `FR${tvaKey.toString().padStart(2, '0')}${company.siren}`; setProfileData({ ...profileData, type_client: 'entreprise', name: company.nom_complet, address: address, siret: foundSiret, tva: calculatedTva }); setHasTva(true); showToast("Informations récupérées !", "success"); } else showToast("Aucune entreprise trouvée.", "error"); } catch (err) { showToast("Le service est indisponible.", "error"); } setIsSaving(false); } }); }; const handleToggleTva = () => { if (isCollaborator) return; setHasTva(!hasTva); }; const handleSaveProfile = async () => { if(!profileData.name.trim() || !profileData.address.trim()) return showToast("Veuillez remplir le nom et l'adresse.", "error"); if (profileData.type_client === 'entreprise' && !profileData.siret.trim()) return showToast("Veuillez remplir le SIRET.", "error"); let cleanTva = 'NON_ASSUJETTI'; if (profileData.type_client === 'entreprise' && hasTva) { if (!profileData.tva || profileData.tva.trim() === '') return showToast("Veuillez renseigner votre numéro de TVA.", "error"); cleanTva = profileData.tva.replace(/\s+/g, '').toUpperCase(); if (!/^[A-Z]{2}[A-Z0-9]{2,12}$/.test(cleanTva)) return showToast("Format de TVA invalide.", "error"); } setIsSaving(true); try { const payload = { ...profileData, tva: cleanTva, uiMode: uiMode }; const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/profile/update', { method: 'POST', body: JSON.stringify(payload) }); if((await res.json()).status === 'success') { setProfileData({ ...profileData, tva: profileData.type_client === 'entreprise' && hasTva ? cleanTva : '' }); showToast("Profil mis à jour !"); refreshData(); } } finally { setIsSaving(false); } }; const handleRequestEmailChange = async (e) => { if (e) e.preventDefault(); const newEmail = emailChangeData.email.trim(); if (!newEmail || !newEmail.includes('@')) return showToast("E-mail invalide.", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/profile/request_email', { method: 'POST', body: JSON.stringify({ email: newEmail }) }); const d = await res.json(); if (d.status === 'success') { showToast("Un code de vérification a été envoyé à cette nouvelle adresse.", "info"); setEmailChangeData(prev => ({ ...prev, step: 'verify' })); } else { showToast(d.message, 'error'); } } catch(err) { showToast("Erreur réseau.", "error"); } setIsSaving(false); }; const handleConfirmEmailChange = async (e) => { e.preventDefault(); const code = emailChangeData.code.trim(); if (!code) return showToast("Code manquant.", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/profile/update_email', { method: 'POST', body: JSON.stringify({ code }) }); const d = await res.json(); if (d.status === 'success') { showToast("E-mail mis à jour avec succès !", "success"); setEmailChangeData({ step: 'input', email: '', code: '' }); refreshData(); } else { showToast(d.message, 'error'); } } catch(err) { showToast("Erreur réseau.", "error"); } setIsSaving(false); }; const handleSavePassword = async () => { if(!passwordData.oldPassword || !passwordData.newPassword) return showToast("Champs manquants", "error"); setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/password/update', { method: 'POST', body: JSON.stringify({ oldPassword: passwordData.oldPassword, password: passwordData.newPassword }) }); const d = await res.json(); if(d.status === 'success') { showToast("Mot de passe mis à jour"); setPasswordData({oldPassword:'', newPassword:''}); } else showToast(d.message, 'error'); } finally { setIsSaving(false); } }; const requestAccountDeletion = async (e) => { e.preventDefault(); const pwd = e.target.password.value; if (!pwd) return; setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/request_delete', { method: 'POST', body: JSON.stringify({ password: pwd }) }); const d = await res.json(); if (d.status === 'success') { showToast("E-mail de confirmation envoyé.", "info"); setShowDeleteModal(false); } else { showToast(d.message, 'error'); } } catch(e) { showToast("Erreur", "error"); } setIsSaving(false); }; const togglePush = () => { if (pushEnabled) { localStorage.setItem('eco_push_enabled', 'false'); setPushEnabled(false); showToast("Notifications web désactivées.", "info"); } else { if (!window.Notification) return showToast("Votre navigateur ne supporte pas les notifications.", "error"); Notification.requestPermission().then(perm => { if (perm === 'granted') { localStorage.setItem('eco_push_enabled', 'true'); setPushEnabled(true); showToast("Notifications activées !", "success"); } else if (perm === 'denied') { setShowPushTutorial(true); } }); } }; const enableTotp = async () => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/profile/setup_totp', { method: 'POST' }); const d = await res.json(); if (d.status === 'success') { setTotpSetupData(d.data); } else { showToast(d.message, 'error'); } } catch(e) { showToast("Erreur réseau", "error"); } setIsSaving(false); }; const confirmTotp = async () => { setIsSaving(true); try { const res = await fetch(window.ECO_CONFIG.apiBaseUrl + 'clients/profile/update_2fa', { method: 'POST', body: JSON.stringify({ method: 'totp' }) }); if ((await res.json()).status === 'success') { setProfileData({...profileData, two_factor_method: 'totp'}); setTotpSetupData(null); showToast("2FA par application activée avec succès !", "success"); } } catch(e) {} setIsSaving(false); }; // --- RENDU JSX --- return (
Paramètres de profil et sécurité.
Protégez votre compte avec une étape de sécurité supplémentaire lors de la connexion.
Gérez les accès de votre équipe à vos panneaux.
Consultez nos conditions d'utilisation et notre politique de confidentialité.
Recevez une alerte visuelle et sonore sur votre PC/Mobile dès qu'un riverain poste un message.
La suppression de votre compte effacera définitivement tous vos panneaux, factures et fichiers (D.O.E inclus).
{myClientData.wallet_balance < 0 ? (Scannez ce QR Code avec votre application d'authentification (Google Authenticator, Authy, etc.) :
Ou saisissez cette clé manuellement :
{totpSetupData.secret}
Cliquez sur l'icône de cadenas à gauche de la barre d'adresse URL, puis cherchez la ligne "Notifications" et passez-la sur "Autoriser". Rechargez ensuite la page.
Allez dans le menu Safari > Réglages > Sites web > Notifications. Cherchez "eco-panneau.fr" dans la liste et sélectionnez "Autoriser".
Appuyez sur l'icône cadenas ou menu dans la barre d'adresse > Paramètres du site > Notifications > Autoriser.
Les notifications sur iOS nécessitent que vous "Ajoutiez le site sur l'écran d'accueil" (bouton Partager > Sur l'écran d'accueil). Lancez ensuite l'application depuis votre écran d'accueil pour réactiver l'alerte.
Avant de partir