Files
familysync/apps/pwa/src/components/InstructionSheet.tsx
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

192 lines
5.8 KiB
TypeScript

/**
* 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 your browser (Chrome or Edge) 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 (
<div
role="dialog"
aria-modal="true"
aria-label={`Re-enable notifications on ${platform}`}
style={{
position: 'fixed',
inset: 0,
background: 'var(--color-overlay, rgba(0,0,0,0.32))',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
zIndex: 1000,
}}
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<div
style={{
background: 'var(--color-surface, #ffffff)',
borderRadius: '12px 12px 0 0',
padding: 'var(--space-6, 24px)',
maxHeight: '90dvh',
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-4, 16px)',
fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
}}
>
{/* Header */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<h2
style={{
margin: 0,
fontSize: 'var(--text-heading-size, 18px)',
fontWeight: 600,
lineHeight: 'var(--text-heading-line-height, 1.25)',
color: 'var(--color-text-primary, #111318)',
}}
>
How to enable notifications
</h2>
<button
onClick={onClose}
aria-label="Close instructions"
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
minWidth: '44px',
minHeight: '44px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--color-text-secondary, #5c6472)',
borderRadius: 'var(--space-1, 4px)',
}}
>
<X size={20} aria-hidden="true" />
</button>
</div>
{/* Steps */}
<ol
style={{
margin: 0,
padding: 0,
listStyle: 'none',
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-3, 12px)',
}}
>
{steps.map((step, i) => (
<li
key={i}
style={{
display: 'flex',
alignItems: 'flex-start',
gap: 'var(--space-3, 12px)',
}}
>
<span
style={{
flexShrink: 0,
width: '28px',
height: '28px',
borderRadius: '50%',
background: 'var(--color-destructive, #DC2626)',
color: '#ffffff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '13px',
fontWeight: 600,
fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
}}
>
{i + 1}
</span>
<span
style={{
fontSize: 'var(--text-body-size, 15px)',
lineHeight: 'var(--text-body-line-height, 1.5)',
color: 'var(--color-text-primary, #111318)',
paddingTop: '4px',
}}
>
{step}
</span>
</li>
))}
</ol>
{/* Done button */}
<button
onClick={onClose}
style={{
background: 'var(--color-text-primary, #111318)',
color: '#ffffff',
border: 'none',
borderRadius: 'var(--space-1, 4px)',
minHeight: '44px',
padding: '0 var(--space-4, 16px)',
fontSize: 'var(--text-label-size, 13px)',
fontWeight: 600,
cursor: 'pointer',
fontFamily: 'var(--font-family-base, system-ui, sans-serif)',
alignSelf: 'stretch',
}}
>
Done
</button>
</div>
</div>
);
}