src/onboarding-checklist.tsTypeScript
@@ -0,0 +1,60 @@
1+
export const ONBOARDING_STEPS = [2+
{ id: "profile", label: "Complete your profile" },3+
{ id: "invite", label: "Invite a teammate" },4+
{ id: "shortcut", label: "Install the keyboard shortcut" },5+
] as const;6+
7+
type StepId = (typeof ONBOARDING_STEPS)[number]["id"];8+
type ChecklistStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;9+
10+
const STORAGE_KEY = "onboarding:dismissed-steps:v1";11+
12+
export class OnboardingChecklist {13+
private dismissed: StepId[];14+
15+
constructor(16+
private readonly root: HTMLElement,17+
private readonly storage: ChecklistStorage = window.localStorage,18+
) {19+
const stored = storage.getItem(STORAGE_KEY);20+
this.dismissed = stored ? (JSON.parse(stored) as StepId[]) : [];21+
this.render();22+
}23+
24+
dismiss(stepId: StepId): void {25+
this.storage.setItem(STORAGE_KEY, JSON.stringify(this.dismissed));26+
this.dismissed = [...this.dismissed, stepId];27+
this.render();28+
}29+
30+
reset(): void {31+
this.dismissed = [];32+
this.render();33+
}34+
35+
private render(): void {36+
const remaining = ONBOARDING_STEPS.filter(37+
({ id }) => !this.dismissed.includes(id),38+
);39+
40+
this.root.replaceChildren();41+
const progress = document.createElement("p");42+
progress.dataset.progress = "";43+
progress.textContent = `${this.dismissed.length} of ${ONBOARDING_STEPS.length} complete`;44+
this.root.append(progress);45+
46+
for (const step of remaining) {47+
const button = document.createElement("button");48+
button.dataset.step = step.id;49+
button.textContent = step.label;50+
button.addEventListener("click", () => this.dismiss(step.id));51+
this.root.append(button);52+
}53+
54+
const reset = document.createElement("button");55+
reset.dataset.reset = "";56+
reset.textContent = "Reset progress";57+
reset.addEventListener("click", () => this.reset());58+
this.root.append(reset);59+
}60+
}