(core) Update onboarding flow

Summary:
A new onboarding page is now shown to all new users visiting the doc
menu for the first time. Tutorial cards on the doc menu have been
replaced with a new version that tracks completion progress, alongside
a new card that opens the orientation video.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4296
This commit is contained in:
George Gevoian
2024-07-22 11:10:57 -04:00
parent 3fd8719d8a
commit 4740f1f933
40 changed files with 1462 additions and 706 deletions

View File

@@ -392,6 +392,10 @@ export class AppModelImpl extends Disposable implements AppModel {
this.behavioralPromptsManager.reset();
};
G.window.resetOnboarding = () => {
getUserPrefObs(this.userPrefsObs, 'showNewUserQuestions').set(true);
};
this.autoDispose(subscribe(urlState().state, this.topAppModel.orgs, async (_use, s, orgs) => {
this._updateLastVisitedOrgDomain(s, orgs);
}));

View File

@@ -7,7 +7,7 @@ import {AppModel, reportError} from 'app/client/models/AppModel';
import {reportMessage, UserError} from 'app/client/models/errors';
import {urlState} from 'app/client/models/gristUrlState';
import {ownerName} from 'app/client/models/WorkspaceInfo';
import {IHomePage} from 'app/common/gristUrls';
import {IHomePage, isFeatureEnabled} from 'app/common/gristUrls';
import {isLongerThan} from 'app/common/gutil';
import {SortPref, UserOrgPrefs, ViewPref} from 'app/common/Prefs';
import * as roles from 'app/common/roles';
@@ -59,6 +59,8 @@ export interface HomeModel {
shouldShowAddNewTip: Observable<boolean>;
onboardingTutorial: Observable<Document|null>;
createWorkspace(name: string): Promise<void>;
renameWorkspace(id: number, name: string): Promise<void>;
deleteWorkspace(id: number, forever: boolean): Promise<void>;
@@ -141,6 +143,8 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
public readonly shouldShowAddNewTip = Observable.create(this,
!this._app.behavioralPromptsManager.hasSeenPopup('addNew'));
public readonly onboardingTutorial = Observable.create<Document|null>(this, null);
private _userOrgPrefs = Observable.create<UserOrgPrefs|undefined>(this, this._app.currentOrg?.userOrgPrefs);
constructor(private _app: AppModel, clientScope: ClientScope) {
@@ -176,6 +180,8 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
this.importSources.set(importSources);
this._app.refreshOrgUsage().catch(reportError);
this._loadWelcomeTutorial().catch(reportError);
}
// Accessor for the AppModel containing this HomeModel.
@@ -370,6 +376,28 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
return templateWss;
}
private async _loadWelcomeTutorial() {
const {templateOrg, onboardingTutorialDocId} = getGristConfig();
if (
!isFeatureEnabled('tutorials') ||
!templateOrg ||
!onboardingTutorialDocId ||
this._app.dismissedPopups.get().includes('onboardingCards')
) {
return;
}
try {
const doc = await this._app.api.getTemplate(onboardingTutorialDocId);
if (this.isDisposed()) { return; }
this.onboardingTutorial.set(doc);
} catch (e) {
console.error(e);
reportError('Failed to load welcome tutorial');
}
}
private async _saveUserOrgPref<K extends keyof UserOrgPrefs>(key: K, value: UserOrgPrefs[K]) {
const org = this._app.currentOrg;
if (org) {