• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / def59674-18f9-4de3-b49d-139136c466b5

08 Dec 2023 09:55PM CUT coverage: 73.596% (+0.05%) from 73.551%
def59674-18f9-4de3-b49d-139136c466b5

Pull #4177

circleci

jwhitlock
Move commands below executors
Pull Request #4177: MPP-3390: CircleCI config refactor

1970 of 2915 branches covered (0.0%)

Branch coverage included in aggregate %.

6261 of 8269 relevant lines covered (75.72%)

19.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.76
/frontend/src/components/dashboard/FreeOnboarding.tsx
1
import styles from "./FreeOnboarding.module.scss";
1✔
2
import { ProfileData } from "../../hooks/api/profile";
3
import { VisuallyHidden } from "../VisuallyHidden";
1✔
4
import { getRuntimeConfig } from "../../config";
1✔
5
import { useL10n } from "../../hooks/l10n";
1✔
6
import WomanEmail from "./images/woman-email.svg";
1✔
7
import CheckMark from "./images/welcome-to-relay-check.svg";
1✔
8
import Plus from "./images/welcome-to-relay-plus.svg";
1✔
9
import Congratulations from "./images/free-onboarding-congratulations.svg";
1✔
10
import VerticalArrow from "./images/free-onboarding-vertical-arrow.svg";
1✔
11
import Emails from "./images/free-onboarding-emails.svg";
1✔
12
import WorkingMan from "./images/free-onboarding-work-anywhere.svg";
1✔
13
import Extension from "./images/free-onboarding-relay-extension.svg";
1✔
14
import SmallArrow from "./images/free-onboarding-arrow.svg";
1✔
15
import LargeArrow from "./images/free-onboarding-arrow-large.svg";
1✔
16
import Image from "next/image";
1✔
17
import { Button, LinkButton } from "../Button";
1✔
18
import { event as gaEvent } from "react-ga";
1✔
19
import { useGaViewPing } from "../../hooks/gaViewPing";
1✔
20
import { AliasData } from "../../hooks/api/aliases";
21
import { UserData } from "../../hooks/api/user";
22
import { RuntimeData } from "../../hooks/api/runtimeData";
23
import { EmailForwardingModal } from "./EmailForwardingModal";
1✔
24
import { useState } from "react";
1✔
25
import { supportsChromeExtension } from "../../functions/userAgent";
1✔
26
import { CheckBadgeIcon, ChevronRightIcon } from "../Icons";
1✔
27
import { AliasList } from "./aliases/AliasList";
1✔
28

29
export type Props = {
30
  profile: ProfileData;
31
  onNextStep: (step: number) => void;
32
  onPickSubdomain: (subdomain: string) => void;
33
  generateNewMask: () => void;
34
  hasReachedFreeMaskLimit: boolean;
35
  aliases: AliasData[];
36
  user: UserData;
37
  runtimeData?: RuntimeData;
38
  onUpdate: (alias: AliasData, updatedFields: Partial<AliasData>) => void;
39
};
40

41
/**
42
 * Shows the user how to take advantage of Premium features when they've just upgraded.
43
 */
