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

mozilla / fx-private-relay / a2bc0383-1205-4ebd-979f-e7ee6dba9a0d

18 Dec 2023 05:15PM UTC coverage: 73.514% (-0.7%) from 74.258%
a2bc0383-1205-4ebd-979f-e7ee6dba9a0d

push

circleci

jwhitlock
Add provider_id="" to SocialApp init

1962 of 2913 branches covered (0.0%)

Branch coverage included in aggregate %.

6273 of 8289 relevant lines covered (75.68%)

19.91 hits per line

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

73.17
/frontend/src/components/dashboard/aliases/Alias.tsx
1
import { useRef, useState, ReactNode } from "react";
2✔
2
import { useToggleState, useTooltipTriggerState } from "react-stately";
2✔
3
import {
4
  mergeProps,
5
  useToggleButton,
6
  useTooltip,
7
  useTooltipTrigger,
8
} from "react-aria";
2✔
9
import styles from "./Alias.module.scss";
2✔
10
import { ArrowDownIcon, CopyIcon, HideIcon } from "../../Icons";
2✔
11
import IllustrationHoliday from "../../../../public/illustrations/holiday.svg";
2✔
12
import IllustrationLibrary from "../../../../public/illustrations/library.svg";
2✔
13
import {
14
  AliasData,
15
  getFullAddress,
16
  isBlockingLevelOneTrackers,
17
} from "../../../hooks/api/aliases";
2✔
18
import { LabelEditor } from "./LabelEditor";
2✔
19
import { UserData } from "../../../hooks/api/user";
20
import { ProfileData } from "../../../hooks/api/profile";
21
import { renderDate } from "../../../functions/renderDate";
2✔
22
import { AliasDeletionButton } from "./AliasDeletionButton";
2✔
23
import { getRuntimeConfig } from "../../../config";
2✔
24
import { getLocale } from "../../../functions/getLocale";
2✔
25
import { BlockLevel, BlockLevelSlider } from "./BlockLevelSlider";
2✔
26
import { RuntimeData } from "../../../hooks/api/runtimeData";
27
import { isFlagActive } from "../../../functions/waffle";
2✔
28
import { isPeriodicalPremiumAvailableInCountry } from "../../../functions/getPlan";
2✔
29
import { useL10n } from "../../../hooks/l10n";
2✔
30
import { Localized } from "../../Localized";
2✔
31

32
// Deprecated, flag "mask_redesign" is in use and uses <MaskCard/> instead of <Alias/>
33

34
export type Props = {
35
  alias: AliasData;
36
  user: UserData;
37
  profile: ProfileData;
38
  onUpdate: (updatedFields: Partial<AliasData>) => void;
39
  onDelete: () => void;
40
  isOpen: boolean;
41
  onChangeOpen: (isOpen: boolean) => void;
42
  showLabelEditor?: boolean;
43
  runtimeData?: RuntimeData;
44
};
45

46
/**
47
 * A card to manage (toggle it on/off, view details, ...) a single alias.
48
 */
