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

mozilla / fx-private-relay / d4d9f278-d845-4992-8c81-4f3757c427a1

08 Sep 2025 02:07PM UTC coverage: 86.303% (-1.8%) from 88.121%
d4d9f278-d845-4992-8c81-4f3757c427a1

Pull #5842

circleci

joeherm
fix(deploy): Update CircleCI to use common Dockerfile for building frontend
Pull Request #5842: fix(deploy): Unify Dockerfiles

2744 of 3951 branches covered (69.45%)

Branch coverage included in aggregate %.

17910 of 19981 relevant lines covered (89.64%)

9.96 hits per line

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

63.33
/frontend/src/components/dashboard/aliases/MaskCard.tsx
1
import {
2
  createContext,
3
  ReactElement,
4
  ReactNode,
5
  useCallback,
6
  useContext,
7
  useEffect,
8
  useId,
9
  useRef,
10
  useState,
11
} from "react";
3✔
12
import {
13
  RadioGroupProps,
14
  RadioGroupState,
15
  useRadioGroupState,
16
  useToggleState,
17
} from "react-stately";
3✔
18
import {
19
  AriaRadioGroupProps,
20
  AriaRadioProps,
21
  useFocusRing,
22
  useRadio,
23
  useRadioGroup,
24
  useToggleButton,
25
} from "react-aria";
3✔
26
import Link from "next/link";
3✔
27
import styles from "./MaskCard.module.scss";
3✔
28
import CalendarIcon from "./images/calendar.svg";
3✔
29
import EmailIcon from "./images/email.svg";
3✔
30
import {
31
  AliasData,
32
  isBlockingLevelOneTrackers,
33
} from "../../../hooks/api/aliases";
3✔
34
import { UserData } from "../../../hooks/api/user";
35
import { ProfileData } from "../../../hooks/api/profile";
36
import { RuntimeData } from "../../../hooks/api/types";
37
import { useL10n } from "../../../hooks/l10n";
3✔
38
import { LabelEditor } from "./LabelEditor";
3✔
39
import { ArrowDownIcon, CopyIcon, LockIcon } from "../../Icons";
3✔
40
import Image from "../../Image";
3✔
41
import { getLocale } from "../../../functions/getLocale";
3✔
42
import { isFlagActive } from "../../../functions/waffle";
3✔
43
import { renderDate } from "../../../functions/renderDate";
3✔
44
import { AliasDeletionButton } from "./AliasDeletionButton";
3✔
45
import { VisuallyHidden } from "./../../VisuallyHidden";
3✔
46
import HorizontalArrow from "./../images/free-onboarding-horizontal-arrow.svg";
3✔
47
import { AliasDeletionButtonPermanent } from "./AliasDeletionButtonPermanent";
3✔
48

49
export type Props = {
50
  mask: AliasData;
51
  user: UserData;
52
  profile: ProfileData;
53
  onUpdate: (updatedFields: Partial<AliasData>) => void;
54
  onDelete: () => void;
55
  isOpen: boolean;
56
  onChangeOpen: (isOpen: boolean) => void;
57
  showLabelEditor?: boolean;
58
  runtimeData?: RuntimeData;
59
  placeholder?: string;
60
  isOnboarding?: boolean;
61
  children?: ReactNode;
62
  copyAfterMaskGeneration: boolean;
63
  setModalOpenedState?: (state: boolean) => void;
64
};
65