44
export const FreeOnboarding = (props: Props) => {
1✔
45
  const l10n = useL10n();
×
46
  const [isModalOpen, setIsModalOpen] = useState(false);
×
47

48
  let step = null;
×
49
  let button = null;
×
50
  let skipButton = null;
×
51
  let next = null;
×
52

53
  // TODO: Add GA events - for view events and pings
54
  const skipStepOneButtonRef = useGaViewPing({
×
55
    category: "Free Onboarding",
56
    label: "free-onboarding-step-1-skip",
57
    value: 1,
58
  });
59

60
  const skipStepTwoButtonRef = useGaViewPing({
×
61
    category: "Free Onboarding",
62
    label: "free-onboarding-step-2-skip",
63
    value: 1,
64
  });
65

66
  const nextStepTwoButtonRef = useGaViewPing({
×
67
    category: "Free Onboarding",
68
    label: "free-onboarding-step-2-next",
69
    value: 1,
70
  });
71

72
  const skipStepThreeButtonRef = useGaViewPing({
×
73
    category: "Free Onboarding",
74
    label: "free-onboarding-step-3-skip",
75
    value: 1,
76
  });
77

78
  if (props.profile.onboarding_free_state === 0) {
×
79
    const skipMaskCreation = () => {
×
80
      props.onNextStep(3);
×
81
      gaEvent({
×
82
        category: "Free Onboarding",
83
        action: "Engage",
84
        label: "onboarding-step-1-skip",
85
        value: 1,
86
      });
87
    };
88

89
    const createNewMask = () => {
×
90
      props.generateNewMask();
×
91
      props.onNextStep(1);
×
92
      gaEvent({
×
93
        category: "Free Onboarding",
94
        action: "Engage",
95
        label: "onboarding-step-1-create-random-mask",
96
        value: 1,
97
      });
98
    };
99

100
    step = <StepOne />;
×
101

102
    skipButton = (
103
      <button
104
        ref={skipStepOneButtonRef}
105
        className={styles["skip-link"]}
106
        onClick={skipMaskCreation}
107
      >
108
        {l10n.getString("profile-free-onboarding-skip-step")}
109
      </button>
110
    );
111

112
    button = (
113
      <Button className={styles["generate-new-mask"]} onClick={createNewMask}>
114
        {l10n.getString("profile-free-onboarding-welcome-generate-new-mask")}
115
        <Image src={Plus} alt="" />
116
      </Button>
117
    );
118
  }
119

120
  if (props.profile.onboarding_free_state === 1) {
×
121
    const skipMaskTesting = () => {
×
122
      props.onNextStep(3);
×
123
      gaEvent({
×
124
        category: "Free Onboarding",
125
        action: "Engage",
126
        label: "onboarding-step-2-skip",
127
        value: 1,
128
      });
129
    };
130

131
    const nextStep = () => {
×
132
      props.onNextStep(2);
×
133
      gaEvent({
×
134
        category: "Free Onboarding",
135
        action: "Engage",
136
        label: "onboarding-step-2-next",
137
        value: 1,
138
      });
139
    };
140

141
    const forwardedEmail = () => {
×
142
      props.onNextStep(2);
×
143
      gaEvent({
×
144
        category: "Free Onboarding",
145
        action: "Engage",
146
        label: "onboarding-step-2-continue",
147
        value: 1,
148
      });
149
    };
150

151
    step = (
152
      <StepTwo
153
        aliases={props.aliases}
154
        profile={props.profile}
155
        user={props.user}
156
        runtimeData={props.runtimeData}
157
        continue={forwardedEmail}
158
        isModalOpen={isModalOpen}
159
        setIsModalOpen={setIsModalOpen}
160
        onUpdate={props.onUpdate}
161
      />
162
    );
163

164
    next = (
165
      <button
166
        ref={nextStepTwoButtonRef}
167
        className={styles["next-link"]}
168
        onClick={nextStep}
169
      >
170
        {l10n.getString("profile-free-onboarding-next-step")}
171
        <ChevronRightIcon className={styles.chevron} width={16} alt="" />
172
      </button>
173
    );
174

175
    button = (
176
      <Button
177
        className={styles["generate-new-mask"]}
178
        onClick={() => {
179
          setIsModalOpen(true);
×
180
        }}
181
      >
182
        {l10n.getString(
183
          "profile-free-onboarding-copy-mask-how-forwarding-works",
184
        )}
185
      </Button>
186
    );
187

188
    skipButton = (
189
      <button
190
        ref={skipStepTwoButtonRef}
191
        className={styles["skip-link"]}
192
        onClick={skipMaskTesting}
193
      >
194
        {l10n.getString("profile-free-onboarding-skip-step")}
195
      </button>
196
    );
197
  }
198

199
  if (props.profile.onboarding_free_state === 2) {
×
200
    const linkForBrowser = supportsChromeExtension()
×
201
      ? "https://chrome.google.com/webstore/detail/firefox-relay/lknpoadjjkjcmjhbjpcljdednccbldeb?utm_source=fx-relay&utm_medium=onboarding&utm_campaign=install-addon"
202
      : "https://addons.mozilla.org/firefox/addon/private-relay/";
203

204
    const skipAddonStep = () => {
×
205
      props.onNextStep(3);
×
206
      gaEvent({
×
207
        category: "Free Onboarding",
208
        action: "Engage",
209
        label: "onboarding-step-3-skip",
210
        value: 1,
211
      });
212
    };
213

214
    const finish = () => {
×
215
      props.onNextStep(3);
×
216
      gaEvent({
×
217
        category: "Free Onboarding",
218
        action: "Engage",
219
        label: "onboarding-step-3-complete",
220
        value: 1,
221
      });
222
    };
223

224
    step = <StepThree />;
×
225

226
    next = (
227
      <button
228
        ref={nextStepTwoButtonRef}
229
        className={styles["next-link"]}
230
        onClick={finish}
231
      >
232
        {l10n.getString("profile-free-onboarding-addon-finish")}
233
        <ChevronRightIcon className={styles.chevron} width={16} alt="" />
234
      </button>
235
    );
236

237
    button = (
238
      <>
239
        <LinkButton
240
          href={linkForBrowser}
241
          target="_blank"
242
          className={`is-hidden-with-addon ${styles["get-addon-button"]}`}
243
        >
244
          {l10n.getString("profile-free-onboarding-addon-get-extension")}
245
        </LinkButton>
246
        <div className={`${styles["addon-description"]} is-visible-with-addon`}>
247
          <div className={styles["action-complete"]}>
248
            <CheckBadgeIcon alt="" width={18} height={18} />
249
            {l10n.getString("multi-part-onboarding-premium-extension-added")}
250
          </div>
251
        </div>
252
      </>
253
    );
254

255
    skipButton = (
256
      <button
257
        ref={skipStepThreeButtonRef}
258
        className={styles["skip-link"]}
259
        onClick={skipAddonStep}
260
      >
261
        {l10n.getString("profile-free-onboarding-skip-step")}
262
      </button>
263
    );
264
  }
265

266
  return (
267
    <section className={styles.onboarding}>
268
      {step}
269
      <div className={styles.controls}>
270
        {button}
271
        <div className={styles["progress-container"]}>
272
          <VisuallyHidden>
273
            <progress
274
              max={getRuntimeConfig().maxOnboardingAvailable}
275
              value={props.profile.onboarding_free_state + 1}
276
            >
277
              {l10n.getString("multi-part-onboarding-step-counter", {
278
                step: props.profile.onboarding_free_state,
279
                max: getRuntimeConfig().maxOnboardingAvailable,
280
              })}
281
            </progress>
282
          </VisuallyHidden>
283
          {next}
284
          <ol className={styles["styled-progress-bar"]} aria-hidden={true}>
285
            <li
286
              className={
287
                props.profile.onboarding_free_state >= 0
×
288
                  ? styles["is-completed"]
289
                  : undefined
290
              }
291
            >
292
              <span></span>1
293
            </li>
294
            <li
295
              className={
296
                props.profile.onboarding_free_state >= 1
×
297
                  ? styles["is-completed"]
298
                  : undefined
299
              }
300
            >
301
              <span></span>2
302
            </li>
303
            <li
304
              className={
305
                props.profile.onboarding_free_state >= 2
×
306
                  ? styles["is-completed"]
307
                  : undefined
308
              }
309
            >
310
              <span></span>3
311
            </li>
312
          </ol>
313
        </div>
314
        {skipButton}
315
      </div>
316
    </section>
317
  );
318
};
319

