feat(05-08): PermissionDeniedBanner + App mount + CalendarShell onOpenSettings wiring
- Create PermissionDeniedBanner.tsx: role=alert banner shown only when permission=denied AND notificationsEnabled=1 (OS-revoked case, D-10) with AlertCircle icon, 'Notifications blocked' heading, inline 'How to enable' button that opens OS-specific instruction sheet (iOS 4-step / Android 4-step) - Mount PermissionDeniedBanner and SettingsSheet in App.tsx; wire onOpenSettings state from avatar tap through CalendarShell → AppNav → PhoneNav/DesktopNav - CalendarShell accepts optional onOpenSettings prop, threads to both AppNav usages - playwright-cli verified: banner renders with exact UI-SPEC copy when permission=denied+was-enabled; banner absent when permission=granted; 'How to enable' opens instruction sheet with correct Android steps; SettingsSheet opens from avatar click with toggle + permission-denied hint
This commit is contained in:
+19
-1
@@ -15,21 +15,36 @@
|
||||
* navigateFallback ('/index.html') in vite.config.ts covers SPA deep-links to
|
||||
* /lists/* — the SW denylist only excludes /callback, /api/, and /health, so
|
||||
* /lists/* is served from cache correctly.
|
||||
*
|
||||
* Phase 5 additions:
|
||||
* - PermissionDeniedBanner: shown below AppNav when OS permission revoked (D-10)
|
||||
* - SettingsSheet: avatar-triggered bottom sheet with master notifications toggle (D-09)
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router'
|
||||
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'
|
||||
import { PermissionDeniedBanner } from './components/PermissionDeniedBanner.js'
|
||||
import { SettingsSheet } from './components/SettingsSheet.js'
|
||||
|
||||
export default function App() {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
{/* Permission-denied banner: shown when OS revoked and user had notifications on (D-10) */}
|
||||
<PermissionDeniedBanner />
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/calendar" replace />} />
|
||||
<Route path="/calendar" element={<CalendarShell />} />
|
||||
<Route
|
||||
path="/calendar"
|
||||
element={<CalendarShell onOpenSettings={() => setSettingsOpen(true)} />}
|
||||
/>
|
||||
<Route path="/lists" element={<ListsIndex />} />
|
||||
<Route path="/lists/:listId" element={<ListDetail />} />
|
||||
</Routes>
|
||||
@@ -37,6 +52,9 @@ export default function App() {
|
||||
{/* Post-install permission prompt (D-08): renders only when isInstalled() is true
|
||||
and Notification.permission === 'default' and not dismissed */}
|
||||
<PushPermissionPrompt />
|
||||
|
||||
{/* Settings sheet — master notifications toggle (D-09), opened from avatar */}
|
||||
<SettingsSheet isOpen={settingsOpen} onClose={() => setSettingsOpen(false)} />
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user