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

mozilla / fx-private-relay / e6596aaf-157d-4581-acf2-f4e4f97a1130

21 Sep 2023 08:17PM CUT coverage: 74.433% (-0.1%) from 74.552%
e6596aaf-157d-4581-acf2-f4e4f97a1130

push

circleci

web-flow
Merge pull request #3907 from mozilla/fix-ga-events-MPP-3422

Fix ga events mpp 3422

1895 of 2761 branches covered (0.0%)

Branch coverage included in aggregate %.

21 of 21 new or added lines in 5 files covered. (100.0%)

5977 of 7815 relevant lines covered (76.48%)

18.39 hits per line

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

77.63
/frontend/src/components/dashboard/ProfileBanners.tsx
1
import { ReactNode } from "react";
2
import { event as gaEvent } from "react-ga";
1✔
3

4
import Image from "next/image";
1✔
5
import styles from "./ProfileBanners.module.scss";
1✔
6
import FirefoxLogo from "./images/fx-logo.svg";
1✔
7
import BundleLogo from "./images/vpn-and-relay-logo.svg";
1✔
8
import AddonIllustration from "./images/banner-addon.svg";
1✔
9
import RelayLogo from "./images/placeholder-logo.svg";
1✔
10
import {
11
  getBundlePrice,
12
  getPeriodicalPremiumPrice,
13
  isBundleAvailableInCountry,
14
  isPeriodicalPremiumAvailableInCountry,
15
  isPhonesAvailableInCountry,
16
  RuntimeDataWithBundleAvailable,
17
  RuntimeDataWithPeriodicalPremiumAvailable,
18
} from "../../functions/getPlan";
1✔
19
import {
20
  isUsingFirefox,
21
  supportsChromeExtension,
22
  supportsFirefoxExtension,
23
} from "../../functions/userAgent";
1✔
24
import { ProfileData } from "../../hooks/api/profile";
25
import { UserData } from "../../hooks/api/user";
26
import { RuntimeData } from "../../../src/hooks/api/runtimeData";
27
import { Banner } from "../Banner";
1✔
28
import { renderDate } from "../../functions/renderDate";
1✔
29
import { SubdomainPicker } from "./SubdomainPicker";
1✔
30
import { useMinViewportWidth } from "../../hooks/mediaQuery";
1✔
31
import { AliasData } from "../../hooks/api/aliases";
32
import { PremiumPromoBanners } from "./PremiumPromoBanners";
1✔
33
import { useL10n } from "../../hooks/l10n";
1✔
34

35
export type Props = {
36
  profile: ProfileData;
37
  user: UserData;
38
  onCreateSubdomain: (chosenSubdomain: string) => Promise<void>;
39
  runtimeData?: RuntimeData;
40
  aliases: AliasData[];
41
};
42

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

59
  const bounceStatus = props.profile.bounce_status;
40✔
60
  if (bounceStatus[0]) {
40!
61
    banners.push(
×
62
      <BounceBanner
63
        key="bounce-banner"
64
        email={props.user.email}
65
        profile={props.profile}
66
      />,
67
    );
68
  }
69

70
  if (isBundleAvailableInCountry(props.runtimeData) && !props.profile.has_vpn) {
40!
71
    banners.push(
×
72
      <BundlePromoBanner
73
        key="bundle-promo"
74
        runtimeData={props.runtimeData}
75
        profileData={props.profile}
76
      />,
77
    );
78
  }
79

80
  banners.push(
40✔
81
    <SubdomainPicker
82
      key="subdomain-picker"
83
      profile={props.profile}
84
      onCreate={props.onCreateSubdomain}
85
    />,
86
  );
87

88
  // Don't show the "Get Firefox" banner if we have an extension available,
89
  // to avoid banner overload:
90
  if (!isUsingFirefox() && (!supportsChromeExtension() || !isLargeScreen)) {
40✔
91
    banners.push(<NoFirefoxBanner key="firefox-banner" />);
35✔
92
  }
93

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

107
  if (supportsChromeExtension() && isLargeScreen) {
40✔
108
    // This pushes a banner promoting the add-on - detecting the add-on
109
    // and determining whether to show it based on that is a bit slow,
110
    // so we'll just let the add-on hide it:
111
    banners.push(<NoChromeExtensionBanner key="chrome-extension-banner" />);
1✔
112
  }
113