320
const StepOne = () => {
1✔
321
  const l10n = useL10n();
×
322

323
  return (
324
    <div className={`${styles.step} ${styles["step-welcome"]}`}>
325
      <div className={styles["welcome-header"]}>
326
        <h1>{l10n.getString("profile-free-onboarding-welcome-headline")}</h1>
327
        <p>{l10n.getString("profile-free-onboarding-welcome-description")}</p>
328
      </div>
329
      <div className={styles["content-wrapper"]}>
330
        <Image src={WomanEmail} alt="" width={475} />
331
        <div className={styles["content-text"]}>
332
          <div>
333
            <Image className={styles["hidden-mobile"]} src={CheckMark} alt="" />
334
            <p className={styles["headline"]}>
335
              {l10n.getString(
336
                "profile-free-onboarding-welcome-item-headline-1",
337
              )}
338
            </p>
339
            <p className={styles["description"]}>
340
              {l10n.getString(
341
                "profile-free-onboarding-welcome-item-description-1",
342
              )}
343
            </p>
344
          </div>
345
          <div>
346
            <p className={styles["headline"]}>
347
              {l10n.getString(
348
                "profile-free-onboarding-welcome-item-headline-2",
349
              )}
350
            </p>
351
            <p className={styles["description"]}>
352
              {l10n.getString(
353
                "profile-free-onboarding-welcome-item-description-2",
354
              )}
355
            </p>
356
          </div>
357
        </div>
358
      </div>
359
    </div>
360
  );
361
};
362

363
type StepTwoProps = {
364
  aliases: AliasData[];
365
  profile: ProfileData;
366
  user: UserData;
367
  runtimeData?: RuntimeData;
368
  isModalOpen: boolean;
369
  setIsModalOpen: (isOpen: boolean) => void;
370
  continue: () => void;
371
  onUpdate: (alias: AliasData, updatedFields: Partial<AliasData>) => void;
372
};
373

