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

mozilla / fx-private-relay / 3f1a4922-6438-402b-8a31-b68b7edbe989

pending completion
3f1a4922-6438-402b-8a31-b68b7edbe989

push

circleci

GitHub Actions — l10n sync
Merge in latest l10n strings

1561 of 2384 branches covered (65.48%)

Branch coverage included in aggregate %.

5107 of 6949 relevant lines covered (73.49%)

19.12 hits per line

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

96.0
/frontend/src/components/landing/BundleBanner.tsx
1
import { FluentVariable } from "@fluent/bundle";
2
import Image, { StaticImageData } from "next/image";
2✔
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 womanInBanner400w from "./images/bundle-banner-woman-400w.png";
2✔
13
import womanInBanner768w from "./images/bundle-banner-woman-768w.png";
2✔
14
import bundleFloatOne from "./images/bundle-float-1.svg";
2✔
15
import bundleFloatTwo from "./images/bundle-float-2.svg";
2✔
16
import bundleFloatThree from "./images/bundle-float-3.svg";
2✔
17
import { trackPlanPurchaseStart } from "../../functions/trackPurchase";
2✔
18
import { useGaViewPing } from "../../hooks/gaViewPing";
2✔
19
import { useL10n } from "../../hooks/l10n";
2✔
20
import { Localized } from "../Localized";
2✔
21

22
export type Props = {
23
  runtimeData: RuntimeData;
24
};
25

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

33
const FloatingFeatures = (props: FloatingFeaturesProps) => {
2✔
34
  const l10n = useL10n();
6✔
35

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

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

56
export const BundleBanner = (props: Props) => {
2✔
57
  const l10n = useL10n();
2✔
58

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

69
  const bundleUpgradeCta = useGaViewPing({
2✔
70
    category: "Bundle banner",
71
    label: "bundle-banner-upgrade-promo",
72
  });
73

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

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