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

mozilla / fx-private-relay / 3ce233ef-696e-43c5-8086-1f352694b276

28 May 2025 02:40PM UTC coverage: 85.366% (+0.01%) from 85.353%
3ce233ef-696e-43c5-8086-1f352694b276

Pull #5572

circleci

groovecoder
for MPP-3439: add IDNAEmailCleaner to clean email address domains with non-ASCII chars
Pull Request #5572: for MPP-3439: add IDNAEmailCleaner to clean email address domains with non-ASCII chars

2473 of 3619 branches covered (68.33%)

Branch coverage included in aggregate %.

73 of 74 new or added lines in 4 files covered. (98.65%)

9 existing lines in 1 file now uncovered.

17576 of 19867 relevant lines covered (88.47%)

9.57 hits per line

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

76.92
/frontend/src/components/dashboard/ProfileBanners.tsx
1
import { ReactNode } from "react";
2

3
import styles from "./ProfileBanners.module.scss";
1✔
4
import FirefoxLogo from "./images/fx-logo.svg";
1✔
5
import BundleLogo from "./images/vpn-and-relay-logo.svg";
1✔
6
import AddonIllustration from "./images/banner-addon.svg";
1✔
7
import {
8
  getBundlePrice,
9
  isBundleAvailableInCountry,
10
  RuntimeDataWithBundleAvailable,
11
} from "../../functions/getPlan";
1✔
12
import {
13
  isUsingFirefox,
14
  supportsFirefoxExtension,
15
} from "../../functions/userAgent";
1✔
16
import { ProfileData } from "../../hooks/api/profile";
17
import { UserData } from "../../hooks/api/user";
18
import { RuntimeData } from "../../../src/hooks/api/runtimeData";
19
import { Banner } from "../Banner";
1✔
20
import Image from "../Image";
1✔
21
import { renderDate } from "../../functions/renderDate";
1✔
22
import { SubdomainPicker } from "./SubdomainPicker";
1✔
23
import { useMinViewportWidth } from "../../hooks/mediaQuery";
1✔
24
import { AliasData } from "../../hooks/api/aliases";
25
import { useGaEvent } from "../../hooks/gaEvent";
1✔
26
import { useL10n } from "../../hooks/l10n";
1✔
27

28
export type Props = {
29
  profile: ProfileData;
30
  user: UserData;
31
  onCreateSubdomain: (chosenSubdomain: string) => Promise<void>;
32
  runtimeData?: RuntimeData;
33
  aliases: AliasData[];
34
};
35

36
/**
37
 * Displays relevant banners for on the user's profile page.
38
 *
39
 * Examples are:
40
 * - A banner to download Firefox if you're not on Firefox.
41
 * - A banner to download the add-on if you don't have it.
42
 * - A banner to upgrade to Premium if you don't have it but can purchase it.
43
 * - A banner to inform the user of emails that failed to be delivered.
44
 * - Anything else we might come up with in the future.
45
 *
46
 * See also {@link Banner}.
47
 */
48
export const ProfileBanners = (props: Props) => {
45✔
49
  const banners: ReactNode[] = [];
45✔
50
  const isLargeScreen = useMinViewportWidth("md");
45✔
51

52
  const bounceStatus = props.profile.bounce_status;
45✔
53
  if (bounceStatus[0]) {
45!
UNCOV
54
    banners.push(
×
55
      <BounceBanner
56
        key="bounce-banner"
57
        email={props.user.email}
58
        profile={props.profile}
59
      />,
60
    );
61
  }
62

63
  if (isBundleAvailableInCountry(props.runtimeData) && !props.profile.has_vpn) {
45!
UNCOV
64
    banners.push(
×
65
      <BundlePromoBanner
66
        key="bundle-promo"
67
        runtimeData={props.runtimeData}
68
        profileData={props.profile}
69
      />,
70
    );
71
  }
72

73
  banners.push(
45✔
74
    <SubdomainPicker
75
      key="subdomain-picker"
76
      profile={props.profile}
77
      onCreate={props.onCreateSubdomain}
78
    />,
79
  );
80

81
  // Don't show the "Get Firefox" banner if we have an extension available,
82
  // to avoid banner overload:
83
  if (!isUsingFirefox() || !isLargeScreen) {
45✔
84
    banners.push(<NoFirefoxBanner key="firefox-banner" />);
42✔
85
  }
86

87
  if (
45✔
88
    supportsFirefoxExtension() &&
52✔
89
    isLargeScreen &&
90
    // This identifies mock data used for demonstration purposes.
91
    // See /frontend/src/apiMocks/mockData.ts:
92
    props.profile.api_token !== "demo"
93
  ) {
94
    // This pushes a banner promoting the add-on - detecting the add-on
95
    // and determining whether to show it based on that is a bit slow,
96
    // so we'll just let the add-on hide it:
97
    banners.push(
3✔
98
      <NoAddonBanner profileData={props.profile} key="addon-banner" />,
99
    );
100
  }
101

102
  return <div className={styles["profile-banners"]}>{banners}</div>;
103
};
104

