Handle birthday confusion by adding a util that determines if manually updating bday could even help
This commit is contained in:
@@ -6,10 +6,6 @@ import {msg} from '@lingui/core/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {
|
|
||||||
SupportCode,
|
|
||||||
useCreateSupportLink,
|
|
||||||
} from '#/lib/hooks/useCreateSupportLink'
|
|
||||||
import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
||||||
import {useIsBirthdateUpdateAllowed} from '#/state/birthdate'
|
import {useIsBirthdateUpdateAllowed} from '#/state/birthdate'
|
||||||
import {useSession, useSessionApi} from '#/state/session'
|
import {useSession, useSessionApi} from '#/state/session'
|
||||||
@@ -43,6 +39,7 @@ import {logger} from '#/ageAssurance/logger'
|
|||||||
import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state'
|
import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state'
|
||||||
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
|
import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
|
||||||
import {
|
import {
|
||||||
|
canBirthdateUpdateIncreaseAccess,
|
||||||
createGeolocationString,
|
createGeolocationString,
|
||||||
getAgeAssuranceDataFromDeviceSignals,
|
getAgeAssuranceDataFromDeviceSignals,
|
||||||
isLegacyBirthdateBug,
|
isLegacyBirthdateBug,
|
||||||
@@ -67,7 +64,6 @@ export function NoAccessScreen() {
|
|||||||
const region = useAgeAssuranceRegionConfig()
|
const region = useAgeAssuranceRegionConfig()
|
||||||
const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed()
|
const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed()
|
||||||
const {logoutCurrentAccount} = useSessionApi()
|
const {logoutCurrentAccount} = useSessionApi()
|
||||||
const createSupportLink = useCreateSupportLink()
|
|
||||||
const geolocation = useGeolocation()
|
const geolocation = useGeolocation()
|
||||||
const {setDeviceGeolocation} = useDeviceGeolocationApi()
|
const {setDeviceGeolocation} = useDeviceGeolocationApi()
|
||||||
const locationControl = Dialog.useDialogControl()
|
const locationControl = Dialog.useDialogControl()
|
||||||
@@ -77,8 +73,13 @@ export function NoAccessScreen() {
|
|||||||
const isBlocked = aa.state.status === aa.Status.Blocked
|
const isBlocked = aa.state.status === aa.Status.Blocked
|
||||||
const isAARegion = !!region
|
const isAARegion = !!region
|
||||||
const hasDeclaredAge = aa.flags.hasDeclaredAge
|
const hasDeclaredAge = aa.flags.hasDeclaredAge
|
||||||
|
const birthdateMightIncreaseAccess = Boolean(
|
||||||
|
region && canBirthdateUpdateIncreaseAccess({region, metadata}),
|
||||||
|
)
|
||||||
const canUpdateBirthday =
|
const canUpdateBirthday =
|
||||||
isBirthdateUpdateAllowed || isLegacyBirthdateBug(metadata?.birthdate || '')
|
(isBirthdateUpdateAllowed ||
|
||||||
|
isLegacyBirthdateBug(metadata?.birthdate || '')) &&
|
||||||
|
birthdateMightIncreaseAccess
|
||||||
const geolocationString = createGeolocationString(geolocation, i18n.locale)
|
const geolocationString = createGeolocationString(geolocation, i18n.locale)
|
||||||
const isUsingGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE
|
const isUsingGPS = !!geolocation.deviceGeolocation?.countryCode && IS_NATIVE
|
||||||
|
|
||||||
@@ -136,20 +137,7 @@ export function NoAccessScreen() {
|
|||||||
|
|
||||||
{orgAdmonition}
|
{orgAdmonition}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : null
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>
|
|
||||||
If you believe your birthdate is incorrect, please{' '}
|
|
||||||
<SimpleInlineLinkText
|
|
||||||
to={createSupportLink({code: SupportCode.AA_BIRTHDATE})}
|
|
||||||
label={_(msg`Click here to contact our support team`)}
|
|
||||||
style={[textStyles]}>
|
|
||||||
contact our support team
|
|
||||||
</SimpleInlineLinkText>
|
|
||||||
.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -180,149 +168,152 @@ export function NoAccessScreen() {
|
|||||||
<AgeAssuranceBadge />
|
<AgeAssuranceBadge />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{hasDeclaredAge ? (
|
<View style={[a.gap_lg]}>
|
||||||
<>
|
{hasDeclaredAge ? (
|
||||||
{isAARegion ? (
|
<>
|
||||||
<>
|
{isAARegion ? (
|
||||||
<View style={[a.gap_lg]}>
|
<>
|
||||||
<Text style={[textStyles]}>
|
<View style={[a.gap_lg]}>
|
||||||
<Trans>Hey there!</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>
|
|
||||||
You are accessing Bluesky from a region that legally
|
|
||||||
requires us to verify your age before allowing you to
|
|
||||||
access the app.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{region && (
|
|
||||||
<Text style={[textStyles]}>
|
<Text style={[textStyles]}>
|
||||||
{isUsingGPS ? (
|
<Trans>Hey there!</Trans>
|
||||||
<Trans>
|
</Text>
|
||||||
Based on your device's location, we think you're
|
<Text style={[textStyles]}>
|
||||||
in{' '}
|
<Trans>
|
||||||
<Text style={[textStyles, a.font_bold]}>
|
You are accessing Bluesky from a region that legally
|
||||||
{geolocationString}
|
requires us to verify your age before allowing you
|
||||||
</Text>
|
to access the app.
|
||||||
.
|
</Trans>
|
||||||
</Trans>
|
</Text>
|
||||||
) : (
|
|
||||||
<>
|
{region && (
|
||||||
|
<Text style={[textStyles]}>
|
||||||
|
{isUsingGPS ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
Based on your network, we think you're in{' '}
|
Based on your device's location, we think you're
|
||||||
|
in{' '}
|
||||||
<Text style={[textStyles, a.font_bold]}>
|
<Text style={[textStyles, a.font_bold]}>
|
||||||
{geolocationString}
|
{geolocationString}
|
||||||
</Text>
|
</Text>
|
||||||
. This estimate may be inaccurate if you're
|
.
|
||||||
using a VPN.
|
|
||||||
</Trans>
|
</Trans>
|
||||||
</>
|
) : (
|
||||||
)}
|
<>
|
||||||
{IS_NATIVE && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<SimpleInlineLinkText
|
|
||||||
label={_(msg`Update your location`)}
|
|
||||||
{...createStaticClick(() => {
|
|
||||||
locationControl.open()
|
|
||||||
})}
|
|
||||||
style={[textStyles]}>
|
|
||||||
<Trans>
|
<Trans>
|
||||||
Tap here to update your location via GPS.
|
Based on your network, we think you're in{' '}
|
||||||
|
<Text style={[textStyles, a.font_bold]}>
|
||||||
|
{geolocationString}
|
||||||
|
</Text>
|
||||||
|
. This estimate may be inaccurate if you're
|
||||||
|
using a VPN.
|
||||||
</Trans>
|
</Trans>
|
||||||
</SimpleInlineLinkText>
|
</>
|
||||||
<DeviceLocationRequestDialog
|
)}
|
||||||
control={locationControl}
|
{IS_NATIVE && (
|
||||||
onLocationAcquired={props => {
|
<>
|
||||||
const access =
|
{' '}
|
||||||
computeAgeAssuranceRegionAccess(
|
<SimpleInlineLinkText
|
||||||
props.geolocation,
|
label={_(msg`Update your location`)}
|
||||||
)
|
{...createStaticClick(() => {
|
||||||
if (access !== aa.Access.Full) {
|
locationControl.open()
|
||||||
props.disableDialogAction()
|
})}
|
||||||
props.setDialogError(
|
style={[textStyles]}>
|
||||||
_(
|
<Trans>
|
||||||
msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`,
|
Tap here to update your location with GPS.
|
||||||
),
|
</Trans>
|
||||||
)
|
</SimpleInlineLinkText>
|
||||||
} else {
|
<DeviceLocationRequestDialog
|
||||||
props.closeDialog(() => {
|
control={locationControl}
|
||||||
// set this after close!
|
onLocationAcquired={props => {
|
||||||
setDeviceGeolocation(props.geolocation)
|
const access =
|
||||||
Toast.show(
|
computeAgeAssuranceRegionAccess(
|
||||||
_(msg`Thanks! You're all set.`),
|
props.geolocation,
|
||||||
{
|
|
||||||
type: 'success',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
})
|
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.`,
|
||||||
)}
|
),
|
||||||
</Text>
|
)
|
||||||
)}
|
} else {
|
||||||
|
props.closeDialog(() => {
|
||||||
|
// set this after close!
|
||||||
|
setDeviceGeolocation(props.geolocation)
|
||||||
|
Toast.show(
|
||||||
|
_(msg`Thanks! You're all set.`),
|
||||||
|
{
|
||||||
|
type: 'success',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{!aa.flags.isOverRegionMinAccessAge && (
|
{!aa.flags.isOverRegionMinAccessAge && (
|
||||||
<Text style={[textStyles]}>
|
<Text style={[textStyles]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Unfortunately, your declared age indicates that you
|
Unfortunately, your declared age indicates that
|
||||||
are not old enough to access Bluesky in your region.
|
you are not old enough to access Bluesky in your
|
||||||
</Trans>
|
region.
|
||||||
</Text>
|
</Trans>
|
||||||
)}
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isBlocked && birthdateUpdateText}
|
{!isBlocked && birthdateUpdateText}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{aa.flags.isOverRegionMinAccessAge && <AccessSection />}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<Text style={[textStyles]}>
|
||||||
|
<Trans>
|
||||||
|
Unfortunately, the birthdate you have saved to your
|
||||||
|
profile makes you too young to access Bluesky.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{birthdateUpdateText}
|
||||||
</View>
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<Text style={[textStyles]}>
|
||||||
|
<Trans>Hi there!</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[textStyles]}>
|
||||||
|
<Trans>
|
||||||
|
In order to provide an age-appropriate experience, we need
|
||||||
|
to know your birthdate. This is a one-time thing, and your
|
||||||
|
data will be kept private.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[textStyles]}>
|
||||||
|
<Trans>
|
||||||
|
Set your birthdate below and we'll get you back to posting
|
||||||
|
and exploring in no time!
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Click here to update your birthdate`)}
|
||||||
|
onPress={() => birthdateControl.open()}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Add your birthdate</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
{aa.flags.isOverRegionMinAccessAge && <AccessSection />}
|
{orgAdmonition}
|
||||||
</>
|
</View>
|
||||||
) : (
|
)}
|
||||||
<View style={[a.gap_lg]}>
|
</View>
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>
|
|
||||||
Unfortunately, the birthdate you have saved to your
|
|
||||||
profile makes you too young to access Bluesky.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{birthdateUpdateText}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<View style={[a.gap_lg]}>
|
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>Hi there!</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>
|
|
||||||
In order to provide an age-appropriate experience, we need
|
|
||||||
to know your birthdate. This is a one-time thing, and your
|
|
||||||
data will be kept private.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text style={[textStyles]}>
|
|
||||||
<Trans>
|
|
||||||
Set your birthdate below and we'll get you back to posting
|
|
||||||
and exploring in no time!
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
label={_(msg`Click here to update your birthdate`)}
|
|
||||||
onPress={() => birthdateControl.open()}>
|
|
||||||
<ButtonText>
|
|
||||||
<Trans>Add your birthdate</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{orgAdmonition}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={[a.pt_lg, a.gap_xl, {maxWidth: 280}]}>
|
<View style={[a.pt_lg, a.gap_xl, {maxWidth: 280}]}>
|
||||||
<Logo width={120} textFill={t.atoms.text.color} />
|
<Logo width={120} textFill={t.atoms.text.color} />
|
||||||
@@ -540,7 +531,7 @@ function AccessSection() {
|
|||||||
<ButtonText>{verifyCta}</ButtonText>
|
<ButtonText>{verifyCta}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{allowsDeviceVerification && (
|
{allowsDeviceVerification ? (
|
||||||
<Text
|
<Text
|
||||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
|
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
@@ -558,9 +549,7 @@ function AccessSection() {
|
|||||||
platforms.
|
platforms.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
) : lastInitiatedAt && timeAgo && diff ? (
|
||||||
|
|
||||||
{lastInitiatedAt && timeAgo && diff ? (
|
|
||||||
<Text
|
<Text
|
||||||
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}
|
style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}
|
||||||
title={i18n.date(lastInitiatedAt, {
|
title={i18n.date(lastInitiatedAt, {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import type * as AgeRange from 'expo-age-range'
|
import type * as AgeRange from 'expo-age-range'
|
||||||
import {
|
import {
|
||||||
type AppBskyAgeassuranceDefs,
|
AppBskyAgeassuranceDefs,
|
||||||
|
computeAgeAssuranceRegionAccess,
|
||||||
getAgeAssuranceRegionConfig,
|
getAgeAssuranceRegionConfig,
|
||||||
type ModerationPrefs,
|
type ModerationPrefs,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -109,6 +110,97 @@ export function getAgeAssuranceDataFromDeviceSignals(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ranks access levels from most to least restrictive so we can compare two
|
||||||
|
* outcomes. Higher number = more access.
|
||||||
|
*/
|
||||||
|
const ACCESS_RANK: Record<string, number> = {
|
||||||
|
[AgeAssuranceAccess.None]: 0,
|
||||||
|
[AgeAssuranceAccess.Safe]: 1,
|
||||||
|
[AgeAssuranceAccess.Full]: 2,
|
||||||
|
// `unknown` isn't a real granted level; treat it as the floor.
|
||||||
|
[AgeAssuranceAccess.Unknown]: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether correcting the user's declared age (i.e. updating their birthdate)
|
||||||
|
* could meaningfully improve their standing in the current region.
|
||||||
|
*
|
||||||
|
* There are two ways a birthdate update can help:
|
||||||
|
*
|
||||||
|
* 1. Raising the rule-engine access level. Some regions grant `safe`/`full` off
|
||||||
|
* a sufficient *declared* age, so a user whose birthdate is wrong (too young)
|
||||||
|
* can unlock more by correcting it. Other regions gate higher access purely
|
||||||
|
* on an *assured* age (or account date), where a declared age changes
|
||||||
|
* nothing.
|
||||||
|
* 2. Crossing the region's `minAccessAge`. Below it the user is hard-blocked
|
||||||
|
* with no verify path (see `isOverRegionMinAccessAge` gating in the
|
||||||
|
* NoAccessScreen); crossing it unlocks the verify flow, which is itself a
|
||||||
|
* path to more access even when the rule-engine level would still be `none`.
|
||||||
|
*
|
||||||
|
* We answer by simulating the real rule engine: hold `accountCreatedAt` and
|
||||||
|
* `assuredAge` fixed and re-run access for a set of candidate declared ages
|
||||||
|
* drawn from the region's declared-age rule thresholds and its `minAccessAge`.
|
||||||
|
* If any candidate yields strictly more access, or crosses `minAccessAge` when
|
||||||
|
* the current declared age doesn't, a birthdate update could help. Simulating
|
||||||
|
* rather than statically inspecting rules means first-match precedence (e.g. an
|
||||||
|
* assured/account rule pre-empting a declared rule) is handled correctly for
|
||||||
|
* free.
|
||||||
|
*/
|
||||||
|
export function canBirthdateUpdateIncreaseAccess({
|
||||||
|
region,
|
||||||
|
metadata,
|
||||||
|
}: {
|
||||||
|
region: AppBskyAgeassuranceDefs.ConfigRegion
|
||||||
|
metadata?: AgeAssuranceMetadata
|
||||||
|
}): boolean {
|
||||||
|
const baseline = computeAgeAssuranceRegionAccess(region, {
|
||||||
|
accountCreatedAt: metadata?.accountCreatedAt,
|
||||||
|
declaredAge: metadata?.declaredAge,
|
||||||
|
assuredAge: metadata?.assuredAge,
|
||||||
|
})
|
||||||
|
const baselineRank =
|
||||||
|
ACCESS_RANK[baseline?.access ?? AgeAssuranceAccess.Unknown]
|
||||||
|
const baselineOverMin =
|
||||||
|
metadata?.declaredAge !== undefined &&
|
||||||
|
metadata.declaredAge >= region.minAccessAge
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Candidate declared ages to probe: each declared-age rule's threshold and
|
||||||
|
* the region's `minAccessAge`, plus one below each (to cover
|
||||||
|
* `IfDeclaredUnderAge` and the min-age boundary). Anything a birthdate edit
|
||||||
|
* could achieve is captured by crossing one of these thresholds, so we don't
|
||||||
|
* need to sweep every integer.
|
||||||
|
*/
|
||||||
|
const thresholds = new Set<number>([region.minAccessAge])
|
||||||
|
for (const rule of region.rules) {
|
||||||
|
if (
|
||||||
|
AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule) ||
|
||||||
|
AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule)
|
||||||
|
) {
|
||||||
|
thresholds.add(rule.age)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const candidates = new Set<number>()
|
||||||
|
for (const threshold of thresholds) {
|
||||||
|
candidates.add(threshold)
|
||||||
|
candidates.add(Math.max(0, threshold - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const declaredAge of candidates) {
|
||||||
|
const result = computeAgeAssuranceRegionAccess(region, {
|
||||||
|
accountCreatedAt: metadata?.accountCreatedAt,
|
||||||
|
declaredAge,
|
||||||
|
assuredAge: metadata?.assuredAge,
|
||||||
|
})
|
||||||
|
const rank = ACCESS_RANK[result?.access ?? AgeAssuranceAccess.Unknown]
|
||||||
|
const overMin = declaredAge >= region.minAccessAge
|
||||||
|
if (rank > baselineRank || (overMin && !baselineOverMin)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to get the age assurance region config based on current geolocation.
|
* Hook to get the age assurance region config based on current geolocation.
|
||||||
* Does not fall-back to our app defaults. If no config is found, returns
|
* Does not fall-back to our app defaults. If no config is found, returns
|
||||||
|
|||||||
@@ -235,20 +235,20 @@ function RegionNotice() {
|
|||||||
estimate may be inaccurate if you're using a VPN.
|
estimate may be inaccurate if you're using a VPN.
|
||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
</Text>
|
{IS_NATIVE && (
|
||||||
)}
|
<Text style={[a.text_sm, a.leading_snug]}>
|
||||||
|
{' '}
|
||||||
{IS_NATIVE && (
|
<Trans>
|
||||||
<Text style={[a.text_sm, a.leading_snug]}>
|
<InlineLinkText
|
||||||
<Trans>
|
label={l`Update your location`}
|
||||||
<InlineLinkText
|
{...createStaticClick(() => {
|
||||||
label={l`Update your location`}
|
locationControl.open()
|
||||||
{...createStaticClick(() => {
|
})}>
|
||||||
locationControl.open()
|
Tap here to update your location with GPS.
|
||||||
})}>
|
</InlineLinkText>
|
||||||
Tap here to update your location with GPS.
|
</Trans>
|
||||||
</InlineLinkText>
|
</Text>
|
||||||
</Trans>
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user