[Contacts] Contacts matching flow (#9486)

* add expo-contacts

* add expo-sms

* update copy

* add basic settings screen

* state machine, flow screen

* phone input screen

* otp screen

* tweak spacing

* resend code logic

* add layoutanimationconfig

* check availablility in settings screen

* get contacts

* matches screen

* search, temp setup for matches UI

* add a bunch of number parsing logic with libphonenumber

* cast to looser type

* rename sync to find

FCF (Find Contacts Flow)

* update geolocation hook

* nicer design for settings screen

* add completed state

* up border contrast

* update expo deps

* add pending spinner

* add country whitelist

* add empty state screen

* drop add more functionality

* upload -> import

* fix typo

* fix permission string

* copy updates

* rm envelope icon

* update sms copy

* add inviteinfo component

* woke is back

* [Contacts] NUXes (#9515)

* add a bunch of number parsing logic with libphonenumber

* add banner nux

* add announcement nux

* native only nux

* rm shitty animation

* move isNative check

* [Contacts] Onboarding step (#9489)

* add a bunch of number parsing logic with libphonenumber

* restructure onboarding to better support dynamic screens

* integrate existing flow into onboarding

* add intro step

* lift state up to allow going back freely

* gate onboarding by geo if unsupported country

* add done button to standalone flow

* [Contacts] Add `contact-match` notification type to feed (#9519)

* add a bunch of number parsing logic with libphonenumber

* add contact-joined notif type

* update string, api package

* Update NotificationFeedItem.tsx

* Update NotificationFeedItem.tsx

* Delete SyncContactsFlow.web.tsx

* fix follow back btn for this case

* [Contacts] API integration (#9487)

* api integration for flow

* copy tweak

* tweaks after running it

* wire up status page

* rename toast

* use api lib

* rm temp code

* maybe fix otp error

* clear code on error/resend

* add 1s delay to verify success

* update package versions

* Delete SyncContactsFlow.web.tsx

* try and fix yarn.lock lint

* even woker

* fix uppercase friends

* surfdude feedback

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>

* rm log

* allow resend on invalid code

* Update GetContacts.tsx

* overwrite country code if possible

* fix Apple's Best Feature

* devenv latest

* disable bounces

* interactive keyboard dismiss

* copy changes

* national format

* allow resending immediately

* move success state down a bit

* Update FindContactsSettings.tsx

* [Contacts] More onboarding changes (#9491)

* integrate existing flow into onboarding

* refreshed onboarding styles, rm stepper

* center content on web

* Add back dismiss button for internal onboarding

* Import sort

---------

Co-authored-by: Eric Bailey <git@esb.lol>

* add matches query to shadow

* add clockwise arrow

* update status design, fix dismiss, fix queries

* add metrics

* add more comments

* Update metrics.ts

* refetch both queries on PTR

* fix shadow state in matches page

* reduce empty space at bottom

* show contact info on matches

* filter out contacts without numbers at an earlier stage

* Error handling ✨

* get notifs working

* filter out businesses

* rm log

* remove TODO from learn more links

* try and exclude from web bundle

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Samuel Newman
2025-12-12 20:32:06 +02:00
committed by GitHub
parent d3a6db4594
commit ad7d49ef8f
98 changed files with 4906 additions and 1079 deletions
+61 -60
View File
@@ -1,28 +1,38 @@
import {useMemo, useReducer} from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {View} from 'react-native'
import * as bcp47Match from 'bcp-47-match'
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
import {useGate} from '#/lib/statsig/statsig'
import {isNative} from '#/platform/detection'
import {useLanguagePrefs} from '#/state/preferences'
import {
Layout,
OnboardingControls,
OnboardingHeaderSlot,
} from '#/screens/Onboarding/Layout'
import {Context, initialState, reducer} from '#/screens/Onboarding/state'
import {
Context,
createInitialOnboardingState,
reducer,
} from '#/screens/Onboarding/state'
import {StepFinished} from '#/screens/Onboarding/StepFinished'
import {StepInterests} from '#/screens/Onboarding/StepInterests'
import {StepProfile} from '#/screens/Onboarding/StepProfile'
import {atoms as a, useTheme} from '#/alf'
import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/contacts/country-allowlist'
import {useFindContactsFlowState} from '#/components/contacts/state'
import {Portal} from '#/components/Portal'
import {ScreenTransition} from '#/components/ScreenTransition'
import {ENV} from '#/env'
import {StepFindContacts} from './StepFindContacts'
import {StepFindContactsIntro} from './StepFindContactsIntro'
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
export function Onboarding() {
const {_} = useLingui()
const gate = useGate()
const t = useTheme()
const {contentLanguages} = useLanguagePrefs()
const probablySpeaksEnglish = useMemo(() => {
@@ -36,70 +46,61 @@ export function Onboarding() {
probablySpeaksEnglish &&
gate('onboarding_suggested_starterpacks')
const [state, dispatch] = useReducer(reducer, {
...initialState,
totalSteps: 4 + (showSuggestedStarterpacks ? 1 : 0),
experiments: {
onboarding_suggested_accounts: true,
onboarding_value_prop: true,
onboarding_suggested_starterpacks: showSuggestedStarterpacks,
},
})
const findContactsEnabled =
useIsFindContactsFeatureEnabledBasedOnGeolocation()
const showFindContacts =
isNative && findContactsEnabled && !gate('disable_onboarding_find_contacts')
const interestsDisplayNames = useMemo(() => {
return {
news: _(msg`News`),
journalism: _(msg`Journalism`),
nature: _(msg`Nature`),
art: _(msg`Art`),
comics: _(msg`Comics`),
writers: _(msg`Writers`),
culture: _(msg`Culture`),
sports: _(msg`Sports`),
pets: _(msg`Pets`),
animals: _(msg`Animals`),
books: _(msg`Books`),
education: _(msg`Education`),
climate: _(msg`Climate`),
science: _(msg`Science`),
politics: _(msg`Politics`),
fitness: _(msg`Fitness`),
tech: _(msg`Tech`),
dev: _(msg`Software Dev`),
comedy: _(msg`Comedy`),
gaming: _(msg`Video Games`),
food: _(msg`Food`),
cooking: _(msg`Cooking`),
}
}, [_])
const [state, dispatch] = useReducer(
reducer,
{
starterPacksStepEnabled: showSuggestedStarterpacks,
findContactsStepEnabled: showFindContacts,
},
createInitialOnboardingState,
)
const [contactsFlowState, contactsFlowDispatch] = useFindContactsFlowState()
useEnableKeyboardControllerScreen(true)
return (
<Portal>
<OnboardingControls.Provider>
<OnboardingHeaderSlot.Provider>
<Context.Provider
value={useMemo(
() => ({state, dispatch, interestsDisplayNames}),
[state, dispatch, interestsDisplayNames],
)}>
<Layout>
<View style={[a.absolute, a.inset_0, t.atoms.bg]}>
<OnboardingControls.Provider>
<OnboardingHeaderSlot.Provider>
<Context.Provider
value={useMemo(() => ({state, dispatch}), [state, dispatch])}>
<ScreenTransition
key={state.activeStep}
direction={state.stepTransitionDirection}>
{state.activeStep === 'profile' && <StepProfile />}
{state.activeStep === 'interests' && <StepInterests />}
{state.activeStep === 'suggested-accounts' && (
<StepSuggestedAccounts />
direction={state.stepTransitionDirection}
style={a.flex_1}>
{/* FindContactsFlow cannot be nested in Layout */}
{state.activeStep === 'find-contacts' ? (
<StepFindContacts
flowState={contactsFlowState}
flowDispatch={contactsFlowDispatch}
/>
) : (
<Layout>
{state.activeStep === 'profile' && <StepProfile />}
{state.activeStep === 'interests' && <StepInterests />}
{state.activeStep === 'suggested-accounts' && (
<StepSuggestedAccounts />
)}
{state.activeStep === 'suggested-starterpacks' && (
<StepSuggestedStarterpacks />
)}
{state.activeStep === 'find-contacts-intro' && (
<StepFindContactsIntro />
)}
{state.activeStep === 'finished' && <StepFinished />}
</Layout>
)}
{state.activeStep === 'suggested-starterpacks' && (
<StepSuggestedStarterpacks />
)}
{state.activeStep === 'finished' && <StepFinished />}
</ScreenTransition>
</Layout>
</Context.Provider>
</OnboardingHeaderSlot.Provider>
</OnboardingControls.Provider>
</Context.Provider>
</OnboardingHeaderSlot.Provider>
</OnboardingControls.Provider>
</View>
</Portal>
)
}