From 6f6b37c0896dd061d689e437b8003931786a7271 Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 10:45:33 +0100
Subject: [PATCH 1/9] cached load coupon
---
src/components/banners/TopBanner.tsx | 8 +-
src/hooks/usePricing.ts | 197 +++++++++++-------------
src/lib/helpers/campaigns.ts | 45 +++++-
src/pages/api/stripe/get_coupons.ts | 1 +
src/pages/api/stripe/stripe_products.ts | 1 +
src/services/currency.service.ts | 12 +-
src/services/stripe.service.ts | 47 ++++--
7 files changed, 187 insertions(+), 124 deletions(-)
diff --git a/src/components/banners/TopBanner.tsx b/src/components/banners/TopBanner.tsx
index 03be3152d3..33539b0e51 100644
--- a/src/components/banners/TopBanner.tsx
+++ b/src/components/banners/TopBanner.tsx
@@ -49,14 +49,10 @@ const TopBanner = ({ isBannerFixed }: TopBannerProps) => {
{textContent.title.normalText}
-
+
{textContent.title.cta}
-
+
diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts
index 12f7c3fde6..f1575db8b2 100644
--- a/src/hooks/usePricing.ts
+++ b/src/hooks/usePricing.ts
@@ -1,4 +1,3 @@
-/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { currencyService } from '@/services/currency.service';
import { ProductsDataProps, stripeService } from '@/services/stripe.service';
@@ -24,132 +23,122 @@ interface UseStripeProductsAndCurrencyResponse {
products?: ProductsDataProps;
}
-type ActionType =
- | { type: 'SET_PRODUCTS'; payload: ProductsDataProps | undefined }
- | { type: 'SET_LOADING_CARDS'; payload: boolean }
- | { type: 'SET_CURRENCY'; payload: string }
- | { type: 'SET_CURRENCY_VALUE'; payload: string }
- | { type: 'SET_COUPON'; payload: PromoCodeProps | undefined }
- | { type: 'SET_COUPON_LIFETIME'; payload: PromoCodeProps | undefined }
- | { type: 'SET_BUSINESS_COUPON'; payload: PromoCodeProps | undefined }
- | { type: 'SET_LIFETIME_COUPONS'; payload: any | undefined };
-
-const reducer = (state: any, action: ActionType) => {
+interface PricesData {
+ products?: ProductsDataProps;
+ currency: string;
+ currencyValue: string;
+ lifetimeCoupons?: any;
+}
+
+interface CouponsData {
+ coupon?: PromoCodeProps;
+ lifetimeCoupon?: PromoCodeProps;
+ businessCoupon?: PromoCodeProps;
+}
+
+interface PricingState extends PricesData, CouponsData {
+ pricesLoaded: boolean;
+ couponsLoaded: boolean;
+}
+
+type ActionType = { type: 'SET_PRICES'; payload: PricesData } | { type: 'SET_COUPONS'; payload: CouponsData };
+
+const DEFAULT_CURRENCY = { currency: '€', currencyValue: 'eur' };
+
+const initialState: PricingState = {
+ ...DEFAULT_CURRENCY,
+ pricesLoaded: false,
+ couponsLoaded: false,
+};
+
+const reducer = (state: PricingState, action: ActionType): PricingState => {
switch (action.type) {
- case 'SET_PRODUCTS':
- return { ...state, products: action.payload };
- case 'SET_LOADING_CARDS':
- return { ...state, loadingCards: action.payload };
- case 'SET_CURRENCY':
- return { ...state, currency: action.payload };
- case 'SET_CURRENCY_VALUE':
- return { ...state, currencyValue: action.payload };
- case 'SET_COUPON':
- return { ...state, coupon: action.payload };
- case 'SET_COUPON_LIFETIME':
- return { ...state, lifetimeCoupon: action.payload };
- case 'SET_BUSINESS_COUPON':
- return { ...state, businessCoupon: action.payload };
- case 'SET_LIFETIME_COUPONS':
- return { ...state, lifetimeCoupons: action.payload };
+ case 'SET_PRICES':
+ return { ...state, ...action.payload, pricesLoaded: true };
+ case 'SET_COUPONS':
+ return { ...state, ...action.payload, couponsLoaded: true };
default:
return state;
}
};
-function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurrencyResponse {
- const { couponCode, couponCodeForLifetime, couponCodeForBusiness, currencySpecified, fetchLifetimeCoupons } = options;
- const initialState = {
- products: undefined,
- loadingCards: true,
- currency: '€',
- coupon: undefined,
- lifetimeCoupon: undefined,
- businessCoupon: undefined,
- lifetimeCoupons: undefined,
- currencyValue: 'eur',
- };
+const loadPrices = async (currencySpecified?: string): Promise> => {
+ try {
+ const { currency, currencyValue } = await currencyService.filterCurrencyByCountry(currencySpecified);
+ const products = await stripeService.getPrices(currencyValue);
- const [state, dispatch] = useReducer(reducer, initialState);
+ return { products, currency, currencyValue };
+ } catch (err) {
+ console.warn('Primary price fetch failed, falling back to EUR:', err);
- const fetchData = async () => {
try {
- const res = await currencyService.filterCurrencyByCountry(currencySpecified);
- const prices = await stripeService.getPrices(res.currencyValue);
-
- dispatch({ type: 'SET_PRODUCTS', payload: prices });
- dispatch({ type: 'SET_CURRENCY', payload: res.currency });
- dispatch({ type: 'SET_CURRENCY_VALUE', payload: res.currencyValue });
- } catch (err) {
- console.warn('Primary price fetch failed, falling back to EUR:', err);
-
- try {
- const prices = await stripeService.getPrices('eur');
- dispatch({ type: 'SET_PRODUCTS', payload: prices });
- } catch (error) {
- console.error('Critical error: Failed to fetch fallback prices:', error);
- }
-
- dispatch({ type: 'SET_CURRENCY', payload: '€' });
- dispatch({ type: 'SET_CURRENCY_VALUE', payload: 'eur' });
+ const products = await stripeService.getPrices(DEFAULT_CURRENCY.currencyValue);
+ return { products, ...DEFAULT_CURRENCY };
+ } catch (error) {
+ console.error('Critical error: Failed to fetch fallback prices:', error);
+ return { products: undefined, ...DEFAULT_CURRENCY };
}
+ }
+};
- if (fetchLifetimeCoupons) {
- try {
- const lifetimeCoupons = await stripeService.getLifetimeCoupons();
- dispatch({ type: 'SET_LIFETIME_COUPONS', payload: lifetimeCoupons });
- } catch (error) {
- console.error('Error fetching lifetime coupons:', error);
- }
- }
+const loadLifetimeCoupons = async (): Promise => {
+ try {
+ return await stripeService.getLifetimeCoupons();
+ } catch (error) {
+ console.error('Error fetching lifetime coupons:', error);
+ return undefined;
+ }
+};
- if (couponCode) {
- try {
- const coupon = await stripeService.getCoupon(couponCode);
- dispatch({ type: 'SET_COUPON', payload: coupon });
- } catch (err) {
- console.error('Error fetching coupon:', err);
- }
- }
+const loadCoupon = async (couponName?: PromoCodeName): Promise => {
+ if (!couponName) return undefined;
- if (couponCodeForLifetime) {
- const lifetimeCoupon = await stripeService.getCoupon(couponCodeForLifetime);
+ try {
+ return await stripeService.getCoupon(couponName);
+ } catch (error) {
+ console.warn(`Coupon ${couponName} could not be loaded:`, error);
+ return undefined;
+ }
+};
- dispatch({ type: 'SET_COUPON_LIFETIME', payload: lifetimeCoupon });
- }
+function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurrencyResponse {
+ const { couponCode, couponCodeForLifetime, couponCodeForBusiness, currencySpecified, fetchLifetimeCoupons } = options;
- if (couponCodeForBusiness) {
- try {
- const businessCoupon = await stripeService.getCoupon(couponCodeForBusiness);
- dispatch({ type: 'SET_BUSINESS_COUPON', payload: businessCoupon });
- } catch (err) {
- console.warn('Invalid or expired business coupon ignored', err);
- }
- }
- };
+ const [state, dispatch] = useReducer(reducer, initialState);
+ // Prices and coupons don't depend on each other, so they are requested in parallel.
useEffect(() => {
- fetchData().then(() => {
- dispatch({ type: 'SET_LOADING_CARDS', payload: false });
- });
+ let cancelled = false;
+
+ Promise.all([loadPrices(currencySpecified), fetchLifetimeCoupons ? loadLifetimeCoupons() : undefined]).then(
+ ([prices, lifetimeCoupons]) => {
+ if (!cancelled) dispatch({ type: 'SET_PRICES', payload: { ...prices, lifetimeCoupons } });
+ },
+ );
+
+ return () => {
+ cancelled = true;
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
- if (couponCode) {
- stripeService
- .getCoupon(couponCode)
- .then((coupon) => {
- dispatch({ type: 'SET_COUPON', payload: coupon });
- })
- .catch(() => {
- //
- });
- }
- }, [couponCode]);
+ let cancelled = false;
+
+ Promise.all([loadCoupon(couponCode), loadCoupon(couponCodeForLifetime), loadCoupon(couponCodeForBusiness)]).then(
+ ([coupon, lifetimeCoupon, businessCoupon]) => {
+ if (!cancelled) dispatch({ type: 'SET_COUPONS', payload: { coupon, lifetimeCoupon, businessCoupon } });
+ },
+ );
+
+ return () => {
+ cancelled = true;
+ };
+ }, [couponCode, couponCodeForLifetime, couponCodeForBusiness]);
return {
products: state.products,
- loadingCards: state.loadingCards,
+ loadingCards: !state.pricesLoaded || !state.couponsLoaded,
currency: state.currency,
currencyValue: state.currencyValue,
coupon: state.coupon,
diff --git a/src/lib/helpers/campaigns.ts b/src/lib/helpers/campaigns.ts
index 2b3bc92d42..49ac48d301 100644
--- a/src/lib/helpers/campaigns.ts
+++ b/src/lib/helpers/campaigns.ts
@@ -21,7 +21,46 @@ export interface ActiveCampaigns {
earlyReleaseCampaign?: Campaign;
}
-const CAMPAIGN_EXCEPTIONS: ReadonlySet = new Set([]);
+const CAMPAIGN_EXCEPTIONS: ReadonlySet = new Set([
+ PromoCodeName.blackmonth87,
+ PromoCodeName.blackmonthcn87,
+ PromoCodeName.cloudwardsbm87,
+ PromoCodeName.privacybm87,
+ PromoCodeName.heisebm87,
+ PromoCodeName.xatakabm87,
+ PromoCodeName.clubicbm87,
+ PromoCodeName.tdesbm87,
+ PromoCodeName.tdptbm87,
+ PromoCodeName.tditbm87,
+ PromoCodeName.gpesbm87,
+ PromoCodeName.gpitbm87,
+ PromoCodeName.chollobm87,
+
+ PromoCodeName.blackfriday90,
+ PromoCodeName.blackfridaycn90,
+ PromoCodeName.cloudwardsbf90,
+ PromoCodeName.privacybf90,
+ PromoCodeName.heisebf90,
+ PromoCodeName.xatakabf90,
+ PromoCodeName.clubicbf90,
+ PromoCodeName.tdesbf90,
+ PromoCodeName.tdptbf90,
+ PromoCodeName.tditbf90,
+ PromoCodeName.gpesbf90,
+ PromoCodeName.gpitbf90,
+ PromoCodeName.chollobf90,
+
+ PromoCodeName.cyberweek90,
+ PromoCodeName.cyberweekcn90,
+ PromoCodeName.cloudwardscm90,
+ PromoCodeName.privacycm90,
+ PromoCodeName.heisecm90,
+ PromoCodeName.xatakacm90,
+ PromoCodeName.clubiccm90,
+ PromoCodeName.gpescw90,
+ PromoCodeName.gpitcw90,
+ PromoCodeName.chollocw90,
+]);
export const EARLY_RELEASE_CAMPAIGNS: ReadonlySet = new Set([
Campaign.BlackFriday,
@@ -36,8 +75,10 @@ export const isCampaign = (value: unknown): value is Campaign =>
export const hasEarlyRelease = (name: PromoCodeName): boolean => EARLY_RELEASE_CODES.has(name);
+export const isCampaignException = (name: PromoCodeName): boolean => CAMPAIGN_EXCEPTIONS.has(name);
+
export const applyCampaign = (name: PromoCodeName, campaign?: Campaign): ResolvedPromoCode => {
- if (!campaign || CAMPAIGN_EXCEPTIONS.has(name)) return name;
+ if (!campaign || isCampaignException(name)) return name;
return `${name}${CAMPAIGN_SUFFIXES[campaign]}`;
};
diff --git a/src/pages/api/stripe/get_coupons.ts b/src/pages/api/stripe/get_coupons.ts
index bab919aa6e..60655e468d 100644
--- a/src/pages/api/stripe/get_coupons.ts
+++ b/src/pages/api/stripe/get_coupons.ts
@@ -27,6 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
//Return the coupon with its data
+ res.setHeader('Cache-Control', 'public, max-age=60, s-maxage=300');
res.status(200).send(promoCodeData);
} else {
res.status(405).end(); // Method Not Allowed
diff --git a/src/pages/api/stripe/stripe_products.ts b/src/pages/api/stripe/stripe_products.ts
index 4f5a4f05e6..999f558c1f 100644
--- a/src/pages/api/stripe/stripe_products.ts
+++ b/src/pages/api/stripe/stripe_products.ts
@@ -30,6 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
business: businessProductsData,
};
+ res.setHeader('Cache-Control', 'public, max-age=60, s-maxage=300');
res.status(200).json(productsData);
} catch (err) {
const error = err as Error;
diff --git a/src/services/currency.service.ts b/src/services/currency.service.ts
index 86c33ccc0e..c55b661e14 100644
--- a/src/services/currency.service.ts
+++ b/src/services/currency.service.ts
@@ -27,9 +27,15 @@ const objectStorageCurrencyValue = {
brl: 'eur',
};
-const getCountry = async () => {
- const { data } = await axios.get(`${process.env.NEXT_PUBLIC_COUNTRY_API_URL}`);
- return data;
+let countryRequest: Promise<{ country?: string }> | undefined;
+
+const getCountry = () => {
+ if (!countryRequest) {
+ countryRequest = axios.get(`${process.env.NEXT_PUBLIC_COUNTRY_API_URL}`).then(({ data }) => data);
+ countryRequest.catch(() => (countryRequest = undefined));
+ }
+
+ return countryRequest;
};
const filterCurrencyByCountry = async (currencySpecified?: string) => {
diff --git a/src/services/stripe.service.ts b/src/services/stripe.service.ts
index 253dcb9a09..3bd4394aea 100644
--- a/src/services/stripe.service.ts
+++ b/src/services/stripe.service.ts
@@ -3,7 +3,14 @@ import bytes from 'bytes';
import { currency as CURRENCY_MAP, currencyService } from './currency.service';
import { checkout, checkoutForPcComponentes } from '@/lib/auth';
import { PromoCodeName, PromoCodeProps } from '@/lib/types';
-import { ActiveCampaigns, ResolvedPromoCode, applyCampaign, hasEarlyRelease, isCampaign } from '@/lib/helpers/campaigns';
+import {
+ ActiveCampaigns,
+ ResolvedPromoCode,
+ applyCampaign,
+ hasEarlyRelease,
+ isCampaign,
+ isCampaignException,
+} from '@/lib/helpers/campaigns';
import { getGclidFromURL, saveGclidToCookie } from '@/lib/cookies';
import { analyticsService } from './ga.services';
@@ -51,13 +58,19 @@ export interface ProductsDataProps {
};
}
-async function getPrices(currencySpecified?: string) {
+const pricesRequests = new Map>();
+
+async function getPrices(currencySpecified?: string): Promise {
const currency = await getCurrency(currencySpecified);
- const data = await fetchProductData(currency);
- if (data) {
- const transformedData = transformProductData(data.individuals, data.business);
- return transformedData;
+ let request = pricesRequests.get(currency);
+
+ if (!request) {
+ request = fetchProductData(currency).then((data) => (data ? transformProductData(data.individuals, data.business) : undefined));
+ pricesRequests.set(currency, request);
+ request.then((prices) => prices ?? pricesRequests.delete(currency)).catch(() => pricesRequests.delete(currency));
}
+
+ return request;
}
async function getCurrency(currencySpecified?: string): Promise {
@@ -123,8 +136,6 @@ function transformProductData(individualsData: ProductValue[], businessData: Pro
transform(individualsData, 'individuals');
transform(businessData, 'business');
- console.log(transformedData);
-
return transformedData;
}
@@ -155,7 +166,7 @@ function fetchActiveCampaigns(): Promise {
return activeCampaigns;
}
-async function requestCoupon(couponName: ResolvedPromoCode): Promise {
+async function fetchCoupon(couponName: ResolvedPromoCode): Promise {
try {
const res = await axios.get(`${window.origin}/api/stripe/get_coupons`, {
params: {
@@ -175,7 +186,25 @@ async function requestCoupon(couponName: ResolvedPromoCode): Promise>();
+
+function requestCoupon(couponName: ResolvedPromoCode): Promise {
+ let request = couponRequests.get(couponName);
+
+ if (!request) {
+ request = fetchCoupon(couponName);
+ couponRequests.set(couponName, request);
+ request.catch(() => couponRequests.delete(couponName));
+ }
+
+ return request;
+}
+
async function getCoupon(couponName: PromoCodeName): Promise {
+ if (isCampaignException(couponName)) {
+ return requestCoupon(couponName);
+ }
+
const { campaign, earlyReleaseCampaign } = await fetchActiveCampaigns();
const resolvedName = applyCampaign(couponName, hasEarlyRelease(couponName) ? earlyReleaseCampaign : campaign);
From c6d25458bef552fbaf32910493816be6f75e6ce5 Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 10:56:31 +0100
Subject: [PATCH 2/9] fix CypressTest Error
---
src/services/currency.service.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/currency.service.ts b/src/services/currency.service.ts
index c55b661e14..79b6dd4346 100644
--- a/src/services/currency.service.ts
+++ b/src/services/currency.service.ts
@@ -27,7 +27,7 @@ const objectStorageCurrencyValue = {
brl: 'eur',
};
-let countryRequest: Promise<{ country?: string }> | undefined;
+let countryRequest: Promise<{ country: string }> | undefined;
const getCountry = () => {
if (!countryRequest) {
From 4737a6ba262bd630098235491c8b4a79d52c5d9a Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 12:08:02 +0100
Subject: [PATCH 3/9] changes for sonarqube
---
src/hooks/usePricing.ts | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts
index f1575db8b2..2367f446c4 100644
--- a/src/hooks/usePricing.ts
+++ b/src/hooks/usePricing.ts
@@ -110,11 +110,11 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
useEffect(() => {
let cancelled = false;
- Promise.all([loadPrices(currencySpecified), fetchLifetimeCoupons ? loadLifetimeCoupons() : undefined]).then(
- ([prices, lifetimeCoupons]) => {
+ Promise.all([loadPrices(currencySpecified), fetchLifetimeCoupons ? loadLifetimeCoupons() : undefined])
+ .then(([prices, lifetimeCoupons]) => {
if (!cancelled) dispatch({ type: 'SET_PRICES', payload: { ...prices, lifetimeCoupons } });
- },
- );
+ })
+ .catch((error) => console.error('Error loading prices:', error));
return () => {
cancelled = true;
@@ -125,11 +125,11 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
useEffect(() => {
let cancelled = false;
- Promise.all([loadCoupon(couponCode), loadCoupon(couponCodeForLifetime), loadCoupon(couponCodeForBusiness)]).then(
- ([coupon, lifetimeCoupon, businessCoupon]) => {
+ Promise.all([loadCoupon(couponCode), loadCoupon(couponCodeForLifetime), loadCoupon(couponCodeForBusiness)])
+ .then(([coupon, lifetimeCoupon, businessCoupon]) => {
if (!cancelled) dispatch({ type: 'SET_COUPONS', payload: { coupon, lifetimeCoupon, businessCoupon } });
- },
- );
+ })
+ .catch((error) => console.error('Error loading coupons:', error));
return () => {
cancelled = true;
From e96841b3735834d90a0e7dc1019d09bdd315740e Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 13:02:14 +0100
Subject: [PATCH 4/9] remove cache header
---
src/pages/api/stripe/get_coupons.ts | 1 -
src/pages/api/stripe/stripe_products.ts | 1 -
2 files changed, 2 deletions(-)
diff --git a/src/pages/api/stripe/get_coupons.ts b/src/pages/api/stripe/get_coupons.ts
index 60655e468d..bab919aa6e 100644
--- a/src/pages/api/stripe/get_coupons.ts
+++ b/src/pages/api/stripe/get_coupons.ts
@@ -27,7 +27,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
//Return the coupon with its data
- res.setHeader('Cache-Control', 'public, max-age=60, s-maxage=300');
res.status(200).send(promoCodeData);
} else {
res.status(405).end(); // Method Not Allowed
diff --git a/src/pages/api/stripe/stripe_products.ts b/src/pages/api/stripe/stripe_products.ts
index 999f558c1f..4f5a4f05e6 100644
--- a/src/pages/api/stripe/stripe_products.ts
+++ b/src/pages/api/stripe/stripe_products.ts
@@ -30,7 +30,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
business: businessProductsData,
};
- res.setHeader('Cache-Control', 'public, max-age=60, s-maxage=300');
res.status(200).json(productsData);
} catch (err) {
const error = err as Error;
From 14f49ebd5034b6649e1edfb2a8c7bb62276991fd Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 13:18:27 +0100
Subject: [PATCH 5/9] added the change of refactored code as welll
---
src/pages/api/stripe/get_coupons.ts | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/src/pages/api/stripe/get_coupons.ts b/src/pages/api/stripe/get_coupons.ts
index bab919aa6e..e2d6a035dc 100644
--- a/src/pages/api/stripe/get_coupons.ts
+++ b/src/pages/api/stripe/get_coupons.ts
@@ -11,15 +11,21 @@ interface PromotionCode {
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise {
if (req.method === 'OPTIONS') {
res.status(200).json({});
+ return;
}
- if (req.method === 'GET') {
- const couponName = req.query.couponName;
- if (!couponName) {
- res.status(404).end();
- return;
- }
+ if (req.method !== 'GET') {
+ res.status(405).end();
+ return;
+ }
+
+ const couponName = req.query.couponName;
+ if (!couponName) {
+ res.status(404).end();
+ return;
+ }
+ try {
const { data: promoCodeData } = await axios.get(`${process.env.NEXT_PUBLIC_PAYMENTS_API}/promo-code-info`, {
params: {
promotionCode: couponName,
@@ -28,7 +34,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
//Return the coupon with its data
res.status(200).send(promoCodeData);
- } else {
- res.status(405).end(); // Method Not Allowed
+ } catch (err) {
+ const status = axios.isAxiosError(err) && err.response ? err.response.status : 502;
+ res.status(status).json({ message: 'Coupon not available' });
}
}
From 90833a46f205d461f311c86f88ebcc3520db6f96 Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Wed, 11 Nov 2026 15:14:15 +0100
Subject: [PATCH 6/9] remove comment
---
src/hooks/usePricing.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts
index 2367f446c4..e2c37c95b3 100644
--- a/src/hooks/usePricing.ts
+++ b/src/hooks/usePricing.ts
@@ -106,7 +106,6 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
const [state, dispatch] = useReducer(reducer, initialState);
- // Prices and coupons don't depend on each other, so they are requested in parallel.
useEffect(() => {
let cancelled = false;
From e0429f2a08017df9bae20f59ebf1d9b9dd77e804 Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Thu, 12 Nov 2026 10:29:17 +0100
Subject: [PATCH 7/9] changes in usePricing
---
src/hooks/usePricing.ts | 79 ++++++++++++++++++++++++++++-------------
1 file changed, 54 insertions(+), 25 deletions(-)
diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts
index e2c37c95b3..60e5288f1b 100644
--- a/src/hooks/usePricing.ts
+++ b/src/hooks/usePricing.ts
@@ -3,6 +3,9 @@ import { currencyService } from '@/services/currency.service';
import { ProductsDataProps, stripeService } from '@/services/stripe.service';
import { useEffect, useReducer } from 'react';
import { PromoCodeName, PromoCodeProps } from '@/lib/types';
+import { notificationService } from '@/components/Snackbar';
+
+type LifetimeCoupons = Record;
type UsePricingOptions = {
couponCode?: PromoCodeName;
@@ -21,6 +24,7 @@ interface UseStripeProductsAndCurrencyResponse {
businessCoupon?: PromoCodeProps;
lifetimeCoupons?: any;
products?: ProductsDataProps;
+ pricesError: boolean;
}
interface PricesData {
@@ -28,6 +32,7 @@ interface PricesData {
currency: string;
currencyValue: string;
lifetimeCoupons?: any;
+ pricesError: boolean;
}
interface CouponsData {
@@ -44,9 +49,11 @@ interface PricingState extends PricesData, CouponsData {
type ActionType = { type: 'SET_PRICES'; payload: PricesData } | { type: 'SET_COUPONS'; payload: CouponsData };
const DEFAULT_CURRENCY = { currency: '€', currencyValue: 'eur' };
+const PRICES_ERROR_MESSAGE = 'We could not load the prices. Please reload the page and try again.';
const initialState: PricingState = {
...DEFAULT_CURRENCY,
+ pricesError: false,
pricesLoaded: false,
couponsLoaded: false,
};
@@ -62,32 +69,27 @@ const reducer = (state: PricingState, action: ActionType): PricingState => {
}
};
-const loadPrices = async (currencySpecified?: string): Promise> => {
+const loadPrices = async (currencySpecified?: string): Promise> => {
try {
const { currency, currencyValue } = await currencyService.filterCurrencyByCountry(currencySpecified);
const products = await stripeService.getPrices(currencyValue);
+ if (!products) throw new Error(`No prices available for ${currencyValue}`);
+
return { products, currency, currencyValue };
} catch (err) {
console.warn('Primary price fetch failed, falling back to EUR:', err);
- try {
- const products = await stripeService.getPrices(DEFAULT_CURRENCY.currencyValue);
- return { products, ...DEFAULT_CURRENCY };
- } catch (error) {
- console.error('Critical error: Failed to fetch fallback prices:', error);
- return { products: undefined, ...DEFAULT_CURRENCY };
- }
+ const products = await stripeService.getPrices(DEFAULT_CURRENCY.currencyValue);
+
+ if (!products) throw new Error(`No prices available for ${DEFAULT_CURRENCY.currencyValue}`);
+
+ return { products, ...DEFAULT_CURRENCY };
}
};
-const loadLifetimeCoupons = async (): Promise => {
- try {
- return await stripeService.getLifetimeCoupons();
- } catch (error) {
- console.error('Error fetching lifetime coupons:', error);
- return undefined;
- }
+const loadLifetimeCoupons = async (): Promise => {
+ return stripeService.getLifetimeCoupons();
};
const loadCoupon = async (couponName?: PromoCodeName): Promise => {
@@ -109,11 +111,28 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
useEffect(() => {
let cancelled = false;
- Promise.all([loadPrices(currencySpecified), fetchLifetimeCoupons ? loadLifetimeCoupons() : undefined])
- .then(([prices, lifetimeCoupons]) => {
- if (!cancelled) dispatch({ type: 'SET_PRICES', payload: { ...prices, lifetimeCoupons } });
- })
- .catch((error) => console.error('Error loading prices:', error));
+ Promise.allSettled([
+ loadPrices(currencySpecified),
+ fetchLifetimeCoupons ? loadLifetimeCoupons() : Promise.resolve(undefined),
+ ]).then(([pricesResult, lifetimeCouponsResult]) => {
+ if (cancelled) return;
+
+ const pricesError = pricesResult.status === 'rejected';
+
+ if (pricesError) {
+ console.error('Error loading prices:', pricesResult.reason);
+ notificationService.openErrorToast(PRICES_ERROR_MESSAGE);
+ }
+
+ if (lifetimeCouponsResult.status === 'rejected') {
+ console.error('Error fetching lifetime coupons:', lifetimeCouponsResult.reason);
+ }
+
+ const prices = pricesResult.status === 'fulfilled' ? pricesResult.value : DEFAULT_CURRENCY;
+ const lifetimeCoupons = lifetimeCouponsResult.status === 'fulfilled' ? lifetimeCouponsResult.value : undefined;
+
+ dispatch({ type: 'SET_PRICES', payload: { ...prices, lifetimeCoupons, pricesError } });
+ });
return () => {
cancelled = true;
@@ -124,11 +143,20 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
useEffect(() => {
let cancelled = false;
- Promise.all([loadCoupon(couponCode), loadCoupon(couponCodeForLifetime), loadCoupon(couponCodeForBusiness)])
- .then(([coupon, lifetimeCoupon, businessCoupon]) => {
- if (!cancelled) dispatch({ type: 'SET_COUPONS', payload: { coupon, lifetimeCoupon, businessCoupon } });
- })
- .catch((error) => console.error('Error loading coupons:', error));
+ Promise.allSettled([
+ loadCoupon(couponCode),
+ loadCoupon(couponCodeForLifetime),
+ loadCoupon(couponCodeForBusiness),
+ ]).then((results) => {
+ if (cancelled) return;
+
+ const [coupon, lifetimeCoupon, businessCoupon] = results.map((result) => {
+ if (result.status === 'rejected') console.error('Error loading coupons:', result.reason);
+ return result.status === 'fulfilled' ? result.value : undefined;
+ });
+
+ dispatch({ type: 'SET_COUPONS', payload: { coupon, lifetimeCoupon, businessCoupon } });
+ });
return () => {
cancelled = true;
@@ -144,6 +172,7 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
businessCoupon: state.businessCoupon,
lifetimeCoupon: state.lifetimeCoupon,
lifetimeCoupons: state.lifetimeCoupons,
+ pricesError: state.pricesError,
};
}
From b9d5d16d5b03d86326acb0c9b9eb5231e0eab8c8 Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Thu, 12 Nov 2026 11:02:26 +0100
Subject: [PATCH 8/9] changes for sonarqube
---
src/hooks/usePricing.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/hooks/usePricing.ts b/src/hooks/usePricing.ts
index 60e5288f1b..e21530fca6 100644
--- a/src/hooks/usePricing.ts
+++ b/src/hooks/usePricing.ts
@@ -132,7 +132,7 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
const lifetimeCoupons = lifetimeCouponsResult.status === 'fulfilled' ? lifetimeCouponsResult.value : undefined;
dispatch({ type: 'SET_PRICES', payload: { ...prices, lifetimeCoupons, pricesError } });
- });
+ }).catch((err) => console.error('Error loading prices:', err));
return () => {
cancelled = true;
@@ -156,7 +156,7 @@ function usePricing(options: UsePricingOptions = {}): UseStripeProductsAndCurren
});
dispatch({ type: 'SET_COUPONS', payload: { coupon, lifetimeCoupon, businessCoupon } });
- });
+ }).catch((err) => console.error('Error loading coupons:', err));
return () => {
cancelled = true;
From 48dc6a11bb5e217457b446e1360fd8b03edcfe2a Mon Sep 17 00:00:00 2001
From: internxt-yu
Date: Fri, 13 Nov 2026 12:09:51 +0100
Subject: [PATCH 9/9] fix topBanner
---
src/components/banners/TopBanner.tsx | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/components/banners/TopBanner.tsx b/src/components/banners/TopBanner.tsx
index a5da9444d7..38cb110755 100644
--- a/src/components/banners/TopBanner.tsx
+++ b/src/components/banners/TopBanner.tsx
@@ -53,12 +53,6 @@ const TopBanner = ({ isBannerFixed, lang: pageLang }: TopBannerProps) => {
{textContent.title.normalText}
-
{textContent.title.cta}