1 import { useEffect } from 'react';
2 import { useLocation } from 'react-router';
4 import { c } from 'ttag';
6 import { usePaymentStatus } from '@proton/account/paymentStatus/hooks';
7 import { useSubscription } from '@proton/account/subscription/hooks';
8 import { useUser } from '@proton/account/user/hooks';
9 import { Button } from '@proton/atoms';
10 import Loader from '@proton/components/components/loader/Loader';
11 import useModalState from '@proton/components/components/modalTwo/useModalState';
12 import { useModalTwoStatic } from '@proton/components/components/modalTwo/useModalTwo';
13 import Price from '@proton/components/components/price/Price';
14 import SettingsParagraph from '@proton/components/containers/account/SettingsParagraph';
15 import SettingsSection from '@proton/components/containers/account/SettingsSection';
16 import { isFreeSubscription, isSplittedUser } from '@proton/payments';
17 import { getAppHref } from '@proton/shared/lib/apps/helper';
18 import { APPS } from '@proton/shared/lib/constants';
19 import { isElectronApp } from '@proton/shared/lib/helpers/desktop';
20 import { isManagedExternally } from '@proton/shared/lib/helpers/subscription';
21 import { BillingPlatform, ChargebeeEnabled } from '@proton/shared/lib/interfaces';
23 import { openLinkInBrowser } from '../desktop/openExternalLink';
24 import CreditsModal from './CreditsModal';
25 import InAppPurchaseModal from './subscription/InAppPurchaseModal';
27 const redirectFromDesktop = '?open=credit-modal';
29 const CreditsSection = () => {
30 const location = useLocation();
31 const [user] = useUser();
32 const [subscription] = useSubscription();
33 const [creditModalProps, setCreditModalOpen, renderCreditModal] = useModalState();
34 const [externalSubscriptionModal, showExternalSubscriptionModal] = useModalTwoStatic(InAppPurchaseModal);
35 const [paymentStatus, paymentStatusLoading] = usePaymentStatus();
37 const [{ Credit, Currency, ChargebeeUser }] = useUser();
39 const openCreditModal = () => {
40 setCreditModalOpen(true);
44 const searchParams = new URLSearchParams(location.search);
45 if (searchParams.get('open') === 'credit-modal') {
46 void openCreditModal();
48 }, [location.search]);
50 if (!subscription || paymentStatusLoading) {
54 let upcomingSubscriptionPrice: number = 0;
55 // In case of Chargebee, they don't display the balance for the upcoming subscription.
56 // Instead, they count it as already paid, and they return credits balance already taking into account
57 // the upcoming subscription.
59 !isFreeSubscription(subscription) &&
60 subscription?.BillingPlatform !== BillingPlatform.Chargebee &&
61 subscription?.UpcomingSubscription
63 upcomingSubscriptionPrice =
64 subscription.UpcomingSubscription.Amount - subscription.UpcomingSubscription.Discount;
67 let availableCredits = Credit - upcomingSubscriptionPrice;
68 if (availableCredits < 0) {
72 // Splitted users can't add credits in both v4 and v5 APIs, so we hide this option for them until the migration is
74 const hideAddCredits = isSplittedUser(user.ChargebeeUser, user.ChargebeeUserExists, subscription.BillingPlatform);
80 .t`When your subscription renews, we will apply any available credits before we charge the payment method above.`}
82 {hideAddCredits ? null : (
83 <div className="mb-7">
87 if (isManagedExternally(subscription)) {
88 showExternalSubscriptionModal({
95 openLinkInBrowser(getAppHref(`/dashboard${redirectFromDesktop}`, APPS.PROTONACCOUNT));
99 void openCreditModal();
101 >{c('Action').t`Add credits`}</Button>
104 <div className="px-4 mb-4 flex justify-space-between">
105 <span className="text-bold" data-testid="unused-credits">{c('Credits').t`Available credits`}</span>
106 <span className="text-bold" data-testid="available-credits">
107 {ChargebeeUser === ChargebeeEnabled.CHARGEBEE_FORCED && availableCredits > 0 ? (
108 <Price currency={Currency}>{availableCredits}</Price>
110 availableCredits / 100
115 {renderCreditModal && paymentStatus && <CreditsModal status={paymentStatus} {...creditModalProps} />}
116 {externalSubscriptionModal}
121 export default CreditsSection;