• 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

88.37
/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 AddonIllustration from "./images/banner-addon.svg";
1✔
6
import {
7
  isUsingFirefox,
8
  supportsFirefoxExtension,
9
} from "../../functions/userAgent";
1✔
10
import { ProfileData } from "../../hooks/api/profile";
11
import { UserData } from "../../hooks/api/user";
12
import { RuntimeData } from "../../hooks/api/types";
13
import { Banner } from "../Banner";
1✔
14
import Image from "../Image";
1✔
15
import { renderDate } from "../../functions/renderDate";
1✔
16
import { SubdomainPicker } from "./SubdomainPicker";
1✔
17
import { useMinViewportWidth } from "../../hooks/mediaQuery";
1✔
18
import { AliasData } from "../../hooks/api/aliases";
19
import { useGaEvent } from "../../hooks/gaEvent";
1✔
20
import { useL10n } from "../../hooks/l10n";
1✔
21

22
export type Props = {
23
  profile: ProfileData;
24
  user: UserData;
25
  onCreateSubdomain: (chosenSubdomain: string) => Promise<void>;
26
  runtimeData?: RuntimeData;
27
  aliases: AliasData[];
28
};
29

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

46
  const bounceStatus = props.profile.bounce_status;
45✔
47
  if (bounceStatus[0]) {
45!
48
    banners.push(
×
49
      <BounceBanner
50
        key="bounce-banner"
51
        email={props.user.email}
52
        profile={props.profile}
53
      />,
54
    );
55
  }
56

57
  banners.push(
45✔
58
    <SubdomainPicker
59
      key="subdomain-picker"
60
      profile={props.profile}
61
      onCreate={props.onCreateSubdomain}
62
    />,
63
  );
64

65
  // Don't show the "Get Firefox" banner if we have an extension available,
66
  // to avoid banner overload:
67
  if (!isUsingFirefox() || !isLargeScreen) {
45✔
68
    banners.push(<NoFirefoxBanner key="firefox-banner" />);
42✔
69
  }
70

71
  if (
45✔
72
    supportsFirefoxExtension() &&
52✔
73
    isLargeScreen &&
74
    // This identifies mock data used for demonstration purposes.
75
    // See /frontend/src/apiMocks/mockData.ts:
76
    props.profile.api_token !== "demo"
77
  ) {
78
    // This pushes a banner promoting the add-on - detecting the add-on
79
    // and determining whether to show it based on that is a bit slow,
80
    // so we'll just let the add-on hide it:
81
    banners.push(
3✔
82
      <NoAddonBanner profileData={props.profile} key="addon-banner" />,
83
    );
84
  }
85

86
  return <div className={styles["profile-banners"]}>{banners}</div>;
87
};
88

89
type BounceBannerProps = {
90
  email: string;
91
  profile: ProfileData;
92
};
93
const BounceBanner = (props: BounceBannerProps) => {
1✔
94
  const l10n = useL10n();
×
95

96
  return (
97
    <Banner type="warning" title={l10n.getString("banner-bounced-headline")}>
98
      {l10n.getFragment("banner-bounced-copy", {
99
        vars: {
100
          username: props.email,
101
          bounce_type: props.profile.bounce_status[1],
102
          date: renderDate(props.profile.next_email_try, l10n),
103
        },
104
        elems: {
105
          em: <em />,
106
        },
107
      })}
108
    </Banner>
109
  );
110
};
111

112
const NoFirefoxBanner = () => {
1✔
113
  const l10n = useL10n();
42✔
114
  const gaEvent = useGaEvent();
42✔
115

116
  return (
117
    <Banner
118
      type="promo"
119
      title={l10n.getString("banner-download-firefox-headline")}
120
      illustration={{
121
        img: <Image src={FirefoxLogo} alt="" width={60} height={60} />,
122
      }}
123
      cta={{
124
        target:
125
          "https://www.mozilla.org/firefox/new/?utm_source=fx-relay&utm_medium=banner&utm_campaign=download-fx",
126
        content: l10n.getString("banner-download-firefox-cta"),
127
        size: "large",
128
        gaViewPing: {
129
          category: "Download Firefox",
130
          label: "profile-banner-download-firefox",
131
        },
132
        onClick: () => {
133
          gaEvent({
×
134
            category: "Download Firefox",
135
            action: "Engage",
136
            label: "profile-banner-download-firefox",
137
          });
138
        },
139
      }}
140
    >
141
      <p>{l10n.getString("banner-download-firefox-copy-2")}</p>
142
    </Banner>
143
  );
144
};
145

146
type NoAddonBannerProps = {
147
  profileData: ProfileData;
148
};
149

150
const NoAddonBanner = (props: NoAddonBannerProps) => {
1✔
151
  const l10n = useL10n();
3✔
152
  const gaEvent = useGaEvent();
3✔
153

154
  return (
155
    <Banner
156
      type="promo"
157
      title={l10n.getString("banner-download-install-extension-headline")}
158
      illustration={{
159
        img: <Image src={AddonIllustration} alt="" width={60} height={60} />,
160
      }}
161
      cta={{
162
        target:
163
          "https://addons.mozilla.org/firefox/addon/private-relay/?utm_source=fx-relay&utm_medium=banner&utm_campaign=install-addon",
164
        content: l10n.getString("banner-download-install-extension-cta"),
165
        size: "large",
166
        gaViewPing: {
167
          category: "Download Extension",
168
          label: "profile-banner-download-firefox-extension",
169
        },
170
        onClick: () => {
171
          gaEvent({
×
172
            category: "Download Firefox",
173
            action: "Engage",
174
            label: "profile-banner-download-firefox-extension",
175
          });
176
        },
177
      }}
178
      dismissal={{
179
        key: `firefox-extension-banner-${props.profileData.id}`,
180
      }}
181
      hiddenWithAddon={true}
182
    >
183
      <p>{l10n.getString("banner-download-install-extension-copy-2")}</p>
184
    </Banner>
185
  );
186
};
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