Improve NUX geo gating from #9549
This commit is contained in:
@@ -18,7 +18,16 @@ const FIND_CONTACTS_FEATURE_COUNTRY_ALLOWLIST = [
|
|||||||
'IT',
|
'IT',
|
||||||
] satisfies CountryCode[] as string[]
|
] satisfies CountryCode[] as string[]
|
||||||
|
|
||||||
export function isFindContactsFeatureEnabled(countryCode: string): boolean {
|
export function isFindContactsFeatureEnabled(countryCode?: string): boolean {
|
||||||
|
if (IS_DEV) return true
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This should never happen unless geolocation fails entirely. In that
|
||||||
|
* case, let the user try, since it should work as long as they have a
|
||||||
|
* phone number from one of the allow-listed countries.
|
||||||
|
*/
|
||||||
|
if (!countryCode) return true
|
||||||
|
|
||||||
return FIND_CONTACTS_FEATURE_COUNTRY_ALLOWLIST.includes(
|
return FIND_CONTACTS_FEATURE_COUNTRY_ALLOWLIST.includes(
|
||||||
countryCode.toUpperCase(),
|
countryCode.toUpperCase(),
|
||||||
)
|
)
|
||||||
@@ -26,12 +35,5 @@ export function isFindContactsFeatureEnabled(countryCode: string): boolean {
|
|||||||
|
|
||||||
export function useIsFindContactsFeatureEnabledBasedOnGeolocation() {
|
export function useIsFindContactsFeatureEnabledBasedOnGeolocation() {
|
||||||
const location = useGeolocation()
|
const location = useGeolocation()
|
||||||
|
|
||||||
if (IS_DEV) return true
|
|
||||||
|
|
||||||
// they can try, by they'll need a phone number
|
|
||||||
// from one of the allowlisted countries
|
|
||||||
if (!location.countryCode) return true
|
|
||||||
|
|
||||||
return isFindContactsFeatureEnabled(location.countryCode)
|
return isFindContactsFeatureEnabled(location.countryCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,26 +6,33 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/contacts/country-allowlist'
|
import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
|
import {useNuxDialogContext} from '#/components/dialogs/nuxs'
|
||||||
|
import {
|
||||||
|
createIsEnabledCheck,
|
||||||
|
isExistingUserAsOf,
|
||||||
|
} from '#/components/dialogs/nuxs/utils'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import {IS_E2E} from '#/env'
|
||||||
import {navigate} from '#/Navigation'
|
import {navigate} from '#/Navigation'
|
||||||
|
|
||||||
|
export const enabled = createIsEnabledCheck(props => {
|
||||||
|
return (
|
||||||
|
!IS_E2E &&
|
||||||
|
isNative &&
|
||||||
|
isExistingUserAsOf(
|
||||||
|
'2025-12-16T00:00:00.000Z',
|
||||||
|
props.currentProfile.createdAt,
|
||||||
|
) &&
|
||||||
|
isFindContactsFeatureEnabled(props.geolocation.countryCode)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
export function FindContactsAnnouncement() {
|
export function FindContactsAnnouncement() {
|
||||||
const isFeatureEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
|
||||||
|
|
||||||
if (!isFeatureEnabled) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Inner />
|
|
||||||
}
|
|
||||||
|
|
||||||
function Inner() {
|
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const nuxDialogs = useNuxDialogContext()
|
const nuxDialogs = useNuxDialogContext()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {type AppBskyActorDefs} from '@atproto/api'
|
|||||||
|
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative} from '#/platform/detection'
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {Nux, useNuxs, useResetNuxs, useSaveNux} from '#/state/queries/nuxs'
|
import {Nux, useNuxs, useResetNuxs, useSaveNux} from '#/state/queries/nuxs'
|
||||||
import {
|
import {
|
||||||
@@ -20,13 +19,13 @@ import {
|
|||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {type SessionAccount, useSession} from '#/state/session'
|
import {type SessionAccount, useSession} from '#/state/session'
|
||||||
import {useOnboardingState} from '#/state/shell'
|
import {useOnboardingState} from '#/state/shell'
|
||||||
|
import {
|
||||||
|
enabled as isFindContactsAnnouncementEnabled,
|
||||||
|
FindContactsAnnouncement,
|
||||||
|
} from '#/components/dialogs/nuxs/FindContactsAnnouncement'
|
||||||
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
|
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
|
||||||
import {ENV} from '#/env'
|
import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
|
||||||
/*
|
import {useGeolocation} from '#/geolocation'
|
||||||
* NUXs
|
|
||||||
*/
|
|
||||||
import {FindContactsAnnouncement} from './FindContactsAnnouncement'
|
|
||||||
import {isExistingUserAsOf} from './utils'
|
|
||||||
|
|
||||||
type Context = {
|
type Context = {
|
||||||
activeNux: Nux | undefined
|
activeNux: Nux | undefined
|
||||||
@@ -35,22 +34,11 @@ type Context = {
|
|||||||
|
|
||||||
const queuedNuxs: {
|
const queuedNuxs: {
|
||||||
id: Nux
|
id: Nux
|
||||||
enabled?: (props: {
|
enabled?: (props: EnabledCheckProps) => boolean
|
||||||
gate: ReturnType<typeof useGate>
|
|
||||||
currentAccount: SessionAccount
|
|
||||||
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
|
||||||
preferences: UsePreferencesQueryResponse
|
|
||||||
}) => boolean
|
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
id: Nux.FindContactsAnnouncement,
|
id: Nux.FindContactsAnnouncement,
|
||||||
enabled: ({currentProfile}) => {
|
enabled: isFindContactsAnnouncementEnabled,
|
||||||
return (
|
|
||||||
isNative &&
|
|
||||||
ENV !== 'e2e' &&
|
|
||||||
isExistingUserAsOf('2025-12-16T00:00:00.000Z', currentProfile.createdAt)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -101,6 +89,7 @@ function Inner({
|
|||||||
preferences: UsePreferencesQueryResponse
|
preferences: UsePreferencesQueryResponse
|
||||||
}) {
|
}) {
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
|
const geolocation = useGeolocation()
|
||||||
const {nuxs} = useNuxs()
|
const {nuxs} = useNuxs()
|
||||||
const [snoozed, setSnoozed] = useState(() => {
|
const [snoozed, setSnoozed] = useState(() => {
|
||||||
return isSnoozed()
|
return isSnoozed()
|
||||||
@@ -143,7 +132,13 @@ function Inner({
|
|||||||
// then check gate (track exposure)
|
// then check gate (track exposure)
|
||||||
if (
|
if (
|
||||||
enabled &&
|
enabled &&
|
||||||
!enabled({gate, currentAccount, currentProfile, preferences})
|
!enabled({
|
||||||
|
gate,
|
||||||
|
currentAccount,
|
||||||
|
currentProfile,
|
||||||
|
preferences,
|
||||||
|
geolocation,
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -178,6 +173,7 @@ function Inner({
|
|||||||
currentAccount,
|
currentAccount,
|
||||||
currentProfile,
|
currentProfile,
|
||||||
preferences,
|
preferences,
|
||||||
|
geolocation,
|
||||||
])
|
])
|
||||||
|
|
||||||
const ctx = useMemo(() => {
|
const ctx = useMemo(() => {
|
||||||
|
|||||||
@@ -1,3 +1,24 @@
|
|||||||
|
import {type AppBskyActorDefs} from '@atproto/api'
|
||||||
|
|
||||||
|
import {type useGate} from '#/lib/statsig/statsig'
|
||||||
|
import {type UsePreferencesQueryResponse} from '#/state/queries/preferences'
|
||||||
|
import {type SessionAccount} from '#/state/session'
|
||||||
|
import {type Geolocation} from '#/geolocation'
|
||||||
|
|
||||||
|
export type EnabledCheckProps = {
|
||||||
|
gate: ReturnType<typeof useGate>
|
||||||
|
currentAccount: SessionAccount
|
||||||
|
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
||||||
|
preferences: UsePreferencesQueryResponse
|
||||||
|
geolocation: Geolocation
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIsEnabledCheck(
|
||||||
|
cb: (props: EnabledCheckProps) => boolean,
|
||||||
|
) {
|
||||||
|
return cb
|
||||||
|
}
|
||||||
|
|
||||||
const ONE_DAY = 1000 * 60 * 60 * 24
|
const ONE_DAY = 1000 * 60 * 60 * 24
|
||||||
|
|
||||||
export function isDaysOld(days: number, createdAt?: string) {
|
export function isDaysOld(days: number, createdAt?: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user