49
export const Alias = (props: Props) => {
146✔
50
  const l10n = useL10n();
141✔
51
  const [justCopied, setJustCopied] = useState(false);
141✔
52

53
  const expandButtonRef = useRef<HTMLButtonElement>(null);
140✔
54
  const expandButtonState = useToggleState({
140✔
55
    isSelected: props.isOpen === true,
56
    onChange: props.onChangeOpen,
57
  });
58
  const expandButtonProps = useToggleButton(
140✔
59
    {},
60
    expandButtonState,
61
    expandButtonRef,
62
  ).buttonProps;
63

64
  const address = getFullAddress(props.alias);
140✔
65

66
  const copyAddressToClipboard = () => {
140✔
67
    navigator.clipboard.writeText(address);
×
68
    setJustCopied(true);
×
69
    setTimeout(() => {
×
70
      setJustCopied(false);
×
71
    }, 1 * 1000);
72
  };
73

74
  const labelEditor = props.showLabelEditor ? (
140✔
75
    <div className={styles["label-editor-wrapper"]}>
76
      <LabelEditor
77
        label={props.alias.description}
78
        onSubmit={(newLabel) => props.onUpdate({ description: newLabel })}
3✔
79
      />
80
    </div>
81
  ) : null;
82

83
  let backgroundImage = undefined;
140✔
84
  if (
140!
85
    [
86
      "Holiday",
87
      "Holidays",
88
      "Vakantie",
89
      "Urlaub",
90
      "Ferien",
91
      "Vacances",
92
    ].includes(props.alias.description)
93
  ) {
94
    backgroundImage = IllustrationHoliday.src;
×
95
  }
96
  if (
140!
97
    [
98
      "Library",
99
      "Bibliotheek",
100
      "Bibliotek",
101
      "Bibliothek",
102
      "Biblioteek",
103
      "Bibliothèque",
104
    ].includes(props.alias.description)
105
  ) {
106
    backgroundImage = IllustrationLibrary.src;
×
107
  }
108

109
  const setBlockLevel = (blockLevel: BlockLevel) => {
140✔
110
    if (blockLevel === "none") {
1!
111
      // The back-end rejects requests trying to set this property for free users:
112
      const blockPromotionals = props.profile.has_premium ? false : undefined;
×
113
      return props.onUpdate({
×
114
        enabled: true,
115
        block_list_emails: blockPromotionals,
116
      });
117
    }
118
    if (blockLevel === "promotional") {
1!
119
      return props.onUpdate({ enabled: true, block_list_emails: true });
×
120
    }
121
    if (blockLevel === "all") {
1✔
122
      // The back-end rejects requests trying to set this property for free users:
123
      const blockPromotionals = props.profile.has_premium ? true : undefined;
1!
124
      return props.onUpdate({
1✔
125
        enabled: false,
126
        block_list_emails: blockPromotionals,
127
      });
128
    }
129
  };
130

131
  const classNames = [
140✔
132
    styles["alias-card"],
133
    props.alias.enabled ? styles["is-enabled"] : styles["is-disabled"],
140!
134
    expandButtonState.isSelected
140!
135
      ? styles["is-expanded"]
136
      : styles["is-collapsed"],
137
    props.alias.block_list_emails
140!
138
      ? styles["is-blocking-promotionals"]
139
      : styles["is-not-blocking-promotionals"],
140
    isBlockingLevelOneTrackers(props.alias, props.profile)
140!
141
      ? styles["is-removing-trackers"]
142
      : styles["is-not-removing-trackers"],
143
  ].join(" ");
144

145
  return (
146
    <div
147
      className={classNames}
148
      style={{
149
        backgroundImage: backgroundImage
140!
150
          ? `url(${backgroundImage}), none`
151
          : undefined,
152
      }}
153
    >
154
      <div className={styles["main-data"]}>
155
        <div className={styles.controls}>
156
          <TrackerRemovalIndicator
157
            alias={props.alias}
158
            profile={props.profile}
159
          />
160
          {labelEditor}
161
          <span className={styles["copy-controls"]}>
162
            <span className={styles["copy-button-wrapper"]}>
163
              <button
164
                className={styles["copy-button"]}
165
                title={l10n.getString("profile-label-click-to-copy")}
166
                aria-label={l10n.getString("profile-label-click-to-copy-alt", {
167
                  address: address,
168
                })}
169
                onClick={copyAddressToClipboard}
170
              >
171
                <samp className={styles.address}>{address}</samp>
172
                <span className={styles["copy-icon"]}>
173
                  <CopyIcon alt="" />
174
                </span>
175
              </button>
176
              <span
177
                aria-hidden={!justCopied}
178
                className={`${styles["copied-confirmation"]} ${
179
                  justCopied ? styles["is-shown"] : ""
140!
180
                }`}
181
              >
182
                {l10n.getString("profile-label-copied")}
183
              </span>
184
            </span>
185
          </span>
186
        </div>
187
        <div className={styles["block-level-label-wrapper"]}>
188
          <BlockLevelLabel alias={props.alias} />
189
        </div>
190
        {/* This <Stats> will be hidden on small screens: */}
191
        <Stats
192
          alias={props.alias}
193
          profile={props.profile}
194
          runtimeData={props.runtimeData}
195
        />
196
        <div className={styles["expand-toggle"]}>
197
          <button {...expandButtonProps} ref={expandButtonRef}>
198
            <ArrowDownIcon
199
              alt={l10n.getString(
200
                expandButtonState.isSelected
140!
201
                  ? "profile-details-collapse"
202
                  : "profile-details-expand",
203
              )}
204
              width={16}
205
              height={16}
206
            />
207
          </button>
208
        </div>
209
      </div>
210
      <div className={styles["secondary-data"]}>
211
        {/* This <Stats> will be hidden on large screens: */}
212
        <Stats
213
          alias={props.alias}
214
          profile={props.profile}
215
          runtimeData={props.runtimeData}
216
        />
217
        <div className={styles.row}>
218
          <BlockLevelSlider
219
            alias={props.alias}
220
            onChange={setBlockLevel}
221
            hasPremium={props.profile.has_premium}
222
            premiumAvailableInCountry={isPeriodicalPremiumAvailableInCountry(
223
              props.runtimeData,
224
            )}
225
          />
226
        </div>
227
        <div className={styles.row}>
228
          <dl>
229
            <div className={`${styles["forward-target"]} ${styles.metadata}`}>
230
              <dt>{l10n.getString("profile-label-forward-emails")}</dt>
231
              <dd>{props.user.email}</dd>
232
            </div>
233
            <div className={`${styles["date-created"]} ${styles.metadata}`}>
234
              <dt>{l10n.getString("profile-label-created")}</dt>
235
              <dd>{renderDate(props.alias.created_at, l10n)}</dd>
236
            </div>
237
          </dl>
238
          <AliasDeletionButton onDelete={props.onDelete} alias={props.alias} />
239
        </div>
240
      </div>
241
    </div>
242
  );
243
};
244

