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

mozilla / fx-private-relay / 093164e9-6aa6-4b7b-adb3-ec19388e9081

14 May 2025 02:50PM CUT coverage: 85.189% (-0.008%) from 85.197%
093164e9-6aa6-4b7b-adb3-ec19388e9081

Pull #5550

circleci

groovecoder
for MPP-3957: start update_fxrelay_allowlist_collection command
Pull Request #5550: for MPP-3957: start update_fxrelay_allowlist_collection command

2468 of 3609 branches covered (68.38%)

Branch coverage included in aggregate %.

77 of 101 new or added lines in 3 files covered. (76.24%)

23 existing lines in 3 files now uncovered.

17393 of 19705 relevant lines covered (88.27%)

9.64 hits per line

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

29.03
/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 { Button, LinkButton } from "../Button";
1✔
17
import Image from "../Image";
1✔
18
import { useGaViewPing } from "../../hooks/gaViewPing";
1✔
19
import { useGaEvent } from "../../hooks/gaEvent";
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: (options: { mask_type: "random" }) => Promise<void>;
34
  hasReachedFreeMaskLimit: boolean;
35
  aliases: AliasData[];
36
  user: UserData;
37
  runtimeData?: RuntimeData;
38
  onUpdate: (alias: AliasData, updatedFields: Partial<AliasData>) => void;
39
  hasAtleastOneMask: boolean;
40
};
41

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

50
  let step = null;
×
51
  let button = null;
×
52
  let skipButton = null;
×
53
  let next = null;
×
54

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

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

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

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

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

91
    const createNewMask = async () => {
×
92
      let moveToNextStep = true;
×
93

94
      try {
×
95
        await props.generateNewMask({ mask_type: "random" });
×
96
      } catch (_e) {
97
        // On error, we can only move to the next step if the user has atleast 1 mask.
98
        if (!props.hasAtleastOneMask) {
×
99
          moveToNextStep = false;
×
100
        }
101
      }
102

103
      if (moveToNextStep) {
×
104
        props.onNextStep(1);
×
105
        gaEvent({
×
106
          category: "Free Onboarding",
107
          action: "Engage",
108
          label: "onboarding-step-1-create-random-mask",
109
          value: 1,
110
        });
111
      }
112
    };
113

114
    step = <StepOne />;
×
115

116
    skipButton = (
117
      <button
118
        ref={skipStepOneButtonRef}
119
        className={styles["skip-link"]}
120
        onClick={skipMaskCreation}
121
      >
122
        {l10n.getString("profile-free-onboarding-skip-step")}
123
      </button>
124
    );
125

126
    button = (
127
      <Button className={styles["generate-new-mask"]} onClick={createNewMask}>
128
        {l10n.getString("profile-free-onboarding-welcome-generate-new-mask")}
129
        <Image src={Plus} alt="" />
130
      </Button>
131
    );
132
  }
133

134
  if (props.profile.onboarding_free_state === 1) {
×
135
    const skipMaskTesting = () => {
×
136
      props.onNextStep(3);
×
137
      gaEvent({
×
138
        category: "Free Onboarding",
139
        action: "Engage",
140
        label: "onboarding-step-2-skip",
141
        value: 1,
142
      });
143
    };
144

145
    const nextStep = () => {
×
146
      props.onNextStep(2);
×
147
      gaEvent({
×
148
        category: "Free Onboarding",
149
        action: "Engage",
150
        label: "onboarding-step-2-next",
151
        value: 1,
152
      });
153
    };
154

155
    const forwardedEmail = () => {
×
156
      props.onNextStep(2);
×
157
      gaEvent({
×
158
        category: "Free Onboarding",
159
        action: "Engage",
160
        label: "onboarding-step-2-continue",
161
        value: 1,
162
      });
163
    };
164

165
    step = (
166
      <StepTwo
167
        aliases={props.aliases}
168
        profile={props.profile}
169
        user={props.user}
170
        runtimeData={props.runtimeData}
171
        continue={forwardedEmail}
172
        isModalOpen={isModalOpen}
173
        setIsModalOpen={setIsModalOpen}
174
        onUpdate={props.onUpdate}
175
      />
176
    );
177

178
    next = (
179
      <button
180
        ref={nextStepTwoButtonRef}
181
        className={styles["next-link"]}
182
        onClick={nextStep}
183
      >
184
        {l10n.getString("profile-free-onboarding-next-step")}
185
        <ChevronRightIcon className={styles.chevron} width={16} alt="" />
186
      </button>
187
    );
188

189
    button = (
190
      <Button
191
        className={styles["generate-new-mask"]}
192
        onClick={() => {
193
          setIsModalOpen(true);
×
194
        }}
195
      >
196
        {l10n.getString(
197
          "profile-free-onboarding-copy-mask-how-forwarding-works",
198
        )}
199
      </Button>
200
    );
201

202
    skipButton = (
203
      <button
204
        ref={skipStepTwoButtonRef}
205
        className={styles["skip-link"]}
206
        onClick={skipMaskTesting}
207
      >
208
        {l10n.getString("profile-free-onboarding-skip-step")}
209
      </button>
210
    );
211
  }
