• 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

39.29
/frontend/src/components/dashboard/aliases/AddressPickerModal.tsx
1
import {
2
  ChangeEventHandler,
3
  FocusEventHandler,
4
  FormEventHandler,
5
  ReactElement,
6
  ReactNode,
7
  useRef,
8
  useState,
9
} from "react";
5✔
10
import Link from "next/link";
5✔
11
import { ReactLocalization } from "@fluent/react";
12
import {
13
  OverlayContainer,
14
  FocusScope,
15
  useDialog,
16
  useModal,
17
  useOverlay,
18
  useButton,
19
  AriaOverlayProps,
20
} from "react-aria";
5✔
21
import styles from "./AddressPickerModal.module.scss";
5✔
22
import { InfoBulbIcon } from "../../Icons";
5✔
23
import { Button } from "../../Button";
5✔
24
import { InfoTooltip } from "../../InfoTooltip";
5✔
25
import { useL10n } from "../../../hooks/l10n";
5✔
26

27
export type Props = {
28
  isOpen: boolean;
29
  onClose: () => void;
30
  onPick: (address: string, settings: { blockPromotionals: boolean }) => void;
31
  subdomain: string;
32
};
33

34
/**
35
 * Modal in which the user can create a new custom alias,
36
 * while also being educated on why they don't need to do that.
37
 */
38
export const AddressPickerModal = (props: Props) => {
5✔
39
  const l10n = useL10n();
×
40
  const [address, setAddress] = useState("");
×
41
  const [promotionalsBlocking, setPromotionalsBlocking] = useState(false);
×
42
  const cancelButtonRef = useRef<HTMLButtonElement>(null);
×
43
  const cancelButton = useButton(
×
44
    { onPress: () => props.onClose() },
×
45
    cancelButtonRef,
46
  );
47
  /**
48
   * We need a Ref to the address picker field so that we can call the browser's
49
   * native validation APIs.
50
   * See:
51
   * - https://beta.reactjs.org/learn/manipulating-the-dom-with-refs
52
   * - https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setCustomValidity
53
   */
54
  const addressFieldRef = useRef<HTMLInputElement>(null);
×
55

56
  const onChange: ChangeEventHandler<HTMLInputElement> = (event) => {
×
57
    setAddress(event.target.value);
×
58
  };
59
  const onFocus: FocusEventHandler<HTMLInputElement> = () => {
×
60
    addressFieldRef.current?.setCustomValidity("");
×
61
  };
62
  const onBlur: FocusEventHandler<HTMLInputElement> = () => {
×
63
    addressFieldRef.current?.setCustomValidity(
×
64
      getAddressValidationMessage(address, l10n) ?? "",
×
65
    );
66
    addressFieldRef.current?.reportValidity();
×
67
  };
68

69
  const onSubmit: FormEventHandler = (event) => {
×
70
    event.preventDefault();
×
71

72
    const validationMessage = getAddressValidationMessage(address, l10n);
×
73
    if (validationMessage) {
×
74
      addressFieldRef.current?.setCustomValidity(validationMessage);
×
75
      addressFieldRef.current?.reportValidity();
×
76
      return;
×
77
    }
78
    props.onPick(address.toLowerCase(), {
×
79
      blockPromotionals: promotionalsBlocking,
80
    });
81
  };
82

83
  return (
84
    <>
85
      <OverlayContainer>
86
        <PickerDialog
87
          title={l10n.getString("modal-custom-alias-picker-heading-2")}
88
          onClose={() => props.onClose()}
×
89
          isOpen={props.isOpen}
90
          isDismissable={true}
91
        >
92
          <form onSubmit={onSubmit}>
93
            <div className={styles["form-wrapper"]}>
94
              <div className={styles.prefix}>
95
                <label htmlFor="address">
96
                  {l10n.getString(
97
                    "modal-custom-alias-picker-form-prefix-label-3",
98
                  )}
99
                </label>
100
                <input
101
                  id="address"
102
                  type="text"
103
                  value={address}
104
                  onChange={onChange}
105
                  onFocus={onFocus}
106
                  onBlur={onBlur}
107
                  ref={addressFieldRef}
108
                  placeholder={l10n.getString(
109
                    "modal-custom-alias-picker-form-prefix-placeholder-2",
110
                  )}
111
                  autoCapitalize="none"
112
                />
113
              </div>
114
            </div>
115
            <div className={styles["promotionals-blocking-control"]}>
116
              <input
117
                type="checkbox"
118
                id="promotionalsBlocking"
119
                onChange={(event) =>
120
                  setPromotionalsBlocking(event.target.checked)
×
121
                }
122
              />
123
              <label htmlFor="promotionalsBlocking">
124
                {l10n.getString(
125
                  "popover-custom-alias-explainer-promotional-block-checkbox",
126
                )}
127
              </label>
128
              <InfoTooltip
129
                alt={l10n.getString(
130
                  "popover-custom-alias-explainer-promotional-block-tooltip-trigger",
131
                )}
132
                iconColor="black"
133
              >
134
                <h3>
135
                  {l10n.getString(
136
                    "popover-custom-alias-explainer-promotional-block-checkbox",
137
                  )}
138
                </h3>
139
                <p className={styles["promotionals-blocking-description"]}>
140
                  {l10n.getString(
141
                    "popover-custom-alias-explainer-promotional-block-tooltip-2",
142
                  )}
143
                  <Link href="/faq#faq-promotional-email-blocking">
144
                    {l10n.getString("banner-label-data-notification-body-cta")}
145
                  </Link>
146
                </p>
147
              </InfoTooltip>
148
            </div>
149
            <div className={styles.tip}>
150
              <span className={styles["tip-icon"]}>
151
                <InfoBulbIcon alt="" />
152
              </span>
153
              <p>{l10n.getString("modal-custom-alias-picker-tip")}</p>
154
            </div>
155
            <hr />
156
            <div className={styles.buttons}>
157
              <button
158
                {...cancelButton.buttonProps}
159
                ref={cancelButtonRef}
160
                className={styles["cancel-button"]}
161
              >
162
                {l10n.getString("profile-label-cancel")}
163
              </button>
164
              <Button type="submit" disabled={address.length === 0}>
165
                {l10n.getString(
166
                  "modal-custom-alias-picker-form-submit-label-2",
167
                )}
168
              </Button>
169
            </div>
170
          </form>
171
        </PickerDialog>
172
      </OverlayContainer>
173
    </>
174
  );
175
};
176