245
type StatsProps = {
246
  alias: AliasData;
247
  profile: ProfileData;
248
  runtimeData?: RuntimeData;
249
};
250
const Stats = (props: StatsProps) => {
2✔
251
  const l10n = useL10n();
280✔
252
  const numberFormatter = new Intl.NumberFormat(getLocale(l10n), {
280✔
253
    notation: "compact",
254
    compactDisplay: "short",
255
  });
256

257
  return (
258
    <div className={styles["alias-stats"]}>
259
      <BlockedTooltip>
260
        <span className={styles.number}>
261
          {numberFormatter.format(props.alias.num_blocked)}
262
        </span>
263
        <span className={styles.label}>
264
          {l10n.getString("profile-label-blocked")}
265
        </span>
266
      </BlockedTooltip>
267
      <ForwardedTooltip>
268
        <span className={styles.number}>
269
          {numberFormatter.format(props.alias.num_forwarded)}
270
        </span>
271
        <span className={styles.label}>
272
          {l10n.getString("profile-label-forwarded")}
273
        </span>
274
      </ForwardedTooltip>
275

276
      {/* If user is not premium, hide the replies count */}
277
      {props.profile.has_premium && (
280✔
278
        <RepliesTooltip>
279
          <span className={styles.number}>
280
            {numberFormatter.format(props.alias.num_replied)}
281
          </span>
282
          <span className={styles.label}>
283
            {l10n.getString("profile-label-replies")}
284
          </span>
285
        </RepliesTooltip>
286
      )}
287

288
      {/*
289
        If the back-end does not yet support providing tracker blocking stats,
290
        hide the blocked trackers count:
291
       */}
292
      {isFlagActive(props.runtimeData, "tracker_removal") &&
280!
293
        typeof props.alias.num_level_one_trackers_blocked === "number" && (
294
          <TrackersRemovedTooltip>
295
            <span className={styles.number}>
296
              {numberFormatter.format(
297
                props.alias.num_level_one_trackers_blocked,
298
              )}
299
            </span>
300
            <span className={styles.label}>
301
              {l10n.getString("profile-label-trackers-removed")}
302
            </span>
303
          </TrackersRemovedTooltip>
304
        )}
305
    </div>
306
  );
307
};
308

309
type TooltipProps = {
310
  children: ReactNode;
311
};
312
const ForwardedTooltip = (props: TooltipProps) => {
2✔
313
  const l10n = useL10n();
280✔
314
  const triggerState = useTooltipTriggerState({ delay: 0 });
280✔
315
  const triggerRef = useRef<HTMLSpanElement>(null);
280✔
316
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
280✔
317

318
  const { tooltipProps } = useTooltip({}, triggerState);
280✔
319

320
  return (
321
    <div className={styles["stat-wrapper"]}>
322
      <span
323
        ref={triggerRef}
324
        {...tooltipTrigger.triggerProps}
325
        className={`${styles.stat} ${styles["forwarded-stat"]}`}
326
      >
327
        {props.children}
328
      </span>
329
      {triggerState.isOpen && (
280✔
330
        <div
331
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
332
          className={styles.tooltip}
333
        >
334
          <p>
335
            <span>{l10n.getString("profile-forwarded-copy-2")}</span>
336
          </p>
337
          <p>
338
            <strong>{l10n.getString("profile-forwarded-note")}</strong>&nbsp;
339
            <span>
340
              {l10n.getString("profile-forwarded-note-copy", {
341
                size: getRuntimeConfig().emailSizeLimitNumber,
342
                unit: getRuntimeConfig().emailSizeLimitUnit,
343
              })}
344
            </span>
345
          </p>
346
        </div>
347
      )}
348
    </div>
349
  );
350
};
351

352
const BlockedTooltip = (props: TooltipProps) => {
2✔
353
  const l10n = useL10n();
280✔
354
  const triggerState = useTooltipTriggerState({ delay: 0 });
280✔
355
  const triggerRef = useRef<HTMLSpanElement>(null);
280✔
356
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
280✔
357

358
  const { tooltipProps } = useTooltip({}, triggerState);
280✔
359
  return (
360
    <div className={styles["stat-wrapper"]}>
361
      <span
362
        ref={triggerRef}
363
        {...tooltipTrigger.triggerProps}
364
        className={`${styles.stat} ${styles["blocked-stat"]}`}
365
      >
366
        {props.children}
367
      </span>
368
      {triggerState.isOpen && (
280✔
369
        <div
370
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
371
          className={styles.tooltip}
372
        >
373
          {l10n.getString("profile-blocked-copy-2")}
374
        </div>
375
      )}
376
    </div>
377
  );
378
};
379

