iOS bug fixes
This commit is contained in:
@@ -6,6 +6,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
|
||||||
<meta name="theme-color" content="#1a73e8" />
|
<meta name="theme-color" content="#1a73e8" />
|
||||||
<meta name="description" content="RosterChirp - team messaging" />
|
<meta name="description" content="RosterChirp - team messaging" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="RosterChirp" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
||||||
<title>RosterChirp</title>
|
<title>RosterChirp</title>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { AuthProvider, useAuth } from './contexts/AuthContext.jsx';
|
import { AuthProvider, useAuth } from './contexts/AuthContext.jsx';
|
||||||
import { SocketProvider } from './contexts/SocketContext.jsx';
|
import { SocketProvider } from './contexts/SocketContext.jsx';
|
||||||
@@ -6,6 +7,50 @@ import Login from './pages/Login.jsx';
|
|||||||
import Chat from './pages/Chat.jsx';
|
import Chat from './pages/Chat.jsx';
|
||||||
import ChangePassword from './pages/ChangePassword.jsx';
|
import ChangePassword from './pages/ChangePassword.jsx';
|
||||||
|
|
||||||
|
// ── iOS "Add to Home Screen" banner ───────────────────────────────────────────
|
||||||
|
// iOS Safari does not fire beforeinstallprompt. Push notifications require the
|
||||||
|
// app to be installed as a PWA. This banner is shown to any iOS Safari user who
|
||||||
|
// has not yet added the app to their Home Screen.
|
||||||
|
const IOS_BANNER_KEY = 'rc_ios_install_dismissed';
|
||||||
|
|
||||||
|
function IOSInstallBanner() {
|
||||||
|
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
|
||||||
|
const isStandalone = window.navigator.standalone === true;
|
||||||
|
const [dismissed, setDismissed] = useState(() => localStorage.getItem(IOS_BANNER_KEY) === '1');
|
||||||
|
|
||||||
|
if (!isIOS || isStandalone || dismissed) return null;
|
||||||
|
|
||||||
|
const dismiss = () => {
|
||||||
|
localStorage.setItem(IOS_BANNER_KEY, '1');
|
||||||
|
setDismissed(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 9999,
|
||||||
|
background: 'var(--primary, #1a73e8)', color: '#fff',
|
||||||
|
padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12,
|
||||||
|
boxShadow: '0 -2px 12px rgba(0,0,0,0.25)',
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ fontWeight: 700, fontSize: 14, marginBottom: 2 }}>Add to Home Screen</div>
|
||||||
|
<div style={{ fontSize: 12, lineHeight: 1.4, opacity: 0.9 }}>
|
||||||
|
To receive push notifications, tap the{' '}
|
||||||
|
<svg style={{ display: 'inline', verticalAlign: 'middle', margin: '0 2px' }} width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/>
|
||||||
|
<polyline points="16 6 12 2 8 6"/>
|
||||||
|
<line x1="12" y1="2" x2="12" y2="15"/>
|
||||||
|
</svg>
|
||||||
|
{' '}Share button, then select <strong>"Add to Home Screen"</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onClick={dismiss} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#fff', padding: 4, flexShrink: 0, opacity: 0.9 }}>
|
||||||
|
<svg width="18" height="18" 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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ProtectedRoute({ children }) {
|
function ProtectedRoute({ children }) {
|
||||||
const { user, loading, mustChangePassword } = useAuth();
|
const { user, loading, mustChangePassword } = useAuth();
|
||||||
if (loading) return (
|
if (loading) return (
|
||||||
@@ -36,6 +81,7 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
|
<IOSInstallBanner />
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* All routes go through jama auth */}
|
{/* All routes go through jama auth */}
|
||||||
<Route path="/*" element={
|
<Route path="/*" element={
|
||||||
|
|||||||
@@ -29,38 +29,6 @@ function urlBase64ToUint8Array(base64String) {
|
|||||||
return outputArray;
|
return outputArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── iOS "Add to Home Screen" install banner ───────────────────────────────────
|
|
||||||
// iOS Safari does not fire beforeinstallprompt. Push notifications on iOS require
|
|
||||||
// the app to be installed as a PWA (added to Home Screen). This banner guides users.
|
|
||||||
const IOS_BANNER_KEY = 'rc_ios_install_dismissed';
|
|
||||||
function IOSInstallBanner({ onDismiss }) {
|
|
||||||
return (
|
|
||||||
<div style={{
|
|
||||||
position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 9999,
|
|
||||||
background: 'var(--primary)', color: '#fff',
|
|
||||||
padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12,
|
|
||||||
boxShadow: '0 -2px 12px rgba(0,0,0,0.25)',
|
|
||||||
}}>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<div style={{ fontWeight: 700, fontSize: 14, marginBottom: 2 }}>Add to Home Screen</div>
|
|
||||||
<div style={{ fontSize: 12, lineHeight: 1.4, opacity: 0.9 }}>
|
|
||||||
To receive push notifications, tap the{' '}
|
|
||||||
{/* iOS share icon */}
|
|
||||||
<svg style={{ display: 'inline', verticalAlign: 'middle', margin: '0 2px' }} width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/>
|
|
||||||
<polyline points="16 6 12 2 8 6"/>
|
|
||||||
<line x1="12" y1="2" x2="12" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
{' '}Share button, then select <strong>"Add to Home Screen"</strong>.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button onClick={onDismiss} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#fff', padding: 4, flexShrink: 0, opacity: 0.9 }}>
|
|
||||||
<svg width="18" height="18" 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>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat() {
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -79,9 +47,6 @@ export default function Chat() {
|
|||||||
const [helpDismissed, setHelpDismissed] = useState(true); // true until status loaded
|
const [helpDismissed, setHelpDismissed] = useState(true); // true until status loaded
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
const [showSidebar, setShowSidebar] = useState(true);
|
const [showSidebar, setShowSidebar] = useState(true);
|
||||||
const isIOSSafari = /iphone|ipad/i.test(navigator.userAgent) && !window.navigator.standalone;
|
|
||||||
const [iosBannerDismissed, setIosBannerDismissed] = useState(() => localStorage.getItem(IOS_BANNER_KEY) === '1');
|
|
||||||
const showIOSBanner = isIOSSafari && !iosBannerDismissed;
|
|
||||||
|
|
||||||
// Check if help should be shown on login
|
// Check if help should be shown on login
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -585,7 +550,7 @@ export default function Chat() {
|
|||||||
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
||||||
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
||||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||||
{showIOSBanner && <IOSInstallBanner onDismiss={() => { localStorage.setItem(IOS_BANNER_KEY, '1'); setIosBannerDismissed(true); }} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -614,7 +579,7 @@ export default function Chat() {
|
|||||||
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
||||||
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
||||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||||
{showIOSBanner && <IOSInstallBanner onDismiss={() => { localStorage.setItem(IOS_BANNER_KEY, '1'); setIosBannerDismissed(true); }} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -674,7 +639,7 @@ export default function Chat() {
|
|||||||
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
||||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||||
{modal === 'newchat' && <NewChatModal onClose={() => setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); setPage('chat'); }} />}
|
{modal === 'newchat' && <NewChatModal onClose={() => setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); setPage('chat'); }} />}
|
||||||
{showIOSBanner && <IOSInstallBanner onDismiss={() => { localStorage.setItem(IOS_BANNER_KEY, '1'); setIosBannerDismissed(true); }} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -707,7 +672,7 @@ export default function Chat() {
|
|||||||
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
|
||||||
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
||||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||||
{showIOSBanner && <IOSInstallBanner onDismiss={() => { localStorage.setItem(IOS_BANNER_KEY, '1'); setIosBannerDismissed(true); }} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -752,7 +717,7 @@ export default function Chat() {
|
|||||||
)}
|
)}
|
||||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||||
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
|
||||||
{showIOSBanner && <IOSInstallBanner onDismiss={() => { localStorage.setItem(IOS_BANNER_KEY, '1'); setIosBannerDismissed(true); }} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user