feat(05-04): usePushSubscription hook + PushPermissionPrompt + App mount

- Create apps/pwa/src/hooks/usePushSubscription.ts: subscribe (in tap handler, VAPID key cached), unsubscribe, permission; health-check on mount (D-10); urlBase64ToUint8Array helper; prefetchVapidKey export
- Create apps/pwa/src/components/PushPermissionPrompt.tsx: WalkthroughSheet-style bottom sheet, Bell icon, 'Stay in the loop' heading, 48px Enable CTA (var(--color-member-0)), 44px Not-now ghost, no backdrop-dismiss, pushPermissionDismissed key, Loader2 spinner while awaiting
- Mount PushPermissionPrompt in App.tsx (for installed-PWA path) and InstallPrompt.tsx (for post-Android-install justInstalled path)
- Build green; tsc clean
This commit is contained in:
Lucas Berger
2026-06-09 21:09:41 -04:00
parent e5953ebb31
commit bf8f63b47c
4 changed files with 553 additions and 4 deletions
+4
View File
@@ -22,6 +22,7 @@ import { CalendarShell } from './components/CalendarShell.js'
import { ListsIndex } from './routes/ListsIndex.js'
import { ListDetail } from './routes/ListDetail.js'
import { BottomTabBar } from './components/BottomTabBar.js'
import { PushPermissionPrompt } from './components/PushPermissionPrompt.js'
export default function App() {
return (
@@ -33,6 +34,9 @@ export default function App() {
<Route path="/lists/:listId" element={<ListDetail />} />
</Routes>
<BottomTabBar />
{/* Post-install permission prompt (D-08): renders only when isInstalled() is true
and Notification.permission === 'default' and not dismissed */}
<PushPermissionPrompt />
</BrowserRouter>
)
}