v0.12.26 iOS notification bug fix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rosterchirp-backend",
|
"name": "rosterchirp-backend",
|
||||||
"version": "0.12.35",
|
"version": "0.12.36",
|
||||||
"description": "RosterChirp backend server",
|
"description": "RosterChirp backend server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${1:-0.12.35}"
|
VERSION="${1:-0.12.36}"
|
||||||
ACTION="${2:-}"
|
ACTION="${2:-}"
|
||||||
REGISTRY="${REGISTRY:-}"
|
REGISTRY="${REGISTRY:-}"
|
||||||
IMAGE_NAME="rosterchirp"
|
IMAGE_NAME="rosterchirp"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rosterchirp-frontend",
|
"name": "rosterchirp-frontend",
|
||||||
"version": "0.12.35",
|
"version": "0.12.36",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -63,15 +63,19 @@ const isMobileDevice = isIOS || isAndroid;
|
|||||||
|
|
||||||
function TestNotificationsModal({ onClose }) {
|
function TestNotificationsModal({ onClose }) {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const { user } = useAuth();
|
||||||
|
const isAdmin = user?.role === 'admin';
|
||||||
const [debugData, setDebugData] = useState(null);
|
const [debugData, setDebugData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(false);
|
||||||
const [testing, setTesting] = useState(false);
|
const [testing, setTesting] = useState(false);
|
||||||
|
const [permission, setPermission] = useState(
|
||||||
const permission = (typeof Notification !== 'undefined') ? Notification.permission : 'unsupported';
|
(typeof Notification !== 'undefined') ? Notification.permission : 'unsupported'
|
||||||
|
);
|
||||||
const [cachedToken, setCachedToken] = useState(localStorage.getItem('rc_fcm_token'));
|
const [cachedToken, setCachedToken] = useState(localStorage.getItem('rc_fcm_token'));
|
||||||
const [lastError, setLastError] = useState(localStorage.getItem('rc_fcm_error'));
|
const [lastError, setLastError] = useState(localStorage.getItem('rc_fcm_error'));
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
|
if (!isAdmin) return; // debug endpoint is admin-only
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await api.pushDebug();
|
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) => {
|
const doTest = async (mode) => {
|
||||||
setTesting(true);
|
setTesting(true);
|
||||||
@@ -112,6 +131,7 @@ function TestNotificationsModal({ onClose }) {
|
|||||||
const reregister = () => {
|
const reregister = () => {
|
||||||
localStorage.removeItem('rc_fcm_token');
|
localStorage.removeItem('rc_fcm_token');
|
||||||
localStorage.removeItem('rc_fcm_error');
|
localStorage.removeItem('rc_fcm_error');
|
||||||
|
localStorage.removeItem('rc_webpush_endpoint'); // clear iOS webpush cache too
|
||||||
setCachedToken(null);
|
setCachedToken(null);
|
||||||
setLastError(null);
|
setLastError(null);
|
||||||
window.dispatchEvent(new CustomEvent('rosterchirp:push-init'));
|
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} />}
|
{!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} />}
|
{lastError && <DebugRow label="Last reg. error" value={lastError} bad={true} />}
|
||||||
</div>
|
</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' }}>
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||||
<button className="btn btn-sm btn-primary" onClick={reregister}>Re-register</button>
|
<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>}
|
{!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 && (
|
{showMenu && (
|
||||||
<div ref={menuRef} className="footer-menu">
|
<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">
|
<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>
|
<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>
|
</button>
|
||||||
|
|||||||
@@ -262,7 +262,15 @@ export default function Chat() {
|
|||||||
const handleVisibility = () => {
|
const handleVisibility = () => {
|
||||||
if (document.visibilityState === 'visible') registerPush();
|
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);
|
document.addEventListener('visibilitychange', handleVisibility);
|
||||||
window.addEventListener('rosterchirp:push-init', handlePushInit);
|
window.addEventListener('rosterchirp:push-init', handlePushInit);
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user