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

mozilla / fx-private-relay / 74d01625-9661-4180-904e-0bd85eb6c11f

29 May 2025 12:03PM CUT coverage: 85.583% (+0.2%) from 85.353%
74d01625-9661-4180-904e-0bd85eb6c11f

push

circleci

web-flow
Merge pull request #5573 from mozilla/MPP-4155-new-megabundle-banner

MPP-4155 New Megabundle Banner Landing Page

2490 of 3629 branches covered (68.61%)

Branch coverage included in aggregate %.

60 of 62 new or added lines in 8 files covered. (96.77%)

19 existing lines in 6 files now uncovered.

17598 of 19843 relevant lines covered (88.69%)

9.97 hits per line

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

68.0
/frontend/src/components/landing/BundleBanner.tsx
1
import { FluentVariable } from "@fluent/bundle";
2
import { StaticImageData } from "next/image";
3
import {
4
  getBundlePrice,
5
  getBundleSubscribeLink,
6
  isBundleAvailableInCountry,
7
} from "../../functions/getPlan";
2✔
8
import { RuntimeData } from "../../hooks/api/runtimeData";
9
import { MaskIcon, PhoneIcon, VpnIcon } from "../Icons";
2✔
10
import styles from "./BundleBanner.module.scss";
2✔
11
import { LinkButton } from "../Button";
2✔
12
import Image from "../Image";
2✔
13
import womanInBanner400w from "./images/bundle-banner-woman-400w.png";
2✔
14
import womanInBanner768w from "./images/bundle-banner-woman-768w.png";
2✔
15
import bundleFloatOne from "./images/bundle-float-1.svg";
2✔
16
import bundleFloatTwo from "./images/bundle-float-2.svg";
2✔
17
import bundleFloatThree from "./images/bundle-float-3.svg";
2✔
18
import { trackPlanPurchaseStart } from "../../functions/trackPurchase";
2✔
19
import { useGaViewPing } from "../../hooks/gaViewPing";
2✔
20
import { useGaEvent } from "../../hooks/gaEvent";
2✔
21
import { useL10n } from "../../hooks/l10n";
2✔
22
import { Localized } from "../Localized";
2✔
23

24
export type Props = {
25
  runtimeData: RuntimeData;
26
};
27

28
type FloatingFeaturesProps = {
29
  icon: StaticImageData;
30
  text: string;
31
  position: string;
32
  vars?: Record<string, FluentVariable>;
33
};
34

35
const FloatingFeatures = (props: FloatingFeaturesProps) => {
2✔
UNCOV
36
  const l10n = useL10n();
×
37

38
  const text = props.vars ? (
39
    <Localized id={props.text} vars={props.vars}>
×
40
      <span className={styles["float-features-text"]} />
41
    </Localized>
42
  ) : (
43
    <span className={styles["float-features-text"]}>
44
      {l10n.getString(props.text)}
45
    </span>
46
  );
47

48
  return (
49
    <div
50
      className={`${styles[props.position]} ${styles["float-features-item"]}`}
51
    >
52
      <Image alt="" src={props.icon} />
53
      {text}
54
    </div>
55
  );
56
};
57

58
export const BundleBanner = (props: Props) => {
2✔
UNCOV
59
  const l10n = useL10n();
×
UNCOV
60
  const gaEvent = useGaEvent();
×
61

62
  const mainImage = (
63
    <img
64
      src={womanInBanner400w.src}
65
      srcSet={`${womanInBanner768w.src} 768w, ${womanInBanner400w.src} 400w`}
66
      sizes={`(max-width: 600px) 400px, 768px`}
67
      alt=""
68
      className={styles["main-image"]}
69
    />
70
  );
71

UNCOV
72
  const bundleUpgradeCta = useGaViewPing({
×
73
    category: "Bundle banner",
74
    label: "bundle-banner-upgrade-promo",
75
  });
76

77
  return (
78
    <div className={styles["bundle-banner-wrapper"]}>
79
      <div className={styles["first-section"]}>
80
        <div className={styles["main-img-wrapper"]}>{mainImage}</div>
81
        <div className={styles["float-features-wrapper"]}>
82
          <FloatingFeatures
83
            icon={bundleFloatOne}
84
            text="bundle-feature-one"
85
            position="feature-one"
86
            vars={{
87
              num_vpn_servers: "400",
88
            }}
89
          />
90
          <FloatingFeatures
91
            icon={bundleFloatTwo}
92
            text="bundle-feature-two"
93
            position="feature-two"
94
            vars={{
95
              num_vpn_countries: "30",
96
            }}
97
          />
98
          <FloatingFeatures
99
            icon={bundleFloatThree}
100
            text="bundle-feature-three"
101
            position="feature-three"
102
          />
103
        </div>
104
      </div>
105
      {isBundleAvailableInCountry(props.runtimeData) && (
×
106
        <div className={styles["second-section"]}>
107
          <div className={styles["bundle-banner-description"]}>
108
            {props.runtimeData && (
×
109
              <h2>
110
                {l10n.getString("bundle-banner-header-2", {
111
                  monthly_price: getBundlePrice(props.runtimeData, l10n),
112
                })}
113
              </h2>
114
            )}
115
            <p>{l10n.getString("bundle-banner-body-3", { savings: "40%" })}</p>
116
            <p className={styles["bundle-banner-one-year-plan-headline"]}>
117
              <strong>{l10n.getString("bundle-banner-plan-header-2")}</strong>
118
            </p>
119
            <ul className={styles["bundle-banner-value-props"]}>
120
              <li>
121
                <MaskIcon alt="" width="15" height="15" />
122
                {l10n.getString("bundle-banner-plan-modules-email-masking")}
123
              </li>
124
              <li>
125
                <PhoneIcon alt="" width="15" height="20" />
126
                {l10n.getString("bundle-banner-plan-modules-phone-masking")}
127
              </li>
128
              <li>
129
                <VpnIcon alt="" width="20" height="20" />
130
                {l10n.getString("bundle-banner-plan-modules-mozilla-vpn")}
131
              </li>
132
            </ul>
133

134
            <div className={styles["bottom-section"]}>
135
              <LinkButton
136
                ref={bundleUpgradeCta}
137
                className={styles["button"]}
138
                href={getBundleSubscribeLink(props.runtimeData)}
139
                onClick={() =>
140
                  trackPlanPurchaseStart(
×
141
                    gaEvent,
142
                    { plan: "bundle" },
143
                    { label: "bundle-banner-upgrade-promo" },
144
                  )
145
                }
146
              >
147
                {l10n.getString("bundle-banner-cta")}
148
              </LinkButton>
149
              <Localized
150
                id={"bundle-banner-money-back-guarantee"}
151
                vars={{
152
                  days_guarantee: "30",
153
                }}
154
              >
155
                <span className={styles["money-back-guarantee"]} />
156
              </Localized>
157
            </div>
158
          </div>
159
        </div>
160
      )}
161
    </div>
162
  );
163
};
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