diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx index 09dc1e976e..b82594ab61 100644 --- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx +++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx @@ -1,17 +1,16 @@ import {View} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Trans} from '@lingui/react/macro' +import {Trans, useLingui} from '@lingui/react/macro' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {regionName} from '#/locale/helpers' import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' import {Admonition} from '#/components/Admonition' import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {AgeAssuranceConfigUnavailableError} from '#/components/ageAssurance/AgeAssuranceErrors' import { - AgeAssuranceInitDialog, - useDialogControl, + AgeAssuranceInitDialog, + useDialogControl, } from '#/components/ageAssurance/AgeAssuranceInitDialog' import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import {Button, ButtonText} from '#/components/Button' @@ -25,206 +24,324 @@ import {useAgeAssurance} from '#/ageAssurance' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' -import {useDeviceGeolocationApi} from '#/geolocation' +import { + type Geolocation, + useDeviceGeolocationApi, + useGeolocation, +} from '#/geolocation' +import {USRegionNameToRegionCode} from '#/geolocation/util' +import {device, useStorage} from '#/storage' + +const USRegionCodeToRegionName: {[regionCode: string]: string} = + Object.fromEntries( + Object.entries(USRegionNameToRegionCode).map(([name, code]) => [ + code, + name, + ]), + ) + +function formatRegion( + geolocation: Geolocation, + appLang: string, +): string | undefined { + const {countryCode, regionCode} = geolocation + if (!countryCode) return undefined + const country = regionName(countryCode, appLang) + // If `regionName` couldn't resolve a real name and fell through to the raw + // code, we'd rather show nothing than a bare ISO code in the prose. + if (country === countryCode) return undefined + if (regionCode && countryCode === 'US') { + const state = USRegionCodeToRegionName[regionCode] + if (state) return `${state}, ${country}` + } + return country +} export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { - const aa = useAgeAssurance() - if (aa.state.access === aa.Access.Full) return null - if (aa.state.error === 'config') { - return ( - - - - ) - } - return + const aa = useAgeAssurance() + if (aa.state.access === aa.Access.Full) return null + if (aa.state.error === 'config') { + return ( + + + + ) + } + return } function Inner({style}: ViewStyleProp & {}) { - const t = useTheme() - const {_, i18n} = useLingui() - const ax = useAnalytics() - const control = useDialogControl() - const appealControl = Dialog.useDialogControl() - const locationControl = Dialog.useDialogControl() - const getTimeAgo = useGetTimeAgo() - const {gtPhone} = useBreakpoints() - const {setDeviceGeolocation} = useDeviceGeolocationApi() - const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() + const t = useTheme() + const {t: l, i18n} = useLingui() + const ax = useAnalytics() + const control = useDialogControl() + const appealControl = Dialog.useDialogControl() + const getTimeAgo = useGetTimeAgo() + const {gtPhone} = useBreakpoints() - const copy = useAgeAssuranceCopy() - const aa = useAgeAssurance() - const {status, lastInitiatedAt} = aa.state - const isBlocked = status === aa.Status.Blocked - const hasInitiated = !!lastInitiatedAt - const hasCompletedFlow = status === aa.Status.Assured - const timeAgo = lastInitiatedAt - ? getTimeAgo(lastInitiatedAt, new Date()) - : null - const diff = lastInitiatedAt - ? dateDiff(lastInitiatedAt, new Date(), 'down') - : null + const copy = useAgeAssuranceCopy() + const aa = useAgeAssurance() + const {status, lastInitiatedAt} = aa.state + const isBlocked = status === aa.Status.Blocked + const hasInitiated = !!lastInitiatedAt + const hasCompletedFlow = status === aa.Status.Assured + const timeAgo = lastInitiatedAt + ? getTimeAgo(lastInitiatedAt, new Date()) + : null + const diff = lastInitiatedAt + ? dateDiff(lastInitiatedAt, new Date(), 'down') + : null - return ( - <> - - + return ( + <> + + - - - - - + + + + + + + + + + + {copy.notice} + + {hasCompletedFlow && ( + + + If you are 18 years of age or older and want + to try again, click the button below and use + a different verification method if one is + available in your region. If you have + questions or concerns,{' '} + { + appealControl.open() + })}> + our support team can help. + + + + )} + + + + + {isBlocked ? ( + + + You are currently unable to access Bluesky's Age + Assurance flow. Please{' '} + { + appealControl.open() + ax.metric( + 'ageAssurance:appealDialogOpen', + {}, + ) + })}> + contact our moderation team + {' '} + if you believe this is an error. + + + ) : ( + <> + + + + + {lastInitiatedAt && timeAgo && diff ? ( + + {diff.value === 0 ? ( + + Last initiated just now + + ) : ( + + Last initiated {timeAgo} ago + + )} + + ) : ( + + + Age assurance only takes a few + minutes + + + )} + + + )} + - - - - {copy.notice} - {hasCompletedFlow && ( - - - If you are 18 years of age or older and want to try again, - click the button below and use a different verification method - if one is available in your region. If you have questions or - concerns,{' '} - { - appealControl.open() - })}> - our support team can help. - - - - )} - - {IS_NATIVE && ( - <> - - - Is your location not accurate?{' '} - { - locationControl.open() - })}> - Tap here to confirm your location. - {' '} - - - - { - const access = computeAgeAssuranceRegionAccess( - props.geolocation, - ) - if (access !== aa.Access.Full) { - props.disableDialogAction() - props.setDialogError( - _( - msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`, - ), - ) - } else { - props.closeDialog(() => { - // set this after close! - setDeviceGeolocation(props.geolocation) - Toast.show(_(msg`Thanks! You're all set.`), { - type: 'success', - }) - }) - } - }} - /> - - )} - - - {isBlocked ? ( - - - You are currently unable to access Bluesky's Age Assurance flow. - Please{' '} - { - appealControl.open() - ax.metric('ageAssurance:appealDialogOpen', {}) - })}> - contact our moderation team - {' '} - if you believe this is an error. - - - ) : ( - <> - - - - - {lastInitiatedAt && timeAgo && diff ? ( - - {diff.value === 0 ? ( - Last initiated just now - ) : ( - Last initiated {timeAgo} ago - )} - - ) : ( - - Age assurance only takes a few minutes - - )} - - - )} - - - - ) + + ) +} + +function RegionNotice() { + const {t: l, i18n} = useLingui() + const aa = useAgeAssurance() + const geolocation = useGeolocation() + const [deviceGeolocation] = useStorage(device, ['deviceGeolocation']) + const {setDeviceGeolocation} = useDeviceGeolocationApi() + const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() + const locationControl = Dialog.useDialogControl() + + const region = formatRegion(geolocation, i18n.locale) + if (!region) return null + + const isGPS = !!deviceGeolocation?.countryCode + + return ( + <> + {IS_NATIVE && ( + { + const access = computeAgeAssuranceRegionAccess( + props.geolocation, + ) + if (access !== aa.Access.Full) { + props.disableDialogAction() + props.setDialogError( + l`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`, + ) + } else { + props.closeDialog(() => { + // set this after close! + setDeviceGeolocation(props.geolocation) + Toast.show(l`Thanks! You're all set.`, { + type: 'success', + }) + }) + } + }} + /> + )} + + + {isGPS ? ( + + Based on your device's location, we think you're in{' '} + {region}. + Some features are limited because of regional age laws. + + ) : ( + + Based on your network, we think you're in{' '} + {region}. + Some features are limited because of regional age laws. + + )} + + + {!isGPS && + (IS_NATIVE ? ( + + + This estimate may be inaccurate if you're using a + VPN.{' '} + { + locationControl.open() + })}> + Tap here to confirm your location. + + + + ) : ( + + + This estimate may be inaccurate if you're using a + VPN. + + + ))} + + {isGPS && IS_NATIVE && ( + + + { + locationControl.open() + })}> + Tap here to confirm your location. + + + + )} + + ) }