mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
New-user onboarding per docs/onboarding-plan.md: on the first login of a pubkey
on a device, a skippable guided tour escorts the user around bitvid — a scrim
dims the page with a spotlight CUTOUT over each step's anchor, ringed by a
pulsing glow driven by the theme's accent tokens, plus an anchored popover with
Back/Next/Skip, progress dots, and arrow/Esc keyboard nav.
- js/ui/onboarding/tourEngine.js: dependency-free engine. Spotlight = one
element whose 200vmax box-shadow (--surface-overlay-darker) dims everything
except the cutout; an ::after ring animates between --color-accent and
--color-accent-strong. Positioning via CSS custom properties set from JS (the
sanctioned dynamic-var pattern; visual styling lives in an injected token-only
stylesheet). Steps with missing anchors auto-skip, so one script serves all
layouts. Repositions on resize/scroll.
- js/ui/onboarding/bitvidTour.js: the step script (welcome → feeds →
subscriptions → upload → profile → setup CTAs that deep-link into the storage
and wallet panes via profileController.show).
- js/services/onboardingService.js: once-per-pubkey-per-device flags
(bitvid:onboarding:v1, settingsRestorePrompt-style).
- app.maybeOfferOnboardingTour fires 3.5s after login (feed already rendered
thanks to the warm cold-boot work; never blocks login);
app.startOnboardingTour({force}) + a "Take the tour" button in the profile
modal footer make it re-launchable.
- Lint allowlists: tourEngine.js added to the inline-style allowlist (CSS-var
positioning, VideoCard precedent) and the design-token ALLOWED_FILES
(injected stylesheet geometry, RevertModal precedent) — all colors are tokens.
Tests: tests/onboarding-tour.test.mjs (5 — flag lifecycle, tour render/advance/
finish, skip teardown, missing-anchor exclusion, once-vs-force offering, final
CTA deep-link + completion). Lint + build green.
Phases 2-4 (getting-started checklist, empty-state upgrades, fresh-npub create-
account + key backup + kind-0 bootstrap) tracked in the plan + TODO #59.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
71 lines
1.9 KiB
JavaScript
71 lines
1.9 KiB
JavaScript
// First-run onboarding state (docs/onboarding-plan.md). Tracks, per pubkey per
|
|
// device, whether the guided tour has been offered/completed/skipped — same
|
|
// local-flag pattern as settingsRestorePrompt. Never blocks login; purely a
|
|
// localStorage map: { [pubkey]: { status: "completed"|"skipped", at } }.
|
|
|
|
const ONBOARDING_STORAGE_KEY = "bitvid:onboarding:v1";
|
|
|
|
function normalizePubkey(pubkey) {
|
|
return typeof pubkey === "string" ? pubkey.trim().toLowerCase() : "";
|
|
}
|
|
|
|
function readMap() {
|
|
if (typeof localStorage === "undefined") {
|
|
return {};
|
|
}
|
|
try {
|
|
const raw = localStorage.getItem(ONBOARDING_STORAGE_KEY);
|
|
const parsed = raw ? JSON.parse(raw) : null;
|
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function writeMap(map) {
|
|
if (typeof localStorage === "undefined") {
|
|
return;
|
|
}
|
|
try {
|
|
localStorage.setItem(ONBOARDING_STORAGE_KEY, JSON.stringify(map));
|
|
} catch (error) {
|
|
// best-effort
|
|
}
|
|
}
|
|
|
|
// True when this pubkey has never completed or skipped the tour on this device.
|
|
export function shouldOfferOnboarding(pubkey) {
|
|
const key = normalizePubkey(pubkey);
|
|
if (!key) {
|
|
return false;
|
|
}
|
|
const entry = readMap()[key];
|
|
return !entry || (entry.status !== "completed" && entry.status !== "skipped");
|
|
}
|
|
|
|
export function markOnboarding(pubkey, status) {
|
|
const key = normalizePubkey(pubkey);
|
|
if (!key || (status !== "completed" && status !== "skipped")) {
|
|
return;
|
|
}
|
|
const map = readMap();
|
|
map[key] = { status, at: Date.now() };
|
|
writeMap(map);
|
|
}
|
|
|
|
// For the re-launchable "Take the tour" entry point: forget the flag so a rerun
|
|
// behaves like a fresh offer (rerunning does not need this, but tests do).
|
|
export function resetOnboarding(pubkey) {
|
|
const key = normalizePubkey(pubkey);
|
|
if (!key) {
|
|
return;
|
|
}
|
|
const map = readMap();
|
|
if (key in map) {
|
|
delete map[key];
|
|
writeMap(map);
|
|
}
|
|
}
|
|
|
|
export const ONBOARDING_KEY = ONBOARDING_STORAGE_KEY;
|