diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-11-08 09:04:06 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-08 09:04:06 -0800 |
commit | 4afed4be281b6319c328938e4ed757624a78b13c (patch) | |
tree | 7a7744801c2964a3981c3e3ed1772f8226276c6b /src/state/shell/onboarding.tsx | |
parent | 3a211017d3d972fb442069e38d1b8ff1a2edbd57 (diff) | |
download | voidsky-4afed4be281b6319c328938e4ed757624a78b13c.tar.zst |
Move onboarding state to new persistence + reducer context (#1835)
Diffstat (limited to 'src/state/shell/onboarding.tsx')
-rw-r--r-- | src/state/shell/onboarding.tsx | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/src/state/shell/onboarding.tsx b/src/state/shell/onboarding.tsx new file mode 100644 index 000000000..5963cc50e --- /dev/null +++ b/src/state/shell/onboarding.tsx @@ -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', + } +} |