177
type PickerDialogProps = {
178
  title: string | ReactElement;
179
  children: ReactNode;
180
  isOpen: boolean;
181
  onClose?: () => void;
182
};
183
const PickerDialog = (props: PickerDialogProps & AriaOverlayProps) => {
5✔
184
  const wrapperRef = useRef<HTMLDivElement>(null);
×
185
  const { overlayProps, underlayProps } = useOverlay(props, wrapperRef);
×
186
  const { modalProps } = useModal();
×
187
  const { dialogProps, titleProps } = useDialog({}, wrapperRef);
×
188

189
  return (
190
    <div className={styles.underlay} {...underlayProps}>
191
      <FocusScope contain restoreFocus autoFocus>
192
        <div
193
          className={styles["dialog-wrapper"]}
194
          {...overlayProps}
195
          {...dialogProps}
196
          {...modalProps}
197
          ref={wrapperRef}
198
        >
199
          <div className={styles.hero}>
200
            <h3 {...titleProps}>{props.title}</h3>
201
          </div>
202
          {props.children}
203
        </div>
204
      </FocusScope>
205
    </div>
206
  );
207
};
208

209
export function getAddressValidationMessage(
11✔
210
  address: string,
211
  l10n: ReactLocalization,
212
): null | string {
213
  if (address.length === 0) {
11!
214
    return null;
×
215
  }
216
  if (address.includes(" ")) {
11✔
217
    return l10n.getString(
2✔
218
      "modal-custom-alias-picker-form-prefix-spaces-warning",
219
    );
220
  }
221
  // Regular expression:
222
  //
223
  //   ^[a-z0-9]  Starts with a lowercase letter or number;
224
  //
225
  //   (...)?     followed by zero or one of:
226
  //
227
  //              [a-z0-9-.]{0,61} zero up to 61 lowercase letters, numbers, hyphens, or periods, and
228
  //              [a-z0-9]         a lowercase letter or number (but not a hyphen),
229
  //
230
  //   $          and nothing following that.
231
  //
232
  // All that combines to 1-63 lowercase characters, numbers, or hyphens,
233
  // but not starting or ending with a hyphen, aligned with the backend's
234
  // validation (`valid_address_pattern` in emails/models.py).
235
  if (!/^[a-z0-9]([a-z0-9-.]{0,61}[a-z0-9])?$/.test(address)) {
9✔
236
    return l10n.getString(
7✔
237
      "modal-custom-alias-picker-form-prefix-invalid-warning-2",
238
    );
239
  }
240
  return null;
2✔
241
}
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