add new gated screen to onboarding
This commit is contained in:
@@ -7,6 +7,7 @@ export type Gate =
|
|||||||
| 'explore_show_suggested_feeds'
|
| 'explore_show_suggested_feeds'
|
||||||
| 'old_postonboarding'
|
| 'old_postonboarding'
|
||||||
| 'onboarding_add_video_feed'
|
| 'onboarding_add_video_feed'
|
||||||
|
| 'onboarding_suggested_accounts'
|
||||||
| 'post_follow_profile_suggested_accounts'
|
| 'post_follow_profile_suggested_accounts'
|
||||||
| 'post_threads_v2_unspecced'
|
| 'post_threads_v2_unspecced'
|
||||||
| 'remove_show_latest_button'
|
| 'remove_show_latest_button'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
atoms as a,
|
atoms as a,
|
||||||
flatten,
|
flatten,
|
||||||
native,
|
native,
|
||||||
TextStyleProp,
|
type TextStyleProp,
|
||||||
useBreakpoints,
|
useBreakpoints,
|
||||||
useTheme,
|
useTheme,
|
||||||
web,
|
web,
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import {useCallback, useContext, useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
|
import {
|
||||||
|
DescriptionText,
|
||||||
|
OnboardingControls,
|
||||||
|
TitleText,
|
||||||
|
} from '#/screens/Onboarding/Layout'
|
||||||
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {IconCircle} from '#/components/IconCircle'
|
||||||
|
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwise} from '#/components/icons/ArrowRotateCounterClockwise'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||||
|
import {EmojiSad_Stroke2_Corner0_Rounded as EmojiSad} from '#/components/icons/Emoji'
|
||||||
|
import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
|
||||||
|
export function StepSuggestedAccounts() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
|
const {dispatch} = useContext(Context)
|
||||||
|
const [saving, setSaving] = useState(false)
|
||||||
|
const onboardDispatch = useOnboardingDispatch()
|
||||||
|
|
||||||
|
const saveInterests = useCallback(async () => {
|
||||||
|
setSaving(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSaving(false)
|
||||||
|
dispatch({type: 'next'})
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.info(`onboading: error saving interests`)
|
||||||
|
logger.error(e)
|
||||||
|
}
|
||||||
|
}, [setSaving, dispatch])
|
||||||
|
|
||||||
|
const skipOnboarding = useCallback(() => {
|
||||||
|
onboardDispatch({type: 'finish'})
|
||||||
|
dispatch({type: 'finish'})
|
||||||
|
}, [onboardDispatch, dispatch])
|
||||||
|
|
||||||
|
const isError = false
|
||||||
|
|
||||||
|
const title = isError ? (
|
||||||
|
<Trans>Oh no! Something went wrong.</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Suggested Accounts</Trans>
|
||||||
|
)
|
||||||
|
const description = isError ? (
|
||||||
|
<Trans>
|
||||||
|
We weren't able to connect. Please try again to continue setting up your
|
||||||
|
account. If it continues to fail, you can skip this flow.
|
||||||
|
</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>We'll use this to help customize your experience.</Trans>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.align_start]} testID="onboardingInterests">
|
||||||
|
<IconCircle
|
||||||
|
icon={isError ? EmojiSad : Hashtag}
|
||||||
|
style={[
|
||||||
|
a.mb_2xl,
|
||||||
|
isError
|
||||||
|
? {
|
||||||
|
backgroundColor: t.palette.negative_50,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
]}
|
||||||
|
iconStyle={[
|
||||||
|
isError
|
||||||
|
? {
|
||||||
|
color: t.palette.negative_900,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TitleText>{title}</TitleText>
|
||||||
|
<DescriptionText>{description}</DescriptionText>
|
||||||
|
|
||||||
|
<View style={[a.w_full, a.pt_2xl]} />
|
||||||
|
|
||||||
|
<OnboardingControls.Portal>
|
||||||
|
{isError ? (
|
||||||
|
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
|
||||||
|
<Button
|
||||||
|
// disabled={isFetching}
|
||||||
|
variant="solid"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Retry`)}
|
||||||
|
// onPress={() => refetch()}
|
||||||
|
>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Retry</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon icon={ArrowRotateCounterClockwise} position="right" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Skip this flow`)}
|
||||||
|
onPress={skipOnboarding}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Skip</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}>
|
||||||
|
<Button
|
||||||
|
disabled={saving}
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Follow all accounts`)}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Follow all</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={saving}
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
label={_(msg`Continue to next step`)}
|
||||||
|
onPress={saveInterests}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Continue</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon icon={saving ? Loader : ChevronRight} />
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</OnboardingControls.Portal>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,21 +1,29 @@
|
|||||||
import React from 'react'
|
import {useMemo, useReducer} from 'react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {Layout, OnboardingControls} from '#/screens/Onboarding/Layout'
|
import {Layout, OnboardingControls} from '#/screens/Onboarding/Layout'
|
||||||
import {Context, initialState, reducer} from '#/screens/Onboarding/state'
|
import {Context, initialState, reducer} from '#/screens/Onboarding/state'
|
||||||
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 {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
import {StepSuggestedAccounts} from './StepSuggestedAccounts'
|
||||||
|
|
||||||
export function Onboarding() {
|
export function Onboarding() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const [state, dispatch] = React.useReducer(reducer, {
|
const gate = useGate()
|
||||||
|
const showSuggestedAccounts = gate('onboarding_suggested_accounts')
|
||||||
|
const [state, dispatch] = useReducer(reducer, {
|
||||||
...initialState,
|
...initialState,
|
||||||
|
totalSteps: showSuggestedAccounts ? 4 : 3,
|
||||||
|
experiments: {
|
||||||
|
onboarding_suggested_accounts: showSuggestedAccounts,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const interestsDisplayNames = React.useMemo(() => {
|
const interestsDisplayNames = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
news: _(msg`News`),
|
news: _(msg`News`),
|
||||||
journalism: _(msg`Journalism`),
|
journalism: _(msg`Journalism`),
|
||||||
@@ -46,13 +54,16 @@ export function Onboarding() {
|
|||||||
<Portal>
|
<Portal>
|
||||||
<OnboardingControls.Provider>
|
<OnboardingControls.Provider>
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={React.useMemo(
|
value={useMemo(
|
||||||
() => ({state, dispatch, interestsDisplayNames}),
|
() => ({state, dispatch, interestsDisplayNames}),
|
||||||
[state, dispatch, interestsDisplayNames],
|
[state, dispatch, interestsDisplayNames],
|
||||||
)}>
|
)}>
|
||||||
<Layout>
|
<Layout>
|
||||||
{state.activeStep === 'profile' && <StepProfile />}
|
{state.activeStep === 'profile' && <StepProfile />}
|
||||||
{state.activeStep === 'interests' && <StepInterests />}
|
{state.activeStep === 'interests' && <StepInterests />}
|
||||||
|
{state.activeStep === 'suggested-accounts' && (
|
||||||
|
<StepSuggestedAccounts />
|
||||||
|
)}
|
||||||
{state.activeStep === 'finished' && <StepFinished />}
|
{state.activeStep === 'finished' && <StepFinished />}
|
||||||
</Layout>
|
</Layout>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
export type OnboardingState = {
|
export type OnboardingState = {
|
||||||
hasPrev: boolean
|
hasPrev: boolean
|
||||||
totalSteps: number
|
totalSteps: number
|
||||||
activeStep: 'profile' | 'interests' | 'finished'
|
activeStep: 'profile' | 'interests' | 'suggested-accounts' | 'finished'
|
||||||
activeStepIndex: number
|
activeStepIndex: number
|
||||||
|
|
||||||
interestsStepResults: {
|
interestsStepResults: {
|
||||||
@@ -34,6 +34,10 @@ export type OnboardingState = {
|
|||||||
backgroundColor: AvatarColor
|
backgroundColor: AvatarColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
experiments?: {
|
||||||
|
onboarding_suggested_accounts?: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnboardingAction =
|
export type OnboardingAction =
|
||||||
@@ -160,6 +164,19 @@ export function reducer(
|
|||||||
|
|
||||||
switch (a.type) {
|
switch (a.type) {
|
||||||
case 'next': {
|
case 'next': {
|
||||||
|
if (s.experiments?.onboarding_suggested_accounts) {
|
||||||
|
if (s.activeStep === 'profile') {
|
||||||
|
next.activeStep = 'interests'
|
||||||
|
next.activeStepIndex = 2
|
||||||
|
} else if (s.activeStep === 'interests') {
|
||||||
|
next.activeStep = 'suggested-accounts'
|
||||||
|
next.activeStepIndex = 3
|
||||||
|
}
|
||||||
|
if (s.activeStep === 'suggested-accounts') {
|
||||||
|
next.activeStep = 'finished'
|
||||||
|
next.activeStepIndex = 4
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (s.activeStep === 'profile') {
|
if (s.activeStep === 'profile') {
|
||||||
next.activeStep = 'interests'
|
next.activeStep = 'interests'
|
||||||
next.activeStepIndex = 2
|
next.activeStepIndex = 2
|
||||||
@@ -167,9 +184,22 @@ export function reducer(
|
|||||||
next.activeStep = 'finished'
|
next.activeStep = 'finished'
|
||||||
next.activeStepIndex = 3
|
next.activeStepIndex = 3
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'prev': {
|
case 'prev': {
|
||||||
|
if (s.experiments?.onboarding_suggested_accounts) {
|
||||||
|
if (s.activeStep === 'interests') {
|
||||||
|
next.activeStep = 'profile'
|
||||||
|
next.activeStepIndex = 1
|
||||||
|
} else if (s.activeStep === 'suggested-accounts') {
|
||||||
|
next.activeStep = 'interests'
|
||||||
|
next.activeStepIndex = 2
|
||||||
|
} else if (s.activeStep === 'finished') {
|
||||||
|
next.activeStep = 'suggested-accounts'
|
||||||
|
next.activeStepIndex = 3
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (s.activeStep === 'interests') {
|
if (s.activeStep === 'interests') {
|
||||||
next.activeStep = 'profile'
|
next.activeStep = 'profile'
|
||||||
next.activeStepIndex = 1
|
next.activeStepIndex = 1
|
||||||
@@ -177,6 +207,7 @@ export function reducer(
|
|||||||
next.activeStep = 'interests'
|
next.activeStep = 'interests'
|
||||||
next.activeStepIndex = 2
|
next.activeStepIndex = 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'finish': {
|
case 'finish': {
|
||||||
|
|||||||
Reference in New Issue
Block a user