[Reduced Onboarding] Add new step, new state to reducer (#3931)

* Add new step, new state to reducer

* Don't set default feeds
zio/stable
Eric Bailey 2024-05-10 23:19:16 -05:00 committed by GitHub
parent 08979f37e7
commit 80ce6f980e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 132 additions and 76 deletions

View File

@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
import {useAnalytics} from '#/lib/analytics/analytics' import {useAnalytics} from '#/lib/analytics/analytics'
import {BSKY_APP_ACCOUNT_DID, IS_PROD_SERVICE} from '#/lib/constants' import {BSKY_APP_ACCOUNT_DID, IS_PROD_SERVICE} from '#/lib/constants'
import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent, useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
@ -41,6 +41,7 @@ export function StepFinished() {
const [saving, setSaving] = React.useState(false) const [saving, setSaving] = React.useState(false)
const {mutateAsync: overwriteSavedFeeds} = useOverwriteSavedFeedsMutation() const {mutateAsync: overwriteSavedFeeds} = useOverwriteSavedFeedsMutation()
const {getAgent} = useAgent() const {getAgent} = useAgent()
const gate = useGate()
const finishOnboarding = React.useCallback(async () => { const finishOnboarding = React.useCallback(async () => {
setSaving(true) setSaving(true)
@ -67,40 +68,46 @@ export function StepFinished() {
(async () => { (async () => {
await getAgent().setInterestsPref({tags: selectedInterests}) await getAgent().setInterestsPref({tags: selectedInterests})
// TODO: In the reduced onboarding, we'll want to exit early here. /*
* In the reduced onboading experiment, we'll rely on the default
* feeds set in `createAgentAndCreateAccount`. No feeds will be
* selected in onboarding and therefore we don't need to run this
* code (which would overwrite the other feeds already set).
*/
if (!gate('reduced_onboarding_and_home_algo')) {
const otherFeeds = selectedFeeds.length
? selectedFeeds.map(f => ({
type: 'feed',
value: f,
pinned: true,
id: TID.nextStr(),
}))
: []
const otherFeeds = selectedFeeds.length /*
? selectedFeeds.map(f => ({ * If no selected feeds and we're in prod, add the discover feed
type: 'feed', * (mimics old behavior)
value: f, */
if (
IS_PROD_SERVICE(getAgent().service.toString()) &&
!otherFeeds.length
) {
otherFeeds.push({
...DISCOVER_SAVED_FEED,
pinned: true, pinned: true,
id: TID.nextStr(), id: TID.nextStr(),
})) })
: [] }
/* await overwriteSavedFeeds([
* If no selected feeds and we're in prod, add the discover feed {
* (mimics old behavior) ...TIMELINE_SAVED_FEED,
*/ pinned: true,
if ( id: TID.nextStr(),
IS_PROD_SERVICE(getAgent().service.toString()) && },
!otherFeeds.length ...otherFeeds,
) { ])
otherFeeds.push({
...DISCOVER_SAVED_FEED,
pinned: true,
id: TID.nextStr(),
})
} }
await overwriteSavedFeeds([
{
...TIMELINE_SAVED_FEED,
pinned: true,
id: TID.nextStr(),
},
...otherFeeds,
])
})(), })(),
]) ])
} catch (e: any) { } catch (e: any) {
@ -123,6 +130,7 @@ export function StepFinished() {
overwriteSavedFeeds, overwriteSavedFeeds,
track, track,
getAgent, getAgent,
gate,
]) ])
React.useEffect(() => { React.useEffect(() => {

View File

@ -0,0 +1,55 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
DescriptionText,
OnboardingControls,
TitleText,
} from '#/screens/Onboarding/Layout'
import {Context} from '#/screens/Onboarding/state'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {IconCircle} from '#/components/IconCircle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {StreamingLive_Stroke2_Corner0_Rounded as StreamingLive} from '#/components/icons/StreamingLive'
export function StepProfile() {
const {_} = useLingui()
const {dispatch} = React.useContext(Context)
const onContinue = React.useCallback(() => {
dispatch({type: 'next'})
}, [dispatch])
return (
<View style={[a.align_start]}>
<IconCircle icon={StreamingLive} style={[a.mb_2xl]} />
<TitleText>
<Trans>Give your profile a face</Trans>
</TitleText>
<DescriptionText>
<Trans>
Help people know you're not a bot by uploading a picture or creating
an avatar.
</Trans>
</DescriptionText>
<OnboardingControls.Portal>
<Button
variant="gradient"
color="gradient_sky"
size="large"
label={_(msg`Continue to next step`)}
onPress={onContinue}>
<ButtonText>
<Trans>Continue</Trans>
</ButtonText>
<ButtonIcon icon={ChevronRight} position="right" />
</Button>
</OnboardingControls.Portal>
</View>
)
}

View File

@ -16,6 +16,7 @@ import {StepFinished} from '#/screens/Onboarding/StepFinished'
import {StepFollowingFeed} from '#/screens/Onboarding/StepFollowingFeed' import {StepFollowingFeed} from '#/screens/Onboarding/StepFollowingFeed'
import {StepInterests} from '#/screens/Onboarding/StepInterests' import {StepInterests} from '#/screens/Onboarding/StepInterests'
import {StepModeration} from '#/screens/Onboarding/StepModeration' import {StepModeration} from '#/screens/Onboarding/StepModeration'
import {StepProfile} from '#/screens/Onboarding/StepProfile'
import {StepSuggestedAccounts} from '#/screens/Onboarding/StepSuggestedAccounts' import {StepSuggestedAccounts} from '#/screens/Onboarding/StepSuggestedAccounts'
import {StepTopicalFeeds} from '#/screens/Onboarding/StepTopicalFeeds' import {StepTopicalFeeds} from '#/screens/Onboarding/StepTopicalFeeds'
import {Portal} from '#/components/Portal' import {Portal} from '#/components/Portal'
@ -65,6 +66,7 @@ export function Onboarding() {
[state, dispatch, interestsDisplayNames], [state, dispatch, interestsDisplayNames],
)}> )}>
<Layout> <Layout>
{state.activeStep === 'profile' && <StepProfile />}
{state.activeStep === 'interests' && <StepInterests />} {state.activeStep === 'interests' && <StepInterests />}
{state.activeStep === 'suggestedAccounts' && ( {state.activeStep === 'suggestedAccounts' && (
<StepSuggestedAccounts /> <StepSuggestedAccounts />

View File

@ -6,6 +6,7 @@ export type OnboardingState = {
hasPrev: boolean hasPrev: boolean
totalSteps: number totalSteps: number
activeStep: activeStep:
| 'profile'
| 'interests' | 'interests'
| 'suggestedAccounts' | 'suggestedAccounts'
| 'followingFeed' | 'followingFeed'
@ -28,6 +29,10 @@ export type OnboardingState = {
topicalFeedsStepResults: { topicalFeedsStepResults: {
feedUris: string[] feedUris: string[]
} }
profileStepResults: {
imageUri?: string
imageMime?: string
}
} }
export type OnboardingAction = export type OnboardingAction =
@ -57,6 +62,11 @@ export type OnboardingAction =
type: 'setTopicalFeedsStepResults' type: 'setTopicalFeedsStepResults'
feedUris: string[] feedUris: string[]
} }
| {
type: 'setProfileStepResults'
imageUri: string
imageMime: string
}
export type ApiResponseMap = { export type ApiResponseMap = {
interests: string[] interests: string[]
@ -91,6 +101,10 @@ export const initialState: OnboardingState = {
topicalFeedsStepResults: { topicalFeedsStepResults: {
feedUris: [], feedUris: [],
}, },
profileStepResults: {
imageUri: '',
imageMime: '',
},
} }
export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: { export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: {
@ -240,8 +254,8 @@ export function reducer(
export const initialStateReduced: OnboardingState = { export const initialStateReduced: OnboardingState = {
hasPrev: false, hasPrev: false,
totalSteps: 7, totalSteps: 3,
activeStep: 'interests', activeStep: 'profile',
activeStepIndex: 1, activeStepIndex: 1,
interestsStepResults: { interestsStepResults: {
@ -261,6 +275,10 @@ export const initialStateReduced: OnboardingState = {
topicalFeedsStepResults: { topicalFeedsStepResults: {
feedUris: [], feedUris: [],
}, },
profileStepResults: {
imageUri: '',
imageMime: '',
},
} }
export function reducerReduced( export function reducerReduced(
@ -271,51 +289,27 @@ export function reducerReduced(
switch (a.type) { switch (a.type) {
case 'next': { case 'next': {
if (s.activeStep === 'interests') { if (s.activeStep === 'profile') {
next.activeStep = 'suggestedAccounts' next.activeStep = 'interests'
next.activeStepIndex = 2 next.activeStepIndex = 2
} else if (s.activeStep === 'suggestedAccounts') { } else if (s.activeStep === 'interests') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'topicalFeeds'
next.activeStepIndex = 5
} else if (s.activeStep === 'topicalFeeds') {
next.activeStep = 'moderation'
next.activeStepIndex = 6
} else if (s.activeStep === 'moderation') {
next.activeStep = 'finished' next.activeStep = 'finished'
next.activeStepIndex = 7 next.activeStepIndex = 3
} }
break break
} }
case 'prev': { case 'prev': {
if (s.activeStep === 'suggestedAccounts') { if (s.activeStep === 'interests') {
next.activeStep = 'interests' next.activeStep = 'profile'
next.activeStepIndex = 1 next.activeStepIndex = 1
} else if (s.activeStep === 'followingFeed') {
next.activeStep = 'suggestedAccounts'
next.activeStepIndex = 2
} else if (s.activeStep === 'algoFeeds') {
next.activeStep = 'followingFeed'
next.activeStepIndex = 3
} else if (s.activeStep === 'topicalFeeds') {
next.activeStep = 'algoFeeds'
next.activeStepIndex = 4
} else if (s.activeStep === 'moderation') {
next.activeStep = 'topicalFeeds'
next.activeStepIndex = 5
} else if (s.activeStep === 'finished') { } else if (s.activeStep === 'finished') {
next.activeStep = 'moderation' next.activeStep = 'interests'
next.activeStepIndex = 6 next.activeStepIndex = 2
} }
break break
} }
case 'finish': { case 'finish': {
next = initialState next = initialStateReduced
break break
} }
case 'setInterestsStepResults': { case 'setInterestsStepResults': {
@ -326,22 +320,18 @@ export function reducerReduced(
break break
} }
case 'setSuggestedAccountsStepResults': { case 'setSuggestedAccountsStepResults': {
next.suggestedAccountsStepResults = {
accountDids: next.suggestedAccountsStepResults.accountDids.concat(
a.accountDids,
),
}
break break
} }
case 'setAlgoFeedsStepResults': { case 'setAlgoFeedsStepResults': {
next.algoFeedsStepResults = {
feedUris: a.feedUris,
}
break break
} }
case 'setTopicalFeedsStepResults': { case 'setTopicalFeedsStepResults': {
next.topicalFeedsStepResults = { break
feedUris: next.topicalFeedsStepResults.feedUris.concat(a.feedUris), }
case 'setProfileStepResults': {
next.profileStepResults = {
imageUri: a.imageUri,
imageMime: a.imageMime,
} }
break break
} }
@ -349,7 +339,7 @@ export function reducerReduced(
const state = { const state = {
...next, ...next,
hasPrev: next.activeStep !== 'interests', hasPrev: next.activeStep !== 'profile',
} }
logger.debug(`onboarding`, { logger.debug(`onboarding`, {
@ -362,6 +352,7 @@ export function reducerReduced(
suggestedAccountsStepResults: state.suggestedAccountsStepResults, suggestedAccountsStepResults: state.suggestedAccountsStepResults,
algoFeedsStepResults: state.algoFeedsStepResults, algoFeedsStepResults: state.algoFeedsStepResults,
topicalFeedsStepResults: state.topicalFeedsStepResults, topicalFeedsStepResults: state.topicalFeedsStepResults,
profileStepResults: state.profileStepResults,
}) })
if (s.activeStep !== state.activeStep) { if (s.activeStep !== state.activeStep) {