Skip to content

Commit

Permalink
Move onboarding state to new persistence + reducer context (#1835)
Browse files Browse the repository at this point in the history
  • Loading branch information
pfrazee authored Nov 8, 2023
1 parent 3a21101 commit 4afed4b
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 167 deletions.
106 changes: 0 additions & 106 deletions src/state/models/discovery/onboarding.ts

This file was deleted.

6 changes: 0 additions & 6 deletions src/state/models/root-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {logger} from '#/logger'
// remove after backend testing finishes
// -prf
import {applyDebugHeader} from 'lib/api/debug-appview-proxy-header'
import {OnboardingModel} from './discovery/onboarding'

export const appInfo = z.object({
build: z.string(),
Expand All @@ -44,7 +43,6 @@ export class RootStoreModel {
shell = new ShellUiModel(this)
preferences = new PreferencesModel(this)
me = new MeModel(this)
onboarding = new OnboardingModel(this)
invitedUsers = new InvitedUsers(this)
handleResolutions = new HandleResolutionsCache()
profiles = new ProfilesCache(this)
Expand All @@ -71,7 +69,6 @@ export class RootStoreModel {
appInfo: this.appInfo,
session: this.session.serialize(),
me: this.me.serialize(),
onboarding: this.onboarding.serialize(),
preferences: this.preferences.serialize(),
invitedUsers: this.invitedUsers.serialize(),
mutedThreads: this.mutedThreads.serialize(),
Expand All @@ -89,9 +86,6 @@ export class RootStoreModel {
if (hasProp(v, 'me')) {
this.me.hydrate(v.me)
}
if (hasProp(v, 'onboarding')) {
this.onboarding.hydrate(v.onboarding)
}
if (hasProp(v, 'session')) {
this.session.hydrate(v.session)
}
Expand Down
7 changes: 4 additions & 3 deletions src/state/models/ui/create-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {cleanError} from 'lib/strings/errors'
import {getAge} from 'lib/strings/time'
import {track} from 'lib/analytics/analytics'
import {logger} from '#/logger'
import {DispatchContext as OnboardingDispatchContext} from '#/state/shell/onboarding'

const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago

Expand Down Expand Up @@ -90,7 +91,7 @@ export class CreateAccountModel {
}
}

async submit() {
async submit(onboardingDispatch: OnboardingDispatchContext) {
if (!this.email) {
this.setStep(2)
return this.setError('Please enter your email.')
Expand All @@ -111,7 +112,7 @@ export class CreateAccountModel {
this.setIsProcessing(true)

try {
this.rootStore.onboarding.start() // start now to avoid flashing the wrong view
onboardingDispatch({type: 'start'}) // start now to avoid flashing the wrong view
await this.rootStore.session.createAccount({
service: this.serviceUrl,
email: this.email,
Expand All @@ -122,7 +123,7 @@ export class CreateAccountModel {
/* dont await */ this.rootStore.preferences.setBirthDate(this.birthDate)
track('Create Account')
} catch (e: any) {
this.rootStore.onboarding.skip() // undo starting the onboard
onboardingDispatch({type: 'skip'}) // undo starting the onboard
let errMsg = e.toString()
if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
errMsg =
Expand Down
6 changes: 3 additions & 3 deletions src/state/persisted/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const accountSchema = z.object({
did: z.string(),
refreshJwt: z.string().optional(),
accessJwt: z.string().optional(),
handle: z.string(),
displayName: z.string(),
aviUrl: z.string(),
handle: z.string().optional(),
displayName: z.string().optional(),
aviUrl: z.string().optional(),
})

export const schema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion src/state/shell/color-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
setState(persisted.get('colorMode'))
updateDocument(persisted.get('colorMode'))
})
}, [setStateWrapped])
}, [setState])

return (
<stateContext.Provider value={state}>
Expand Down
6 changes: 5 additions & 1 deletion src/state/shell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
import {Provider as MinimalModeProvider} from './minimal-mode'
import {Provider as ColorModeProvider} from './color-mode'
import {Provider as AltTextRequiredProvider} from './alt-text-required'
import {Provider as OnboardingProvider} from './onboarding'

export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open'
export {
Expand All @@ -16,14 +17,17 @@ export {
useRequireAltTextEnabled,
useSetRequireAltTextEnabled,
} from './alt-text-required'
export {useOnboardingState, useOnboardingDispatch} from './onboarding'

export function Provider({children}: React.PropsWithChildren<{}>) {
return (
<DrawerOpenProvider>
<DrawerSwipableProvider>
<MinimalModeProvider>
<ColorModeProvider>
<AltTextRequiredProvider>{children}</AltTextRequiredProvider>
<OnboardingProvider>
<AltTextRequiredProvider>{children}</AltTextRequiredProvider>
</OnboardingProvider>
</ColorModeProvider>
</MinimalModeProvider>
</DrawerSwipableProvider>
Expand Down
119 changes: 119 additions & 0 deletions src/state/shell/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react'
import * as persisted from '#/state/persisted'
import {track} from '#/lib/analytics/analytics'

export const OnboardingScreenSteps = {
Welcome: 'Welcome',
RecommendedFeeds: 'RecommendedFeeds',
RecommendedFollows: 'RecommendedFollows',
Home: 'Home',
} as const

type OnboardingStep =
(typeof OnboardingScreenSteps)[keyof typeof OnboardingScreenSteps]
const OnboardingStepsArray = Object.values(OnboardingScreenSteps)

type Action =
| {type: 'set'; step: OnboardingStep}
| {type: 'next'; currentStep?: OnboardingStep}
| {type: 'start'}
| {type: 'finish'}
| {type: 'skip'}

export type StateContext = persisted.Schema['onboarding'] & {
isComplete: boolean
isActive: boolean
}
export type DispatchContext = (action: Action) => void

const stateContext = React.createContext<StateContext>(
compute(persisted.defaults.onboarding),
)
const dispatchContext = React.createContext<DispatchContext>((_: Action) => {})

function reducer(state: StateContext, action: Action): StateContext {
switch (action.type) {
case 'set': {
if (OnboardingStepsArray.includes(action.step)) {
persisted.write('onboarding', {step: action.step})
return compute({...state, step: action.step})
}
return state
}
case 'next': {
const currentStep = action.currentStep || state.step
let nextStep = 'Home'
if (currentStep === 'Welcome') {
nextStep = 'RecommendedFeeds'
} else if (currentStep === 'RecommendedFeeds') {
nextStep = 'RecommendedFollows'
} else if (currentStep === 'RecommendedFollows') {
nextStep = 'Home'
}
persisted.write('onboarding', {step: nextStep})
return compute({...state, step: nextStep})
}
case 'start': {
track('Onboarding:Begin')
persisted.write('onboarding', {step: 'Welcome'})
return compute({...state, step: 'Welcome'})
}
case 'finish': {
track('Onboarding:Complete')
persisted.write('onboarding', {step: 'Home'})
return compute({...state, step: 'Home'})
}
case 'skip': {
track('Onboarding:Skipped')
persisted.write('onboarding', {step: 'Home'})
return compute({...state, step: 'Home'})
}
default: {
throw new Error('Invalid action')
}
}
}

export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, dispatch] = React.useReducer(
reducer,
compute(persisted.get('onboarding')),
)

React.useEffect(() => {
return persisted.onUpdate(() => {
dispatch({
type: 'set',
step: persisted.get('onboarding').step as OnboardingStep,
})
})
}, [dispatch])

return (
<stateContext.Provider value={state}>
<dispatchContext.Provider value={dispatch}>
{children}
</dispatchContext.Provider>
</stateContext.Provider>
)
}

export function useOnboardingState() {
return React.useContext(stateContext)
}

export function useOnboardingDispatch() {
return React.useContext(dispatchContext)
}

export function isOnboardingActive() {
return compute(persisted.get('onboarding')).isActive
}

function compute(state: persisted.Schema['onboarding']): StateContext {
return {
...state,
isActive: state.step !== 'Home',
isComplete: state.step === 'Home',
}
}
15 changes: 8 additions & 7 deletions src/view/com/auth/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,35 @@ import {observer} from 'mobx-react-lite'
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {Welcome} from './onboarding/Welcome'
import {RecommendedFeeds} from './onboarding/RecommendedFeeds'
import {RecommendedFollows} from './onboarding/RecommendedFollows'
import {useSetMinimalShellMode} from '#/state/shell/minimal-mode'
import {useOnboardingState, useOnboardingDispatch} from '#/state/shell'

export const Onboarding = observer(function OnboardingImpl() {
const pal = usePalette('default')
const store = useStores()
const setMinimalShellMode = useSetMinimalShellMode()
const onboardingState = useOnboardingState()
const onboardingDispatch = useOnboardingDispatch()

React.useEffect(() => {
setMinimalShellMode(true)
}, [setMinimalShellMode])

const next = () => store.onboarding.next()
const skip = () => store.onboarding.skip()
const next = () => onboardingDispatch({type: 'next'})
const skip = () => onboardingDispatch({type: 'skip'})

return (
<SafeAreaView testID="onboardingView" style={[s.hContentRegion, pal.view]}>
<ErrorBoundary>
{store.onboarding.step === 'Welcome' && (
{onboardingState.step === 'Welcome' && (
<Welcome skip={skip} next={next} />
)}
{store.onboarding.step === 'RecommendedFeeds' && (
{onboardingState.step === 'RecommendedFeeds' && (
<RecommendedFeeds next={next} />
)}
{store.onboarding.step === 'RecommendedFollows' && (
{onboardingState.step === 'RecommendedFollows' && (
<RecommendedFollows next={next} />
)}
</ErrorBoundary>
Expand Down
Loading

0 comments on commit 4afed4b

Please sign in to comment.