380
const TrackersRemovedTooltip = (props: TooltipProps) => {
2✔
381
  const l10n = useL10n();
×
382
  const triggerState = useTooltipTriggerState({ delay: 0 });
×
383
  const triggerRef = useRef<HTMLSpanElement>(null);
×
384
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
×
385

386
  const { tooltipProps } = useTooltip({}, triggerState);
×
387
  return (
388
    <div className={styles["stat-wrapper"]}>
389
      <span
390
        ref={triggerRef}
391
        {...tooltipTrigger.triggerProps}
392
        className={`${styles.stat} ${styles["trackers-removed-stat"]}`}
393
      >
394
        {props.children}
395
      </span>
396
      {triggerState.isOpen && (
×
397
        <div
398
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
399
          className={styles.tooltip}
400
        >
401
          <p>{l10n.getString("profile-trackers-removed-tooltip-part1")}</p>
402
          <Localized
403
            id="profile-trackers-removed-tooltip-part2-2"
404
            elems={{
405
              b: <b />,
406
            }}
407
          >
408
            <p />
409
          </Localized>
410
        </div>
411
      )}
412
    </div>
413
  );
414
};
415

416
const RepliesTooltip = (props: TooltipProps) => {
2✔
417
  const l10n = useL10n();
88✔
418
  const triggerState = useTooltipTriggerState({ delay: 0 });
88✔
419
  const triggerRef = useRef<HTMLSpanElement>(null);
88✔
420
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
88✔
421

422
  const { tooltipProps } = useTooltip({}, triggerState);
88✔
423

424
  return (
425
    <div className={styles["stat-wrapper"]}>
426
      <span
427
        ref={triggerRef}
428
        {...tooltipTrigger.triggerProps}
429
        className={`${styles.stat} ${styles["replies-stat"]}`}
430
      >
431
        {props.children}
432
      </span>
433
      {triggerState.isOpen && (
88✔
434
        <div
435
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
436
          className={styles.tooltip}
437
        >
438
          {l10n.getString("profile-replies-tooltip")}
439
        </div>
440
      )}
441
    </div>
442
  );
443
};
444

445
type BlockLevelLabelProps = {
446
  alias: AliasData;
447
};
448
const BlockLevelLabel = (props: BlockLevelLabelProps) => {
2✔
449
  const l10n = useL10n();
140✔
450
  if (props.alias.enabled === false) {
140!
451
    return (
452
      <b
453
        className={`${styles["block-level-label"]} ${styles["block-level-all-label"]}`}
454
      >
455
        {l10n.getString("profile-promo-email-blocking-label-none")}
456
      </b>
457
    );
458
  }
459

460
  if (props.alias.block_list_emails === true) {
140!
461
    return (
462
      <b
463
        className={`${styles["block-level-label"]} ${styles["block-level-promotional-label"]}`}
464
      >
465
        {l10n.getString("profile-promo-email-blocking-label-promotionals")}
466
      </b>
467
    );
468
  }
469

470
  return null;
140✔
471
};
472

473
type TrackerRemovalIndicatorProps = {
474
  alias: AliasData;
475
  profile: ProfileData;
476
};
477
const TrackerRemovalIndicator = (props: TrackerRemovalIndicatorProps) => {
2✔
478
  const l10n = useL10n();
140✔
479
  const tooltipState = useTooltipTriggerState({ delay: 0 });
140✔
480
  const triggerRef = useRef<HTMLButtonElement>(null);
140✔
481
  const { triggerProps, tooltipProps: triggerTooltipProps } = useTooltipTrigger(
140✔
482
    {},
483
    tooltipState,
484
    triggerRef,
485
  );
486
  const { tooltipProps } = useTooltip(triggerTooltipProps, tooltipState);
140✔
487

488
  if (!isBlockingLevelOneTrackers(props.alias, props.profile)) {
140✔
489
    return null;
140✔
490
  }
491

492
  return (
493
    <span className={styles["tracker-removal-indicator-wrapper"]}>
494
      <button
495
        ref={triggerRef}
496
        {...triggerProps}
497
        aria-label={l10n.getString("profile-indicator-tracker-removal-alt")}
498
      >
499
        <HideIcon alt="" />
500
      </button>
501
      {tooltipState.isOpen && (
×
502
        <span
503
          className={styles["tracker-removal-indicator-tooltip"]}
504
          {...mergeProps(triggerTooltipProps, tooltipProps)}
505
        >
506
          {l10n.getString("profile-indicator-tracker-removal-tooltip")}
507
        </span>
508
      )}
509
    </span>
510
  );
511
};
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