114
  if (
40✔
115
    !props.profile.has_premium &&
82✔
116
    isPeriodicalPremiumAvailableInCountry(props.runtimeData) &&
117
    props.aliases.length > 0
118
  ) {
119
    // Only show updated Premium Banners to users in US/CAN
120
    {
121
      isPhonesAvailableInCountry(props.runtimeData)
19!
122
        ? banners.push(<PremiumPromoBanners showFirstPremiumBanner={true} />)
123
        : banners.push(
124
            <LoyalistPremiumBanner
125
              key="premium-banner"
126
              runtimeData={props.runtimeData}
127
            />,
128
            // <NoPremiumBanner key="premium-banner" runtimeData={props.runtimeData} />
129
          );
130
    }
131
  }
132

133
  return <div className={styles["profile-banners"]}>{banners}</div>;
134
};
135

136
type BounceBannerProps = {
137
  email: string;
138
  profile: ProfileData;
139
};
140
const BounceBanner = (props: BounceBannerProps) => {
1✔
141
  const l10n = useL10n();
×
142

143
  return (
144
    <Banner type="warning" title={l10n.getString("banner-bounced-headline")}>
145
      {l10n.getFragment("banner-bounced-copy", {
146
        vars: {
147
          username: props.email,
148
          bounce_type: props.profile.bounce_status[1],
149
          date: renderDate(props.profile.next_email_try, l10n),
150
        },
151
        elems: {
152
          em: <em />,
153
        },
154
      })}
155
    </Banner>
156
  );
157
};
158

159
const NoFirefoxBanner = () => {
1✔
160
  const l10n = useL10n();
35✔
161

162
  return (
163
    <Banner
164
      type="promo"
165
      title={l10n.getString("banner-download-firefox-headline")}
166
      illustration={{
167
        img: <Image src={FirefoxLogo} alt="" width={60} height={60} />,
168
      }}
169
      cta={{
170
        target:
171
          "https://www.mozilla.org/firefox/new/?utm_source=fx-relay&utm_medium=banner&utm_campaign=download-fx",
172
        content: l10n.getString("banner-download-firefox-cta"),
173
        size: "large",
174
        gaViewPing: {
175
          category: "Download Firefox",
176
          label: "profile-banner-download-firefox",
177
        },
178
        onClick: () => {
179
          gaEvent({
×
180
            category: "Download Firefox",
181
            action: "Engage",
182
            label: "profile-banner-download-firefox",
183
          });
184
        },
185
      }}
186
    >
187
      <p>{l10n.getString("banner-download-firefox-copy-2")}</p>
188
    </Banner>
189
  );
190
};
191

192
const NoAddonBanner = () => {
1✔
193
  const l10n = useL10n();
3✔
194

195
  return (
196
    <Banner
197
      type="promo"
198
      title={l10n.getString("banner-download-install-extension-headline")}
199
      illustration={{
200
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
201
      }}
202
      cta={{
203
        target:
204
          "https://addons.mozilla.org/firefox/addon/private-relay/?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
205
        content: l10n.getString("banner-download-install-extension-cta"),
206
        size: "large",
207
        gaViewPing: {
208
          category: "Download Extension",
209
          label: "profile-banner-download-firefox-extension",
210
        },
211
        onClick: () => {
212
          gaEvent({
×
213
            category: "Download Firefox",
214
            action: "Engage",
215
            label: "profile-banner-download-firefox-extension",
216
          });
217
        },
218
      }}
219
      hiddenWithAddon={true}
220
    >
221
      <p>{l10n.getString("banner-download-install-extension-copy-2")}</p>
222
    </Banner>
223
  );
224
};
225

226
const NoChromeExtensionBanner = () => {
1✔
227
  const l10n = useL10n();
1✔
228

229
  return (
230
    <Banner
231
      type="promo"
232
      title={l10n.getString(
233
        "banner-download-install-chrome-extension-headline",
234
      )}
235
      illustration={{
236
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
237
      }}
238
      cta={{
239
        target:
240
          "https://chrome.google.com/webstore/detail/firefox-relay/lknpoadjjkjcmjhbjpcljdednccbldeb?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
241
        content: l10n.getString("banner-download-install-chrome-extension-cta"),
242
        size: "large",
243
        gaViewPing: {
244
          category: "Download Extension",
245
          label: "profile-banner-download-chrome-extension",
246
        },
247
        onClick: () => {
248
          gaEvent({
×
249
            category: "Download Firefox",
250
            action: "Engage",
251
            label: "profile-banner-download-chrome-extension",
252
          });
253
        },
254
      }}
255
      hiddenWithAddon={true}
256
    >
257
      <p>{l10n.getString("banner-download-install-chrome-extension-copy-2")}</p>
258
    </Banner>
259
  );
260
};
261