212

213
  if (props.profile.onboarding_free_state === 2) {
×
UNCOV
214
    const linkForBrowser = supportsChromeExtension()
×
215
      ? "https://chrome.google.com/webstore/detail/firefox-relay/lknpoadjjkjcmjhbjpcljdednccbldeb?utm_source=fx-relay&utm_medium=onboarding&utm_campaign=install-addon"
216
      : "https://addons.mozilla.org/firefox/addon/private-relay/";
217

218
    const skipAddonStep = () => {
×
219
      props.onNextStep(3);
×
UNCOV
220
      gaEvent({
×
221
        category: "Free Onboarding",
222
        action: "Engage",
223
        label: "onboarding-step-3-skip",
224
        value: 1,
225
      });
226
    };
227

UNCOV
228
    const finish = () => {
×
229
      // this will trigger confetti in the dashboard
230
      props.onNextStep(4);
×
UNCOV
231
      gaEvent({
×
232
        category: "Free Onboarding",
233
        action: "Engage",
234
        label: "onboarding-step-3-complete",
235
        value: 1,
236
      });
237
    };
238

UNCOV
239
    step = <StepThree />;
×
240

241
    next = (
242
      <button
243
        ref={nextStepTwoButtonRef}
244
        className={styles["next-link"]}
245
        onClick={finish}
246
      >
247
        {l10n.getString("profile-free-onboarding-addon-finish")}
248
        <ChevronRightIcon className={styles.chevron} width={16} alt="" />
249
      </button>
250
    );
251

252
    button = (
253
      <>
254
        <LinkButton
255
          href={linkForBrowser}
256
          target="_blank"
257
          className={`is-hidden-with-addon ${styles["get-addon-button"]}`}
258
        >
259
          {l10n.getString("profile-free-onboarding-addon-get-extension")}
260
        </LinkButton>
261
        <div className={`${styles["addon-description"]} is-visible-with-addon`}>
262
          <div className={styles["action-complete"]}>
263
            <CheckBadgeIcon alt="" width={18} height={18} />
264
            {l10n.getString("multi-part-onboarding-premium-extension-added")}
265
          </div>
266
        </div>
267
      </>
268
    );
269

270
    skipButton = (
271
      <button
272
        ref={skipStepThreeButtonRef}
273
        className={styles["skip-link"]}
274
        onClick={skipAddonStep}
275
      >
276
        {l10n.getString("profile-free-onboarding-skip-step")}
277
      </button>
278
    );
279
  }
280

281
  return (
282
    <section className={styles.onboarding}>
283
      {step}
284
      <div className={styles.controls}>
285
        {button}
286
        <div className={styles["progress-container"]}>
287
          <VisuallyHidden>
288
            <progress
289
              max={getRuntimeConfig().maxOnboardingAvailable}
290
              value={props.profile.onboarding_free_state + 1}
291
            >
292
              {l10n.getString("multi-part-onboarding-step-counter", {
293
                step: props.profile.onboarding_free_state,
294
                max: getRuntimeConfig().maxOnboardingAvailable,
295
              })}
296
            </progress>
297
          </VisuallyHidden>
298
          {next}
299
          <ol className={styles["styled-progress-bar"]} aria-hidden={true}>
300
            <li
301
              className={
302
                props.profile.onboarding_free_state >= 0
×
303
                  ? styles["is-completed"]
304
                  : undefined
305
              }
306
            >
307
              <span></span>1
308
            </li>
309
            <li
310
              className={
311
                props.profile.onboarding_free_state >= 1
×
312
                  ? styles["is-completed"]
313
                  : undefined
314
              }
315
            >
316
              <span></span>2
317
            </li>
318
            <li
319
              className={
320
                props.profile.onboarding_free_state >= 2
×
321
                  ? styles["is-completed"]
322
                  : undefined
323
              }
324
            >
325
              <span></span>3
326
            </li>
327
          </ol>
328
        </div>
329
        {skipButton}
330
      </div>
331
    </section>
332
  );
