Repository navigation
Expand file tree
/
Copy pathdebateowl-intro.ts
More file actions
84 lines (75 loc) · 2.89 KB
/
Copy pathdebateowl-intro.ts
File metadata and controls
84 lines (75 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* The one-time DebateOwl welcome: the owl says hello, says what changed (the look only) and that
* DebateLab is becoming DebateOwl. It opens once per browser, inside the signed-in app.
* Closing it continues straight into the product tour's Start debate step.
*/
export const INTRO_SEEN_KEY = 'debateowl_intro_seen_v1';
/**
* The owl's personal name, the same in every language, used where the owl introduces itself
* (debateOwlIntro.greetingNamed). Change it here only. Empty, the owl speaks as DebateOwl.
*/
export const OWL_NAME = 'Ollie';
/** `?intro=show` opens the welcome even when seen or under test; `?intro=skip` keeps it closed. */
export const INTRO_QUERY_PARAM = 'intro';
type IntroStorage = Pick<Storage, 'getItem' | 'setItem'>;
interface IntroEnvironment {
VITE_E2E?: string;
VITE_LOCAL_PREVIEW?: string;
}
/**
* Whether this page load opens the welcome. Playwright (VITE_E2E) and the local verify stack
* (VITE_LOCAL_PREVIEW) never see it unless the URL asks. Any stored value counts as seen, and
* storage that can't be read keeps it closed, since a flag that can't be saved would reopen it
* on every load.
*/
export function shouldShowIntro({ search, storage, env }: {
search: string;
storage: IntroStorage | undefined;
env: IntroEnvironment;
}): boolean {
const requested = new URLSearchParams(search).get(INTRO_QUERY_PARAM);
if (requested === 'show') return true;
if (requested === 'skip') return false;
if (env.VITE_E2E === '1' || env.VITE_LOCAL_PREVIEW === 'true') return false;
try {
return storage !== undefined && storage.getItem(INTRO_SEEN_KEY) === null;
} catch {
return false;
}
}
export function markIntroSeen(storage: IntroStorage | undefined, now = new Date()): void {
try {
storage?.setItem(INTRO_SEEN_KEY, now.toISOString());
} catch {
// Private mode or a full quota: the welcome closes for this load either way.
}
}
export function browserStorage(): IntroStorage | undefined {
try {
return typeof window === 'undefined' ? undefined : window.localStorage;
} catch {
return undefined;
}
}
/** The visible part of the first visit. `idle` means nothing is on screen. */
export type FirstRunStep = 'intro' | 'start-debate' | 'tour' | 'idle';
/**
* The welcome and the product tour are one first-run moment.
* The welcome, when it is due, opens first. Closing it continues into the tour's
* Start debate step when that account has not finished the tour. With no welcome,
* an unfinished tour still opens on its own, at that same first step. A finished
* tour stays closed, including when `?intro=show` replays the welcome.
*/
export function firstRunStep({
showIntro,
introDismissed,
tourCompleted,
}: {
showIntro: boolean;
introDismissed: boolean;
tourCompleted: boolean;
}): FirstRunStep {
if (showIntro && !introDismissed) return 'intro';
if (tourCompleted) return 'idle';
return showIntro ? 'start-debate' : 'tour';
}