From ac713bac0872bfb89b51ca6c4d5f25faf4f137eb Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 14 Aug 2025 16:53:09 +0300 Subject: [PATCH] add new gated screen to onboarding --- src/lib/statsig/gates.ts | 1 + src/screens/Onboarding/Layout.tsx | 2 +- .../StepSuggestedAccounts/index.tsx | 146 ++++++++++++++++++ src/screens/Onboarding/index.tsx | 19 ++- src/screens/Onboarding/state.ts | 57 +++++-- 5 files changed, 207 insertions(+), 18 deletions(-) create mode 100644 src/screens/Onboarding/StepSuggestedAccounts/index.tsx diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index ef6dc1d4df..7fe8320109 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -7,6 +7,7 @@ export type Gate = | 'explore_show_suggested_feeds' | 'old_postonboarding' | 'onboarding_add_video_feed' + | 'onboarding_suggested_accounts' | 'post_follow_profile_suggested_accounts' | 'post_threads_v2_unspecced' | 'remove_show_latest_button' diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 16c37358ff..15c300fed8 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -11,7 +11,7 @@ import { atoms as a, flatten, native, - TextStyleProp, + type TextStyleProp, useBreakpoints, useTheme, web, diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx new file mode 100644 index 0000000000..bf7b71c5b3 --- /dev/null +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -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 ? ( + Oh no! Something went wrong. + ) : ( + Suggested Accounts + ) + const description = isError ? ( + + 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. + + ) : ( + We'll use this to help customize your experience. + ) + + return ( + + + + {title} + {description} + + + + + {isError ? ( + + + + + ) : ( + + + + + )} + + + ) +} diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx index a5c423ca19..b878438f17 100644 --- a/src/screens/Onboarding/index.tsx +++ b/src/screens/Onboarding/index.tsx @@ -1,21 +1,29 @@ -import React from 'react' +import {useMemo, useReducer} from 'react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useGate} from '#/lib/statsig/statsig' import {Layout, OnboardingControls} from '#/screens/Onboarding/Layout' import {Context, initialState, reducer} from '#/screens/Onboarding/state' import {StepFinished} from '#/screens/Onboarding/StepFinished' import {StepInterests} from '#/screens/Onboarding/StepInterests' import {StepProfile} from '#/screens/Onboarding/StepProfile' import {Portal} from '#/components/Portal' +import {StepSuggestedAccounts} from './StepSuggestedAccounts' export function Onboarding() { const {_} = useLingui() - const [state, dispatch] = React.useReducer(reducer, { + const gate = useGate() + const showSuggestedAccounts = gate('onboarding_suggested_accounts') + const [state, dispatch] = useReducer(reducer, { ...initialState, + totalSteps: showSuggestedAccounts ? 4 : 3, + experiments: { + onboarding_suggested_accounts: showSuggestedAccounts, + }, }) - const interestsDisplayNames = React.useMemo(() => { + const interestsDisplayNames = useMemo(() => { return { news: _(msg`News`), journalism: _(msg`Journalism`), @@ -46,13 +54,16 @@ export function Onboarding() { ({state, dispatch, interestsDisplayNames}), [state, dispatch, interestsDisplayNames], )}> {state.activeStep === 'profile' && } {state.activeStep === 'interests' && } + {state.activeStep === 'suggested-accounts' && ( + + )} {state.activeStep === 'finished' && } diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts index cbb466245a..42732b8725 100644 --- a/src/screens/Onboarding/state.ts +++ b/src/screens/Onboarding/state.ts @@ -11,7 +11,7 @@ import { export type OnboardingState = { hasPrev: boolean totalSteps: number - activeStep: 'profile' | 'interests' | 'finished' + activeStep: 'profile' | 'interests' | 'suggested-accounts' | 'finished' activeStepIndex: number interestsStepResults: { @@ -34,6 +34,10 @@ export type OnboardingState = { backgroundColor: AvatarColor } } + + experiments?: { + onboarding_suggested_accounts?: boolean + } } export type OnboardingAction = @@ -160,22 +164,49 @@ export function reducer( switch (a.type) { case 'next': { - if (s.activeStep === 'profile') { - next.activeStep = 'interests' - next.activeStepIndex = 2 - } else if (s.activeStep === 'interests') { - next.activeStep = 'finished' - next.activeStepIndex = 3 + 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') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } else if (s.activeStep === 'interests') { + next.activeStep = 'finished' + next.activeStepIndex = 3 + } } break } case 'prev': { - if (s.activeStep === 'interests') { - next.activeStep = 'profile' - next.activeStepIndex = 1 - } else if (s.activeStep === 'finished') { - next.activeStep = 'interests' - next.activeStepIndex = 2 + 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') { + next.activeStep = 'profile' + next.activeStepIndex = 1 + } else if (s.activeStep === 'finished') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } } break }