v0.12.26 iOS notification bug fix

This commit is contained in:
2026-03-28 14:51:00 -04:00
parent f40bb123d2
commit 252c0e09cb
5 changed files with 43 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "rosterchirp-frontend",
"version": "0.12.35",
"version": "0.12.36",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -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 && <DebugRow label="Firebase Admin" value={debugData.firebaseAdminReady ? 'Ready' : 'Not ready'} ok={debugData.firebaseAdminReady} bad={!debugData.firebaseAdminReady} />}
{lastError && <DebugRow label="Last reg. error" value={lastError} bad={true} />}
</div>
{permission === 'default' && (
<button className="btn btn-sm btn-primary" onClick={handleGrantPermission} style={{ marginBottom: 8 }}>
Grant Permission
</button>
)}
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button className="btn btn-sm btn-primary" onClick={reregister}>Re-register</button>
{!isIOS && <button className="btn btn-sm btn-secondary" onClick={clearToken}>Clear token</button>}
@@ -325,7 +350,8 @@ export default function UserFooter({ onProfile, onHelp, onAbout, mobileCompact=f
{showMenu && (
<div ref={menuRef} className="footer-menu">
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
<span style={{ fontWeight: 700, fontSize: 14, color: 'var(--text-primary)', paddingLeft: 4 }}>User Menu</span>
<button onClick={() => setShowMenu(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', color: 'var(--text-tertiary)', lineHeight: 1 }} aria-label="Close menu">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>

View File

@@ -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 () => {