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

mozilla / fx-private-relay / 8b39734c-31ce-4dbf-9b05-171797c18cae

02 Oct 2025 04:03PM UTC coverage: 88.815% (-0.05%) from 88.863%
8b39734c-31ce-4dbf-9b05-171797c18cae

Pull #5924

circleci

vpremamozilla
MPP-4348: propagate UTM params from landing page to SubPlat and Accounts URLs
Pull Request #5924: MPP-4348: propagate UTM params from landing page to SubPlat and Accounts URLs

2932 of 3953 branches covered (74.17%)

Branch coverage included in aggregate %.

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

4 existing lines in 3 files now uncovered.

18086 of 19712 relevant lines covered (91.75%)

11.52 hits per line

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

89.91
/frontend/src/functions/getPlan.ts
1
import { ReactLocalization } from "@fluent/react";
2
import { PlanData, ProductData, RuntimeData } from "../hooks/api/types";
3
import { getLocale } from "./getLocale";
16✔
4

5
const getPlan = <P extends Partial<PlanData>>(
16✔
6
  productData: ProductData<P>,
7
  billingPeriod: keyof P,
8
) => {
9
  const languageCode = navigator.language.split("-")[0].toLowerCase();
419✔
10
  const countryPlans =
11
    productData.plan_country_lang_mapping[productData.country_code];
419✔
12
  const plan =
13
    countryPlans[languageCode] ?? countryPlans[Object.keys(countryPlans)[0]];
419✔
14

15
  return plan[billingPeriod];
419✔
16
};
17

18
export const getPeriodicalPremiumPrice = (
44✔
19
  runtimeData: RuntimeDataWithPeriodicalPremiumAvailable,
20
  billingPeriod: keyof PlanData,
21
  l10n: ReactLocalization,
22
) => {
23
  const plan = getPlan(runtimeData.PERIODICAL_PREMIUM_PLANS, billingPeriod);
43✔
24
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
43✔
25
    style: "currency",
26
    currency: plan.currency,
27
  });
28
  return formatter.format(plan.price);
43✔
29
};
30

31
export const getPeriodicalPremiumYearlyPrice = (
16✔
32
  runtimeData: RuntimeDataWithPeriodicalPremiumAvailable,
33
  billingPeriod: keyof PlanData,
34
  l10n: ReactLocalization,
35
) => {
36
  const plan = getPlan(runtimeData.PERIODICAL_PREMIUM_PLANS, billingPeriod);
9✔
37
  const total = plan.price * 12;
9✔
38
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
9✔
39
    style: "currency",
40
    currency: plan.currency,
41
  });
42
  return formatter.format(total);
9✔
43
};
44

45
/**
46
 * Given {@link RuntimeDataWithPeriodicalPremiumAvailable}, returns the URL the user should visit to purchase Premium.
47
 */
48
export const getPeriodicalPremiumSubscribeLink = (
162✔
49
  runtimeData: RuntimeDataWithPeriodicalPremiumAvailable,
50
  billingPeriod: keyof PlanData,
51
) => {
52
  const plan = getPlan(runtimeData.PERIODICAL_PREMIUM_PLANS, billingPeriod);
161✔
53

54
  const baseHref = plan.id
161✔
55
    ? `${runtimeData.FXA_ORIGIN}/subscriptions/products/${runtimeData.PERIODICAL_PREMIUM_PRODUCT_ID}?plan=${plan.id}`
56
    : (plan.url ?? "");
2!
57

58
  if (typeof window !== "undefined" && baseHref) {
161!
59
    const url = new URL(baseHref, window.location.origin);
161✔
60
    const inbound = new URLSearchParams(window.location.search);
161✔
61

62
    (
63
      [
161✔
64
        "utm_source",
65
        "utm_campaign",
66
        "utm_medium",
67
        "utm_content",
68
        "utm_term",
69
      ] as const
70
    ).forEach((k) => {
71
      const v = inbound.get(k);
805✔
72
      if (v && !url.searchParams.has(k)) url.searchParams.set(k, v);
805!
73
    });
74

75
    return url.toString();
161✔
76
  }
77

NEW
78
  return baseHref;
×
79
};
80

81
export const getPhonesPrice = (
42✔
82
  runtimeData: RuntimeDataWithPhonesAvailable,
83
  billingPeriod: keyof PlanData,
84
  l10n: ReactLocalization,
85
) => {
86
  const plan = getPlan(runtimeData.PHONE_PLANS, billingPeriod);
41✔
87
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
41✔
88
    style: "currency",
89
    currency: plan.currency,
90
  });
91
  return formatter.format(plan.price);
41✔
92
};
93

94
export const getPhonesYearlyPrice = (
16✔
95
  runtimeData: RuntimeDataWithPhonesAvailable,
96
  billingPeriod: keyof PlanData,
97
  l10n: ReactLocalization,
98
) => {
99
  const plan = getPlan(runtimeData.PHONE_PLANS, billingPeriod);
8✔
100
  const total = plan.price * 12;
8✔
101
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
8✔
102
    style: "currency",
103
    currency: plan.currency,
104
  });
105
  return formatter.format(total);
8✔
106
};
107

108
export const getPhoneSubscribeLink = (
45✔
109
  runtimeData: RuntimeDataWithPhonesAvailable,
110
  billingPeriod: keyof PlanData,
111
) => {
112
  const plan = getPlan(runtimeData.PHONE_PLANS, billingPeriod);
44✔
113
  if (plan.id) {
44✔
114
    return `${runtimeData.FXA_ORIGIN}/subscriptions/products/${runtimeData.PHONE_PRODUCT_ID}?plan=${plan.id}`;
42✔
115
  }
116
  return plan.url ?? "";
2!
117
};
118

