/** * PermissionDeniedBanner — persistent OS-revoked notification banner (D-10). * * Shown ONLY when: * - Notification.permission === 'denied' (OS revoked), AND * - localStorage.notificationsEnabled was previously '1' (user had it on) * * Silent re-subscribe (D-10) covers the expired-subscription case. This banner * is strictly for the OS-revoked case — the one case the app cannot silently fix. * * UI-SPEC §Surface 3: * - role="alert" (assertive live region — permission loss is high-priority) * - AlertCircle (--color-destructive) + "Notifications blocked" heading * - "Re-enable in your browser settings." + "How to enable" inline button * - No dismiss — persists until OS permission is restored * - "How to enable" opens an OS-specific instruction sheet (iOS 4-step / Android 4-step) * * Security: T-05-24 — all copy is plain-text JSX children, no dangerouslySetInnerHTML. */ import { useState } from 'react'; import { AlertCircle } from 'lucide-react'; import { readNotificationsEnabled } from '../hooks/usePushSubscription.js'; import { InstructionSheet } from './InstructionSheet.js'; // ── PermissionDeniedBanner ──────────────────────────────────────────────── export function PermissionDeniedBanner() { const [instructionsOpen, setInstructionsOpen] = useState(false); // Only show when OS permission is 'denied' AND the user previously had notifications on. // This is the OS-revoked case (D-10). Silent re-subscribe handles the expired-sub case. const permissionDenied = typeof Notification !== 'undefined' && Notification.permission === 'denied'; const wasEnabled = readNotificationsEnabled(); if (!permissionDenied || !wasEnabled) return null; return ( <>