feat(ucp): NexusPBX v2.5.0 Portail Utilisateur isolé (Refs #4)

This commit is contained in:
sorcier committed 2026-11-11 18:47:25 +01:00
1 parent 7922e338e3
commit 72e43f1a9a
18 files changed
+3499 -62

No files matched your search

+414
View File
@@ -0,0 +1,414 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../src/Database.php';
require_once __DIR__ . '/../src/FirewallManager.php';
require_once __DIR__ . '/../src/UcpAuth.php';
use App\Database;
use App\UcpAuth;
$db = Database::getConnection();
$ucpEnabled = UcpAuth::isUcpEnabled($db);
$currentUser = $ucpEnabled ? UcpAuth::getCurrentUser($db) : null;
$isAuth = ($currentUser !== null);
$branding = $db->query('SELECT company_name, company_logo FROM settings WHERE id = 1 LIMIT 1')->fetch(\PDO::FETCH_ASSOC) ?: [];
$companyName = trim((string)($branding['company_name'] ?? ''));
$companyLogo = trim((string)($branding['company_logo'] ?? ''));
$appVerData = file_exists(__DIR__ . '/../version.json') ? json_decode((string)file_get_contents(__DIR__ . '/../version.json'), true) : [];
$appVersion = $appVerData['version'] ?? '2.5.0';
$csrfToken = UcpAuth::getCsrfToken();
?>
<!DOCTYPE html>
<html lang="fr" class="h-full bg-slate-50">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($companyName ?: 'NexusPBX') ?> — Portail Utilisateur (UCP)</title>
<link rel="icon" type="image/png" href="/assets/favicon.png?v=<?= file_exists(__DIR__ . '/assets/favicon.png') ? filemtime(__DIR__ . '/assets/favicon.png') : 1 ?>">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
}
}
}
};
</script>
<style>
body { background-color: #f8fafc; color: #0f172a; }
.ucp-card { background: #ffffff; border: 1px solid #e2e8f0; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04); }
::-webkit-scrollbar { width: 7px; height: 7px; }
::-webkit-scrollbar-track { background: #f1f5f9; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body class="min-h-full font-sans antialiased text-slate-800">
<!-- Toast Container -->
<div id="ucp-toast-container" class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm w-full pointer-events-none px-4 sm:px-0"></div>
<?php if (!$ucpEnabled): ?>
<div class="min-h-screen flex items-center justify-center p-6">
<div class="max-w-md w-full ucp-card rounded-2xl p-8 text-center space-y-3">
<div class="w-12 h-12 mx-auto rounded-xl bg-amber-50 border border-amber-200 flex items-center justify-center text-amber-600 text-2xl">🔒</div>
<h1 class="text-lg font-bold text-slate-900">Portail Utilisateur désactivé</h1>
<p class="text-sm text-slate-500">Ce service a été temporairement désactivé par votre administrateur système.</p>
</div>
</div>
<?php elseif (!$isAuth): ?>
<!-- ÉCRAN DE CONNEXION UCP (THÈME CLAIR ÉPURÉ) -->
<div class="min-h-screen flex flex-col items-center justify-center p-4 bg-gradient-to-br from-slate-50 via-blue-50/40 to-indigo-50/50">
<div class="w-full max-w-md ucp-card rounded-3xl p-8 shadow-xl shadow-slate-200/60 border border-slate-200/80">
<div class="flex flex-col items-center text-center mb-7">
<?php if ($companyLogo !== ''): ?>
<img src="<?= htmlspecialchars($companyLogo) ?>" alt="Logo" class="h-14 w-auto object-contain mb-3 rounded-xl">
<?php else: ?>
<img src="/assets/logo.png?v=1" alt="NexusPBX" class="h-14 w-auto object-contain mb-3">
<?php endif; ?>
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-indigo-50 border border-indigo-100 text-indigo-700 text-[11px] font-semibold uppercase tracking-wider mb-2">
Portail Utilisateur • UCP
</span>
<h1 class="text-xl font-bold text-slate-900"><?= htmlspecialchars($companyName ?: 'NexusPBX') ?></h1>
<p class="text-xs text-slate-500 mt-1">Connectez-vous pour gérer vos postes, renvois, messagerie et annuaire</p>
</div>
<form id="ucp-login-form" class="space-y-4">
<div>
<label for="ucp-login-user" class="block text-xs font-semibold text-slate-700 mb-1.5">Identifiant utilisateur</label>
<input type="text" id="ucp-login-user" name="username" required autocomplete="username" autofocus
placeholder="ex: fred ou fred@entreprise.fr"
class="w-full px-3.5 py-2.5 rounded-xl bg-slate-50 border border-slate-300 text-sm text-slate-900 focus:bg-white focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none transition">
</div>
<div>
<label for="ucp-login-pass" class="block text-xs font-semibold text-slate-700 mb-1.5">Mot de passe</label>
<input type="password" id="ucp-login-pass" name="password" required autocomplete="current-password"
placeholder="••••••••"
class="w-full px-3.5 py-2.5 rounded-xl bg-slate-50 border border-slate-300 text-sm text-slate-900 focus:bg-white focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none transition">
</div>
<div id="ucp-login-error" class="hidden rounded-xl bg-rose-50 border border-rose-200 px-3.5 py-2.5 text-xs font-medium text-rose-700"></div>
<button type="submit" id="ucp-login-btn"
class="w-full py-2.5 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800 text-white text-sm font-semibold shadow-sm shadow-indigo-500/20 transition">
Se connecter au portail
</button>
</form>
<div class="mt-6 pt-5 border-t border-slate-100 text-center text-[11px] text-slate-400">
Espace utilisateur isolé • Protégé par Fail2ban • NexusPBX v<?= htmlspecialchars($appVersion) ?>
</div>
</div>
</div>
<?php else: ?>
<!-- INTERFACE PRINCIPALE UCP -->
<div class="min-h-screen flex flex-col">
<!-- Top Navbar -->
<header class="sticky top-0 z-30 bg-white/95 backdrop-blur border-b border-slate-200">
<div class="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between gap-4">
<div class="flex items-center gap-3 min-w-0">
<?php if ($companyLogo !== ''): ?>
<img src="<?= htmlspecialchars($companyLogo) ?>" alt="Logo" class="h-9 w-auto rounded-lg object-contain shrink-0">
<?php else: ?>
<img src="/assets/logo.png?v=1" alt="NexusPBX" class="h-9 w-auto object-contain shrink-0">
<?php endif; ?>
<div class="min-w-0">
<div class="flex items-center gap-2">
<span class="font-bold text-slate-900 text-sm sm:text-base truncate"><?= htmlspecialchars($companyName ?: 'NexusPBX') ?></span>
<span class="hidden sm:inline-flex px-2 py-0.5 rounded-full bg-indigo-50 text-indigo-700 border border-indigo-100 text-[10px] font-bold uppercase tracking-wide">UCP</span>
</div>
<p class="text-[11px] text-slate-500 truncate">Espace personnel de <strong class="text-slate-700"><?= htmlspecialchars((string)($currentUser['display_name'] ?: $currentUser['username'])) ?></strong></p>
</div>
</div>
<div class="flex items-center gap-2 shrink-0">
<button type="button" onclick="refreshUcpState(true)" title="Actualiser"
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-slate-200 bg-white hover:bg-slate-50 text-xs font-semibold text-slate-600 transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
<span class="hidden sm:inline">Actualiser</span>
</button>
<button type="button" onclick="ucpLogout()"
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-rose-200 bg-rose-50 hover:bg-rose-100 text-xs font-semibold text-rose-700 transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
<span>Déconnexion</span>
</button>
</div>
</div>
<!-- Navigation Onglets -->
<div class="max-w-6xl mx-auto px-4 sm:px-6 border-t border-slate-100 overflow-x-auto">
<nav class="flex items-center gap-1 py-2 whitespace-nowrap" id="ucp-nav">
<button type="button" data-ucp-tab="extensions" onclick="switchUcpTab('extensions')"
class="ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 bg-indigo-50 text-indigo-700">
<span>📱 Mes Postes &amp; Renvois</span>
<span id="ucp-badge-exts" class="px-1.5 py-0.5 rounded-full bg-indigo-100 text-indigo-700 text-[10px] font-bold">0</span>
</button>
<button type="button" data-ucp-tab="voicemail" onclick="switchUcpTab('voicemail')"
class="ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 text-slate-600 hover:bg-slate-100">
<span>📬 Messagerie Vocale</span>
<span id="ucp-badge-vm" class="px-1.5 py-0.5 rounded-full bg-slate-200 text-slate-700 text-[10px] font-bold">0</span>
</button>
<button type="button" data-ucp-tab="cdr" onclick="switchUcpTab('cdr')"
class="ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 text-slate-600 hover:bg-slate-100">
<span>📞 Mes Appels</span>
</button>
<button type="button" data-ucp-tab="phonebook" onclick="switchUcpTab('phonebook')"
class="ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 text-slate-600 hover:bg-slate-100">
<span>📖 Annuaire &amp; Profil</span>
</button>
<button type="button" data-ucp-tab="account" onclick="switchUcpTab('account')"
class="ucp-tab-btn px-3.5 py-2 rounded-xl text-xs font-semibold transition flex items-center gap-2 text-slate-600 hover:bg-slate-100">
<span>🔐 Mon Compte</span>
</button>
</nav>
</div>
</header>
<!-- Contenu principal -->
<main class="flex-1 max-w-6xl w-full mx-auto px-4 sm:px-6 py-6 space-y-6">
<!-- ONGLET 1 : MES POSTES & RENVOIS -->
<section id="ucp-panel-extensions" class="ucp-panel space-y-5">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-slate-900">Mes Postes &amp; Redirections</h2>
<p class="text-xs text-slate-500">Activez le mode Ne Pas Déranger (DND) ou configurez vos renvois vers un autre poste ou votre messagerie vocale.</p>
</div>
</div>
<div id="ucp-extensions-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div class="ucp-card rounded-2xl p-8 text-center text-sm text-slate-400 col-span-full">Chargement de vos postes…</div>
</div>
</section>
<!-- ONGLET 2 : MESSAGERIE VOCALE -->
<section id="ucp-panel-voicemail" class="ucp-panel hidden space-y-5">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-slate-900">Messagerie Vocale</h2>
<p class="text-xs text-slate-500">Écoutez, téléchargez, archivez ou supprimez vos messages vocaux et gérez votre code PIN.</p>
</div>
<div id="ucp-vm-ext-pills" class="flex flex-wrap gap-2"></div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Liste des messages -->
<div class="lg:col-span-2 space-y-4">
<div class="flex items-center justify-between">
<div class="inline-flex rounded-xl bg-white border border-slate-200 p-1 text-xs font-semibold">
<button type="button" onclick="setVmFolderFilter('all')" id="vm-filter-all" class="px-3 py-1.5 rounded-lg bg-indigo-50 text-indigo-700">Tous</button>
<button type="button" onclick="setVmFolderFilter('INBOX')" id="vm-filter-INBOX" class="px-3 py-1.5 rounded-lg text-slate-600 hover:bg-slate-50">Nouveaux</button>
<button type="button" onclick="setVmFolderFilter('Old')" id="vm-filter-Old" class="px-3 py-1.5 rounded-lg text-slate-600 hover:bg-slate-50">Archivés</button>
</div>
</div>
<div id="ucp-vm-list" class="space-y-3">
<div class="ucp-card rounded-2xl p-8 text-center text-sm text-slate-400">Chargement des messages…</div>
</div>
</div>
<!-- Préférences Boîte Vocale -->
<div class="ucp-card rounded-2xl p-5 h-fit space-y-4">
<div>
<h3 class="text-sm font-bold text-slate-900">Préférences Boîte Vocale</h3>
<p id="ucp-vm-prefs-subtitle" class="text-xs text-slate-500 mt-0.5">Poste sélectionné</p>
</div>
<form id="ucp-vm-prefs-form" class="space-y-3">
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">Nouveau code PIN (4 à 12 chiffres)</label>
<input type="password" name="pin" inputmode="numeric" pattern="\d{4,12}" placeholder="Laisser vide pour conserver l'actuel"
class="w-full px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 font-mono focus:bg-white focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">E-mail de notification</label>
<input type="email" name="email" placeholder="prenom@entreprise.fr"
class="w-full px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none">
</div>
<button type="submit" id="ucp-vm-prefs-btn"
class="w-full py-2 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold transition">
Enregistrer les préférences
</button>
</form>
</div>
</div>
</section>
<!-- ONGLET 3 : MES APPELS (MINI-CDR PERSONNEL) -->
<section id="ucp-panel-cdr" class="ucp-panel hidden space-y-5">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-slate-900">Mon Journal d'Appels</h2>
<p class="text-xs text-slate-500">Historique filtré sur vos postes liés uniquement.</p>
</div>
<div class="flex flex-wrap items-center gap-2 text-xs">
<span class="px-3 py-1 rounded-full bg-white border border-slate-200 font-semibold text-slate-700">Total : <strong id="ucp-cdr-total">0</strong></span>
<span class="px-3 py-1 rounded-full bg-emerald-50 border border-emerald-200 font-semibold text-emerald-700">Répondus : <strong id="ucp-cdr-answered">0</strong></span>
<span class="px-3 py-1 rounded-full bg-rose-50 border border-rose-200 font-semibold text-rose-700">Manqués : <strong id="ucp-cdr-missed">0</strong></span>
</div>
</div>
<div class="ucp-card rounded-2xl p-4 grid grid-cols-1 sm:grid-cols-4 gap-3">
<select id="ucp-cdr-direction" onchange="loadPersonalCdr()" class="px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs font-medium text-slate-800">
<option value="all">Toutes directions</option>
<option value="inbound">Appels entrants</option>
<option value="outbound">Appels sortants</option>
<option value="internal">Appels internes</option>
</select>
<select id="ucp-cdr-disposition" onchange="loadPersonalCdr()" class="px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs font-medium text-slate-800">
<option value="all">Tous statuts</option>
<option value="ANSWERED">Répondus uniquement</option>
<option value="MISSED">Manqués / Sans réponse</option>
</select>
<select id="ucp-cdr-period" onchange="loadPersonalCdr()" class="px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs font-medium text-slate-800">
<option value="all">Tout l'historique</option>
<option value="today">Aujourd'hui</option>
<option value="week">7 derniers jours</option>
<option value="month">30 derniers jours</option>
</select>
<input type="text" id="ucp-cdr-search" oninput="debounceLoadCdr()" placeholder="Rechercher un numéro ou nom…"
class="px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-indigo-500">
</div>
<div class="ucp-card rounded-2xl overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-left text-xs">
<thead class="bg-slate-50 border-b border-slate-200 text-slate-500 uppercase text-[11px]">
<tr>
<th class="py-3 px-4">Date &amp; Heure</th>
<th class="py-3 px-4">Direction</th>
<th class="py-3 px-4">De</th>
<th class="py-3 px-4">Vers</th>
<th class="py-3 px-4">Durée</th>
<th class="py-3 px-4">Statut</th>
</tr>
</thead>
<tbody id="ucp-cdr-tbody" class="divide-y divide-slate-100">
<tr><td colspan="6" class="py-8 text-center text-slate-400">Chargement…</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ONGLET 4 : ANNUAIRE & PROFIL -->
<section id="ucp-panel-phonebook" class="ucp-panel hidden space-y-5">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 class="text-lg font-bold text-slate-900">Annuaire de l'Entreprise</h2>
<p class="text-xs text-slate-500">Consultez tous les contacts et mettez à jour la photo ou l'e-mail de vos propres postes.</p>
</div>
<input type="text" id="ucp-pb-search" oninput="renderPhonebookGrid()" placeholder="Rechercher un nom, poste, email…"
class="w-full sm:w-72 px-3.5 py-2 rounded-xl bg-white border border-slate-300 text-xs text-slate-900 focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 focus:outline-none">
</div>
<div id="ucp-pb-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="ucp-card rounded-2xl p-8 text-center text-sm text-slate-400 col-span-full">Chargement de l'annuaire…</div>
</div>
</section>
<!-- ONGLET 5 : MON COMPTE -->
<section id="ucp-panel-account" class="ucp-panel hidden space-y-5">
<div>
<h2 class="text-lg font-bold text-slate-900">Mon Compte UCP</h2>
<p class="text-xs text-slate-500">Sécurisez votre accès personnel au portail utilisateur.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="ucp-card rounded-2xl p-6 space-y-4">
<h3 class="text-sm font-bold text-slate-900">Informations de session</h3>
<dl class="space-y-2.5 text-xs">
<div class="flex justify-between py-2 border-b border-slate-100">
<dt class="text-slate-500">Identifiant</dt>
<dd class="font-mono font-semibold text-slate-900"><?= htmlspecialchars((string)$currentUser['username']) ?></dd>
</div>
<div class="flex justify-between py-2 border-b border-slate-100">
<dt class="text-slate-500">Nom affiché</dt>
<dd class="font-semibold text-slate-900"><?= htmlspecialchars((string)($currentUser['display_name'] ?: $currentUser['username'])) ?></dd>
</div>
<div class="flex justify-between py-2 border-b border-slate-100">
<dt class="text-slate-500">Postes autorisés</dt>
<dd id="ucp-account-exts" class="font-mono font-semibold text-indigo-600">—</dd>
</div>
<div class="flex justify-between py-2">
<dt class="text-slate-500">Dernière connexion</dt>
<dd class="text-slate-700"><?= htmlspecialchars((string)($currentUser['last_login_at'] ?? 'Première connexion')) ?></dd>
</div>
</dl>
</div>
<div class="ucp-card rounded-2xl p-6 space-y-4">
<h3 class="text-sm font-bold text-slate-900">Changer mon mot de passe UCP</h3>
<form id="ucp-password-form" class="space-y-3">
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">Mot de passe actuel</label>
<input type="password" name="current_password" required autocomplete="current-password"
class="w-full px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-indigo-500">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">Nouveau mot de passe (min. 6 caractères)</label>
<input type="password" name="new_password" required minlength="6" autocomplete="new-password"
class="w-full px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-indigo-500">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">Confirmer le nouveau mot de passe</label>
<input type="password" name="confirm_password" required minlength="6" autocomplete="new-password"
class="w-full px-3 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-indigo-500">
</div>
<button type="submit" class="w-full py-2.5 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold transition">
Mettre à jour mon mot de passe
</button>
</form>
</div>
</div>
</section>
</main>
</div>
<!-- MODALE ÉDITION PROFIL ANNUAIRE (PHOTO + EMAIL UNIQUEMENT) -->
<div id="ucp-profile-modal" class="hidden fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-sm flex items-center justify-center p-4">
<div class="w-full max-w-md bg-white rounded-2xl border border-slate-200 shadow-2xl p-6 space-y-5">
<div class="flex items-center justify-between">
<div>
<h3 class="text-base font-bold text-slate-900">Modifier ma fiche annuaire</h3>
<p id="ucp-profile-modal-sub" class="text-xs text-slate-500">Poste —</p>
</div>
<button type="button" onclick="closeProfileModal()" class="text-slate-400 hover:text-slate-700 text-lg font-bold">✕</button>
</div>
<div class="flex items-center gap-4 p-3.5 rounded-xl bg-slate-50 border border-slate-200">
<img id="ucp-profile-preview" src="" alt="Avatar" class="w-14 h-14 rounded-full object-cover border border-slate-200 bg-white hidden">
<div id="ucp-profile-initials" class="w-14 h-14 rounded-full bg-indigo-100 text-indigo-700 font-bold flex items-center justify-center text-lg">?</div>
<div class="flex-1 min-w-0">
<label class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white border border-slate-300 hover:bg-slate-100 text-xs font-semibold text-slate-700 cursor-pointer transition">
📷 Choisir une photo
<input type="file" id="ucp-avatar-input" accept="image/jpeg,image/png,image/webp" class="hidden" onchange="uploadSelectedAvatar()">
</label>
<p class="text-[11px] text-slate-400 mt-1">JPG, PNG ou WebP • 5 Mo max.</p>
</div>
</div>
<form id="ucp-profile-email-form" class="space-y-3">
<input type="hidden" id="ucp-profile-ext" value="">
<div>
<label class="block text-xs font-semibold text-slate-700 mb-1">Adresse e-mail affichée dans l'annuaire</label>
<input type="email" id="ucp-profile-email-input" placeholder="prenom.nom@entreprise.fr"
class="w-full px-3.5 py-2 rounded-xl bg-slate-50 border border-slate-300 text-xs text-slate-900 focus:bg-white focus:outline-none focus:border-indigo-500">
</div>
<div class="flex gap-2 pt-2">
<button type="button" onclick="closeProfileModal()" class="flex-1 py-2 px-4 rounded-xl border border-slate-200 text-xs font-semibold text-slate-600 hover:bg-slate-50">Fermer</button>
<button type="submit" class="flex-1 py-2 px-4 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold">Enregistrer l'e-mail</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<script>
window.UCP_CSRF_TOKEN = <?= json_encode($csrfToken) ?>;
window.UCP_IS_AUTH = <?= json_encode($isAuth) ?>;
</script>
<script src="/assets/ucp.js?v=<?= file_exists(__DIR__ . '/assets/ucp.js') ? filemtime(__DIR__ . '/assets/ucp.js') : time() ?>"></script>
</body>
</html>