Files
siftlode/frontend/src/lib/onboarding.ts
T
peter f21cdfa156 refactor(frontend): every stored key declares its kind, and preferences follow the account
storage.ts's registry gives each key a kind (pref, device, view, cache,
public); a key without one does not compile. A contract test refuses a key
spelled out elsewhere, an account preference read or written straight from
storage instead of through usePref, and an e2e seed of a key nobody reads.
The keys the next account's wipe keeps are the public ones.

The seven preferences kept only in the browser move onto usePref: start on
Home, the onboarding dismissal, the release banner's last version, the
channel manager's layout, the Plex playlist layout, the playlist rail's sort
and channel discovery's sort. usePref reads an older bare-string copy, and a
value only this browser held is saved to the server once, so the account's
other devices get it.

Performance mode is the device's: it is neither read from the account nor
saved to it.
2026-10-20 23:04:48 +02:00

49 lines
2.4 KiB
TypeScript

// Onboarding wizard visibility, kept stable across the OAuth redirect round-trip.
//
// Granting a YouTube scope navigates away to Google's consent screen and back, which
// fully reloads the SPA — so we can't hold the wizard's progress in React state. Instead
// we derive the current step from the granted scopes (me.can_read / me.can_write) and use
// two storage flags to decide whether to auto-open:
//
// ONBOARD_ACTIVE (sessionStorage) — set while the user is mid-flow (e.g. just clicked
// "Connect"); makes the wizard reopen after the
// redirect so they land on the next step.
// ONBOARDING_DISMISSED_PREF (an — set when the user skips the essential read step;
// account preference, usePref) suppresses the auto-popup on future logins, on every
// device (they can still reopen it from Settings → Account).
import { connectGoogle } from "./googleConnect";
import { LS } from "./storage";
import type { UsePrefOptions } from "./usePref";
const ONBOARD_ACTIVE = LS.onboardingActive; // sessionStorage, this tab only
// The account's, so a fresh account still gets the nudge where another one dismissed it. A copy this
// browser kept before the server held it is the string "1".
export const ONBOARDING_DISMISSED_PREF = "onboardingDismissed";
export const ONBOARDING_DISMISSED_OPTIONS: UsePrefOptions<boolean> = {
localKey: LS.onboardingDismissed,
parse: (raw) => raw === true || raw === "1" || raw === 1,
};
export function shouldAutoOpenOnboarding(
me: { can_read: boolean; google_enabled?: boolean },
dismissed: boolean,
): boolean {
// YouTube connect needs Google OAuth configured on this instance. A self-host instance may run
// email+password only — then there's nothing to connect, so don't nudge.
if (me.google_enabled === false) return false;
if (sessionStorage.getItem(ONBOARD_ACTIVE)) return true;
return !me.can_read && !dismissed;
}
/** Mark a grant as in progress, then send the user to Google's consent screen. */
export function beginGrant(access: "read" | "write"): void {
sessionStorage.setItem(ONBOARD_ACTIVE, "1");
connectGoogle(access);
}
/** Leave the wizard's flow (its dismissal, if any, is the caller's preference to set). */
export function endOnboarding(): void {
sessionStorage.removeItem(ONBOARD_ACTIVE);
}