[APP-1684] Some contact import tweaks (#9555)
* Handle download link * Improve NUX geo gating from #9549 * Fix alignment of phone code select * Show full name * Add gate to nux banner * Add gate to settings screen * Invert gate check in settings, whoops
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {Fragment, useMemo} from 'react'
|
||||
import {Text as RNText} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -113,5 +114,5 @@ function Flag({unicodeFlag, svgFlag}: {unicodeFlag: string; svgFlag: any}) {
|
||||
/>
|
||||
)
|
||||
}
|
||||
return unicodeFlag + ' '
|
||||
return <RNText style={[{lineHeight: 21}]}>{unicodeFlag + ' '}</RNText>
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
|
||||
@@ -20,9 +21,8 @@ export function FindContactsBannerNUX() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {visible, close} = useInternalState()
|
||||
const isFeatureEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
||||
|
||||
if (!visible || !isFeatureEnabled) return null
|
||||
if (!visible) return null
|
||||
|
||||
return (
|
||||
<View style={[a.w_full, a.p_lg, a.border_b, t.atoms.border_contrast_low]}>
|
||||
@@ -88,13 +88,17 @@ function useInternalState() {
|
||||
const {nux} = useNux(Nux.FindContactsDismissibleBanner)
|
||||
const {mutate: save, variables} = useSaveNux()
|
||||
const hidden = !!variables
|
||||
const isFeatureEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
||||
const gate = useGate()
|
||||
|
||||
const visible = useMemo(() => {
|
||||
if (isWeb) return false
|
||||
if (hidden) return false
|
||||
if (nux && nux.completed) return false
|
||||
if (!isFeatureEnabled) return false
|
||||
if (gate('disable_settings_find_contacts')) return false
|
||||
return true
|
||||
}, [hidden, nux])
|
||||
}, [hidden, nux, isFeatureEnabled, gate])
|
||||
|
||||
const close = () => {
|
||||
save({
|
||||
|
||||
@@ -18,7 +18,16 @@ const FIND_CONTACTS_FEATURE_COUNTRY_ALLOWLIST = [
|
||||
'IT',
|
||||
] 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(
|
||||
countryCode.toUpperCase(),
|
||||
)
|
||||
@@ -26,12 +35,5 @@ export function isFindContactsFeatureEnabled(countryCode: string): boolean {
|
||||
|
||||
export function useIsFindContactsFeatureEnabledBasedOnGeolocation() {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -104,8 +104,6 @@ export function ViewMatches({
|
||||
match => !state.dismissedMatches.includes(match.profile.did),
|
||||
)
|
||||
|
||||
console.log(matches)
|
||||
|
||||
const followableDids = matches.map(match => match.profile.did)
|
||||
const [didFollowAll, setDidFollowAll] = useState(followableDids.length === 0)
|
||||
|
||||
@@ -449,7 +447,7 @@ function MatchItem({
|
||||
const contactName = useMemo(() => {
|
||||
if (!contact) return null
|
||||
|
||||
const name = contact.firstName ?? contact.lastName ?? contact.name
|
||||
const name = contact.name ?? contact.firstName ?? contact.lastName
|
||||
if (name) return _(msg`Your contact ${name}`)
|
||||
const phone =
|
||||
contact.phoneNumbers?.find(p => p.isPrimary) ?? contact.phoneNumbers?.[0]
|
||||
@@ -520,7 +518,7 @@ function ContactItem({
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
const name = contact.firstName ?? contact.lastName ?? contact.name
|
||||
const name = contact.name ?? contact.firstName ?? contact.lastName
|
||||
const phone =
|
||||
contact.phoneNumbers?.find(phone => phone.isPrimary) ??
|
||||
contact.phoneNumbers?.[0]
|
||||
|
||||
@@ -6,26 +6,33 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
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 {useNuxDialogContext} from '#/components/dialogs/nuxs'
|
||||
import {
|
||||
createIsEnabledCheck,
|
||||
isExistingUserAsOf,
|
||||
} from '#/components/dialogs/nuxs/utils'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_E2E} from '#/env'
|
||||
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() {
|
||||
const isFeatureEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation()
|
||||
|
||||
if (!isFeatureEnabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <Inner />
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const nuxDialogs = useNuxDialogContext()
|
||||
|
||||
@@ -10,7 +10,6 @@ import {type AppBskyActorDefs} from '@atproto/api'
|
||||
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {Nux, useNuxs, useResetNuxs, useSaveNux} from '#/state/queries/nuxs'
|
||||
import {
|
||||
@@ -20,13 +19,13 @@ import {
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {type SessionAccount, useSession} from '#/state/session'
|
||||
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 {ENV} from '#/env'
|
||||
/*
|
||||
* NUXs
|
||||
*/
|
||||
import {FindContactsAnnouncement} from './FindContactsAnnouncement'
|
||||
import {isExistingUserAsOf} from './utils'
|
||||
import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
|
||||
type Context = {
|
||||
activeNux: Nux | undefined
|
||||
@@ -35,22 +34,11 @@ type Context = {
|
||||
|
||||
const queuedNuxs: {
|
||||
id: Nux
|
||||
enabled?: (props: {
|
||||
gate: ReturnType<typeof useGate>
|
||||
currentAccount: SessionAccount
|
||||
currentProfile: AppBskyActorDefs.ProfileViewDetailed
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) => boolean
|
||||
enabled?: (props: EnabledCheckProps) => boolean
|
||||
}[] = [
|
||||
{
|
||||
id: Nux.FindContactsAnnouncement,
|
||||
enabled: ({currentProfile}) => {
|
||||
return (
|
||||
isNative &&
|
||||
ENV !== 'e2e' &&
|
||||
isExistingUserAsOf('2025-12-16T00:00:00.000Z', currentProfile.createdAt)
|
||||
)
|
||||
},
|
||||
enabled: isFindContactsAnnouncementEnabled,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -101,6 +89,7 @@ function Inner({
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) {
|
||||
const gate = useGate()
|
||||
const geolocation = useGeolocation()
|
||||
const {nuxs} = useNuxs()
|
||||
const [snoozed, setSnoozed] = useState(() => {
|
||||
return isSnoozed()
|
||||
@@ -143,7 +132,13 @@ function Inner({
|
||||
// then check gate (track exposure)
|
||||
if (
|
||||
enabled &&
|
||||
!enabled({gate, currentAccount, currentProfile, preferences})
|
||||
!enabled({
|
||||
gate,
|
||||
currentAccount,
|
||||
currentProfile,
|
||||
preferences,
|
||||
geolocation,
|
||||
})
|
||||
) {
|
||||
continue
|
||||
}
|
||||
@@ -178,6 +173,7 @@ function Inner({
|
||||
currentAccount,
|
||||
currentProfile,
|
||||
preferences,
|
||||
geolocation,
|
||||
])
|
||||
|
||||
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
|
||||
|
||||
export function isDaysOld(days: number, createdAt?: string) {
|
||||
|
||||
Reference in New Issue
Block a user