66
export const MaskCard = (props: Props) => {
3✔
67
  const l10n = useL10n();
2✔
68
  const [justCopied, setJustCopied] = useState(false);
2✔
69
  const [promoIsSelected, setPromoIsSelectedState] = useState(false);
2✔
70

71
  const expandButtonRef = useRef<HTMLButtonElement>(null);
2✔
72
  const expandButtonState = useToggleState({
2✔
73
    isSelected: props.isOpen === true,
74
    onChange: props.onChangeOpen,
75
  });
76
  const expandButtonProps = useToggleButton(
2✔
77
    {},
78
    expandButtonState,
79
    expandButtonRef,
80
  ).buttonProps;
81
  // Used to link the expandButton to the area-to-be-expanded:
82
  const detailsElementId = useId();
2✔
83

84
  const copyAddressToClipboard = useCallback(() => {
2✔
85
    navigator.clipboard.writeText(props.mask.full_address);
×
86
    setJustCopied(true);
×
87
    setTimeout(() => {
×
88
      setJustCopied(false);
×
89
    }, 1 * 1000);
90
  }, [props.mask.full_address]);
91

92
  useEffect(() => {
2✔
93
    if (props.copyAfterMaskGeneration) {
2!
94
      copyAddressToClipboard();
×
95
    }
96
  }, [props.copyAfterMaskGeneration, copyAddressToClipboard]);
97

98
  const statNumberFormatter = new Intl.NumberFormat(getLocale(l10n), {
2✔
99
    notation: "compact",
100
    compactDisplay: "short",
101
  });
102

103
  const classNames = [
2✔
104
    styles.card,
105
    props.mask.enabled ? styles["is-enabled"] : styles["is-disabled"],
2!
106
    props.mask.block_list_emails
2!
107
      ? styles["is-blocking-promotionals"]
108
      : styles["is-not-blocking-promotionals"],
109
    isBlockingLevelOneTrackers(props.mask, props.profile)
2!
110
      ? styles["is-removing-trackers"]
111
      : styles["is-not-removing-trackers"],
112
    props.isOnboarding ? styles["is-onboarding"] : "",
2!
113
  ].join(" ");
114

115
  const blockLevel =
116
    props.mask.enabled === false
2!
117
      ? "all"
118
      : props.mask.block_list_emails === true
2!
119
        ? "promotionals"
120
        : "none";
121

122
  return (
123
    <>
124
      <div className={classNames}>
125
        <div className={styles.bar}>
126
          <div className={styles.summary}>
127
            {props.showLabelEditor && (
2✔
128
              <div className={styles["label-editor-wrapper"]}>
129
                <LabelEditor
130
                  label={props.mask.description}
131
                  placeholder={props.placeholder}
132
                  onSubmit={(newLabel) =>
133
                    props.onUpdate({ description: newLabel })
×
134
                  }
135
                />
136
              </div>
137
            )}
138
            <div className={styles["copy-button-wrapper"]}>
139
              <button
140
                className={styles["copy-button"]}
141
                title={l10n.getString("profile-label-click-to-copy")}
142
                aria-label={l10n.getString("profile-label-click-to-copy-alt", {
143
                  address: props.mask.full_address,
144
                })}
145
                onClick={copyAddressToClipboard}
146
              >
147
                <samp className={styles.address}>
148
                  {props.mask.full_address}
149
                </samp>
150
                <span className={styles["copy-icon"]}>
151
                  <CopyIcon alt="" />
152
                </span>
153
              </button>
154
              <span
155
                aria-hidden={!justCopied}
156
                className={styles["copied-confirmation"]}
157
              >
158
                {l10n.getString("profile-label-copied")}
159
              </span>
160
            </div>
161
            <div className={styles["block-level-label"]}>
162
              {props.mask.enabled === false
2!
163
                ? l10n.getString("profile-promo-email-blocking-label-none-2")
164
                : props.mask.block_list_emails === true
2!
165
                  ? l10n.getString(
166
                      "profile-promo-email-blocking-label-promotionals-2",
167
                    )
168
                  : l10n.getString(
169
                      "profile-promo-email-blocking-label-forwarding-2",
170
                    )}
171
            </div>
172
          </div>
173
          <button
174
            {...expandButtonProps}
175
            ref={expandButtonRef}
176
            className={styles["expand-button"]}
177
            aria-expanded={expandButtonState.isSelected}
178
            aria-controls={detailsElementId}
179
          >
180
            <ArrowDownIcon
181
              alt={l10n.getString(
182
                expandButtonState.isSelected
2!
183
                  ? "profile-details-collapse"
184
                  : "profile-details-expand",
185
              )}
186
              width={16}
187
              height={16}
188
            />
189
          </button>
190
        </div>
191
        <div
192
          id={detailsElementId}
193
          className={styles["details-wrapper"]}
194
          hidden={!expandButtonState.isSelected}
195
        >
196
          <div className={styles.details}>
197
            <dl className={styles.stats}>
198
              <div className={`${styles.stat} ${styles["blocked-stat"]}`}>
199
                <dt>{l10n.getString("profile-label-blocked")}</dt>
200
                <dd>{statNumberFormatter.format(props.mask.num_blocked)}</dd>
201
              </div>
202
              <div className={`${styles.stat} ${styles["forwarded-stat"]}`}>
203
                <dt>{l10n.getString("profile-label-forwarded")}</dt>
204
                <dd>{statNumberFormatter.format(props.mask.num_forwarded)}</dd>
205
              </div>
206
              {/* If user is not premium, hide the replies count */}
207
              {props.profile.has_premium && (
2✔
208
                <div className={`${styles.stat} ${styles["replies-stat"]}`}>
209
                  <dt>{l10n.getString("profile-label-replies")}</dt>
210
                  <dd>{statNumberFormatter.format(props.mask.num_replied)}</dd>
211
                </div>
212
              )}
213

214
              {/*
215
              If the back-end does not yet support providing tracker blocking stats,
216
              hide the blocked trackers count:
217
            */}
218
              {isFlagActive(props.runtimeData, "tracker_removal") &&
2!
219
                typeof props.mask.num_level_one_trackers_blocked ===
220
                  "number" && (
221
                  <div
222
                    className={`${styles.stat} ${styles["trackers-removed-stat"]}`}
223
                  >
224
                    <dt>{l10n.getString("profile-label-trackers-removed")}</dt>
225
                    <dd>
226
                      {statNumberFormatter.format(
227
                        props.mask.num_level_one_trackers_blocked,
228
                      )}
229
                    </dd>
230
                  </div>
231
                )}
232
            </dl>
233
            <div
234
              className={`${styles["block-level"]} ${
235
                styles[`is-blocking-${blockLevel}`]
236
              }`}
237
            >
238
              {props.isOnboarding && props.isOpen && (
2!
239
                <div className={styles["onboarding-alias-container"]}>
240
                  <Image src={HorizontalArrow} alt="" />
241
                  <div className={styles["onboarding-alias-text"]}>
242
                    <p>
243
                      {l10n.getString(
244
                        "profile-free-onboarding-copy-mask-what-emails-to-block",
245
                      )}
246
                    </p>
247
                  </div>
248
                </div>
249
              )}
250
              <div className={styles["block-level-setting"]}>
251
                <BlockLevelSegmentedControl
252
                  defaultValue={blockLevel}
253
                  onChange={(blockLevel) => {
254
                    if (blockLevel === "all") {
×
255
                      return props.onUpdate({ enabled: false });
×
256
                    }
257
                    if (blockLevel === "promotionals") {
×
258
                      return props.onUpdate({
×
259
                        enabled: true,
260
                        block_list_emails: true,
261
                      });
262
                    }
263
                    if (blockLevel === "none") {
×
264
                      return props.onUpdate({
×
265
                        enabled: true,
266
                        block_list_emails: false,
267
                      });
268
                    }
269
                  }}
270
                  label={l10n.getString("profile-promo-email-blocking-title")}
271
                >
272
                  <BlockLevelOption
273
                    value="none"
274
                    setPromoSelectedState={setPromoIsSelectedState}
275
                  >
276
                    {l10n.getString("profile-promo-email-blocking-option-none")}
277
                  </BlockLevelOption>
278
                  <BlockLevelOption
279
                    value="promotionals"
280
                    isDisabled={!props.profile.has_premium}
281
                    title={l10n.getString(
282
                      "profile-promo-email-blocking-description-promotionals-locked-label",
283
                    )}
284
                    isPromo={true}
285
                    promoSelectedState={promoIsSelected}
286
                    setPromoSelectedState={setPromoIsSelectedState}
287
                  >
288
                    {!props.profile.has_premium && (
2✔
289
                      <LockIcon
290
                        alt={l10n.getString(
291
                          "profile-promo-email-blocking-description-promotionals-locked-label",
292
                        )}
293
                      />
294
                    )}
295
                    {l10n.getString(
296
                      "profile-promo-email-blocking-option-promotions",
297
                    )}
298
                  </BlockLevelOption>
299
                  <BlockLevelOption
300
                    value="all"
301
                    setPromoSelectedState={setPromoIsSelectedState}
302
                  >
303
                    {l10n.getString("profile-promo-email-blocking-option-all")}
304
                  </BlockLevelOption>
305
                </BlockLevelSegmentedControl>
306
                {promoIsSelected && !props.profile.has_premium ? (
4!
307
                  <div
308
                    className={styles["promotions-locked-description-wrapper"]}
309
                  >
310
                    <strong>
311
                      <LockIcon
312
                        alt={l10n.getString(
313
                          "profile-promo-email-blocking-description-promotionals-locked-label",
314
                        )}
315
                      />
316
                      {l10n.getString(
317
                        "profile-promo-email-blocking-description-promotionals-locked-label",
318
                      )}
319
                    </strong>
320
                    <p>
321
                      {l10n.getString(
322
                        "profile-promo-email-blocking-description-promotionals",
323
                      )}
324
                    </p>
325
                    <Link
326
                      href="/premium#pricing"
327
                      className={styles["upgrade-btn"]}
328
                    >
329
                      {l10n.getString("banner-pack-upgrade-cta")}
330
                    </Link>
331
                  </div>
332
                ) : null}
333
              </div>
334
              <div
335
                className={`${styles["block-level-setting-description"]}
336
              ${
337
                // Only add chevron on mobile for premium users
338
                promoIsSelected &&
2!
339
                !props.profile.has_premium &&
340
                styles["without-chevron"]
341
              }`}
342
              >
343
                {blockLevel === "all" &&
2!
344
                  l10n.getString(
345
                    "profile-promo-email-blocking-description-all-2",
346
                  )}
347
                {blockLevel === "promotionals" && (
2✔
348
                  <>
349
                    <p>
350
                      {l10n.getString(
351
                        "profile-promo-email-blocking-description-promotionals",
352
                      )}
353
                    </p>
354
                    <p>
355
                      <Link href="/faq#faq-promotional-email-blocking">
356
                        {l10n.getString(
357
                          "banner-label-data-notification-body-cta",
358
                        )}
359
                      </Link>
360
                    </p>
361
                  </>
362
                )}
363
                {blockLevel === "none" &&
4✔
364
                  l10n.getString(
365
                    "profile-promo-email-blocking-description-none-3",
366
                  )}
367
              </div>
368
            </div>
369
            <div className={styles.meta}>
370
              <dl>
371
                <div className={styles.metadata}>
372
                  <dt>{l10n.getString("profile-label-created")}</dt>
373
                  <dd>
374
                    <Image src={CalendarIcon} alt="" />
375
                    {renderDate(props.mask.created_at, l10n)}
376
                  </dd>
377
                </div>
378
                <div className={styles.metadata}>
379
                  <dt>{l10n.getString("profile-label-forward-emails")}</dt>
380
                  <dd>
381
                    <Image src={EmailIcon} alt="" />
382
                    {props.user.email}
383
                  </dd>
384
                </div>
385
              </dl>
386
              {!props.isOnboarding && (
2✔
387
                <div className={styles["deletion-button-wrapper"]}>
388
                  {isFlagActive(
389
                    props.runtimeData,
390
                    "custom_domain_management_redesign",
391
                  ) ? (
392
                    <AliasDeletionButtonPermanent
×
393
                      setModalOpenedState={props.setModalOpenedState}
394
                      onDelete={props.onDelete}
395
                      alias={props.mask}
396
                    />
397
                  ) : (
398
                    <AliasDeletionButton
399
                      onDelete={props.onDelete}
400
                      alias={props.mask}
401
                    />
402
                  )}
403
                </div>
404
              )}
405
            </div>
406
          </div>
407
        </div>
408
      </div>
409
      {props.isOnboarding && (
2✔
410
        <div
411
          className={
412
            styles[
413
              expandButtonState.isSelected
×
414
                ? "onboarding-open"
415
                : "onboarding-closed"
416
            ]
417
          }
418
        >
419
          {props.children}
420
        </div>
421
      )}
422
    </>
423
  );
424
};
425

