diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 51d4936..cd933ba 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -23,8 +23,20 @@ server { client_max_body_size 15M; } - # SPA fallback + # site.webmanifest — jamais mis en cache (sinon iOS garde l'ancienne + # config "Add to Home Screen" après un déploiement) + location = /site.webmanifest { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + } + + # SPA fallback — index.html ne doit JAMAIS être mis en cache : la webapp + # installée sur l'écran d'accueil iPad (mode standalone) n'a ni bouton + # reload ni pull-to-refresh, donc si index.html reste en cache le + # WKWebView continue de charger l'ancien bundle après un déploiement. location / { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; try_files $uri $uri/ /index.html; } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6a3fd29..1da1932 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -49,6 +49,26 @@ export default function App() { }).catch(() => {}); }, []); + // Webapp installée sur l'écran d'accueil (iPad) : mode standalone, donc pas + // de bouton reload ni de pull-to-refresh natif. Si l'app est restée en + // arrière-plan un moment (cas typique : on la rouvre depuis l'icône des + // heures/jours après un déploiement serveur), on force un reload complet + // au retour au premier plan pour repartir sur un bundle et des données à jour. + useEffect(() => { + let hiddenAt = null; + const onVisibility = () => { + if (document.visibilityState === 'hidden') { + hiddenAt = Date.now(); + } else if (document.visibilityState === 'visible' && hiddenAt) { + const awayMs = Date.now() - hiddenAt; + hiddenAt = null; + if (awayMs > 2 * 60 * 1000) window.location.reload(); + } + }; + document.addEventListener('visibilitychange', onVisibility); + return () => document.removeEventListener('visibilitychange', onVisibility); + }, []); + return ( } /> diff --git a/frontend/src/api.js b/frontend/src/api.js index c4017c6..d5009e5 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -34,7 +34,7 @@ export const api = { const qs = params ? '?' + new URLSearchParams( Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '') ).toString() : ''; - return fetch(BASE + path + qs, { headers: authHeaders() }).then(handle); + return fetch(BASE + path + qs, { headers: authHeaders(), cache: 'no-store' }).then(handle); }, post: (path, body) => fetch(BASE + path, { @@ -65,7 +65,7 @@ export const api = { postForm: (path, formData) => fetch(BASE + path, { method: 'POST', body: formData, headers: authHeaders() }).then(handle), blob: (path) => - fetch(BASE + path, { headers: authHeaders() }).then(async res => { + fetch(BASE + path, { headers: authHeaders(), cache: 'no-store' }).then(async res => { if (!res.ok) { const t = await res.text(); throw new Error(t || res.statusText); } return res.blob(); }), diff --git a/frontend/src/components/UserMenu.jsx b/frontend/src/components/UserMenu.jsx index 9feac4f..7efc32b 100644 --- a/frontend/src/components/UserMenu.jsx +++ b/frontend/src/components/UserMenu.jsx @@ -25,6 +25,9 @@ function IconAide() { function IconComm() { return ; } +function IconRefresh() { + return ; +} function IconChevronRight() { return ; } @@ -192,6 +195,17 @@ export default function UserMenu() { const go = (path) => { closeMenu(); navigate(path); }; const handleLogout = () => { closeMenu(); logout(); navigate('/login'); }; + // En mode standalone (webapp ajoutée à l'écran d'accueil iPad), il n'y a + // ni bouton reload ni pull-to-refresh natif. On force donc une vraie + // navigation réseau (pas juste reload()) avec un paramètre anti-cache, + // pour être certain de récupérer l'index.html et les données à jour. + const handleForceRefresh = () => { + closeMenu(); + const url = new URL(window.location.href); + url.searchParams.set('_r', Date.now().toString()); + window.location.replace(url.toString()); + }; + const selectView = (v) => { setActiveView(v); closeMenu(); @@ -268,6 +282,9 @@ export default function UserMenu() { +