• 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

65.96
/frontend/src/components/dashboard/EmailForwardingModal.tsx
1
import {
2
  FormEventHandler,
3
  ReactElement,
4
  ReactNode,
5
  useRef,
6
  useState,
7
} from "react";
2✔
8
import {
9
  OverlayContainer,
10
  FocusScope,
11
  useDialog,
12
  useModal,
13
  useOverlay,
14
  usePreventScroll,
15
  AriaOverlayProps,
16
} from "react-aria";
2✔
17
import styles from "./EmailForwardingModal.module.scss";
2✔
18
import { useL10n } from "../../hooks/l10n";
2✔
19
import ForwardEmail from "./images/free-onboarding-forwarding-email.svg";
2✔
20
import ForwardedEmail from "./images/free-onboarding-forwarding-congratulations.svg";
2✔
21
import { Button } from "../Button";
2✔
22
import { StaticImport } from "next/dist/shared/lib/get-img-props";
23
import { CloseIcon } from "../Icons";
2✔
24
import Image from "../Image";
2✔
25
import { aliasEmailTest } from "../../hooks/api/aliases";
2✔
26
import { useGaEvent } from "../../hooks/gaEvent";
2✔
27

28
export type Props = {
29
  isOpen: boolean;
30
  isSet: boolean;
31
  onClose: () => void;
32
  onConfirm: () => void;
33
  onComplete: () => void;
34
};
35

36
/**
37
 * Modal in which the user can provide an email mask to test email forwarding
38
 */
39
export const EmailForwardingModal = (props: Props) => {
2✔
40
  if (!props.isOpen) return null;
2✔
41

42
  return !props.isSet ? (
43
    <OverlayContainer>
1✔
44
      <ConfirmModal {...props} />
45
    </OverlayContainer>
46
  ) : (
47
    <OverlayContainer>
48
      <SuccessModal {...props} />
49
    </OverlayContainer>
50
  );
51
};
52

53
const ConfirmModal = (props: Props) => {
2✔
54
  const l10n = useL10n();
1✔
55
  const [inputValue, setInputValue] = useState("");
1✔
56
  const formRef = useRef<HTMLFormElement>(null);
1✔
57
  const inputRef = useRef<HTMLInputElement>(null);
1✔
58
  const gaEvent = useGaEvent();
1✔
59

60
  const onSubmit: FormEventHandler = async (event) => {
1✔
61
    event.preventDefault();
×
62

63
    const isValid = formRef.current?.reportValidity();
×
64

65
    if (isValid) {
×
66
      inputRef.current?.blur();
×
67

68
      const response = await aliasEmailTest(inputValue);
×
69

70
      if (response) {
×
71
        props.onConfirm();
×
72

73
        gaEvent({
×
74
          category: "Free Onboarding",
75
          action: "Engage",
76
          label: "onboarding-step-2-forwarding-test",
77
          value: 1,
78
        });
79
      }
80
    }
81
  };
82

83
  return (
84
    <PickerDialog
85
      headline={l10n.getString(
86
        "profile-free-onboarding-copy-mask-try-out-email-forwarding",
87
      )}
88
      onClose={() => props.onClose()}
×
89
      image={ForwardEmail}
90
      isOpen={props.isOpen}
91
      isDismissable={true}
92
    >
93
      <div className={styles["paste-email-mask-container"]}>
94
        <p className={styles["modal-title"]}>
95
          {l10n.getString("profile-free-onboarding-copy-mask-paste-the-email")}
96
        </p>
97
        <form
98
          onSubmit={onSubmit}
99
          ref={formRef}
100
          className={styles["label-form"]}
101
        >
102
          <input
103
            value={inputValue}
104
            onChange={(e) => setInputValue(e.target.value)}
×
105
            aria-label={l10n.getString("profile-label-edit-2")}
106
            ref={inputRef}
107
            className={styles["label-input"]}
108
            placeholder={l10n.getString(
109
              "profile-free-onboarding-copy-mask-placeholder-relay-email-mask",
110
            )}
111
            type="email"
112
          />
113
        </form>
114
      </div>
115
      <Button
116
        className={styles["generate-new-mask"]}
117
        type="submit"
118
        onClick={onSubmit}
119
      >
120
        {l10n.getString("profile-free-onboarding-copy-mask-send-email")}
121
      </Button>
122

123
      <button className={styles["nevermind-link"]} onClick={props.onClose}>
124
        {l10n.getString("profile-free-onboarding-copy-mask-nevermind")}
125
      </button>
126
    </PickerDialog>
127
  );
128
};
129

130
const SuccessModal = (props: Props) => {
2✔
131
  const l10n = useL10n();
×
132

133
  return (
134
    <PickerDialog
135
      headline={l10n.getString("profile-free-onboarding-copy-mask-check-inbox")}
136
      onClose={() => props.onClose()}
×
137
      image={ForwardedEmail}
138
      isOpen={props.isOpen}
139
      isDismissable={true}
140
    >
141
      <div className={styles["paste-email-mask-container"]}>
142
        <p className={styles["modal-title-success"]}>
143
          {l10n.getString("profile-free-onboarding-copy-mask-email-this-mask")}
144
        </p>
145
      </div>
146
      <Button
147
        className={styles["generate-new-mask"]}
148
        onClick={props.onComplete}
149
      >
150
        {l10n.getString("profile-free-onboarding-copy-mask-continue")}
151
      </Button>
152
    </PickerDialog>
153
  );
154
};
155

156
type PickerDialogProps = {
157
  title?: string | ReactElement;
158
  headline?: string;
159
  image: string | StaticImport;
160
  isOpen: boolean;
161
  onClose?: () => void;
162
  children?: ReactNode;
163
};
164

165
const PickerDialog = (props: PickerDialogProps & AriaOverlayProps) => {
2✔
166
  const wrapperRef = useRef<HTMLDivElement>(null);
1✔
167
  const l10n = useL10n();
1✔
168
  const { overlayProps, underlayProps } = useOverlay(props, wrapperRef);
1✔
169
  usePreventScroll();
1✔
170
  const { modalProps } = useModal();
1✔
171
  const { dialogProps, titleProps } = useDialog({}, wrapperRef);
1✔
172

173
  return (
174
    <div className={styles.underlay} {...underlayProps}>
175
      <FocusScope contain restoreFocus autoFocus>
176
        <div
177
          className={styles["dialog-wrapper"]}
178
          {...overlayProps}
179
          {...dialogProps}
180
          {...modalProps}
181
          ref={wrapperRef}
182
        >
183
          <CloseIcon
184
            className={styles["close-icon"]}
185
            alt={l10n.getString("profile-free-onboarding-close-modal")}
186
            onClick={props.onClose}
187
          />
188
          <div className={styles.hero}>
189
            <p className={styles.headline}>{props.headline}</p>
190
            <Image src={props.image} alt="" />
191
            <p {...titleProps}>{props.title}</p>
192
            {props.children}
193
          </div>
194
        </div>
195
      </FocusScope>
196
    </div>
197
  );
198
};
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