feat(api)!: always use localized pricing where it is offered (#2646)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-10 21:22:32 +02:00
1 parent 905af5dd5a
commit f708586c59
62 files changed
+3148 -11917

No files matched your search

@@ -29,7 +29,6 @@ import {
FREE_VS_PREMIUM_DESCRIPTOR,
VERIFY_EMAIL_TO_PURCHASE_PREMIUM_DESCRIPTOR,
} from '@app/features/premium/utils/PremiumMessageDescriptors';
import type {PricingMode} from '@app/features/premium/utils/PricingUtils';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import MobileLayout from '@app/features/ui/state/MobileLayout';
@@ -51,7 +50,6 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
const locale = LocaleUtils.getCurrentLocale();
const mobileLayoutState = MobileLayout;
const [isGiftMode, setIsGiftMode] = useState(defaultGiftMode);
const [pricingMode, setPricingMode] = useState<PricingMode>('localized');
const giftSectionRef = useRef<HTMLDivElement | null>(null);
const perksSectionRef = useRef<HTMLDivElement | null>(null);
const countryCode = GeoIP.countryCode;
@@ -66,14 +64,11 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
yearlyPrice,
giftMonthlyPrice,
giftYearlyPrice,
hasPricingChoice,
localizedCurrency,
baseCurrency,
currentSubscriptionPrice,
currentSubscriptionPriceLabel,
currentSubscriptionListPriceLabel,
isCurrentSubscriptionGrandfathered,
} = usePremiumData(countryCode, pricingMode, {premiumState});
} = usePremiumData({premiumState});
const {
loadingPortal,
loadingCancel,
@@ -92,15 +87,9 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
const {loadingCheckout, handleSelectPlan} = useCheckoutActions(
priceIds,
countryCode,
pricingMode,
subscriptionStatus.isGiftSubscription,
mobileLayoutState.enabled,
);
useEffect(() => {
if (!hasPricingChoice && pricingMode === 'base') {
setPricingMode('localized');
}
}, [hasPricingChoice, pricingMode]);
useEffect(() => {
if (!currentUser?.id) return;
void PremiumCommands.refreshPremiumState(countryCode ?? undefined);
@@ -175,12 +164,6 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
<PlutoniumUpsellBanner data-flx="app.plutonium-content.plutonium-upsell-banner" />
<GiftSection
giftSectionRef={giftSectionRef}
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
giftMonthlyPrice={giftMonthlyPrice}
giftYearlyPrice={giftYearlyPrice}
loadingCheckout={loadingCheckout}
@@ -289,12 +272,6 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
<PricingSection
isGiftMode={isGiftMode}
setIsGiftMode={setIsGiftMode}
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
monthlyPrice={monthlyPrice}
yearlyPrice={yearlyPrice}
giftMonthlyPrice={giftMonthlyPrice}
@@ -308,12 +285,6 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
) : (
<GiftSection
giftSectionRef={giftSectionRef}
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
giftMonthlyPrice={giftMonthlyPrice}
giftYearlyPrice={giftYearlyPrice}
loadingCheckout={loadingCheckout}
@@ -340,12 +311,6 @@ export const PlutoniumContent = observer(({defaultGiftMode = false}: PlutoniumCo
{!subscriptionStatus.isPremium && (
<BottomCTASection
isGiftMode={isGiftMode}
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
monthlyPrice={monthlyPrice}
yearlyPrice={yearlyPrice}
giftMonthlyPrice={giftMonthlyPrice}
@@ -2,10 +2,8 @@
import {PurchaseDisclaimer} from '@app/features/app/components/dialogs/components/PurchaseDisclaimer';
import styles from '@app/features/app/components/dialogs/components/plutonium/BottomCTASection.module.css';
import {PricingContextPanel} from '@app/features/app/components/dialogs/components/plutonium/PricingContextPanel';
import {PurchaseDisabledWrapper} from '@app/features/app/components/dialogs/components/plutonium/PurchaseDisabledWrapper';
import {PREMIUM_PRODUCT_FULL_NAME} from '@app/features/app/config/I18nDisplayConstants';
import type {PricingMode} from '@app/features/premium/utils/PricingUtils';
import {Button} from '@app/features/ui/button/Button';
import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
@@ -20,12 +18,6 @@ const CLAIM_YOUR_ACCOUNT_TO_PURCHASE_DESCRIPTOR = msg({
interface BottomCTASectionProps {
isGiftMode: boolean;
countryCode: string | null;
pricingMode: PricingMode;
setPricingMode: (value: PricingMode) => void;
hasPricingChoice: boolean;
localizedCurrency: string | null;
baseCurrency: string | null;
monthlyPrice: string;
yearlyPrice: string;
giftMonthlyPrice: string;
@@ -39,12 +31,6 @@ interface BottomCTASectionProps {
export const BottomCTASection: React.FC<BottomCTASectionProps> = observer(
({
isGiftMode,
countryCode,
pricingMode,
setPricingMode,
hasPricingChoice,
localizedCurrency,
baseCurrency,
monthlyPrice,
yearlyPrice,
giftMonthlyPrice,
@@ -63,17 +49,6 @@ export const BottomCTASection: React.FC<BottomCTASectionProps> = observer(
<h2 className={styles.title} data-flx="app.plutonium.bottom-cta-section.title">
{isGiftMode ? <Trans>Ready to buy a gift?</Trans> : <Trans>Ready to upgrade?</Trans>}
</h2>
<PricingContextPanel
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
isGiftMode={isGiftMode}
compact
data-flx="app.plutonium.bottom-cta-section.pricing-context-panel"
/>
<div className={styles.buttonContainer} data-flx="app.plutonium.bottom-cta-section.button-container">
{!isGiftMode ? (
<>
@@ -5,7 +5,6 @@ import gridStyles from '@app/features/app/components/dialogs/components/PricingG
import {PurchaseDisclaimer} from '@app/features/app/components/dialogs/components/PurchaseDisclaimer';
import styles from '@app/features/app/components/dialogs/components/plutonium/GiftSection.module.css';
import {SectionHeader} from '@app/features/app/components/dialogs/components/plutonium/PlutoniumSectionHeader';
import {PricingContextPanel} from '@app/features/app/components/dialogs/components/plutonium/PricingContextPanel';
import {PurchaseDisabledWrapper} from '@app/features/app/components/dialogs/components/plutonium/PurchaseDisabledWrapper';
import {PREMIUM_PRODUCT_FULL_NAME, PREMIUM_PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import {
@@ -16,7 +15,6 @@ import {
SHARE_PREMIUM_EXPERIENCE_DESCRIPTOR,
VIEW_PREMIUM_PERKS_DESCRIPTOR,
} from '@app/features/premium/utils/PremiumMessageDescriptors';
import type {PricingMode} from '@app/features/premium/utils/PricingUtils';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {ArrowDownIcon} from '@phosphor-icons/react';
@@ -38,12 +36,6 @@ const MESSAGE_1_MONTH_GIFT_DESCRIPTOR = msg({
interface GiftSectionProps {
giftSectionRef: React.RefObject<HTMLDivElement | null>;
countryCode: string | null;
pricingMode: PricingMode;
setPricingMode: (value: PricingMode) => void;
hasPricingChoice: boolean;
localizedCurrency: string | null;
baseCurrency: string | null;
giftMonthlyPrice: string;
giftYearlyPrice: string;
loadingCheckout: boolean;
@@ -55,12 +47,6 @@ interface GiftSectionProps {
export const GiftSection: React.FC<GiftSectionProps> = observer(
({
giftSectionRef,
countryCode,
pricingMode,
setPricingMode,
hasPricingChoice,
localizedCurrency,
baseCurrency,
giftMonthlyPrice,
giftYearlyPrice,
loadingCheckout,
@@ -82,16 +68,6 @@ export const GiftSection: React.FC<GiftSectionProps> = observer(
})}
data-flx="app.plutonium.gift-section.section-header"
/>
<PricingContextPanel
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
isGiftMode
data-flx="app.plutonium.gift-section.pricing-context-panel"
/>
<div className={gridStyles.gridWrapper} data-flx="app.plutonium.gift-section.div--2">
<div className={gridStyles.gridTwoColumns} data-flx="app.plutonium.gift-section.div--3">
<PurchaseDisabledWrapper
@@ -1,53 +0,0 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
.container {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.toggleContainer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
.panel {
border: 0.0625rem solid var(--border-color);
border-radius: 0.75rem;
background-color: var(--background-tertiary);
padding: 0.875rem 1rem;
}
.panelCompact {
padding: 0.75rem 0.875rem;
}
.title {
margin: 0 0 0.5rem;
font-weight: 700;
font-size: 0.95rem;
line-height: 1.35rem;
color: var(--text-primary);
}
.list {
margin: 0;
padding-left: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.375rem;
color: var(--text-primary-muted);
font-size: 0.875rem;
line-height: 1.35rem;
}
.summary {
margin: 0;
color: var(--text-primary-muted);
font-size: 0.875rem;
line-height: 1.35rem;
text-align: center;
}
@@ -1,490 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import styles from '@app/features/app/components/dialogs/components/plutonium/PricingContextPanel.module.css';
import {ToggleButton} from '@app/features/app/components/dialogs/components/ToggleButton';
import {
BLIK_PAYMENT_METHOD,
MB_WAY_PAYMENT_METHOD,
PAYMENT_PROVIDER_NAME,
PIX_PAYMENT_METHOD,
UPI_PAYMENT_METHOD,
} from '@app/features/app/config/I18nDisplayConstants';
import {getCurrencyCodeLabel, type PricingMode} from '@app/features/premium/utils/PricingUtils';
import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
const BRL_GIFT_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized BRL gift checkout',
comment: 'Plutonium gift checkout panel title for buyers in Brazil who selected localized BRL pricing.',
});
const BRL_GIFT_PIX_DETAIL_DESCRIPTOR = msg({
message:
'{pixPaymentMethod} can be used for one-time BRL payments when {paymentProviderName} offers it on the checkout page.',
comment:
'Plutonium gift checkout guidance for Brazil. Explains that Pix is available for one-time gift purchases via the payment provider.',
});
const BRL_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR = msg({
message: 'If you pay with a card in BRL, it still needs to be issued in Brazil for the purchase to stay eligible.',
comment:
'Plutonium gift checkout guidance for Brazil. Clarifies that BRL card payments require a Brazil-issued card.',
});
const INR_GIFT_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized INR gift checkout',
comment: 'Plutonium gift checkout panel title for buyers in India who selected localized INR pricing.',
});
const INR_GIFT_UPI_DETAIL_DESCRIPTOR = msg({
message:
'{upiPaymentMethod} can be used for one-time INR payments when {paymentProviderName} offers it on the checkout page.',
comment:
'Plutonium gift checkout guidance for India. Explains that UPI is available for one-time gift purchases via the payment provider.',
});
const INR_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR = msg({
message: 'If you pay with a card in INR, it still needs to be issued in India for the purchase to stay eligible.',
comment: 'Plutonium gift checkout guidance for India. Clarifies that INR card payments require an India-issued card.',
});
const PLN_GIFT_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized PLN gift checkout',
comment: 'Plutonium gift checkout panel title for buyers in Poland who selected localized PLN pricing.',
});
const PLN_GIFT_BLIK_DETAIL_DESCRIPTOR = msg({
message:
'{blikPaymentMethod} can be used for one-time PLN gift payments, even though it does not support subscriptions in {paymentProviderName} checkout.',
comment:
'Plutonium gift checkout guidance for Poland. Explains that BLIK works for gifts but not for recurring subscriptions.',
});
const PLN_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR = msg({
message: 'If you pay with a card in PLN, it still needs to be issued in Poland for the purchase to stay eligible.',
comment:
'Plutonium gift checkout guidance for Poland. Clarifies that PLN card payments require a Poland-issued card.',
});
const TRY_GIFT_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized TRY gift checkout',
comment: 'Plutonium gift checkout panel title for buyers in Türkiye who selected localized TRY pricing.',
});
const TRY_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR = msg({
message:
'TRY gifts are one-time payments. Cards charged in TRY still need to be issued in Türkiye for the purchase to stay eligible.',
comment:
'Plutonium gift checkout guidance for Türkiye. Clarifies one-time-only nature and Türkiye card issuance requirement.',
});
const TRY_GIFT_NO_LOCAL_APP_METHOD_DETAIL_DESCRIPTOR = msg({
message: 'There is no app-based local payment method surfaced here today, so card checkout is the main path.',
comment:
'Plutonium gift checkout guidance for Türkiye. Notes that no local wallet method is currently available; card is the main option.',
});
const EUR_GIFT_TITLE_DESCRIPTOR = msg({
message: 'EUR gift checkout',
comment: 'Plutonium gift checkout panel title for buyers in Portugal paying with EUR.',
});
const EUR_GIFT_MBWAY_DETAIL_DESCRIPTOR = msg({
message:
'{mbWayPaymentMethod} can be used for one-time EUR gift payments when {paymentProviderName} offers it on the checkout page.',
comment:
'Plutonium gift checkout guidance for Portugal. Explains that MB WAY can be used for one-time EUR gift purchases.',
});
const STANDARD_GIFT_TITLE_DESCRIPTOR = msg({
message: 'Standard {baseCurrencyLabel} gift checkout',
comment:
'Plutonium gift checkout panel title when the buyer opted into the standard (non-localized) currency. The currency code is interpolated.',
});
const STANDARD_GIFT_SWITCH_BACK_DETAIL_DESCRIPTOR = msg({
message: 'Switch back to {localizedCurrencyLabel} if you prefer the localized price instead.',
comment:
'Plutonium gift checkout guidance. Reminds the buyer they can revert to localized pricing. The currency code is interpolated.',
});
const EUR_SUBSCRIPTION_TITLE_DESCRIPTOR = msg({
message: 'EUR subscription checkout',
comment: 'Plutonium subscription checkout panel title for subscribers in Portugal paying with EUR.',
});
const EUR_SUBSCRIPTION_MBWAY_ONE_TIME_DETAIL_DESCRIPTOR = msg({
message:
'{mbWayPaymentMethod} is a one-time payment method in {paymentProviderName} checkout, so it is not a recurring subscription path here.',
comment:
'Plutonium subscription checkout guidance for Portugal. Explains that MB WAY cannot be used for recurring subscriptions.',
});
const EUR_SUBSCRIPTION_USE_CARD_OR_GIFT_DETAIL_DESCRIPTOR = msg({
message: 'Use a card for the subscription, or buy a gift instead if you want a one-time EUR payment method.',
comment:
'Plutonium subscription checkout guidance for Portugal. Suggests using a card for recurring billing or a gift for one-time EUR payments.',
});
const STANDARD_PRICING_TITLE_DESCRIPTOR = msg({
message: 'Standard {baseCurrencyLabel} pricing',
comment:
'Plutonium subscription checkout panel title when the subscriber opted into the standard (non-localized) currency. The currency code is interpolated.',
});
const STANDARD_PRICING_FULL_PRICE_DETAIL_DESCRIPTOR = msg({
message: 'You are using the full standard {baseCurrencyLabel} price instead of localized pricing.',
comment:
'Plutonium subscription checkout guidance. States that the standard (non-localized) price applies. The currency code is interpolated.',
});
const STANDARD_PRICING_SKIPS_VERIFICATION_DETAIL_DESCRIPTOR = msg({
message:
'This skips localized card verification. Switch back to {localizedCurrencyLabel} if you prefer the local price.',
comment:
'Plutonium subscription checkout guidance. Notes that the standard path skips localized verification and offers a way back.',
});
const BRL_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized BRL subscription checkout',
comment: 'Plutonium subscription checkout panel title for subscribers in Brazil with localized BRL pricing.',
});
const BRL_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR = msg({
message:
'Cards can keep BRL pricing after a quick {paymentProviderName} verification, but the card must be issued in Brazil.',
comment:
'Plutonium subscription checkout guidance for Brazil. Explains that BRL card subscriptions need a verification step and a Brazil-issued card.',
});
const BRL_SUBSCRIPTION_PIX_RECURRING_DETAIL_DESCRIPTOR = msg({
message:
'{pixPaymentMethod} can skip the card verification step when {paymentProviderName} offers recurring {pixPaymentMethod2} for your checkout.',
comment: 'Plutonium subscription checkout guidance for Brazil. Explains that recurring Pix avoids card verification.',
});
const BRL_SUBSCRIPTION_PIX_FALLBACK_DETAIL_DESCRIPTOR = msg({
message: 'If {pixPaymentMethod} is unavailable or you prefer standard pricing, you can switch to USD at any time.',
comment:
'Plutonium subscription checkout guidance for Brazil. Offers USD as a fallback when Pix is not available or standard pricing is preferred.',
});
const INR_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized INR subscription checkout',
comment: 'Plutonium subscription checkout panel title for subscribers in India with localized INR pricing.',
});
const INR_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR = msg({
message:
'Cards can keep INR pricing after a quick {paymentProviderName} verification, but the card must be issued in India.',
comment:
'Plutonium subscription checkout guidance for India. Explains that INR card subscriptions need a verification step and an India-issued card.',
});
const INR_SUBSCRIPTION_UPI_RECURRING_DETAIL_DESCRIPTOR = msg({
message:
'{upiPaymentMethod} can skip the card verification step when {paymentProviderName} offers recurring {upiPaymentMethod2} for your checkout.',
comment: 'Plutonium subscription checkout guidance for India. Explains that recurring UPI avoids card verification.',
});
const INR_SUBSCRIPTION_UPI_FALLBACK_DETAIL_DESCRIPTOR = msg({
message:
'If recurring {upiPaymentMethod} is unavailable or you prefer standard pricing, you can switch to USD at any time.',
comment:
'Plutonium subscription checkout guidance for India. Offers USD as a fallback when recurring UPI is not available.',
});
const PLN_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized PLN subscription checkout',
comment: 'Plutonium subscription checkout panel title for subscribers in Poland with localized PLN pricing.',
});
const PLN_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR = msg({
message:
'Cards can keep PLN pricing after a quick {paymentProviderName} verification, but the card must be issued in Poland.',
comment:
'Plutonium subscription checkout guidance for Poland. Explains that PLN card subscriptions need a verification step and a Poland-issued card.',
});
const PLN_SUBSCRIPTION_BLIK_UNSUPPORTED_DETAIL_DESCRIPTOR = msg({
message: '{blikPaymentMethod} does not support subscriptions in {paymentProviderName} checkout.',
comment:
'Plutonium subscription checkout guidance for Poland. Clarifies that BLIK is not available for recurring subscriptions.',
});
const PLN_SUBSCRIPTION_BLIK_GIFT_FALLBACK_DETAIL_DESCRIPTOR = msg({
message: 'If you want to use {blikPaymentMethod}, buy a PLN gift instead, or switch to standard EUR pricing.',
comment:
'Plutonium subscription checkout guidance for Poland. Suggests a PLN gift or standard EUR if the subscriber wants BLIK.',
});
const TRY_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR = msg({
message: 'Localized TRY subscription checkout',
comment: 'Plutonium subscription checkout panel title for subscribers in Türkiye with localized TRY pricing.',
});
const TRY_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR = msg({
message:
'Cards can keep TRY pricing after a quick {paymentProviderName} verification, but the card must be issued in Türkiye.',
comment:
'Plutonium subscription checkout guidance for Türkiye. Explains that TRY card subscriptions need a verification step and a Türkiye-issued card.',
});
const TRY_SUBSCRIPTION_NO_LOCAL_APP_METHOD_DETAIL_DESCRIPTOR = msg({
message:
'There is no app-based local subscription method surfaced here today, so local card checkout is the main path.',
comment:
'Plutonium subscription checkout guidance for Türkiye. Notes that no local wallet method is available; card is the main option.',
});
const SWITCH_TO_STANDARD_USD_DETAIL_DESCRIPTOR = msg({
message: 'If you prefer, you can switch to standard USD pricing instead.',
comment: 'Plutonium subscription checkout guidance. Offers USD as a fallback option.',
});
const USING_LOCALIZED_PRICING_SUMMARY_DESCRIPTOR = msg({
message: 'Using localized {localizedCurrencyLabel} pricing.',
comment:
'Compact summary line shown in Plutonium checkout when localized pricing is active. The currency code is interpolated.',
});
const USING_STANDARD_PRICING_SUMMARY_DESCRIPTOR = msg({
message: 'Using standard {baseCurrencyLabel} pricing.',
comment:
'Compact summary line shown in Plutonium checkout when standard pricing is active. The currency code is interpolated.',
});
const PRICING_PREFERENCE_GROUP_LABEL_DESCRIPTOR = msg({
message: 'Pricing preference',
comment:
'Accessible group label for the pricing toggle (localized vs standard) in Plutonium checkout. Not visible on screen.',
});
interface PricingContextPanelProps {
countryCode: string | null;
pricingMode: PricingMode;
setPricingMode: (mode: PricingMode) => void;
hasPricingChoice: boolean;
localizedCurrency: string | null;
baseCurrency: string | null;
isGiftMode: boolean;
compact?: boolean;
}
function getCountryContext(countryCode: string | null): 'BR' | 'IN' | 'PL' | 'PT' | 'TR' | 'OTHER' {
const upperCountryCode = countryCode?.toUpperCase();
switch (upperCountryCode) {
case 'BR':
case 'IN':
case 'PL':
case 'PT':
case 'TR':
return upperCountryCode;
default:
return 'OTHER';
}
}
export const PricingContextPanel: React.FC<PricingContextPanelProps> = observer(
({
countryCode,
pricingMode,
setPricingMode,
hasPricingChoice,
localizedCurrency,
baseCurrency,
isGiftMode,
compact = false,
}) => {
const {i18n} = useLingui();
const countryContext = getCountryContext(countryCode);
const localizedCurrencyLabel = getCurrencyCodeLabel(localizedCurrency);
const baseCurrencyLabel = getCurrencyCodeLabel(baseCurrency);
const guidance = useMemo(() => {
if (isGiftMode) {
if (pricingMode === 'localized') {
switch (countryContext) {
case 'BR':
return {
title: i18n._(BRL_GIFT_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(BRL_GIFT_PIX_DETAIL_DESCRIPTOR, {
pixPaymentMethod: PIX_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(BRL_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR),
],
};
case 'IN':
return {
title: i18n._(INR_GIFT_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(INR_GIFT_UPI_DETAIL_DESCRIPTOR, {
upiPaymentMethod: UPI_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(INR_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR),
],
};
case 'PL':
return {
title: i18n._(PLN_GIFT_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(PLN_GIFT_BLIK_DETAIL_DESCRIPTOR, {
blikPaymentMethod: BLIK_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(PLN_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR),
],
};
case 'TR':
return {
title: i18n._(TRY_GIFT_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(TRY_GIFT_CARD_ISSUANCE_DETAIL_DESCRIPTOR),
i18n._(TRY_GIFT_NO_LOCAL_APP_METHOD_DETAIL_DESCRIPTOR),
],
};
}
}
if (countryContext === 'PT' && baseCurrency === 'EUR') {
return {
title: i18n._(EUR_GIFT_TITLE_DESCRIPTOR),
items: [
i18n._(EUR_GIFT_MBWAY_DETAIL_DESCRIPTOR, {
mbWayPaymentMethod: MB_WAY_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
],
};
}
if (hasPricingChoice && pricingMode === 'base') {
return {
title: i18n._(STANDARD_GIFT_TITLE_DESCRIPTOR, {baseCurrencyLabel}),
items: [i18n._(STANDARD_GIFT_SWITCH_BACK_DETAIL_DESCRIPTOR, {localizedCurrencyLabel})],
};
}
return null;
}
if (countryContext === 'PT' && baseCurrency === 'EUR') {
return {
title: i18n._(EUR_SUBSCRIPTION_TITLE_DESCRIPTOR),
items: [
i18n._(EUR_SUBSCRIPTION_MBWAY_ONE_TIME_DETAIL_DESCRIPTOR, {
mbWayPaymentMethod: MB_WAY_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(EUR_SUBSCRIPTION_USE_CARD_OR_GIFT_DETAIL_DESCRIPTOR),
],
};
}
if (pricingMode === 'base') {
if (hasPricingChoice) {
return {
title: i18n._(STANDARD_PRICING_TITLE_DESCRIPTOR, {baseCurrencyLabel}),
items: [
i18n._(STANDARD_PRICING_FULL_PRICE_DETAIL_DESCRIPTOR, {baseCurrencyLabel}),
i18n._(STANDARD_PRICING_SKIPS_VERIFICATION_DETAIL_DESCRIPTOR, {localizedCurrencyLabel}),
],
};
}
return null;
}
switch (countryContext) {
case 'BR':
return {
title: i18n._(BRL_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(BRL_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR, {
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(BRL_SUBSCRIPTION_PIX_RECURRING_DETAIL_DESCRIPTOR, {
pixPaymentMethod: PIX_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
pixPaymentMethod2: PIX_PAYMENT_METHOD,
}),
i18n._(BRL_SUBSCRIPTION_PIX_FALLBACK_DETAIL_DESCRIPTOR, {
pixPaymentMethod: PIX_PAYMENT_METHOD,
}),
],
};
case 'IN':
return {
title: i18n._(INR_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(INR_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR, {
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(INR_SUBSCRIPTION_UPI_RECURRING_DETAIL_DESCRIPTOR, {
upiPaymentMethod: UPI_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
upiPaymentMethod2: UPI_PAYMENT_METHOD,
}),
i18n._(INR_SUBSCRIPTION_UPI_FALLBACK_DETAIL_DESCRIPTOR, {
upiPaymentMethod: UPI_PAYMENT_METHOD,
}),
],
};
case 'PL':
return {
title: i18n._(PLN_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(PLN_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR, {
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(PLN_SUBSCRIPTION_BLIK_UNSUPPORTED_DETAIL_DESCRIPTOR, {
blikPaymentMethod: BLIK_PAYMENT_METHOD,
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(PLN_SUBSCRIPTION_BLIK_GIFT_FALLBACK_DETAIL_DESCRIPTOR, {blikPaymentMethod: BLIK_PAYMENT_METHOD}),
],
};
case 'TR':
return {
title: i18n._(TRY_SUBSCRIPTION_LOCALIZED_TITLE_DESCRIPTOR),
items: [
i18n._(TRY_SUBSCRIPTION_CARD_VERIFICATION_DETAIL_DESCRIPTOR, {
paymentProviderName: PAYMENT_PROVIDER_NAME,
}),
i18n._(TRY_SUBSCRIPTION_NO_LOCAL_APP_METHOD_DETAIL_DESCRIPTOR),
i18n._(SWITCH_TO_STANDARD_USD_DETAIL_DESCRIPTOR),
],
};
default:
return null;
}
}, [
baseCurrency,
baseCurrencyLabel,
countryContext,
hasPricingChoice,
isGiftMode,
localizedCurrencyLabel,
pricingMode,
i18n.locale,
]);
const summary = useMemo(() => {
if (pricingMode === 'localized') {
if (hasPricingChoice) {
return i18n._(USING_LOCALIZED_PRICING_SUMMARY_DESCRIPTOR, {localizedCurrencyLabel});
}
return null;
}
if (hasPricingChoice) {
return i18n._(USING_STANDARD_PRICING_SUMMARY_DESCRIPTOR, {baseCurrencyLabel});
}
return null;
}, [baseCurrencyLabel, hasPricingChoice, localizedCurrencyLabel, pricingMode, i18n.locale]);
return (
<div className={styles.container} data-flx="app.plutonium.pricing-context-panel.container">
{hasPricingChoice && (
<div
className={styles.toggleContainer}
role="group"
aria-label={i18n._(PRICING_PREFERENCE_GROUP_LABEL_DESCRIPTOR)}
data-flx="app.plutonium.pricing-context-panel.toggle-container"
>
<ToggleButton
active={pricingMode === 'localized'}
onClick={() => setPricingMode('localized')}
label={<Trans>Local {localizedCurrencyLabel}</Trans>}
data-flx="app.plutonium.pricing-context-panel.toggle-button.set-pricing-mode"
/>
<ToggleButton
active={pricingMode === 'base'}
onClick={() => setPricingMode('base')}
label={<Trans>Standard {baseCurrencyLabel}</Trans>}
data-flx="app.plutonium.pricing-context-panel.toggle-button.set-pricing-mode--2"
/>
</div>
)}
{summary && compact && (
<p className={styles.summary} data-flx="app.plutonium.pricing-context-panel.summary">
{summary}
</p>
)}
{guidance && !compact && (
<div
className={clsx(styles.panel, compact && styles.panelCompact)}
data-flx="app.plutonium.pricing-context-panel.panel"
>
<h3 className={styles.title} data-flx="app.plutonium.pricing-context-panel.title">
{guidance.title}
</h3>
<ul className={styles.list} data-flx="app.plutonium.pricing-context-panel.list">
{guidance.items.map((item) => (
<li key={item} data-flx="app.plutonium.pricing-context-panel.li">
{item}
</li>
))}
</ul>
</div>
)}
</div>
);
},
);
@@ -3,7 +3,6 @@
import {PricingCard} from '@app/features/app/components/dialogs/components/PricingCard';
import gridStyles from '@app/features/app/components/dialogs/components/PricingGrid.module.css';
import {PurchaseDisclaimer} from '@app/features/app/components/dialogs/components/PurchaseDisclaimer';
import {PricingContextPanel} from '@app/features/app/components/dialogs/components/plutonium/PricingContextPanel';
import styles from '@app/features/app/components/dialogs/components/plutonium/PricingSection.module.css';
import {PurchaseDisabledWrapper} from '@app/features/app/components/dialogs/components/plutonium/PurchaseDisabledWrapper';
import {ToggleButton} from '@app/features/app/components/dialogs/components/ToggleButton';
@@ -14,7 +13,6 @@ import {
ONE_TIME_PURCHASE_DESCRIPTOR,
VIEW_PREMIUM_PERKS_DESCRIPTOR,
} from '@app/features/premium/utils/PremiumMessageDescriptors';
import type {PricingMode} from '@app/features/premium/utils/PricingUtils';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {ArrowDownIcon} from '@phosphor-icons/react';
@@ -73,12 +71,6 @@ const MESSAGE_1_MONTH_GIFT_DESCRIPTOR = msg({
interface PricingSectionProps {
isGiftMode: boolean;
setIsGiftMode: (value: boolean) => void;
countryCode: string | null;
pricingMode: PricingMode;
setPricingMode: (value: PricingMode) => void;
hasPricingChoice: boolean;
localizedCurrency: string | null;
baseCurrency: string | null;
monthlyPrice: string;
yearlyPrice: string;
giftMonthlyPrice: string;
@@ -93,12 +85,6 @@ export const PricingSection: React.FC<PricingSectionProps> = observer(
({
isGiftMode,
setIsGiftMode,
countryCode,
pricingMode,
setPricingMode,
hasPricingChoice,
localizedCurrency,
baseCurrency,
monthlyPrice,
yearlyPrice,
giftMonthlyPrice,
@@ -133,16 +119,6 @@ export const PricingSection: React.FC<PricingSectionProps> = observer(
data-flx="app.plutonium.pricing-section.toggle-button.set-is-gift-mode--2"
/>
</div>
<PricingContextPanel
countryCode={countryCode}
pricingMode={pricingMode}
setPricingMode={setPricingMode}
hasPricingChoice={hasPricingChoice}
localizedCurrency={localizedCurrency}
baseCurrency={baseCurrency}
isGiftMode={isGiftMode}
data-flx="app.plutonium.pricing-section.pricing-context-panel"
/>
<div className={gridStyles.gridWrapper} data-flx="app.plutonium.pricing-section.div">
<div className={gridStyles.gridTwoColumns} data-flx="app.plutonium.pricing-section.div--2">
{!isGiftMode ? (
@@ -18,7 +18,6 @@ import type {CheckoutPaymentMethod, PriceIds} from '@app/features/premium/comman
import * as PremiumCommands from '@app/features/premium/commands/PremiumCommands';
import {recordPremiumCheckoutReturnIntent} from '@app/features/premium/utils/PremiumCheckoutReturnIntent';
import {MANAGE_SUBSCRIPTION_DESCRIPTOR} from '@app/features/premium/utils/PremiumMessageDescriptors';
import type {PricingMode} from '@app/features/premium/utils/PricingUtils';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {openExternalUrl} from '@app/features/ui/utils/NativeUtils';
@@ -246,7 +245,6 @@ function alternativePaymentMethodForCurrency(
export const useCheckoutActions = (
priceIds: PriceIds | null,
countryCode: string | null,
pricingMode: PricingMode,
isGiftSubscription: boolean,
mobileEnabled: boolean,
) => {
@@ -552,7 +550,6 @@ export const useCheckoutActions = (
priceId,
countryCode ?? undefined,
isGift,
pricingMode,
paymentMethod,
);
await openCheckoutUrl(checkoutUrl, {promptKind: 'payment', skipMobilePrompt});
@@ -572,11 +569,7 @@ export const useCheckoutActions = (
}
setLoadingCheckout(true);
try {
const checkoutUrl = await PremiumCommands.createLocalizedCardPreapprovalSession(
priceId,
countryCode,
pricingMode,
);
const checkoutUrl = await PremiumCommands.createLocalizedCardPreapprovalSession(priceId, countryCode);
await openCheckoutUrl(checkoutUrl, {
promptKind: 'localized_card_preapproval',
skipMobilePrompt,
@@ -641,7 +634,6 @@ export const useCheckoutActions = (
getAlternativePaymentMethodPrompt,
isGiftSubscription,
mobileEnabled,
pricingMode,
i18n,
],
);
@@ -2,7 +2,7 @@
import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions';
import type {CurrentSubscriptionPrice, PriceIds} from '@app/features/premium/commands/PremiumCommands';
import {formatMinorUnitPrice, type PricingMode} from '@app/features/premium/utils/PricingUtils';
import {formatMinorUnitPrice} from '@app/features/premium/utils/PricingUtils';
import * as LocaleUtils from '@app/features/user/utils/LocaleUtils';
import type {PremiumStateResponse} from '@fluxer/schema/src/domains/premium/PremiumSchemas';
import {useMemo} from 'react';
@@ -35,35 +35,19 @@ export interface PremiumData {
yearlyPrice: string;
giftMonthlyPrice: string;
giftYearlyPrice: string;
localizedPriceIds: PriceIds | null;
basePriceIds: PriceIds | null;
hasPricingChoice: boolean;
selectedPricingMode: PricingMode;
localizedCurrency: string | null;
baseCurrency: string | null;
currentSubscriptionPrice: CurrentSubscriptionPrice | null;
currentSubscriptionPriceLabel: string | null;
currentSubscriptionListPriceLabel: string | null;
isCurrentSubscriptionGrandfathered: boolean;
}
export const usePremiumData = (
_countryCode: string | null,
pricingMode: PricingMode = 'localized',
{
premiumState = null,
}: {
premiumState?: PremiumStateResponse | null;
} = {},
): PremiumData => {
export const usePremiumData = ({
premiumState = null,
}: {
premiumState?: PremiumStateResponse | null;
} = {}): PremiumData => {
const locale = LocaleUtils.getCurrentLocale();
const localizedPriceIds = premiumState?.pricing.localized ?? null;
const basePriceIds = premiumState?.pricing.base ?? null;
const hasPricingChoice =
typeof localizedPriceIds?.currency === 'string' &&
typeof basePriceIds?.currency === 'string' &&
localizedPriceIds.currency !== basePriceIds.currency;
const priceIds = pricingMode === 'base' && hasPricingChoice ? basePriceIds : localizedPriceIds;
const priceIds = premiumState?.pricing.localized ?? null;
const monthlyPrice = useMemo(() => {
return formatPriceLabel(priceIds?.monthly_amount_minor, priceIds?.currency, locale);
}, [locale, priceIds?.currency, priceIds?.monthly_amount_minor]);
@@ -97,12 +81,6 @@ export const usePremiumData = (
yearlyPrice,
giftMonthlyPrice,
giftYearlyPrice,
localizedPriceIds,
basePriceIds,
hasPricingChoice,
selectedPricingMode: pricingMode,
localizedCurrency: localizedPriceIds?.currency ?? null,
baseCurrency: hasPricingChoice ? (basePriceIds?.currency ?? null) : null,
currentSubscriptionPrice,
currentSubscriptionPriceLabel,
currentSubscriptionListPriceLabel,
@@ -23,7 +23,6 @@ export const MACOS_SCREEN_RECORDING_PERMISSION_NAME = 'Screen Recording';
export const PIX_PAYMENT_METHOD = 'Pix';
export const UPI_PAYMENT_METHOD = 'UPI';
export const BLIK_PAYMENT_METHOD = 'BLIK';
export const MB_WAY_PAYMENT_METHOD = 'MB WAY';
export const SUPPORT_EMAIL = '[email protected]';
export const SUPPORT_EMAIL_MAILTO = `mailto:${SUPPORT_EMAIL}`;
export const I18N_EMAIL = '[email protected]';