diff --git a/apps/pwa/src/components/InstructionSheet.tsx b/apps/pwa/src/components/InstructionSheet.tsx new file mode 100644 index 0000000..ba5d8d5 --- /dev/null +++ b/apps/pwa/src/components/InstructionSheet.tsx @@ -0,0 +1,189 @@ +/** + * InstructionSheet — shared OS-specific notification re-enable guidance bottom sheet. + * + * Extracted from PermissionDeniedBanner (Phase 5 UAT-05-T4 fix). + * Used by both PermissionDeniedBanner and SettingsSheet. + * + * Security: T-05-24 — all copy is plain-text JSX children, no dangerouslySetInnerHTML. + */ + +import { X } from 'lucide-react' + +// ── OS detection ────────────────────────────────────────────────────────── + +function isIOS(): boolean { + return /iPad|iPhone|iPod/.test(navigator.userAgent) && + !(window as unknown as { MSStream?: unknown }).MSStream +} + +// ── Instruction steps ──────────────────────────────────────────────────── + +const IOS_STEPS = [ + 'Open Settings on your iPhone', + 'Scroll down and tap Safari', + 'Tap Notifications', + 'Allow notifications for FamilySync', +] + +const ANDROID_STEPS = [ + 'Open Chrome on your phone', + 'Tap the three-dot menu → Settings', + 'Tap Site Settings → Notifications', + 'Find FamilySync and tap Allow', +] + +// ── Instruction sheet ──────────────────────────────────────────────────── + +interface InstructionSheetProps { + onClose: () => void +} + +export function InstructionSheet({ onClose }: InstructionSheetProps) { + const steps = isIOS() ? IOS_STEPS : ANDROID_STEPS + const platform = isIOS() ? 'iOS' : 'Android' + + return ( +
{ + if (e.target === e.currentTarget) onClose() + }} + > +
+ {/* Header */} +
+

+ How to enable notifications +

+ +
+ + {/* Steps */} +
    + {steps.map((step, i) => ( +
  1. + + {i + 1} + + + {step} + +
  2. + ))} +
+ + {/* Done button */} + +
+
+ ) +} diff --git a/apps/pwa/src/components/PermissionDeniedBanner.tsx b/apps/pwa/src/components/PermissionDeniedBanner.tsx index c51e500..a0ee1dd 100644 --- a/apps/pwa/src/components/PermissionDeniedBanner.tsx +++ b/apps/pwa/src/components/PermissionDeniedBanner.tsx @@ -19,187 +19,9 @@ */ import { useState } from 'react' -import { AlertCircle, X } from 'lucide-react' +import { AlertCircle } from 'lucide-react' import { readNotificationsEnabled } from '../hooks/usePushSubscription.js' - -// ── OS detection ────────────────────────────────────────────────────────── - -function isIOS(): boolean { - return /iPad|iPhone|iPod/.test(navigator.userAgent) && - !(window as unknown as { MSStream?: unknown }).MSStream -} - -// ── Instruction steps ──────────────────────────────────────────────────── - -const IOS_STEPS = [ - 'Open Settings on your iPhone', - 'Scroll down and tap Safari', - 'Tap Notifications', - 'Allow notifications for FamilySync', -] - -const ANDROID_STEPS = [ - 'Open Chrome on your phone', - 'Tap the three-dot menu → Settings', - 'Tap Site Settings → Notifications', - 'Find FamilySync and tap Allow', -] - -// ── Instruction sheet ──────────────────────────────────────────────────── - -interface InstructionSheetProps { - onClose: () => void -} - -function InstructionSheet({ onClose }: InstructionSheetProps) { - const steps = isIOS() ? IOS_STEPS : ANDROID_STEPS - const platform = isIOS() ? 'iOS' : 'Android' - - return ( -
{ - if (e.target === e.currentTarget) onClose() - }} - > -
- {/* Header */} -
-

- How to enable notifications -

- -
- - {/* Steps */} -
    - {steps.map((step, i) => ( -
  1. - - {i + 1} - - - {step} - -
  2. - ))} -
- - {/* Done button */} - -
-
- ) -} +import { InstructionSheet } from './InstructionSheet.js' // ── PermissionDeniedBanner ────────────────────────────────────────────────