Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -0,0 +1,616 @@
|
||||
<?php
|
||||
$lang = $_SESSION['lang'] ?? 'fr';
|
||||
|
||||
$PAGE_TITLE = $PAGE_TITLE ?? ($translations['site_name'] ?? 'uslugi.fr');
|
||||
$HEAD_EXTRA = $HEAD_EXTRA ?? '';
|
||||
|
||||
$initiales = $initiales ?? ($GLOBALS['initiales'] ?? 'U');
|
||||
$avatarSvg = $avatar_svg ?? ($GLOBALS['avatar_svg'] ?? null);
|
||||
$pushCfg = $pushCfg ?? ($GLOBALS['pushCfg'] ?? []);
|
||||
$alerte_abonnement = $alerte_abonnement ?? ($GLOBALS['alerte_abonnement'] ?? null);
|
||||
|
||||
require_once __DIR__ . '/../contact_admin.php';
|
||||
|
||||
// =====================================================
|
||||
// [RETOUR / REFERER SÉCURISÉ]
|
||||
// - Autorise le retour seulement si le referer est sur le même host
|
||||
// =====================================================
|
||||
$ref = $_SERVER['HTTP_REFERER'] ?? '';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$safeRef = '';
|
||||
if ($ref) {
|
||||
$u = parse_url($ref);
|
||||
if (!empty($u['host']) && $u['host'] === $host) {
|
||||
$safeRef = $ref;
|
||||
}
|
||||
}
|
||||
$fallbackBack = $safeRef ?: '/';
|
||||
|
||||
// =====================================================
|
||||
// [DETECTION MOBILE]
|
||||
// - Très simple détection UA pour le logo adaptatif
|
||||
// =====================================================
|
||||
$is_mobile = preg_match('/Mobile|Android|iPhone|iPad/i', $_SERVER['HTTP_USER_AGENT']);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<title><?= htmlspecialchars($PAGE_TITLE, ENT_QUOTES, 'UTF-8') ?></title>
|
||||
<?= $HEAD_EXTRA ?>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
|
||||
<!-- Police Montserrat -->
|
||||
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script defer src="/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
|
||||
<!-- Styles personnalisés -->
|
||||
<link rel="stylesheet" href="<?= asset('/includes/site_style.css') ?>">
|
||||
<link rel="stylesheet" href="<?= asset('/includes/custom_style.css') ?>"> <!-- ta home -->
|
||||
<link rel="stylesheet" href="<?= asset('/includes/mobil_style.css') ?>">
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
// Pour n'afficher qu'une fois par compte (PWA)
|
||||
$pushOnboardKey = 'uslugi_push_onboard_v1_' . (int)($_SESSION['compte_id'] ?? 0);
|
||||
|
||||
// helper traduction (si tu n'as pas déjà $t ici)
|
||||
$t = function(string $k, string $fallback = '') use (&$translations): string {
|
||||
$val = $translations[$k] ?? null;
|
||||
return ($val === null || $val === '') ? $fallback : (string)$val;
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="modal fade" id="pushOnboardModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<?= htmlspecialchars($t('push_onboard_title', 'Activer les notifications ?'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="<?= htmlspecialchars($t('push_onboard_close_aria', 'Fermer'), ENT_QUOTES, 'UTF-8') ?>">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<?= htmlspecialchars($t('push_onboard_body', 'Vous recevrez des alertes pour les nouveaux messages et rendez-vous.'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
id="pushLaterBtn">
|
||||
<?= htmlspecialchars($t('push_onboard_later', 'Plus tard'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
id="pushEnableBtn">
|
||||
<?= htmlspecialchars($t('push_onboard_allow', 'Autoriser'), ENT_QUOTES, 'UTF-8') ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ================================================
|
||||
[HEADER / BARRE DE NAVIGATION]
|
||||
- Bouton retour sécurisé vers $fallbackBack
|
||||
- Logo adaptatif mobile/desktop
|
||||
- Icône messages avec badge non lus
|
||||
- Menu principal + sous-menu Admin
|
||||
- Menu "?" compact (aide)
|
||||
================================================ -->
|
||||
<header class="site-header py-2 border-bottom">
|
||||
|
||||
|
||||
<?php if (!empty($_SESSION['admin_impersonate'])): ?>
|
||||
<div class="alert alert-warning text-center mb-0 rounded-0">
|
||||
Вы вошли как другой пользователь.
|
||||
<a href="/admin/tools/retour_admin.php"
|
||||
class="btn btn-sm btn-danger ms-2"
|
||||
style="border-radius:999px;">
|
||||
Вернуться в режим администратора
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $alerte_abonnement = $GLOBALS['alerte_abonnement'] ?? null; ?>
|
||||
<?php if (!empty($alerte_abonnement)): ?>
|
||||
<div class="alert alert-warning m-0 py-1 px-2 rounded"
|
||||
role="alert"
|
||||
style="background:#F1F1F1; color:#E03C3F; font-size:14px; text-align:center;">
|
||||
<?= htmlspecialchars((string)$alerte_abonnement, ENT_QUOTES, 'UTF-8') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="container d-flex justify-content-between align-items-center">
|
||||
|
||||
<!-- Marque + Bouton retour + Logo -->
|
||||
<div class="header-left">
|
||||
<div class="brand">
|
||||
<!-- Bouton retour -->
|
||||
<a
|
||||
href="<?= htmlspecialchars($fallbackBack, ENT_QUOTES, 'UTF-8') ?>"
|
||||
id="btnBack"
|
||||
class="btn btn-light btn-circle me-2"
|
||||
style="border-radius:5px; box-shadow:0 1px 2px rgba(0,0,0,.08);"
|
||||
aria-label="<?= htmlspecialchars($translations['retour'] ?? 'Retour', ENT_QUOTES, 'UTF-8') ?>"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M15 6l-6 6 6 6"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Logo -->
|
||||
<a href="/index.php">
|
||||
<img src="/public/uploads/img/logo_u.png"
|
||||
alt="uslugi.fr"
|
||||
class="logo-mobile">
|
||||
|
||||
<img src="/public/uploads/img/logo.png"
|
||||
alt="uslugi.fr"
|
||||
class="logo-desktop">
|
||||
</a>
|
||||
</div>
|
||||
<div class="lang-switch">
|
||||
<?php require __DIR__ . '/../../includes/menu_langue.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Actions header (avatar, messages, menu, aide) -->
|
||||
<nav class="header-actions d-flex align-items-center gap-2 position-relative">
|
||||
|
||||
|
||||
<!-- RDV (+ badge non lus) -->
|
||||
<a
|
||||
id="btn-rdv"
|
||||
href="<?= ($_SESSION['role'] ?? '') === 'praticien'
|
||||
? '/public/praticien/rendez_vous.php'
|
||||
: '/public/client/mes_rendez_vous.php' ?>"
|
||||
class="nav-rdv position-relative"
|
||||
>
|
||||
<img src="/public/uploads/img/bell-on.png" width="22" alt="Rendez-vous">
|
||||
<span
|
||||
id="rdv-badge"
|
||||
style="position:absolute; top:-6px; right:-8px; background:#e11d48; color:#fff;
|
||||
font-size:12px; min-width:18px; height:18px; line-height:18px;
|
||||
text-align:center; border-radius:999px; padding:0 5px; display:none;"
|
||||
>0</span>
|
||||
</a>
|
||||
|
||||
|
||||
<!-- Messages (+ badge non lus) -->
|
||||
<a href="/messages/" class="nav-msg position-relative">
|
||||
<img src="/public/uploads/img/message.png" width="20" alt="Messages">
|
||||
<span
|
||||
id="msg-badge"
|
||||
style="position:absolute; top:-6px; right:-8px; background:#e11d48; color:#fff;
|
||||
font-size:12px; min-width:18px; height:18px; line-height:18px;
|
||||
text-align:center; border-radius:999px; padding:0 5px; display:none;"
|
||||
>0</span>
|
||||
</a>
|
||||
|
||||
<!-- Menu principal -->
|
||||
<div class="menu-wrap">
|
||||
<button type="button" id="btnMenu">
|
||||
<div class="avatar"><?= $initiales ?></div>
|
||||
</button>
|
||||
|
||||
<!-- Menu principal (liste) -->
|
||||
<ul class="dropdown-menu-custom menu-panel" id="menu">
|
||||
<?php if ($_SESSION['role'] === 'client'): ?>
|
||||
<li onclick="location.href='/public/client/mes_rendez_vous.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/client/list_praticien.php'">
|
||||
<?= htmlspecialchars($translations['menu_liste_specialistes']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/client/mes_favori.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_favoris']) ?>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li onclick="location.href='/public/praticien/rendez_vous.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_rendez_vous']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/agenda.php'">
|
||||
<?= htmlspecialchars($translations['menu_mon_agenda']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/disponibilites.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_disponibilites']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/synchronisation.php'">
|
||||
<?= htmlspecialchars($translations['menu_sync_agendas']) ?>
|
||||
</li>
|
||||
<!--<li onclick="location.href='/public/praticien/clients/index.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
</li>-->
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Sous-menu Admin -->
|
||||
<li class="has-submenu">
|
||||
<button
|
||||
type="button"
|
||||
class="submenu-toggle"
|
||||
aria-expanded="false"
|
||||
aria-controls="submenu-admin"
|
||||
>
|
||||
<?= htmlspecialchars($translations['menu_admin']) ?>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
||||
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<ul id="submenu-admin" class="submenu" hidden>
|
||||
<?php if ($_SESSION['role'] === 'praticien'): ?>
|
||||
<li onclick="location.href='/public/praticien/resiliation.php'">
|
||||
<?= htmlspecialchars($translations['menu_gerer_abonnement']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/factures.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_factures']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/cabinet/invitations.php'">
|
||||
<?= htmlspecialchars($translations['secretariat_inviter_secretaire']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/praticien/clients/index.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_clients'] ?? 'Mes clients') ?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li onclick="location.href='/public/<?= $_SESSION['role'] ?>/informations.php'">
|
||||
<?= htmlspecialchars($translations['menu_mes_informations']) ?>
|
||||
</li>
|
||||
|
||||
<li onclick="location.href='/public/<?= $_SESSION['role'] ?>/compte.php'">
|
||||
<?= htmlspecialchars($translations['menu_infos_connexion']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/supprimer_compte.php'">
|
||||
<?= htmlspecialchars($translations['menu_supprimer_compte']) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<!-- Déconnexion -->
|
||||
<li onclick="closeMenu(); openContactAdmin()">
|
||||
<?= htmlspecialchars($translations['contact_admin']) ?>
|
||||
</li>
|
||||
<li onclick="location.href='/public/deconnexion.php'">
|
||||
<?= htmlspecialchars($translations['menu_deconnexion']) ?>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ================================================
|
||||
[SCRIPTS / LOGIQUE CLIENT]
|
||||
- Badge messages non lus (polling)
|
||||
- Ouverture/fermeture du menu principal
|
||||
- Gestion du sous-menu Admin (ARIA)
|
||||
- Bootstrap JS bundle
|
||||
================================================ -->
|
||||
<script>
|
||||
|
||||
(function(){
|
||||
function setBadge(el, n){
|
||||
if (!el) return;
|
||||
n = Number(n || 0);
|
||||
if (n > 0){
|
||||
el.style.display = 'inline-block';
|
||||
el.textContent = String(n > 99 ? '99+' : n);
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
el.textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
async function tickBadges(){
|
||||
try {
|
||||
const r = await fetch('/push/badge_count.php', { credentials:'same-origin', cache:'no-store' });
|
||||
if (!r.ok) return;
|
||||
const j = await r.json();
|
||||
if (!j || !j.ok) return;
|
||||
|
||||
// UI header
|
||||
setBadge(document.getElementById('msg-badge'), j.msg_unread);
|
||||
setBadge(document.getElementById('rdv-badge'), j.rdv_unread);
|
||||
|
||||
// Badge PWA (si supporté)
|
||||
const n = Number(j.app_badge || 0);
|
||||
if ('setAppBadge' in navigator) {
|
||||
try {
|
||||
if (n > 0) await navigator.setAppBadge(n);
|
||||
else if ('clearAppBadge' in navigator) await navigator.clearAppBadge();
|
||||
} catch(_) {}
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
// 1) au chargement
|
||||
tickBadges();
|
||||
|
||||
// 2) refresh (mets 10s au lieu de 5s pour soulager)
|
||||
setInterval(tickBadges, 10000);
|
||||
|
||||
// 3) quand on revient sur l’onglet
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) tickBadges();
|
||||
});
|
||||
|
||||
// optionnel : dispo ailleurs
|
||||
window.refreshBadges = tickBadges;
|
||||
})();
|
||||
|
||||
|
||||
|
||||
// -------------------------------------------------
|
||||
// [MENU PRINCIPAL] — Ouverture / Fermeture
|
||||
// -------------------------------------------------
|
||||
function toggleMenu() {
|
||||
const menu = document.getElementById('menu');
|
||||
if (!menu) return;
|
||||
menu.style.display = (menu.style.display === 'block') ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
const menu = document.getElementById('menu');
|
||||
if (menu) menu.style.display = 'none';
|
||||
closeSubmenus();
|
||||
}
|
||||
|
||||
document.getElementById('btnMenu').addEventListener('click', toggleMenu);
|
||||
|
||||
// Ferme le menu en cliquant à l'extérieur
|
||||
document.addEventListener('click', (e) => {
|
||||
const menu = document.getElementById('menu');
|
||||
if (!menu) return;
|
||||
const toggle = document.getElementById('btnMenu');
|
||||
if (!toggle.contains(e.target) && !menu.contains(e.target)) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Ferme avec la touche Escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeMenu();
|
||||
});
|
||||
|
||||
// -------------------------------------------------
|
||||
// [SOUS-MENU ADMIN] — Toggle + ARIA
|
||||
// -------------------------------------------------
|
||||
document.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.submenu-toggle');
|
||||
if (!btn) return;
|
||||
|
||||
const submenuId = btn.getAttribute('aria-controls');
|
||||
const submenu = document.getElementById(submenuId);
|
||||
if (!submenu) return;
|
||||
|
||||
const isOpen = btn.getAttribute('aria-expanded') === 'true';
|
||||
btn.setAttribute('aria-expanded', String(!isOpen));
|
||||
submenu.hidden = isOpen;
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
function closeSubmenus() {
|
||||
document
|
||||
.querySelectorAll('.submenu-toggle[aria-expanded="true"]')
|
||||
.forEach(btn => {
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
const sub = document.getElementById(btn.getAttribute('aria-controls'));
|
||||
if (sub) sub.hidden = true;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(async function registerSW(){
|
||||
if (!('serviceWorker' in navigator)) return;
|
||||
|
||||
try {
|
||||
// IMPORTANT : scope racine pour couvrir /push/, /messages/, /public/...
|
||||
const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||
|
||||
// optionnel : log utile
|
||||
// console.log('SW registered', reg.scope);
|
||||
|
||||
} catch (e) {
|
||||
console.error('SW register failed', e);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.addEventListener('message', async (e) => {
|
||||
const msg = e.data || {};
|
||||
if (msg.type === 'SET_BADGE') {
|
||||
const n = Number(msg.badgeCount ?? 0);
|
||||
|
||||
// iOS/Safari: parfois pas supporté => silence
|
||||
if ('setAppBadge' in navigator) {
|
||||
try {
|
||||
if (n > 0) await navigator.setAppBadge(n);
|
||||
else if ('clearAppBadge' in navigator) await navigator.clearAppBadge();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(async function pushOnboardingPWA(){
|
||||
const isPWA =
|
||||
window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.navigator.standalone === true;
|
||||
|
||||
if (!isPWA) return;
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return;
|
||||
if (!('Notification' in window)) return;
|
||||
|
||||
const VAPID_PUBLIC = "<?= htmlspecialchars($pushCfg['vapid_public'] ?? '', ENT_QUOTES) ?>";
|
||||
if (!VAPID_PUBLIC) return;
|
||||
|
||||
const onboardKey = "<?= htmlspecialchars($pushOnboardKey, ENT_QUOTES) ?>";
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||
const raw = atob(base64);
|
||||
const arr = new Uint8Array(raw.length);
|
||||
for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
|
||||
return arr;
|
||||
}
|
||||
|
||||
async function getSub(){
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
return await reg.pushManager.getSubscription();
|
||||
}
|
||||
|
||||
async function syncToServer(sub){
|
||||
const res = await fetch('/push/subscribe.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
cache: 'no-store',
|
||||
body: JSON.stringify({
|
||||
endpoint: sub.endpoint,
|
||||
keys: sub.toJSON().keys,
|
||||
userAgent: navigator.userAgent,
|
||||
deviceLabel: 'PWA'
|
||||
})
|
||||
});
|
||||
if (!res.ok) throw new Error('subscribe.php failed: ' + res.status);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function subscribeNow(){
|
||||
const reg = await navigator.serviceWorker.ready;
|
||||
|
||||
// 1) permission
|
||||
const perm = await Notification.requestPermission();
|
||||
if (perm !== 'granted') {
|
||||
// on note qu'on a déjà proposé (évite spam)
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2) subscribe
|
||||
const sub = await reg.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC)
|
||||
});
|
||||
|
||||
// 3) sync DB
|
||||
await syncToServer(sub);
|
||||
|
||||
// 4) mark done
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Step 1: si sub existe déjà => sync DB silencieux ---
|
||||
try {
|
||||
const existing = await getSub();
|
||||
if (existing) {
|
||||
await syncToServer(existing);
|
||||
// on marque done pour ne pas reproposer
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
// silencieux (pas bloquant)
|
||||
}
|
||||
|
||||
// --- Step 2: pas de sub => afficher modal une seule fois ---
|
||||
let alreadyDone = false;
|
||||
try { alreadyDone = localStorage.getItem(onboardKey) === 'done'; } catch(_) {}
|
||||
|
||||
// Si l'utilisateur a déjà refusé au niveau OS => pas de modal (ou tu peux afficher un toast)
|
||||
if (Notification.permission === 'denied') {
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// Si déjà proposé => stop
|
||||
if (alreadyDone) return;
|
||||
|
||||
// Affiche modal (Bootstrap)
|
||||
const modalEl = document.getElementById('pushOnboardModal');
|
||||
if (!modalEl || typeof bootstrap === 'undefined') return;
|
||||
|
||||
const modal = new bootstrap.Modal(modalEl, { backdrop: 'static' });
|
||||
|
||||
const laterBtn = document.getElementById('pushLaterBtn');
|
||||
const enableBtn = document.getElementById('pushEnableBtn');
|
||||
|
||||
if (laterBtn) {
|
||||
laterBtn.addEventListener('click', () => {
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
modal.hide();
|
||||
}, { once:true });
|
||||
}
|
||||
|
||||
if (enableBtn) {
|
||||
enableBtn.addEventListener('click', async () => {
|
||||
enableBtn.disabled = true;
|
||||
try {
|
||||
await subscribeNow();
|
||||
} catch(e) {
|
||||
// si échec (réseau / serveur), on ne spam pas l'utilisateur
|
||||
try { localStorage.setItem(onboardKey, 'done'); } catch(_) {}
|
||||
} finally {
|
||||
modal.hide();
|
||||
}
|
||||
}, { once:true });
|
||||
}
|
||||
|
||||
modal.show();
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
window.applyBadges = function (data) {
|
||||
const msgUnread = Number(data?.msg_unread || 0);
|
||||
const rdvUnread = Number(data?.rdv_unread || 0);
|
||||
const appBadge = Number(data?.app_badge || (msgUnread + rdvUnread));
|
||||
|
||||
const msgSpan = document.getElementById('msg-badge');
|
||||
const rdvSpan = document.getElementById('rdv-badge');
|
||||
|
||||
if (msgSpan){
|
||||
msgSpan.style.display = msgUnread > 0 ? 'inline-block' : 'none';
|
||||
msgSpan.textContent = msgUnread > 99 ? '99+' : String(msgUnread);
|
||||
}
|
||||
if (rdvSpan){
|
||||
rdvSpan.style.display = rdvUnread > 0 ? 'inline-block' : 'none';
|
||||
rdvSpan.textContent = rdvUnread > 99 ? '99+' : String(rdvUnread);
|
||||
}
|
||||
|
||||
if ('setAppBadge' in navigator) {
|
||||
try {
|
||||
if (appBadge > 0) navigator.setAppBadge(appBadge);
|
||||
else navigator.clearAppBadge?.();
|
||||
} catch(_) {}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user