integrate existing flow into onboarding
This commit is contained in:
@@ -2,21 +2,23 @@ import {GetContacts} from './screens/GetContacts'
|
|||||||
import {PhoneInput} from './screens/PhoneInput'
|
import {PhoneInput} from './screens/PhoneInput'
|
||||||
import {VerifyNumber} from './screens/VerifyNumber'
|
import {VerifyNumber} from './screens/VerifyNumber'
|
||||||
import {ViewMatches} from './screens/ViewMatches'
|
import {ViewMatches} from './screens/ViewMatches'
|
||||||
import {type Action, type State} from './state'
|
import {type Action, FindContactsGoBackContext, type State} from './state'
|
||||||
|
|
||||||
export function FindContactsFlow({
|
export function FindContactsFlow({
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
onBack,
|
||||||
onCancel,
|
onCancel,
|
||||||
context = 'Standalone',
|
context = 'Standalone',
|
||||||
}: {
|
}: {
|
||||||
state: State
|
state: State
|
||||||
dispatch: React.Dispatch<Action>
|
dispatch: React.Dispatch<Action>
|
||||||
|
onBack?: () => void
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
context: 'Onboarding' | 'Standalone'
|
context: 'Onboarding' | 'Standalone'
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<FindContactsGoBackContext value={onBack}>
|
||||||
{state.step === '1: phone input' && (
|
{state.step === '1: phone input' && (
|
||||||
<PhoneInput
|
<PhoneInput
|
||||||
state={state}
|
state={state}
|
||||||
@@ -37,8 +39,13 @@ export function FindContactsFlow({
|
|||||||
<GetContacts state={state} dispatch={dispatch} onCancel={onCancel} />
|
<GetContacts state={state} dispatch={dispatch} onCancel={onCancel} />
|
||||||
)}
|
)}
|
||||||
{state.step === '4: view matches' && (
|
{state.step === '4: view matches' && (
|
||||||
<ViewMatches state={state} dispatch={dispatch} />
|
<ViewMatches
|
||||||
|
state={state}
|
||||||
|
dispatch={dispatch}
|
||||||
|
context={context}
|
||||||
|
onNext={onCancel}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</FindContactsGoBackContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
getCountryCodeFromPastedNumber,
|
getCountryCodeFromPastedNumber,
|
||||||
processPhoneNumber,
|
processPhoneNumber,
|
||||||
} from '../phone-number'
|
} from '../phone-number'
|
||||||
import {type Action, type State} from '../state'
|
import {type Action, type State, useOnPressBackButton} from '../state'
|
||||||
|
|
||||||
export function PhoneInput({
|
export function PhoneInput({
|
||||||
state,
|
state,
|
||||||
@@ -103,10 +103,12 @@ export function PhoneInput({
|
|||||||
|
|
||||||
const paddingBottom = Math.max(insets.bottom, tokens.space.xl)
|
const paddingBottom = Math.max(insets.bottom, tokens.space.xl)
|
||||||
|
|
||||||
|
const onPressBack = useOnPressBackButton()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.h_full]}>
|
<View style={[a.h_full]}>
|
||||||
<Layout.Header.Outer noBottomBorder>
|
<Layout.Header.Outer noBottomBorder>
|
||||||
<Layout.Header.BackButton />
|
<Layout.Header.BackButton onPress={onPressBack} />
|
||||||
<Layout.Header.Content />
|
<Layout.Header.Content />
|
||||||
{showSkipButton ? (
|
{showSkipButton ? (
|
||||||
<Button
|
<Button
|
||||||
@@ -204,12 +206,12 @@ export function PhoneInput({
|
|||||||
<View style={[gutters, {paddingBottom}]}>
|
<View style={[gutters, {paddingBottom}]}>
|
||||||
<Button
|
<Button
|
||||||
disabled={!phoneNumber || isPending}
|
disabled={!phoneNumber || isPending}
|
||||||
label={_(msg`Next`)}
|
label={_(msg`Send code`)}
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={onSubmitNumber}>
|
onPress={onSubmitNumber}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Send code</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
{isPending && <ButtonIcon icon={Loader} />}
|
{isPending && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {Loader} from '#/components/Loader'
|
|||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {OTPInput} from '../components/OTPInput'
|
import {OTPInput} from '../components/OTPInput'
|
||||||
import {type Action, type State} from '../state'
|
import {type Action, type State, useOnPressBackButton} from '../state'
|
||||||
export function VerifyNumber({
|
export function VerifyNumber({
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
@@ -121,10 +121,12 @@ export function VerifyNumber({
|
|||||||
[state.phoneCountryCode],
|
[state.phoneCountryCode],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onPressBack = useOnPressBackButton()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.h_full]}>
|
<View style={[a.h_full]}>
|
||||||
<Layout.Header.Outer noBottomBorder>
|
<Layout.Header.Outer noBottomBorder>
|
||||||
<Layout.Header.BackButton />
|
<Layout.Header.BackButton onPress={onPressBack} />
|
||||||
<Layout.Header.Content />
|
<Layout.Header.Content />
|
||||||
{showSkipButton ? (
|
{showSkipButton ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {useMemo, useRef, useState} from 'react'
|
import {useMemo, useRef, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import * as SMS from 'expo-sms'
|
import * as SMS from 'expo-sms'
|
||||||
import {type ModerationOpts} from '@atproto/api'
|
import {type ModerationOpts} from '@atproto/api'
|
||||||
import {msg, Plural, Trans} from '@lingui/macro'
|
import {msg, Plural, Trans} from '@lingui/macro'
|
||||||
@@ -19,7 +20,7 @@ import {useAgent, useSession} from '#/state/session'
|
|||||||
import {List, type ListMethods} from '#/view/com/util/List'
|
import {List, type ListMethods} from '#/view/com/util/List'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
import {atoms as a, tokens, useGutters, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {SearchInput} from '#/components/forms/SearchInput'
|
import {SearchInput} from '#/components/forms/SearchInput'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
@@ -68,16 +69,22 @@ type Item =
|
|||||||
export function ViewMatches({
|
export function ViewMatches({
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
context,
|
||||||
|
onNext,
|
||||||
}: {
|
}: {
|
||||||
state: Extract<State, {step: '4: view matches'}>
|
state: Extract<State, {step: '4: view matches'}>
|
||||||
dispatch: React.ActionDispatch<[Action]>
|
dispatch: React.ActionDispatch<[Action]>
|
||||||
|
context: 'Onboarding' | 'Standalone'
|
||||||
|
onNext: () => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
const gutter = useGutters([0, 'wide'])
|
const gutter = useGutters([0, 'wide'])
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
const listRef = useRef<ListMethods>(null)
|
const listRef = useRef<ListMethods>(null)
|
||||||
|
|
||||||
// TEMP!!!
|
// TEMP!!!
|
||||||
@@ -322,13 +329,15 @@ export function ViewMatches({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.h_full]}>
|
<View style={[a.h_full]}>
|
||||||
|
{context === 'Standalone' && (
|
||||||
<Layout.Header.Outer noBottomBorder>
|
<Layout.Header.Outer noBottomBorder>
|
||||||
<Layout.Header.BackButton />
|
<Layout.Header.BackButton />
|
||||||
<Layout.Header.Content />
|
<Layout.Header.Content />
|
||||||
<Layout.Header.Slot />
|
<Layout.Header.Slot />
|
||||||
</Layout.Header.Outer>
|
</Layout.Header.Outer>
|
||||||
|
)}
|
||||||
{!isTotallyEmpty && (
|
{!isTotallyEmpty && (
|
||||||
<View style={[gutter, a.mb_md]}>
|
<View style={[gutter, a.mb_md, context === 'Onboarding' && a.mt_sm]}>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder={_(msg`Search contacts`)}
|
placeholder={_(msg`Search contacts`)}
|
||||||
value={search}
|
value={search}
|
||||||
@@ -355,6 +364,29 @@ export function ViewMatches({
|
|||||||
ListFooterComponent={!isEmpty ? <ListFooter /> : null}
|
ListFooterComponent={!isEmpty ? <ListFooter /> : null}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
/>
|
/>
|
||||||
|
{context === 'Onboarding' && (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
a.border_t,
|
||||||
|
a.align_center,
|
||||||
|
a.align_stretch,
|
||||||
|
gutter,
|
||||||
|
a.pt_md,
|
||||||
|
{paddingBottom: insets.bottom + tokens.space.md},
|
||||||
|
]}>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Next`)}
|
||||||
|
onPress={onNext}
|
||||||
|
size="large"
|
||||||
|
color="primary">
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Next</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {useReducer} from 'react'
|
import {createContext, useContext, useReducer} from 'react'
|
||||||
|
import {type GestureResponderEvent} from 'react-native'
|
||||||
import {type ExistingContact} from 'expo-contacts'
|
import {type ExistingContact} from 'expo-contacts'
|
||||||
|
|
||||||
import {type CountryCode} from '#/lib/international-telephone-codes'
|
import {type CountryCode} from '#/lib/international-telephone-codes'
|
||||||
@@ -176,3 +177,17 @@ export function useFindContactsFlowState(
|
|||||||
) {
|
) {
|
||||||
return useReducer(reducer, initialState)
|
return useReducer(reducer, initialState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FindContactsGoBackContext = createContext<
|
||||||
|
(() => void) | undefined
|
||||||
|
>(undefined)
|
||||||
|
export function useOnPressBackButton() {
|
||||||
|
const goBack = useContext(FindContactsGoBackContext)
|
||||||
|
if (!goBack) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return (evt: GestureResponderEvent) => {
|
||||||
|
evt.preventDefault()
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import {useCallback} from 'react'
|
||||||
|
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
||||||
|
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
|
import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
|
||||||
|
import {useFindContactsFlowState} from '#/components/contacts/state'
|
||||||
|
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||||
|
import {useOnboardingInternalState} from '../state'
|
||||||
|
|
||||||
|
export function StepFindContacts() {
|
||||||
|
const [fcfState, fcfDispatch] = useFindContactsFlowState()
|
||||||
|
const {dispatch} = useOnboardingInternalState()
|
||||||
|
|
||||||
|
const onSkip = useCallback(() => {
|
||||||
|
dispatch({type: 'next'})
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
const canGoBack = fcfState.step === '2: verify number'
|
||||||
|
const onBack = useCallback(() => {
|
||||||
|
if (canGoBack) {
|
||||||
|
fcfDispatch({type: 'BACK'})
|
||||||
|
} else {
|
||||||
|
dispatch({type: 'prev'})
|
||||||
|
}
|
||||||
|
}, [dispatch, fcfDispatch, canGoBack])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView edges={['left', 'top', 'right']}>
|
||||||
|
<LayoutAnimationConfig skipEntering skipExiting>
|
||||||
|
<ScreenTransition key={fcfState.step} direction="Forward">
|
||||||
|
<FindContactsFlow
|
||||||
|
context="Onboarding"
|
||||||
|
state={fcfState}
|
||||||
|
dispatch={fcfDispatch}
|
||||||
|
onCancel={onSkip}
|
||||||
|
onBack={onBack}
|
||||||
|
/>
|
||||||
|
</ScreenTransition>
|
||||||
|
</LayoutAnimationConfig>
|
||||||
|
</SafeAreaView>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import {useMemo, useReducer} from 'react'
|
import {useMemo, useReducer} from 'react'
|
||||||
import {msg} from '@lingui/macro'
|
import {View} from 'react-native'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import * as bcp47Match from 'bcp-47-match'
|
import * as bcp47Match from 'bcp-47-match'
|
||||||
|
|
||||||
|
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
@@ -19,15 +19,17 @@ import {
|
|||||||
import {StepFinished} from '#/screens/Onboarding/StepFinished'
|
import {StepFinished} from '#/screens/Onboarding/StepFinished'
|
||||||
import {StepInterests} from '#/screens/Onboarding/StepInterests'
|
import {StepInterests} from '#/screens/Onboarding/StepInterests'
|
||||||
import {StepProfile} from '#/screens/Onboarding/StepProfile'
|
import {StepProfile} from '#/screens/Onboarding/StepProfile'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
import {ScreenTransition} from '#/components/ScreenTransition'
|
import {ScreenTransition} from '#/components/ScreenTransition'
|
||||||
import {ENV} from '#/env'
|
import {ENV} from '#/env'
|
||||||
|
import {StepFindContacts} from './StepFindContacts'
|
||||||
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
||||||
import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
import {StepSuggestedStarterpacks} from './StepSuggestedStarterpacks'
|
||||||
|
|
||||||
export function Onboarding() {
|
export function Onboarding() {
|
||||||
const {_} = useLingui()
|
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
const {contentLanguages} = useLanguagePrefs()
|
const {contentLanguages} = useLanguagePrefs()
|
||||||
const probablySpeaksEnglish = useMemo(() => {
|
const probablySpeaksEnglish = useMemo(() => {
|
||||||
@@ -52,46 +54,23 @@ export function Onboarding() {
|
|||||||
createInitialOnboardingState,
|
createInitialOnboardingState,
|
||||||
)
|
)
|
||||||
|
|
||||||
const interestsDisplayNames = useMemo(() => {
|
useEnableKeyboardControllerScreen(true)
|
||||||
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`),
|
|
||||||
}
|
|
||||||
}, [_])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
|
<View style={[a.absolute, a.inset_0, t.atoms.bg]}>
|
||||||
<OnboardingControls.Provider>
|
<OnboardingControls.Provider>
|
||||||
<OnboardingHeaderSlot.Provider>
|
<OnboardingHeaderSlot.Provider>
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={useMemo(
|
value={useMemo(() => ({state, dispatch}), [state, dispatch])}>
|
||||||
() => ({state, dispatch, interestsDisplayNames}),
|
|
||||||
[state, dispatch, interestsDisplayNames],
|
|
||||||
)}>
|
|
||||||
<Layout>
|
|
||||||
<ScreenTransition
|
<ScreenTransition
|
||||||
key={state.activeStep}
|
key={state.activeStep}
|
||||||
direction={state.stepTransitionDirection}>
|
direction={state.stepTransitionDirection}
|
||||||
|
style={a.flex_1}>
|
||||||
|
{state.activeStep === 'find-contacts' ? (
|
||||||
|
<StepFindContacts />
|
||||||
|
) : (
|
||||||
|
<Layout>
|
||||||
{state.activeStep === 'profile' && <StepProfile />}
|
{state.activeStep === 'profile' && <StepProfile />}
|
||||||
{state.activeStep === 'interests' && <StepInterests />}
|
{state.activeStep === 'interests' && <StepInterests />}
|
||||||
{state.activeStep === 'suggested-accounts' && (
|
{state.activeStep === 'suggested-accounts' && (
|
||||||
@@ -100,13 +79,14 @@ export function Onboarding() {
|
|||||||
{state.activeStep === 'suggested-starterpacks' && (
|
{state.activeStep === 'suggested-starterpacks' && (
|
||||||
<StepSuggestedStarterpacks />
|
<StepSuggestedStarterpacks />
|
||||||
)}
|
)}
|
||||||
{state.activeStep === 'find-contacts' && null}
|
|
||||||
{state.activeStep === 'finished' && <StepFinished />}
|
{state.activeStep === 'finished' && <StepFinished />}
|
||||||
</ScreenTransition>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
)}
|
||||||
|
</ScreenTransition>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</OnboardingHeaderSlot.Provider>
|
</OnboardingHeaderSlot.Provider>
|
||||||
</OnboardingControls.Provider>
|
</OnboardingControls.Provider>
|
||||||
|
</View>
|
||||||
</Portal>
|
</Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user