diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/discovery/onboarding.ts | 94 | ||||
-rw-r--r-- | src/state/models/feeds/custom-feed.ts | 13 | ||||
-rw-r--r-- | src/state/models/root-store.ts | 6 | ||||
-rw-r--r-- | src/state/models/ui/create-account.ts | 4 | ||||
-rw-r--r-- | src/state/models/ui/shell.ts | 7 |
5 files changed, 115 insertions, 9 deletions
diff --git a/src/state/models/discovery/onboarding.ts b/src/state/models/discovery/onboarding.ts new file mode 100644 index 000000000..09c9eac04 --- /dev/null +++ b/src/state/models/discovery/onboarding.ts @@ -0,0 +1,94 @@ +import {makeAutoObservable} from 'mobx' +import {RootStoreModel} from '../root-store' +import {hasProp} from 'lib/type-guards' +import {track} from 'lib/analytics/analytics' + +export const OnboardingScreenSteps = { + Welcome: 'Welcome', + RecommendedFeeds: 'RecommendedFeeds', + Home: 'Home', +} as const + +type OnboardingStep = + (typeof OnboardingScreenSteps)[keyof typeof OnboardingScreenSteps] +const OnboardingStepsArray = Object.values(OnboardingScreenSteps) +export class OnboardingModel { + // state + step: OnboardingStep = 'Home' // default state to skip onboarding, only enabled for new users by calling start() + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable(this, { + rootStore: false, + hydrate: false, + serialize: false, + }) + } + + serialize(): unknown { + return { + step: this.step, + } + } + + hydrate(v: unknown) { + if (typeof v === 'object' && v !== null) { + if ( + hasProp(v, 'step') && + typeof v.step === 'string' && + OnboardingStepsArray.includes(v.step as OnboardingStep) + ) { + this.step = v.step as OnboardingStep + } + } else { + // if there is no valid state, we'll just reset + this.reset() + } + } + + /** + * Returns the name of the next screen in the onboarding process based on the current step or screen name provided. + * @param {OnboardingStep} [currentScreenName] + * @returns name of next screen in the onboarding process + */ + next(currentScreenName?: OnboardingStep) { + currentScreenName = currentScreenName || this.step + if (currentScreenName === 'Welcome') { + this.step = 'RecommendedFeeds' + return this.step + } else if (this.step === 'RecommendedFeeds') { + this.finish() + return this.step + } else { + // if we get here, we're in an invalid state, let's just go Home + return 'Home' + } + } + + start() { + this.step = 'Welcome' + track('Onboarding:Begin') + } + + finish() { + this.step = 'Home' + track('Onboarding:Complete') + } + + reset() { + this.step = 'Welcome' + track('Onboarding:Reset') + } + + skip() { + this.step = 'Home' + track('Onboarding:Skipped') + } + + get isComplete() { + return this.step === 'Home' + } + + get isActive() { + return !this.isComplete + } +} diff --git a/src/state/models/feeds/custom-feed.ts b/src/state/models/feeds/custom-feed.ts index 3c6d52755..2de4534e7 100644 --- a/src/state/models/feeds/custom-feed.ts +++ b/src/state/models/feeds/custom-feed.ts @@ -67,6 +67,19 @@ export class CustomFeedModel { } } + async pin() { + try { + await this.rootStore.preferences.addPinnedFeed(this.uri) + } catch (error) { + this.rootStore.log.error('Failed to pin feed', error) + } finally { + track('CustomFeed:Pin', { + name: this.data.displayName, + uri: this.uri, + }) + } + } + async unsave() { try { await this.rootStore.preferences.removeSavedFeed(this.uri) diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 1d6d3a0d0..6204e0d10 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -27,6 +27,7 @@ import {reset as resetNavigation} from '../../Navigation' // 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(), @@ -44,6 +45,7 @@ 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) @@ -70,6 +72,7 @@ export class RootStoreModel { appInfo: this.appInfo, session: this.session.serialize(), me: this.me.serialize(), + onboarding: this.onboarding.serialize(), shell: this.shell.serialize(), preferences: this.preferences.serialize(), invitedUsers: this.invitedUsers.serialize(), @@ -88,6 +91,9 @@ 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) } diff --git a/src/state/models/ui/create-account.ts b/src/state/models/ui/create-account.ts index 04e1554c6..d9d4f51b9 100644 --- a/src/state/models/ui/create-account.ts +++ b/src/state/models/ui/create-account.ts @@ -109,10 +109,10 @@ export class CreateAccountModel { this.setError('') this.setIsProcessing(true) - // open the onboarding modal after the session is created + // open the onboarding screens after the session is created const sessionReadySub = this.rootStore.onSessionReady(() => { sessionReadySub.remove() - this.rootStore.shell.openModal({name: 'onboarding'}) + this.rootStore.onboarding.start() }) try { diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index d19de4b96..33fdd5710 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -136,10 +136,6 @@ export interface PostLanguagesSettingsModal { name: 'post-languages-settings' } -export interface OnboardingModal { - name: 'onboarding' -} - export type Modal = // Account | AddAppPasswordModal @@ -171,9 +167,6 @@ export type Modal = | WaitlistModal | InviteCodesModal - // Onboarding - | OnboardingModal - // Generic | ConfirmModal |