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.
192 lines
5.8 KiB
TypeScript
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>
|
|
);
|
|
}
|