119
export const getBundlePrice = (
16✔
120
  runtimeData: RuntimeDataWithBundleAvailable,
121
  l10n: ReactLocalization,
122
) => {
123
  const plan = getPlan(runtimeData.BUNDLE_PLANS, "yearly");
10✔
124
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
10✔
125
    style: "currency",
126
    currency: plan.currency,
127
  });
128
  return formatter.format(plan.price);
10✔
129
};
130
export const getBundleSubscribeLink = (
16✔
131
  runtimeData: RuntimeDataWithBundleAvailable,
132
) => {
133
  const plan = getPlan(runtimeData.BUNDLE_PLANS, "yearly");
4✔
134
  if (plan.id) {
4✔
135
    return `${runtimeData.FXA_ORIGIN}/subscriptions/products/${runtimeData.BUNDLE_PRODUCT_ID}?plan=${plan.id}`;
3✔
136
  }
137
  return plan.url ?? "";
1!
138
};
139

140
export const getMegabundlePrice = (
38✔
141
  runtimeData: RuntimeDataWithBundleAvailable,
142
  l10n: ReactLocalization,
143
) => {
144
  const plan = getPlan(runtimeData.MEGABUNDLE_PLANS, "yearly");
37✔
145
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
37✔
146
    style: "currency",
147
    currency: plan.currency,
148
  });
149
  return formatter.format(plan.price);
37✔
150
};
151

152
export const getMegabundleYearlyPrice = (
22✔
153
  runtimeData: RuntimeDataWithBundleAvailable,
154
  l10n: ReactLocalization,
155
) => {
156
  const plan = getPlan(runtimeData.MEGABUNDLE_PLANS, "yearly");
21✔
157
  const total = plan.price * 12;
21✔
158
  const formatter = new Intl.NumberFormat(getLocale(l10n), {
21✔
159
    style: "currency",
160
    currency: plan.currency,
161
  });
162
  return formatter.format(total);
21✔
163
};
164

165
export const getBundleDiscountPercentage = (
16✔
166
  runtimeData: RuntimeDataWithBundleAvailable,
167
) => {
168
  const plan = getPlan(runtimeData.MEGABUNDLE_PLANS, "yearly");
11✔
169
  const individualBundlePrice = getIndividualBundlePrice("monthly");
11✔
170
  const ratio = plan.price / individualBundlePrice;
11✔
171
  const discount = Math.ceil((1 - ratio) * 100);
11✔
172
  return discount;
11✔
173
};
174

175
export const getMegabundleSubscribeLink = (
31✔
176
  runtimeData: RuntimeDataWithBundleAvailable,
177
) => {
178
  const plan = getPlan(runtimeData.MEGABUNDLE_PLANS, "yearly");
30✔
179
  if (plan.id) {
30!
180
    return `${runtimeData.FXA_ORIGIN}/subscriptions/products/${runtimeData.MEGABUNDLE_PRODUCT_ID}?plan=${plan.id}`;
30✔
181
  }
182
  return plan.url ?? "";
×
183
};
184

185
export const getIndividualBundlePrice = (billingPeriod: keyof PlanData) => {
16✔
186
  if (billingPeriod === "yearly") {
24✔
187
    return 14.99 * 12;
1✔
188
  }
189
  return 14.99;
23✔
190
};
191

192
/**
193
 * Helper type that is used to create helper types like
194
 * {@see RuntimeDataWithBundleAvailable}, which in turn can help make sure
195
 * functions like {@see isBundleAvailableInCountry} have been called.
196
 */
197
type RuntimeDataWithPlanAvailable<Plan extends keyof RuntimeData> =
198
  RuntimeData &
199
    Record<Plan, RuntimeData[Plan] & { available_in_country: true }>;
200
export type RuntimeDataWithPeriodicalPremiumAvailable =
201
  RuntimeDataWithPlanAvailable<"PERIODICAL_PREMIUM_PLANS">;
202
export type RuntimeDataWithPhonesAvailable =
203
  RuntimeDataWithPlanAvailable<"PHONE_PLANS">;
204
export type RuntimeDataWithBundleAvailable =
205
  RuntimeDataWithPlanAvailable<"BUNDLE_PLANS">;
206
export type RuntimeDataWithMegabundleAvailable =
207
  RuntimeDataWithPlanAvailable<"MEGABUNDLE_PLANS">;
208

209
export function isPeriodicalPremiumAvailableInCountry(
480✔
210
  runtimeData: RuntimeData | undefined,
211
): runtimeData is RuntimeDataWithPeriodicalPremiumAvailable {
212
  return runtimeData?.PERIODICAL_PREMIUM_PLANS?.available_in_country === true;
479✔
213
}
214

215
export function isPhonesAvailableInCountry(
571✔
216
  runtimeData: RuntimeData | undefined,
217
): runtimeData is RuntimeDataWithPhonesAvailable {
218
  return runtimeData?.PHONE_PLANS?.available_in_country === true;
570✔
219
}
220

221
export function isBundleAvailableInCountry(
273✔
222
  runtimeData: RuntimeData | undefined,
223
): runtimeData is RuntimeDataWithBundleAvailable {
224
  return runtimeData?.BUNDLE_PLANS?.available_in_country === true;
272✔
225
}
226

227
export function isMegabundleAvailableInCountry(
180✔
228
  runtimeData: RuntimeData | undefined,
229
): runtimeData is RuntimeDataWithBundleAvailable {
230
  return runtimeData?.MEGABUNDLE_PLANS?.available_in_country === true;
179✔
231
}
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