262
type NoPremiumBannerProps = {
263
  runtimeData: RuntimeDataWithPeriodicalPremiumAvailable;
264
};
265

266
type BundleBannerProps = {
267
  runtimeData: RuntimeDataWithBundleAvailable;
268
  profileData: ProfileData;
269
};
270

271
// Unused but left in for when we no longer want to use <LoyalistPremiumBanner>
272
// eslint-disable-next-line @typescript-eslint/no-unused-vars
273
const NoPremiumBanner = (props: NoPremiumBannerProps) => {
1✔
274
  const l10n = useL10n();
×
275

276
  return (
×
277
    <Banner
278
      key="premium-banner"
279
      type="promo"
280
      title={l10n.getString("banner-upgrade-headline")}
281
      illustration={{
282
        img: <Image src={RelayLogo} alt="" width={60} height={60} />,
283
      }}
284
      cta={{
285
        target: "/premium#pricing",
286
        content: l10n.getString("banner-upgrade-cta"),
287
        gaViewPing: {
288
          category: "Purchase Button",
289
          label: "profile-banner-promo",
290
        },
291
        onClick: () => {
292
          gaEvent({
×
293
            category: "Purchase Button",
294
            action: "Engage",
295
            label: "profile-banner-promo",
296
          });
297
        },
298
      }}
299
    >
300
      <p>{l10n.getString("banner-upgrade-copy-2")}</p>
301
    </Banner>
302
  );
303
};
304

305
const LoyalistPremiumBanner = (props: NoPremiumBannerProps) => {
1✔
306
  const l10n = useL10n();
19✔
307

308
  return (
19✔
309
    <Banner
310
      key="premium-banner"
311
      type="promo"
312
      title={l10n.getString("banner-upgrade-loyalist-headline-2")}
313
      illustration={{
314
        img: <Image src={FirefoxLogo} alt="" width={60} height={60} />,
315
      }}
316
      cta={{
317
        size: "large",
318
        target: "/premium#pricing",
319
        content: l10n.getString("banner-upgrade-loyalist-cta"),
320
        gaViewPing: {
321
          category: "Purchase Button",
322
          label: "profile-banner-loyalist-promo",
323
        },
324
        onClick: () => {
325
          gaEvent({
×
326
            category: "Purchase Button",
327
            action: "Engage",
328
            label: "profile-banner-loyalist-promo",
329
          });
330
        },
331
      }}
332
    >
333
      <p>
334
        {l10n.getString("banner-upgrade-loyalist-copy-2", {
335
          monthly_price: getPeriodicalPremiumPrice(
336
            props.runtimeData,
337
            "yearly",
338
            l10n,
339
          ),
340
        })}
341
      </p>
342
    </Banner>
343
  );
344
};
345

346
const BundlePromoBanner = (props: BundleBannerProps) => {
1✔
347
  const l10n = useL10n();
×
348

349
  return (
×
350
    <Banner
351
      key="bundle-banner"
352
      type="promo"
353
      illustration={{
354
        img: (
355
          <Image
356
            src={BundleLogo}
357
            alt=""
358
            width={120}
359
            height={60}
360
            className={styles["bundle-logo"]}
361
          />
362
        ),
363
      }}
364
      title={l10n.getString("bundle-banner-dashboard-header")}
365
      cta={{
366
        target: "/premium#pricing",
367
        size: "large",
368
        gaViewPing: {
369
          category: "Purchase Bundle button",
370
          label: "profile-banner-bundle-promo",
371
        },
372
        onClick: () => {
373
          gaEvent({
×
374
            category: "Purchase Bundle button",
375
            action: "Engage",
376
            label: "profile-banner-bundle-promo",
377
          });
378
        },
379
        content: l10n.getString("bundle-banner-dashboard-upgrade-cta"),
380
      }}
381
      dismissal={{
382
        key: `bundle-promo-banner-${props.profileData.id}`,
383
      }}
384
    >
385
      <p>
386
        {l10n.getString("bundle-banner-dashboard-body", {
387
          savings: "40%",
388
          monthly_price: getBundlePrice(props.runtimeData, l10n),
389
        })}
390
      </p>
391
    </Banner>
392
  );
393
};
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