333
};
334

335
const StepOne = () => {
1✔
UNCOV
336
  const l10n = useL10n();
×
337

338
  return (
339
    <div className={`${styles.step} ${styles["step-welcome"]}`}>
340
      <div className={styles["welcome-header"]}>
341
        <h1>{l10n.getString("profile-free-onboarding-welcome-headline")}</h1>
342
        <p>{l10n.getString("profile-free-onboarding-welcome-description")}</p>
343
      </div>
344
      <div className={styles["content-wrapper"]}>
345
        <Image src={WomanEmail} alt="" width={475} />
346
        <div className={styles["content-text"]}>
347
          <div>
348
            <Image className={styles["hidden-mobile"]} src={CheckMark} alt="" />
349
            <p className={styles["headline"]}>
350
              {l10n.getString(
351
                "profile-free-onboarding-welcome-item-headline-1",
352
              )}
353
            </p>
354
            <p className={styles["description"]}>
355
              {l10n.getString(
356
                "profile-free-onboarding-welcome-item-description-1",
357
              )}
358
            </p>
359
          </div>
360
          <div>
361
            <p className={styles["headline"]}>
362
              {l10n.getString(
363
                "profile-free-onboarding-welcome-item-headline-2",
364
              )}
365
            </p>
366
            <p className={styles["description"]}>
367
              {l10n.getString(
368
                "profile-free-onboarding-welcome-item-description-2",
369
              )}
370
            </p>
371
          </div>
372
        </div>
373
      </div>
374
    </div>
375
  );
376
};
377

378
type StepTwoProps = {
379
  aliases: AliasData[];
380
  profile: ProfileData;
381
  user: UserData;
382
  runtimeData?: RuntimeData;
383
  isModalOpen: boolean;
384
  setIsModalOpen: (isOpen: boolean) => void;
385
  continue: () => void;
386
  onUpdate: (alias: AliasData, updatedFields: Partial<AliasData>) => void;
387
};
388

389
const StepTwo = (props: StepTwoProps) => {
1✔
UNCOV
390
  const l10n = useL10n();
×
UNCOV
391
  const [isSet, setIsSet] = useState(false);
×
392

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

466
const StepThree = () => {
1✔
UNCOV
467
  const l10n = useL10n();
×
468

469
  return (
470
    <div
471
      className={`${styles.step} ${styles["step-copy-mask"]} ${styles["mask-use"]}`}
472
    >
473
      <div className={styles["copy-mask-header"]}>
474
        <h1>{l10n.getString("profile-free-onboarding-addon-headline")}</h1>
475
        <p>{l10n.getString("profile-free-onboarding-addon-subheadline")}</p>
476
      </div>
477
      <div className={styles["addon-content-wrapper"]}>
478
        <div className={styles["addon-content-items"]}>
479
          <div className={styles["addon-content-text"]}>
480
            <p className={styles.headline}>
481
              {l10n.getString("profile-free-onboarding-addon-item-headline-1")}
482
            </p>
483
            <p className={styles.description}>
484
              {l10n.getString(
485
                "profile-free-onboarding-addon-item-description-1",
486
              )}
487
            </p>
488
            <Image className={styles["large-arrow"]} src={LargeArrow} alt="" />
489
          </div>
490
          <Image src={WorkingMan} alt="" />
491
        </div>
492

493
        <div className={styles["addon-content-items"]}>
494
          <Image src={Extension} alt="" />
495
          <div className={styles["addon-content-text"]}>
496
            <p className={styles.headline}>
497
              {l10n.getString("profile-free-onboarding-addon-item-headline-2")}
498
            </p>
499
            <p className={styles.description}>
500
              {l10n.getString(
501
                "profile-free-onboarding-addon-item-description-2",
502
              )}
503
            </p>
504
            <Image className={styles["small-arrow"]} src={SmallArrow} alt="" />
505
          </div>
506
        </div>
507
      </div>
508
    </div>
509
  );
510
};
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