Wired onboarding checklist to saved user settings (#19948)

part of https://linear.app/tryghost/issue/IPC-92/add-logic-for-completing-steps
part of https://linear.app/tryghost/issue/IPC-115/make-skip-onboarding-button-work

- updated `onboarding` service to use the `user.accessibility` (poor naming, this is an old field used for general user settings) as it's backing store
- added `onboarding.allStepsCompleted` to allow for "completion" state to be shown before the checklist is marked as completed
- added `onboarding.{complete,dismiss}Checklist()` actions and wired those up to the template

When testing, if you need to reset the checklist you can run this in DevTools console
```
Ember.Namespace.NAMESPACES_BY_ID['ghost-admin'].__container__.lookup('service:onboarding').startChecklist()
```
This commit is contained in:
Kevin Ansfield 2024-03-28 14:19:43 +00:00 committed by GitHub
parent 7e2d842db2
commit 1c219fdcb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 12 deletions

View File

@ -57,11 +57,15 @@
</div> </div>
</div> </div>
<a href="#" class="gh-onboarding-explore-dashboard">Explore your dashboard</a> {{#if this.onboarding.allStepsCompleted}}
<a href="#" class="gh-onboarding-explore-dashboard" {{on "click" this.onboarding.completeChecklist}}>Explore your dashboard</a>
{{/if}}
<p class="gh-onboarding-help">Need some more help? Check out our <a href="https://ghost.org/help?utm_source=admin&utm_campaign=onboarding" target="_blank" rel="noopener noreferrer">Help center</a></p> <p class="gh-onboarding-help">Need some more help? Check out our <a href="https://ghost.org/help?utm_source=admin&utm_campaign=onboarding" target="_blank" rel="noopener noreferrer">Help center</a></p>
<a href="#" class="gh-onboarding-skip">Skip onboarding</a> {{#unless this.onboarding.allStepsCompleted}}
<a href="#" class="gh-onboarding-skip" {{on "click" this.onboarding.dismissChecklist}}>Skip onboarding</a>
{{/unless}}
</div> </div>
{{#if this.showShareModal}} {{#if this.showShareModal}}

View File

@ -1,14 +1,15 @@
import Service, {inject as service} from '@ember/service'; import Service, {inject as service} from '@ember/service';
import {TrackedSet} from 'tracked-built-ins';
import {action} from '@ember/object'; import {action} from '@ember/object';
import {tracked} from '@glimmer/tracking';
const EMPTY_SETTINGS = {
completedSteps: [],
checklistState: 'pending' // pending, started, completed, dismissed
};
export default class OnboardingService extends Service { export default class OnboardingService extends Service {
@service feature; @service feature;
@service session; @service session;
@tracked _completedSteps = new TrackedSet();
ONBOARDING_STEPS = [ ONBOARDING_STEPS = [
'customize-design', 'customize-design',
'first-post', 'first-post',
@ -16,22 +17,102 @@ export default class OnboardingService extends Service {
'share-publication' 'share-publication'
]; ];
get settings() {
const userSettings = JSON.parse(this.session.user.accessibility || '{}');
return userSettings.onboarding || JSON.parse(JSON.stringify(EMPTY_SETTINGS));
}
get isChecklistShown() { get isChecklistShown() {
return this.feature.onboardingChecklist return this.feature.onboardingChecklist
&& this.session.user.isOwnerOnly; && this.session.user.isOwnerOnly
&& !this.checklistCompleted
&& !this.checklistDismissed;
}
get checklistState() {
return this.settings.checklistState;
}
get checklistStarted() {
return this.settings.checklistState === 'started';
}
get checklistCompleted() {
return this.settings.checklistState === 'completed';
}
get checklistDismissed() {
return this.settings.checklistState === 'dismissed';
}
get completedSteps() {
const settings = this.settings;
return settings.completedSteps || [];
} }
get nextStep() { get nextStep() {
return this.ONBOARDING_STEPS.find(step => !this.isStepCompleted(step)); return this.ONBOARDING_STEPS.find(step => !this.isStepCompleted(step));
} }
@action get allStepsCompleted() {
isStepCompleted(step) { return this.ONBOARDING_STEPS.every(step => this.isStepCompleted(step));
return this._completedSteps.has(step);
} }
@action @action
markStepCompleted(step) { async startChecklist() {
this._completedSteps.add(step); const settings = this.settings;
settings.completedSteps = [];
settings.checklistState = 'started';
await this._saveSettings(settings);
}
@action
async completeChecklist() {
const settings = this.settings;
settings.checklistState = 'completed';
await this._saveSettings(settings);
}
@action
async dismissChecklist() {
const settings = this.settings;
settings.checklistState = 'dismissed';
await this._saveSettings(settings);
}
@action
isStepCompleted(step) {
return this.completedSteps.includes(step);
}
@action
async markStepCompleted(step) {
if (this.isStepCompleted(step)) {
return;
}
const settings = this.settings;
settings.completedSteps.push(step);
await this._saveSettings(settings);
}
/* private */
async _saveSettings(settings) {
const userSettings = JSON.parse(this.session.user.accessibility || '{}');
userSettings.onboarding = settings;
this.session.user.accessibility = JSON.stringify(userSettings);
await this.session.user.save();
} }
} }