105
type BounceBannerProps = {
106
  email: string;
107
  profile: ProfileData;
108
};
109
const BounceBanner = (props: BounceBannerProps) => {
1✔
UNCOV
110
  const l10n = useL10n();
×
111

112
  return (
113
    <Banner type="warning" title={l10n.getString("banner-bounced-headline")}>
114
      {l10n.getFragment("banner-bounced-copy", {
115
        vars: {
116
          username: props.email,
117
          bounce_type: props.profile.bounce_status[1],
118
          date: renderDate(props.profile.next_email_try, l10n),
119
        },
120
        elems: {
121
          em: <em />,
122
        },
123
      })}
124
    </Banner>
125
  );
126
};
127

128
const NoFirefoxBanner = () => {
1✔
129
  const l10n = useL10n();
42✔
130
  const gaEvent = useGaEvent();
42✔
131

132
  return (
133
    <Banner
134
      type="promo"
135
      title={l10n.getString("banner-download-firefox-headline")}
136
      illustration={{
137
        img: <Image src={FirefoxLogo} alt="" width={60} height={60} />,
138
      }}
139
      cta={{
140
        target:
141
          "https://www.mozilla.org/firefox/new/?utm_source=fx-relay&utm_medium=banner&utm_campaign=download-fx",
142
        content: l10n.getString("banner-download-firefox-cta"),
143
        size: "large",
144
        gaViewPing: {
145
          category: "Download Firefox",
146
          label: "profile-banner-download-firefox",
147
        },
148
        onClick: () => {
UNCOV
149
          gaEvent({
×
150
            category: "Download Firefox",
151
            action: "Engage",
152
            label: "profile-banner-download-firefox",
153
          });
154
        },
155
      }}
156
    >
157
      <p>{l10n.getString("banner-download-firefox-copy-2")}</p>
158
    </Banner>
159
  );
160
};
161

162
type NoAddonBannerProps = {
163
  profileData: ProfileData;
164
};
165

166
const NoAddonBanner = (props: NoAddonBannerProps) => {
1✔
167
  const l10n = useL10n();
3✔
168
  const gaEvent = useGaEvent();
3✔
169

170
  return (
171
    <Banner
172
      type="promo"
173
      title={l10n.getString("banner-download-install-extension-headline")}
174
      illustration={{
175
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
176
      }}
177
      cta={{
178
        target:
179
          "https://addons.mozilla.org/firefox/addon/private-relay/?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
180
        content: l10n.getString("banner-download-install-extension-cta"),
181
        size: "large",
182
        gaViewPing: {
183
          category: "Download Extension",
184
          label: "profile-banner-download-firefox-extension",
185
        },
186
        onClick: () => {
UNCOV
187
          gaEvent({
×
188
            category: "Download Firefox",
189
            action: "Engage",
190
            label: "profile-banner-download-firefox-extension",
191
          });
192
        },
193
      }}
194
      dismissal={{
195
        key: `firefox-extension-banner-${props.profileData.id}`,
196
      }}
197
      hiddenWithAddon={true}
198
    >
199
      <p>{l10n.getString("banner-download-install-extension-copy-2")}</p>
200
    </Banner>
201
  );
202
};
203

204
type BundleBannerProps = {
205
  runtimeData: RuntimeDataWithBundleAvailable;
206
  profileData: ProfileData;
207
};
208

209
const BundlePromoBanner = (props: BundleBannerProps) => {
1✔
UNCOV
210
  const l10n = useL10n();
×
UNCOV
211
  const gaEvent = useGaEvent();
×
212

UNCOV
213
  return (
×
214
    <Banner
215
      key="bundle-banner"
216
      type="promo"
217
      illustration={{
218
        img: (
219
          <Image
220
            src={BundleLogo}
221
            alt=""
222
            width={120}
223
            height={60}
224
            className={styles["bundle-logo"]}
225
          />
226
        ),
227
      }}
228
      title={l10n.getString("bundle-banner-dashboard-header")}
229
      cta={{
230
        target: "/premium#pricing",
231
        size: "large",
232
        gaViewPing: {
233
          category: "Purchase Bundle button",
234
          label: "profile-banner-bundle-promo",
235
        },
236
        onClick: () => {
UNCOV
237
          gaEvent({
×
238
            category: "Purchase Bundle button",
239
            action: "Engage",
240
            label: "profile-banner-bundle-promo",
241
          });
242
        },
243
        content: l10n.getString("bundle-banner-dashboard-upgrade-cta"),
244
      }}
245
      dismissal={{
246
        key: `bundle-promo-banner-${props.profileData.id}`,
247
      }}
248
    >
249
      <p>
250
        {l10n.getString("bundle-banner-dashboard-body", {
251
          savings: "40%",
252
          monthly_price: getBundlePrice(props.runtimeData, l10n),
253
        })}
254
      </p>
255
    </Banner>
256
  );
257
};
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