diff --git a/src/ageAssurance/components/NoAccessScreen.tsx b/src/ageAssurance/components/NoAccessScreen.tsx index 6e323b7f03..1f50fa9821 100644 --- a/src/ageAssurance/components/NoAccessScreen.tsx +++ b/src/ageAssurance/components/NoAccessScreen.tsx @@ -43,19 +43,20 @@ import {logger} from '#/ageAssurance/logger' import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import { + createGeolocationString, getAgeAssuranceDataFromDeviceSignals, isLegacyBirthdateBug, useAgeAssuranceRegionConfig, } from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' -import {useDeviceGeolocationApi} from '#/geolocation' +import {useDeviceGeolocationApi, useGeolocation} from '#/geolocation' const textStyles = [a.text_md, a.leading_snug] export function NoAccessScreen() { const t = useTheme() - const {_} = useLingui() + const {_, i18n} = useLingui() const ax = useAnalytics() const {gtPhone} = useBreakpoints() const insets = useSafeAreaInsets() @@ -67,6 +68,10 @@ export function NoAccessScreen() { const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() const {logoutCurrentAccount} = useSessionApi() const createSupportLink = useCreateSupportLink() + const geolocation = useGeolocation() + const {setDeviceGeolocation} = useDeviceGeolocationApi() + const locationControl = Dialog.useDialogControl() + const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() const aa = useAgeAssurance() const isBlocked = aa.state.status === aa.Status.Blocked @@ -74,6 +79,8 @@ export function NoAccessScreen() { const hasDeclaredAge = aa.flags.hasDeclaredAge const canUpdateBirthday = isBirthdateUpdateAllowed || isLegacyBirthdateBug(metadata?.birthdate || '') + const geolocationString = createGeolocationString(geolocation, i18n.locale) + const isUsingGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE useEffect(() => { // just counting overall hits here @@ -189,6 +196,78 @@ export function NoAccessScreen() { + {region && ( + + {isUsingGPS ? ( + + Based on your device's location, we think you're + in{' '} + + {geolocationString} + + . + + ) : ( + <> + + Based on your network, we think you're in{' '} + + {geolocationString} + + . This estimate may be inaccurate if you're + using a VPN. + {' '} + {IS_NATIVE && ( + <> + + You can also{' '} + { + locationControl.open() + })} + style={[textStyles]}> + tap here to update your location with GPS. + + + + { + 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', + }, + ) + }) + } + }} + /> + + )} + + )} + + )} + {!aa.flags.isOverRegionMinAccessAge && ( @@ -312,10 +391,7 @@ function AccessSection() { const ax = useAnalytics() const control = useDialogControl() const appealControl = Dialog.useDialogControl() - const locationControl = Dialog.useDialogControl() const getTimeAgo = useGetTimeAgo() - const {setDeviceGeolocation} = useDeviceGeolocationApi() - const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() const {currentAccount} = useSession() const region = useAgeAssuranceRegionConfig() @@ -505,50 +581,6 @@ function AccessSection() { )} - - - {IS_NATIVE && ( - <> - - - Is your location not accurate?{' '} - { - locationControl.open() - })}> - Tap here to update your location with GPS. - - - - - { - 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', - }) - }) - } - }} - /> - - )} - ) diff --git a/src/ageAssurance/debug.ts b/src/ageAssurance/debug.ts index 49a2269351..84c20a31c0 100644 --- a/src/ageAssurance/debug.ts +++ b/src/ageAssurance/debug.ts @@ -9,24 +9,22 @@ import {type OtherRequiredData} from '#/ageAssurance/data' import {IS_DEV, IS_E2E} from '#/env' import {type Geolocation} from '#/geolocation' -export const enabled = (IS_DEV && false) || IS_E2E +export const enabled = (IS_DEV && true) || IS_E2E export const geolocation: Geolocation | undefined = enabled ? { - countryCode: 'AA', - regionCode: undefined, + countryCode: 'US', + regionCode: 'TX', } : undefined -const deviceGeolocationEnabled = false || IS_E2E -export const deviceGeolocation: Geolocation | undefined = - enabled && deviceGeolocationEnabled - ? { - countryCode: 'AA', - regionCode: undefined, - ...geolocation, - } - : undefined +export const deviceGeolocation: Geolocation | undefined = enabled + ? { + countryCode: 'AA', + regionCode: undefined, + ...geolocation, + } + : undefined export const otherRequiredData: OtherRequiredData = { birthdate: new Date(2000, 12, 1).toISOString(), diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index 955e4c129c..b746b45c7c 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -7,6 +7,7 @@ import { } from '@atproto/api' import {getAge} from '#/lib/strings/time' +import {regionName} from '#/locale/helpers' import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const' import { DEVICE_SIGNALS_SUPPORTED, @@ -21,6 +22,7 @@ import { type AgeAssuranceState, } from '#/ageAssurance/types' import {type Geolocation, useGeolocation} from '#/geolocation' +import {USRegionNameToRegionCode} from '#/geolocation/util' /** * Get age assurance region config based on geolocation, with fallback to @@ -206,3 +208,27 @@ export function computeAgeAssuranceFlags({ allowsDeviceVerification, } } + +const USRegionCodeToRegionName: {[regionCode: string]: string} = + Object.fromEntries( + Object.entries(USRegionNameToRegionCode).map(([name, code]) => [ + code, + name, + ]), + ) +export function createGeolocationString( + 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 +} diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx index 3f4f1f98f6..8703390d55 100644 --- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx +++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx @@ -2,7 +2,6 @@ import {View} from 'react-native' 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' @@ -22,40 +21,10 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' +import {createGeolocationString} from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' -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 -} +import {useDeviceGeolocationApi, useGeolocation} from '#/geolocation' export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { const aa = useAgeAssurance() @@ -220,13 +189,12 @@ 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) - const isGPS = !!deviceGeolocation?.countryCode && IS_NATIVE + const region = createGeolocationString(geolocation, i18n.locale) + const isGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE return ( <> @@ -258,8 +226,7 @@ function RegionNotice() { {isGPS ? ( Based on your device's location, we think you're in{' '} - {region}. This - estimate may be inaccurate if you're using a VPN. + {region}. ) : ( diff --git a/src/geolocation/index.tsx b/src/geolocation/index.tsx index c9dc0cb1ed..37d9d4b155 100644 --- a/src/geolocation/index.tsx +++ b/src/geolocation/index.tsx @@ -22,6 +22,8 @@ export * from '#/geolocation/types' const GeolocationContext = createContext({ countryCode: undefined, regionCode: undefined, + serviceGeolocation: undefined, + deviceGeolocation: undefined, }) const DeviceGeolocationAPIContext = createContext<{ @@ -44,7 +46,17 @@ export function Provider({children}: {children: ReactNode}) { 'deviceGeolocation', ]) const geolocation = useMemo(() => { - return mergeGeolocations(deviceGeolocation, geolocationService) + const geo = mergeGeolocations(deviceGeolocation, geolocationService) + // create new objects to avoid cyclical references + geo.serviceGeolocation = { + countryCode: geolocationService?.countryCode, + regionCode: geolocationService?.regionCode, + } + geo.deviceGeolocation = { + countryCode: deviceGeolocation?.countryCode, + regionCode: deviceGeolocation?.regionCode, + } + return geo }, [deviceGeolocation, geolocationService]) useEffect(() => { diff --git a/src/geolocation/types.ts b/src/geolocation/types.ts index 80848730c9..979b4dd742 100644 --- a/src/geolocation/types.ts +++ b/src/geolocation/types.ts @@ -1,4 +1,6 @@ export type Geolocation = { countryCode: string | undefined regionCode: string | undefined + serviceGeolocation?: Geolocation + deviceGeolocation?: Geolocation }