374
const StepTwo = (props: StepTwoProps) => {
1✔
375
  const l10n = useL10n();
×
376
  const [isSet, setIsSet] = useState(false);
×
377

378
  return (
379
    <div className={`${styles.step} ${styles["step-copy-mask"]}`}>
380
      <EmailForwardingModal
381
        isOpen={props.isModalOpen}
382
        onClose={() => {
383
          props.setIsModalOpen(false);
×
384
        }}
385
        onComplete={() => {
386
          props.setIsModalOpen(false);
×
387
          props.continue();
×
388
        }}
389
        onConfirm={() => {
390
          setIsSet(true);
×
391
        }}
392
        isSet={isSet}
393
      />
394
      <div className={styles["copy-mask-header"]}>
395
        <h1>{l10n.getString("profile-free-onboarding-copy-mask-headline")}</h1>
396
        <p>{l10n.getString("profile-free-onboarding-copy-mask-description")}</p>
397
      </div>
398
      <div className={styles["content-wrapper-copy-mask"]}>
399
        <div className={styles["copy-mask-arrow-element"]}>
400
          <Image src={VerticalArrow} alt="" />
401
        </div>
402
        <AliasList
403
          aliases={props.aliases}
404
          onCreate={() => {}}
405
          onUpdate={props.onUpdate}
406
          onDelete={() => {}}
407
          profile={props.profile}
408
          user={props.user}
409
          runtimeData={props.runtimeData}
410
          onboarding={true}
411
        >
412
          <div className={styles["content-wrapper-copy-mask-items"]}>
413
            <div className={styles["content-item"]}>
414
              <Image src={Emails} alt="" />
415
              <div className={styles["content-text"]}>
416
                <p className={styles["headline"]}>
417
                  {l10n.getString(
418
                    "profile-free-onboarding-copy-mask-item-headline-1",
419
                  )}
420
                </p>
421
                <p className={styles["description"]}>
422
                  {l10n.getString(
423
                    "profile-free-onboarding-copy-mask-item-description-1",
424
                  )}
425
                </p>
426
              </div>
427
            </div>
428
            <hr />
429
            <div className={styles["content-item"]}>
430
              <Image src={Congratulations} alt="" />
431
              <div className={styles["content-text"]}>
432
                <p className={styles["headline"]}>
433
                  {l10n.getString(
434
                    "profile-free-onboarding-copy-mask-item-headline-2",
435
                  )}
436
                </p>
437
                <p className={styles["description"]}>
438
                  {l10n.getString(
439
                    "profile-free-onboarding-copy-mask-item-description-2",
440
                  )}
441
                </p>
442
              </div>
443
            </div>
444
          </div>
445
        </AliasList>
446
      </div>
447
    </div>
448
  );
449
};
450

451
const StepThree = () => {
1✔
452
  const l10n = useL10n();
×
453

454
  return (
455
    <div
456
      className={`${styles.step} ${styles["step-copy-mask"]} ${styles["mask-use"]}`}
457
    >
458
      <div className={styles["copy-mask-header"]}>
459
        <h1>{l10n.getString("profile-free-onboarding-addon-headline")}</h1>
460
        <p>{l10n.getString("profile-free-onboarding-addon-subheadline")}</p>
461
      </div>
462
      <div className={styles["addon-content-wrapper"]}>
463
        <div className={styles["addon-content-items"]}>
464
          <div className={styles["addon-content-text"]}>
465
            <p className={styles.headline}>
466
              {l10n.getString("profile-free-onboarding-addon-item-headline-1")}
467
            </p>
468
            <p className={styles.description}>
469
              {l10n.getString(
470
                "profile-free-onboarding-addon-item-description-1",
471
              )}
472
            </p>
473
            <Image className={styles["large-arrow"]} src={LargeArrow} alt="" />
474
          </div>
475
          <Image src={WorkingMan} alt="" />
476
        </div>
477

478
        <div className={styles["addon-content-items"]}>
479
          <Image src={Extension} alt="" />
480
          <div className={styles["addon-content-text"]}>
481
            <p className={styles.headline}>
482
              {l10n.getString("profile-free-onboarding-addon-item-headline-2")}
483
            </p>
484
            <p className={styles.description}>
485
              {l10n.getString(
486
                "profile-free-onboarding-addon-item-description-2",
487
              )}
488
            </p>
489
            <Image className={styles["small-arrow"]} src={SmallArrow} alt="" />
490
          </div>
491
        </div>
492
      </div>
493
    </div>
494
  );
495
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc