Files
flxn-app/src/components/ios-install-prompt.tsx
T
kyle a45c2aeafa fix(pwa): reword the install prompt and keep it off the bottom nav
The banner sat fixed at the viewport bottom at z-index 1000, covering the
authed shell's mobile nav; it now offsets above the nav's geometry when a
signed-in mobile user has one. New casual copy replaces the Twilio line,
the dismiss button gains a localized aria-label, and de/es/ja catalogs
carry the new strings.
2026-08-23 18:27:40 -07:00

76 lines
3.0 KiB
TypeScript

import { useEffect, useState } from 'react'
import { Box, Paper, Group, Text, ActionIcon } from '@mantine/core'
import { DownloadIcon, XIcon } from '@phosphor-icons/react'
import { Trans, useLingui } from '@lingui/react/macro'
import { useAuth } from '@/contexts/auth-context'
import { useIsMobile } from '@/hooks/use-is-mobile'
// Navbar geometry: 4rem height + 0.5rem margin + its safe-area bottom offset.
const ABOVE_MOBILE_NAV_OFFSET = 'calc(4.5rem + env(safe-area-inset-bottom, 0px))'
export function IOSInstallPrompt() {
const { t } = useLingui()
const { user } = useAuth()
const isMobile = useIsMobile()
const [show, setShow] = useState(false)
const [platform, setPlatform] = useState<'ios' | 'android' | null>(null)
useEffect(() => {
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
const isAndroid = /Android/.test(navigator.userAgent)
const isInStandaloneMode =
window.matchMedia('(display-mode: standalone)').matches ||
('standalone' in window.navigator && (window.navigator as any).standalone)
const hasBeenDismissed = localStorage.getItem('pwa-install-prompt-dismissed') === 'true'
if ((isIOS || isAndroid) && !isInStandaloneMode && !hasBeenDismissed) {
setPlatform(isIOS ? 'ios' : 'android')
const timer = setTimeout(() => setShow(true), 3000)
return () => clearTimeout(timer)
}
}, [])
const handleDismiss = () => {
localStorage.setItem('pwa-install-prompt-dismissed', 'true')
setShow(false)
}
if (!show || !platform) return null
const instructions = platform === 'ios'
? t`Tap Share → Add to Home Screen`
: t`Tap Menu (⋮) → Add to Home screen`
// Navbar renders only under _authed, so a signed-in mobile user on a public
// route has no nav to clear.
const aboveBottomNav = Boolean(user) && isMobile
const bottomStyle = aboveBottomNav
? { bottom: ABOVE_MOBILE_NAV_OFFSET, paddingBottom: '8px' }
: { bottom: 0, paddingBottom: 'calc(8px + env(safe-area-inset-bottom, 0px))' }
return (
<Box style={{ position: 'fixed', left: 0, right: 0, zIndex: 1000, padding: '8px', ...bottomStyle }}>
<Paper shadow="lg" p="sm" style={{ background: 'var(--mantine-color-blue-9)', color: 'white' }}>
<Group justify="space-between" wrap="nowrap">
<Group gap="xs" wrap="nowrap" style={{ flex: 1 }}>
<DownloadIcon size={20} style={{ flexShrink: 0 }} />
<Box style={{ flex: 1, minWidth: 0 }}>
<Text size="sm" fw={500} style={{ lineHeight: 1.3 }}>
<Trans>Add FLXN to your home screen — smoother experience, always one tap away</Trans>
</Text>
<Text size="xs" opacity={0.9} style={{ lineHeight: 1.2 }}>
{instructions}
</Text>
</Box>
</Group>
<ActionIcon variant="subtle" color="white" onClick={handleDismiss} aria-label={t`Dismiss`}>
<XIcon size={18} />
</ActionIcon>
</Group>
</Paper>
</Box>
)
}