426
const BlockLevelContext = createContext<RadioGroupState | null>(null);
3✔
427

428
/**
429
 * A "segmented control" (aka a switch with more than two options) used to set
430
 * the block level.
431
 */
432
const BlockLevelSegmentedControl = (
3✔
433
  props: { children: ReactElement[] } & RadioGroupProps &
434
    AriaRadioGroupProps &
435
    Required<Pick<AriaRadioGroupProps, "label">>,
436
) => {
437
  const state = useRadioGroupState(props);
4✔
438
  const { radioGroupProps, labelProps } = useRadioGroup(
4✔
439
    { orientation: "horizontal", ...props },
440
    state,
441
  );
442

443
  // When the block level state changes externally (i.e. from the custom mask generation success modal), we need to update the block level state within the UI.
444
  useEffect(() => {
4✔
445
    if (props.defaultValue) {
2!
446
      state.setSelectedValue(props.defaultValue);
2✔
447
    }
448
    // We only want the state to change when the block level default value changes from an update within a mask generation success modal.
449
    // The state itsself is for visually displaying the block level, and should not be added to the dependencies.
450
    // eslint-disable-next-line react-hooks/exhaustive-deps
451
  }, [props.defaultValue]);
452

453
  return (
454
    <div {...radioGroupProps} className={styles["block-level-control-wrapper"]}>
455
      <span {...labelProps} className={styles["block-level-control-label"]}>
456
        {props.label}
457
      </span>
458
      <div className={styles["block-level-segmented-control"]}>
459
        <BlockLevelContext.Provider value={state}>
460
          {props.children}
461
        </BlockLevelContext.Provider>
462
      </div>
463
    </div>
464
  );
465
};
466

