From 252c0e09cb87c568009c551576827f0ce03e3f41 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sat, 28 Mar 2026 14:51:00 -0400 Subject: [PATCH] v0.12.26 iOS notification bug fix --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- frontend/src/components/UserFooter.jsx | 36 ++++++++++++++++++++++---- frontend/src/pages/Chat.jsx | 10 ++++++- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/backend/package.json b/backend/package.json index a9e3c62..ea57a11 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-backend", - "version": "0.12.35", + "version": "0.12.36", "description": "RosterChirp backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 25c89a7..8775b7c 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.12.35}" +VERSION="${1:-0.12.36}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="rosterchirp" diff --git a/frontend/package.json b/frontend/package.json index b29a0a2..06ba45c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-frontend", - "version": "0.12.35", + "version": "0.12.36", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/UserFooter.jsx b/frontend/src/components/UserFooter.jsx index a6c5322..7339cc3 100644 --- a/frontend/src/components/UserFooter.jsx +++ b/frontend/src/components/UserFooter.jsx @@ -63,15 +63,19 @@ const isMobileDevice = isIOS || isAndroid; function TestNotificationsModal({ onClose }) { const toast = useToast(); + const { user } = useAuth(); + const isAdmin = user?.role === 'admin'; const [debugData, setDebugData] = useState(null); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [testing, setTesting] = useState(false); - - const permission = (typeof Notification !== 'undefined') ? Notification.permission : 'unsupported'; + const [permission, setPermission] = useState( + (typeof Notification !== 'undefined') ? Notification.permission : 'unsupported' + ); const [cachedToken, setCachedToken] = useState(localStorage.getItem('rc_fcm_token')); const [lastError, setLastError] = useState(localStorage.getItem('rc_fcm_error')); const load = async () => { + if (!isAdmin) return; // debug endpoint is admin-only setLoading(true); try { const data = await api.pushDebug(); @@ -83,7 +87,22 @@ function TestNotificationsModal({ onClose }) { } }; - useEffect(() => { load(); }, []); + useEffect(() => { load(); }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const handleGrantPermission = async () => { + if (typeof Notification === 'undefined') { + toast('Notifications not supported on this device/browser', 'error'); + return; + } + const result = await Notification.requestPermission(); + setPermission(result); + if (result === 'granted') { + window.dispatchEvent(new CustomEvent('rosterchirp:push-init')); + toast('Permission granted — registering…', 'success'); + } else { + toast('Permission denied', 'error'); + } + }; const doTest = async (mode) => { setTesting(true); @@ -112,6 +131,7 @@ function TestNotificationsModal({ onClose }) { const reregister = () => { localStorage.removeItem('rc_fcm_token'); localStorage.removeItem('rc_fcm_error'); + localStorage.removeItem('rc_webpush_endpoint'); // clear iOS webpush cache too setCachedToken(null); setLastError(null); window.dispatchEvent(new CustomEvent('rosterchirp:push-init')); @@ -147,6 +167,11 @@ function TestNotificationsModal({ onClose }) { {!isIOS && debugData && } {lastError && } + {permission === 'default' && ( + + )}
{!isIOS && } @@ -325,7 +350,8 @@ export default function UserFooter({ onProfile, onHelp, onAbout, mobileCompact=f {showMenu && (
-
+
+ User Menu diff --git a/frontend/src/pages/Chat.jsx b/frontend/src/pages/Chat.jsx index 9a184da..d3bed4a 100644 --- a/frontend/src/pages/Chat.jsx +++ b/frontend/src/pages/Chat.jsx @@ -262,7 +262,15 @@ export default function Chat() { const handleVisibility = () => { if (document.visibilityState === 'visible') registerPush(); }; - const handlePushInit = () => registerPush(); + // When the user explicitly requests push (via the Notifications toggle or + // re-register button), ask for permission if it hasn't been granted yet. + const handlePushInit = async () => { + if (typeof Notification !== 'undefined' && Notification.permission === 'default') { + const result = await Notification.requestPermission(); + if (result !== 'granted') return; + } + registerPush(); + }; document.addEventListener('visibilitychange', handleVisibility); window.addEventListener('rosterchirp:push-init', handlePushInit); return () => {