467
const BlockLevelOption = (
3✔
468
  props: AriaRadioProps & {
469
    children: ReactNode;
470
    title?: string;
471
    isPromo?: boolean;
472
    promoSelectedState?: boolean;
473
    setPromoSelectedState: (promoSelectedState: boolean) => void;
474
  },
475
) => {
476
  const state = useContext(BlockLevelContext);
12✔
477
  const inputRef = useRef<HTMLInputElement>(null);
12✔
478
  // The `!` is safe here as long as <BlockLevelOption> is only used as a child
479
  // of <BlockLevelSwitch>, which sets the state in the context:
480
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
481
  const { inputProps, isSelected } = useRadio(props, state!, inputRef);
12✔
482
  const { isFocusVisible, focusProps } = useFocusRing();
12✔
483

484
  return (
485
    <label
486
      className={`${isSelected ? styles["is-selected"] : ""} ${
12✔
487
        isFocusVisible ? styles["is-focused"] : ""
12!
488
      } ${props.isDisabled ? styles["is-disabled"] : ""} ${
12✔
489
        props.promoSelectedState ? styles["promo-selected"] : ""
12!
490
      }`}
491
      title={props.title}
492
      onClick={() =>
493
        props.isPromo
×
494
          ? props.setPromoSelectedState(!props.promoSelectedState)
495
          : props.setPromoSelectedState(false)
496
      }
497
    >
498
      <VisuallyHidden>
499
        <input {...inputProps} {...focusProps} ref={inputRef} />
500
      </VisuallyHidden>
501
      {props.children}
502
    </label>
503
  );
504
};
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