diff --git a/package.json b/package.json
index e0352847c..6f3476e41 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"dependencies": {
"@adyen/adyen-web": "^5.42.1",
"@codeceptjs/allure-legacy": "^1.0.2",
- "@inplayer-org/inplayer.js": "^3.13.12",
+ "@inplayer-org/inplayer.js": "^3.13.13",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"dompurify": "^2.3.8",
diff --git a/public/locales/en/user.json b/public/locales/en/user.json
index 0b83411c0..36ca71656 100644
--- a/public/locales/en/user.json
+++ b/public/locales/en/user.json
@@ -70,9 +70,14 @@
"billing_history": "Billing history",
"cancel_subscription": "Cancel subscription",
"card_number": "Card number",
+ "change_plan": "Change Plan",
+ "change_plan_error": "There was a problem saving your subscription plan change.",
"change_subscription": "Change subscription",
"complete_subscription": "Complete subscription",
+ "current_plan": "CURRENT PLAN",
"daily_subscription": "Daily subscription",
+ "downgrade_on": "Pending downgrade on {{date}}",
+ "downgrade_plan_success": "You've successfully changed the subscription plan. Your new plan will start after the end of the current cycle ({{date}}).",
"expiry_date": "Expiry date",
"granted_subscription": "Granted subscription",
"hidden_transactions_one": "One more transaction",
@@ -85,6 +90,7 @@
"no_transactions": "No transactions",
"other": "other",
"payment_method": "Payment method",
+ "pending_downgrade": "Pending downgrade",
"pending_offer_switch": "Will update to a \"{{title}}\" after the next billing date",
"price_paid_with": "{{price}} paid with {{method}}",
"price_paid_with_card": "Price paid with card",
@@ -95,6 +101,7 @@
"subscription_details": "Subscription details",
"subscription_expires_on": "This plan will expire on {{date}}",
"update_payment_details": "Update payment details",
+ "upgrade_plan_success": "You've successfully changed the subscription plan. You can enjoy your additional benefits immediately.",
"weekly_subscription": "Weekly subscription"
}
}
diff --git a/public/locales/es/user.json b/public/locales/es/user.json
index dcdce36db..46547d523 100644
--- a/public/locales/es/user.json
+++ b/public/locales/es/user.json
@@ -70,9 +70,14 @@
"billing_history": "Historial de facturación",
"cancel_subscription": "Cancelar suscripción",
"card_number": "Número de tarjeta",
+ "change_plan": "",
+ "change_plan_error": "",
"change_subscription": "Cambiar suscripción",
"complete_subscription": "Completar suscripción",
+ "current_plan": "",
"daily_subscription": "Suscripción diaria",
+ "downgrade_on": "",
+ "downgrade_plan_success": "",
"expiry_date": "Fecha de vencimiento",
"granted_subscription": "Suscripción otorgada",
"hidden_transactions_one": "Una transacción más",
@@ -86,6 +91,7 @@
"no_transactions": "No hay transacciones",
"other": "otro",
"payment_method": "Método de pago",
+ "pending_downgrade": "",
"pending_offer_switch": "Se actualizará a \"{{title}}\" después de la próxima fecha de facturación",
"price_paid_with": "{{price}} pagado con {{method}}",
"price_paid_with_card": "Precio pagado con tarjeta",
@@ -96,6 +102,7 @@
"subscription_details": "Detalles de la suscripción",
"subscription_expires_on": "Este plan expirará el {{date}}",
"update_payment_details": "Actualizar detalles de pago",
+ "upgrade_plan_success": "",
"weekly_subscription": "Suscripción semanal"
}
}
diff --git a/src/components/OfferSwitch/OfferSwitch.module.scss b/src/components/OfferSwitch/OfferSwitch.module.scss
new file mode 100644
index 000000000..f55259025
--- /dev/null
+++ b/src/components/OfferSwitch/OfferSwitch.module.scss
@@ -0,0 +1,62 @@
+@use 'src/styles/variables';
+@use 'src/styles/theme';
+
+.offerSwitchContainer {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ height: auto;
+ padding: 16px;
+ gap: 25px;
+ color: variables.$gray-white;
+ background-color: theme.$panel-bg;
+ border-radius: 4px;
+}
+
+.activeOfferSwitchContainer {
+ color: variables.$gray-darker;
+ background-color: variables.$white;
+}
+
+.offerSwitchInfoContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ height: 100%;
+ gap: 4px;
+ font-weight: 600;
+}
+
+.offerSwitchPlanContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 2px;
+}
+
+.currentPlanHeading {
+ color: variables.$gray;
+ font-size: 10px;
+}
+
+.activeCurrentPlanHeading {
+ color: variables.$gray;
+}
+
+.nextBillingDate {
+ color: variables.$gray;
+ font-weight: 400;
+ font-size: 12px;
+}
+
+.price {
+ margin-left: auto;
+ font-size: 20px;
+ line-height: 28px;
+}
+
+.paymentFrequency {
+ font-size: 12px;
+}
diff --git a/src/components/OfferSwitch/OfferSwitch.tsx b/src/components/OfferSwitch/OfferSwitch.tsx
new file mode 100644
index 000000000..ecca4ead0
--- /dev/null
+++ b/src/components/OfferSwitch/OfferSwitch.tsx
@@ -0,0 +1,55 @@
+import classNames from 'classnames';
+import { useTranslation } from 'react-i18next';
+
+import styles from './OfferSwitch.module.scss';
+
+import type { Offer } from '#types/checkout';
+import Checkbox from '#components/Checkbox/Checkbox';
+import { formatLocalizedDate, formatPrice } from '#src/utils/formatting';
+
+type OfferSwitchProps = {
+ isCurrentOffer: boolean;
+ pendingDowngradeOfferId: string;
+ offer: Offer;
+ selected: boolean;
+ onChange: (offerId: string) => void;
+ expiresAt: number | undefined;
+};
+
+const OfferSwitch = ({ isCurrentOffer, pendingDowngradeOfferId, offer, selected, onChange, expiresAt }: OfferSwitchProps) => {
+ const { t, i18n } = useTranslation(['user', 'account']);
+ const { customerPriceInclTax, customerCurrency, period } = offer;
+
+ const isPendingDowngrade = pendingDowngradeOfferId === offer.offerId;
+
+ return (
+
+
onChange(offer.offerId)} />
+
+ {(isCurrentOffer || isPendingDowngrade) && (
+
+ {isCurrentOffer && t('user:payment.current_plan').toUpperCase()}
+ {isPendingDowngrade && t('user:payment.pending_downgrade').toUpperCase()}
+
+ )}
+
+
{t(`user:payment.${period === 'month' ? 'monthly' : 'annual'}_subscription`)}
+ {(isCurrentOffer || isPendingDowngrade) && expiresAt && (
+
+ {isCurrentOffer &&
+ !pendingDowngradeOfferId &&
+ t('user:payment.next_billing_date_on', { date: formatLocalizedDate(new Date(expiresAt * 1000), i18n.language) })}
+ {isPendingDowngrade && t('user:payment.downgrade_on', { date: formatLocalizedDate(new Date(expiresAt * 1000), i18n.language) })}
+
+ )}
+
+
+
+ {formatPrice(customerPriceInclTax, customerCurrency, undefined)}
+ /{t(`account:periods.${period}_one`)}
+
+
+ );
+};
+
+export default OfferSwitch;
diff --git a/src/components/Payment/Payment.module.scss b/src/components/Payment/Payment.module.scss
index 08a6daa95..478de3494 100644
--- a/src/components/Payment/Payment.module.scss
+++ b/src/components/Payment/Payment.module.scss
@@ -83,3 +83,20 @@
margin: 0 0 8px;
}
}
+
+.changePlanContainer {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.changePlanButtons {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ gap: 12px;
+}
+
+.changePlanCancelButton {
+ margin-left: auto;
+}
diff --git a/src/components/Payment/Payment.test.tsx b/src/components/Payment/Payment.test.tsx
index eeae8a57c..511a85c7f 100644
--- a/src/components/Payment/Payment.test.tsx
+++ b/src/components/Payment/Payment.test.tsx
@@ -25,6 +25,19 @@ describe('', () => {
isLoading={false}
offerSwitchesAvailable={false}
onShowReceiptClick={vi.fn()}
+ onUpgradeSubscriptionClick={vi.fn()}
+ onShowAllTransactionsClick={vi.fn()}
+ changeSubscriptionPlan={{
+ isLoading: false,
+ isSuccess: false,
+ isError: false,
+ reset: vi.fn(),
+ }}
+ onChangePlanClick={vi.fn()}
+ selectedOfferId={null}
+ setSelectedOfferId={vi.fn()}
+ isUpgradeOffer={undefined}
+ setIsUpgradeOffer={vi.fn()}
/>,
);
diff --git a/src/components/Payment/Payment.tsx b/src/components/Payment/Payment.tsx
index e445166a5..73913bad8 100644
--- a/src/components/Payment/Payment.tsx
+++ b/src/components/Payment/Payment.tsx
@@ -1,23 +1,25 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint';
-import IconButton from '../IconButton/IconButton';
import ExternalLink from '../../icons/ExternalLink';
+import IconButton from '../IconButton/IconButton';
import styles from './Payment.module.scss';
-import TextField from '#components/TextField/TextField';
-import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
+import Alert from '#components/Alert/Alert';
import Button from '#components/Button/Button';
-import type { Customer } from '#types/account';
+import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
+import OfferSwitch from '#components/OfferSwitch/OfferSwitch';
+import TextField from '#components/TextField/TextField';
+import PayPal from '#src/icons/PayPal';
import { formatLocalizedDate, formatPrice } from '#src/utils/formatting';
import { addQueryParam } from '#src/utils/location';
-import type { PaymentDetail, Subscription, Transaction } from '#types/subscription';
import type { AccessModel } from '#types/Config';
-import PayPal from '#src/icons/PayPal';
+import type { Customer } from '#types/account';
import type { Offer } from '#types/checkout';
+import type { PaymentDetail, Subscription, Transaction } from '#types/subscription';
const VISIBLE_TRANSACTIONS = 4;
@@ -39,6 +41,19 @@ type Props = {
canUpdatePaymentMethod: boolean;
canRenewSubscription?: boolean;
canShowReceipts?: boolean;
+ offers?: Offer[];
+ pendingDowngradeOfferId?: string;
+ changeSubscriptionPlan: {
+ isLoading: boolean;
+ isError: boolean;
+ isSuccess: boolean;
+ reset: () => void;
+ };
+ onChangePlanClick: () => void;
+ selectedOfferId: string | null;
+ setSelectedOfferId: (offerId: string | null) => void;
+ isUpgradeOffer: boolean | undefined;
+ setIsUpgradeOffer: (isUpgradeOffer: boolean | undefined) => void;
};
const Payment = ({
@@ -59,6 +74,14 @@ const Payment = ({
canUpdatePaymentMethod,
onUpgradeSubscriptionClick,
offerSwitchesAvailable,
+ offers = [],
+ pendingDowngradeOfferId = '',
+ changeSubscriptionPlan,
+ onChangePlanClick,
+ selectedOfferId,
+ setSelectedOfferId,
+ isUpgradeOffer,
+ setIsUpgradeOffer,
}: Props): JSX.Element => {
const { t, i18n } = useTranslation(['user', 'account']);
const hiddenTransactionsCount = transactions ? transactions?.length - VISIBLE_TRANSACTIONS : 0;
@@ -69,6 +92,27 @@ const Payment = ({
const breakpoint = useBreakpoint();
const isMobile = breakpoint === Breakpoint.xs;
+ const [isChangingOffer, setIsChangingOffer] = useState(false);
+
+ useEffect(() => {
+ if (!isChangingOffer) {
+ setSelectedOfferId(activeSubscription?.accessFeeId ?? null);
+ }
+ }, [activeSubscription, isChangingOffer, setSelectedOfferId]);
+
+ useEffect(() => {
+ setIsChangingOffer(false);
+ }, [activeSubscription?.status, activeSubscription?.pendingSwitchId]);
+
+ useEffect(() => {
+ if (selectedOfferId && offers) {
+ setIsUpgradeOffer(
+ (offers.find((offer) => offer.offerId === selectedOfferId)?.customerPriceInclTax ?? 0) >
+ (offers.find((offer) => offer.offerId === activeSubscription?.accessFeeId)?.customerPriceInclTax ?? 0),
+ );
+ }
+ }, [selectedOfferId, offers, activeSubscription, setIsUpgradeOffer]);
+
function onCompleteSubscriptionClick() {
navigate(addQueryParam(location, 'u', 'choose-offer'));
}
@@ -101,44 +145,84 @@ const Payment = ({
}
}
+ const showChangeSubscriptionButton = offerSwitchesAvailable || (!isChangingOffer && !canRenewSubscription);
+
return (
<>
+ {
+ changeSubscriptionPlan.reset();
+ setIsChangingOffer(false);
+ }}
+ />
{accessModel === 'SVOD' && (
-
{t('user:payment.subscription_details')}
+ {isChangingOffer ? t('user:payment.change_plan') : t('user:payment.subscription_details')}
{activeSubscription ? (
-
-
- {getTitle(activeSubscription.period)}
- {activeSubscription.status === 'active' && !isGrantedSubscription
- ? t('user:payment.next_billing_date_on', { date: formatLocalizedDate(new Date(activeSubscription.expiresAt * 1000), i18n.language) })
- : t('user:payment.subscription_expires_on', { date: formatLocalizedDate(new Date(activeSubscription.expiresAt * 1000), i18n.language) })}
- {pendingOffer && (
- {t('user:payment.pending_offer_switch', { title: getTitle(pendingOffer.period) })}
- )}
-
- {!isGrantedSubscription && (
-
- {formatPrice(activeSubscription.nextPaymentPrice, activeSubscription.nextPaymentCurrency, customer.country)}
- /{t(`account:periods.${activeSubscription.period}`)}
+ {!isChangingOffer && (
+
+
+ {getTitle(activeSubscription.period)}
+ {activeSubscription.status === 'active' && !isGrantedSubscription && !pendingDowngradeOfferId
+ ? t('user:payment.next_billing_date_on', { date: formatLocalizedDate(new Date(activeSubscription.expiresAt * 1000), i18n.language) })
+ : t('user:payment.subscription_expires_on', { date: formatLocalizedDate(new Date(activeSubscription.expiresAt * 1000), i18n.language) })}
+ {(pendingOffer || pendingDowngradeOfferId) && activeSubscription.status !== 'cancelled' && (
+
+ {t('user:payment.pending_offer_switch', {
+ title: getTitle(pendingOffer?.period || offers.find((offer) => offer.offerId === pendingDowngradeOfferId)?.period || 'month'),
+ })}
+
+ )}
- )}
-
- {offerSwitchesAvailable && (
+ {!isGrantedSubscription && (
+
+ {formatPrice(activeSubscription.nextPaymentPrice, activeSubscription.nextPaymentCurrency, customer.country)}
+ /{t(`account:periods.${activeSubscription.period}`)}
+
+ )}
+
+ )}
+ {showChangeSubscriptionButton && (
)}
+ {isChangingOffer && (
+
+ {offers
+ .filter((o) => o.planSwitchEnabled)
+ .map((offer) => (
+
setSelectedOfferId(offerId)}
+ expiresAt={activeSubscription?.expiresAt}
+ />
+ ))}
+
+
+ setIsChangingOffer(false)} variant="text" />
+ {activeSubscription?.status !== 'cancelled' && (
+
+ )}
+
+
+ )}
)}
diff --git a/src/containers/PaymentContainer/PaymentContainer.tsx b/src/containers/PaymentContainer/PaymentContainer.tsx
new file mode 100644
index 000000000..e3dc0f49d
--- /dev/null
+++ b/src/containers/PaymentContainer/PaymentContainer.tsx
@@ -0,0 +1,136 @@
+import { useEffect, useState } from 'react';
+import { useLocation, useNavigate } from 'react-router';
+import shallow from 'zustand/shallow';
+
+import styles from '#src/pages/User/User.module.scss';
+import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
+import Payment from '#components/Payment/Payment';
+import { getReceipt } from '#src/stores/AccountController';
+import { useAccountStore } from '#src/stores/AccountStore';
+import { getSubscriptionSwitches } from '#src/stores/CheckoutController';
+import { useCheckoutStore } from '#src/stores/CheckoutStore';
+import { useConfigStore } from '#src/stores/ConfigStore';
+import { addQueryParam } from '#src/utils/location';
+import useOffers from '#src/hooks/useOffers';
+import { useSubscriptionChange } from '#src/hooks/useSubscriptionChange';
+
+const PaymentContainer = () => {
+ const { accessModel } = useConfigStore(
+ (s) => ({
+ accessModel: s.accessModel,
+ favoritesList: s.config.features?.favoritesList,
+ }),
+ shallow,
+ );
+ const navigate = useNavigate();
+
+ const {
+ user: customer,
+ subscription: activeSubscription,
+ transactions,
+ activePayment,
+ pendingOffer,
+ loading,
+ canRenewSubscription,
+ canUpdatePaymentMethod,
+ canShowReceipts,
+ } = useAccountStore();
+
+ const [showAllTransactions, setShowAllTransactions] = useState(false);
+ const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
+ const [selectedOfferId, setSelectedOfferId] = useState
(activeSubscription?.accessFeeId ?? null);
+ const [isUpgradeOffer, setIsUpgradeOffer] = useState(undefined);
+
+ const { offerSwitches } = useCheckoutStore();
+ const location = useLocation();
+
+ const handleUpgradeSubscriptionClick = async () => {
+ navigate(addQueryParam(location, 'u', 'upgrade-subscription'));
+ };
+
+ const handleShowReceiptClick = async (transactionId: string) => {
+ setIsLoadingReceipt(true);
+
+ try {
+ const receipt = await getReceipt(transactionId);
+
+ if (receipt) {
+ const newWindow = window.open('', `Receipt ${transactionId}`, '');
+ const htmlString = window.atob(receipt);
+
+ if (newWindow) {
+ newWindow.opener = null;
+ newWindow.document.write(htmlString);
+ newWindow.document.close();
+ }
+ }
+ } catch (error: unknown) {
+ throw new Error("Couldn't parse receipt. " + (error instanceof Error ? error.message : ''));
+ }
+
+ setIsLoadingReceipt(false);
+ };
+
+ useEffect(() => {
+ if (accessModel !== 'AVOD') {
+ getSubscriptionSwitches();
+ }
+ }, [accessModel]);
+
+ useEffect(() => {
+ if (!loading && !customer) {
+ navigate('/', { replace: true });
+ }
+ }, [navigate, customer, loading]);
+
+ const { offers } = useOffers();
+
+ const changeSubscriptionPlan = useSubscriptionChange(isUpgradeOffer ?? false, selectedOfferId, customer, activeSubscription?.subscriptionId);
+
+ const onChangePlanClick = async () => {
+ if (selectedOfferId && activeSubscription?.subscriptionId) {
+ changeSubscriptionPlan.mutate({
+ accessFeeId: selectedOfferId.slice(1),
+ subscriptionId: `${activeSubscription.subscriptionId}`,
+ });
+ }
+ };
+
+ if (!customer) {
+ return ;
+ }
+
+ const pendingDowngradeOfferId = (customer.metadata?.[`${activeSubscription?.subscriptionId}_pending_downgrade`] as string) || '';
+
+ return (
+ setShowAllTransactions(true)}
+ showAllTransactions={showAllTransactions}
+ canUpdatePaymentMethod={canUpdatePaymentMethod}
+ canRenewSubscription={canRenewSubscription}
+ onUpgradeSubscriptionClick={handleUpgradeSubscriptionClick}
+ offerSwitchesAvailable={!!offerSwitches.length}
+ canShowReceipts={canShowReceipts}
+ onShowReceiptClick={handleShowReceiptClick}
+ offers={offers}
+ pendingDowngradeOfferId={pendingDowngradeOfferId}
+ changeSubscriptionPlan={changeSubscriptionPlan}
+ onChangePlanClick={onChangePlanClick}
+ selectedOfferId={selectedOfferId}
+ setSelectedOfferId={(offerId: string | null) => setSelectedOfferId(offerId)}
+ isUpgradeOffer={isUpgradeOffer}
+ setIsUpgradeOffer={(isUpgradeOffer: boolean | undefined) => setIsUpgradeOffer(isUpgradeOffer)}
+ />
+ );
+};
+
+export default PaymentContainer;
diff --git a/src/hooks/useOffers.ts b/src/hooks/useOffers.ts
index 62c0eff15..74a93123b 100644
--- a/src/hooks/useOffers.ts
+++ b/src/hooks/useOffers.ts
@@ -16,7 +16,6 @@ const useOffers = () => {
const { clientOffers, sandbox } = useClientIntegration();
const checkoutService: CheckoutService = useService(({ checkoutService }) => checkoutService);
- if (!checkoutService) throw new Error('checkout service is not available');
const { requestedMediaOffers } = useCheckoutStore(({ requestedMediaOffers }) => ({ requestedMediaOffers }), shallow);
const hasTvodOffer = (requestedMediaOffers || []).some((offer) => offer.offerId);
@@ -28,7 +27,7 @@ const useOffers = () => {
return [...(requestedMediaOffers || []).map(({ offerId }) => offerId), ...clientOffers].filter(Boolean);
}, [requestedMediaOffers, clientOffers]);
- const { data: allOffers, isLoading } = useQuery(['offers', offerIds.join('-')], () => checkoutService.getOffers({ offerIds }, sandbox));
+ const { data: allOffers, isLoading } = useQuery(['offers', offerIds.join('-')], () => checkoutService?.getOffers({ offerIds }, sandbox));
// The `offerQueries` variable mutates on each render which prevents the useMemo to work properly.
return useMemo(() => {
diff --git a/src/hooks/useSubscriptionChange.ts b/src/hooks/useSubscriptionChange.ts
new file mode 100644
index 000000000..fa1bf6209
--- /dev/null
+++ b/src/hooks/useSubscriptionChange.ts
@@ -0,0 +1,35 @@
+import { useMutation } from 'react-query';
+
+import { updateUser } from '#src/stores/AccountController';
+import { useAccountStore } from '#src/stores/AccountStore';
+import { changeSubscription } from '#src/stores/CheckoutController';
+import type { Customer } from '#types/account';
+
+export const useSubscriptionChange = (
+ isUpgradeOffer: boolean,
+ selectedOfferId: string | null,
+ customer: Customer | null,
+ activeSubscriptionId: string | number | undefined,
+) => {
+ const updateSubscriptionMetadata = useMutation(updateUser, {
+ onSuccess: () => {
+ useAccountStore.setState({
+ loading: false,
+ });
+ },
+ });
+
+ return useMutation(changeSubscription, {
+ onSuccess: () => {
+ if (!isUpgradeOffer && selectedOfferId) {
+ updateSubscriptionMetadata.mutate({
+ firstName: customer?.firstName || '',
+ lastName: customer?.lastName || '',
+ metadata: {
+ [`${activeSubscriptionId}_pending_downgrade`]: selectedOfferId,
+ },
+ });
+ }
+ },
+ });
+};
diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx
index e1068222c..fb0d9caab 100644
--- a/src/pages/User/User.tsx
+++ b/src/pages/User/User.tsx
@@ -1,31 +1,29 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
+import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
import shallow from 'zustand/shallow';
import styles from './User.module.scss';
+import AccountComponent from '#components/Account/Account';
+import Button from '#components/Button/Button';
+import ConfirmationDialog from '#components/ConfirmationDialog/ConfirmationDialog';
+import Favorites from '#components/Favorites/Favorites';
+import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
+import PaymentContainer from '#src/containers/PaymentContainer/PaymentContainer';
import PlaylistContainer from '#src/containers/PlaylistContainer/PlaylistContainer';
-import { mediaURL } from '#src/utils/formatting';
+import useBreakpoint, { Breakpoint } from '#src/hooks/useBreakpoint';
import AccountCircle from '#src/icons/AccountCircle';
-import Favorite from '#src/icons/Favorite';
import BalanceWallet from '#src/icons/BalanceWallet';
import Exit from '#src/icons/Exit';
+import Favorite from '#src/icons/Favorite';
+import { logout } from '#src/stores/AccountController';
import { useAccountStore } from '#src/stores/AccountStore';
+import { getSubscriptionSwitches } from '#src/stores/CheckoutController';
import { PersonalShelf, useConfigStore } from '#src/stores/ConfigStore';
-import useBreakpoint, { Breakpoint } from '#src/hooks/useBreakpoint';
-import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
-import ConfirmationDialog from '#components/ConfirmationDialog/ConfirmationDialog';
-import Payment from '#components/Payment/Payment';
-import AccountComponent from '#components/Account/Account';
-import Button from '#components/Button/Button';
-import Favorites from '#components/Favorites/Favorites';
-import type { PlaylistItem } from '#types/playlist';
-import { getReceipt, logout } from '#src/stores/AccountController';
import { clear as clearFavorites } from '#src/stores/FavoritesController';
-import { getSubscriptionSwitches } from '#src/stores/CheckoutController';
-import { useCheckoutStore } from '#src/stores/CheckoutStore';
-import { addQueryParam } from '#src/utils/location';
+import { mediaURL } from '#src/utils/formatting';
+import type { PlaylistItem } from '#types/playlist';
const User = (): JSX.Element => {
const { accessModel, favoritesList } = useConfigStore(
@@ -39,24 +37,9 @@ const User = (): JSX.Element => {
const { t } = useTranslation('user');
const breakpoint = useBreakpoint();
const [clearFavoritesOpen, setClearFavoritesOpen] = useState(false);
- const [showAllTransactions, setShowAllTransactions] = useState(false);
- const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
const isLargeScreen = breakpoint > Breakpoint.md;
- const {
- user: customer,
- subscription,
- transactions,
- activePayment,
- pendingOffer,
- loading,
- canUpdateEmail,
- canRenewSubscription,
- canUpdatePaymentMethod,
- canShowReceipts,
- } = useAccountStore();
- const offerSwitches = useCheckoutStore((state) => state.offerSwitches);
- const location = useLocation();
+ const { user: customer, subscription, loading, canUpdateEmail } = useAccountStore();
const onCardClick = (playlistItem: PlaylistItem) => navigate(mediaURL({ media: playlistItem }));
const onLogout = useCallback(async () => {
@@ -64,33 +47,6 @@ const User = (): JSX.Element => {
await logout();
}, []);
- const handleUpgradeSubscriptionClick = async () => {
- navigate(addQueryParam(location, 'u', 'upgrade-subscription'));
- };
-
- const handleShowReceiptClick = async (transactionId: string) => {
- setIsLoadingReceipt(true);
-
- try {
- const receipt = await getReceipt(transactionId);
-
- if (receipt) {
- const newWindow = window.open('', `Receipt ${transactionId}`, '');
- const htmlString = window.atob(receipt);
-
- if (newWindow) {
- newWindow.opener = null;
- newWindow.document.write(htmlString);
- newWindow.document.close();
- }
- }
- } catch (error: unknown) {
- throw new Error("Couldn't parse receipt. " + (error instanceof Error ? error.message : ''));
- }
-
- setIsLoadingReceipt(false);
- };
-
useEffect(() => {
if (accessModel !== 'AVOD') {
getSubscriptionSwitches();
@@ -175,34 +131,7 @@ const User = (): JSX.Element => {
}
/>
)}
- setShowAllTransactions(true)}
- showAllTransactions={showAllTransactions}
- canUpdatePaymentMethod={canUpdatePaymentMethod}
- canRenewSubscription={canRenewSubscription}
- onUpgradeSubscriptionClick={handleUpgradeSubscriptionClick}
- offerSwitchesAvailable={!!offerSwitches.length}
- canShowReceipts={canShowReceipts}
- onShowReceiptClick={handleShowReceiptClick}
- />
- ) : (
-
- )
- }
- />
+ : } />
} />
diff --git a/src/pages/User/__snapshots__/User.test.tsx.snap b/src/pages/User/__snapshots__/User.test.tsx.snap
index 1aec12768..108a913e0 100644
--- a/src/pages/User/__snapshots__/User.test.tsx.snap
+++ b/src/pages/User/__snapshots__/User.test.tsx.snap
@@ -636,6 +636,7 @@ exports[`User Component tests > Payments Page 1`] = `
user:payment.next_billing_date_on
+
Payments Page 1`] = `
- user:payment.cancel_subscription
+ user:payment.change_subscription
diff --git a/src/services/inplayer.account.service.ts b/src/services/inplayer.account.service.ts
index 44bd17fa2..0d4be5d48 100644
--- a/src/services/inplayer.account.service.ts
+++ b/src/services/inplayer.account.service.ts
@@ -423,12 +423,14 @@ function formatUpdateAccount(customer: UpdateCustomerArgs) {
const firstName = customer.firstName?.trim() || '';
const lastName = customer.lastName?.trim() || '';
const fullName = `${firstName} ${lastName}`.trim() || (customer.email as string);
+ const metadata: { [key: string]: string } = {
+ first_name: firstName,
+ surname: lastName,
+ ...customer.metadata,
+ };
const data: UpdateAccountData = {
fullName,
- metadata: {
- first_name: firstName,
- surname: lastName,
- },
+ metadata,
};
return data;
diff --git a/src/services/inplayer.checkout.service.ts b/src/services/inplayer.checkout.service.ts
index 9d509e390..84c00da9c 100644
--- a/src/services/inplayer.checkout.service.ts
+++ b/src/services/inplayer.checkout.service.ts
@@ -208,6 +208,7 @@ const formatOffer = (offer: AccessFee): Offer => {
active: true,
period: offer.access_type.period === 'month' && offer.access_type.quantity === 12 ? 'year' : offer.access_type.period,
freePeriods: offer.trial_period ? 1 : 0,
+ planSwitchEnabled: offer.item.plan_switch_enabled ?? false,
} as Offer;
};
diff --git a/src/services/inplayer.subscription.service.ts b/src/services/inplayer.subscription.service.ts
index a90f207ab..4dc9e3e6c 100644
--- a/src/services/inplayer.subscription.service.ts
+++ b/src/services/inplayer.subscription.service.ts
@@ -1,7 +1,7 @@
import i18next from 'i18next';
import InPlayer, { PurchaseDetails, Card, GetItemAccessV1, SubscriptionDetails as InplayerSubscription } from '@inplayer-org/inplayer.js';
-import type { PaymentDetail, Subscription, Transaction, UpdateCardDetails, UpdateSubscription } from '#types/subscription';
+import type { ChangeSubscription, PaymentDetail, Subscription, Transaction, UpdateCardDetails, UpdateSubscription } from '#types/subscription';
import type { Config } from '#types/Config';
import type { InPlayerError } from '#types/inplayer';
@@ -17,6 +17,7 @@ interface SubscriptionDetails extends InplayerSubscription {
access_type?: {
period: string;
};
+ access_fee_id?: number;
}
export async function getActiveSubscription({ config }: { config: Config }) {
@@ -94,6 +95,18 @@ export const updateSubscription: UpdateSubscription = async ({ offerId, unsubscr
}
};
+export const changeSubscription: ChangeSubscription = async ({ accessFeeId, subscriptionId }) => {
+ try {
+ const response = await InPlayer.Subscription.changeSubscriptionPlan({ access_fee_id: parseInt(accessFeeId), inplayer_token: subscriptionId });
+ return {
+ errors: [],
+ responseData: { message: response.data.message },
+ };
+ } catch {
+ throw new Error('Failed to change subscription');
+ }
+};
+
export const updateCardDetails: UpdateCardDetails = async ({ cardName, cardNumber, cvc, expMonth, expYear, currency }) => {
try {
const response = await InPlayer.Payment.setDefaultCreditCard({ cardName, cardNumber, cvc, expMonth, expYear, currency });
@@ -103,6 +116,7 @@ export const updateCardDetails: UpdateCardDetails = async ({ cardName, cardNumbe
throw new Error('Failed to update card details');
}
};
+
const formatCardDetails = (card: Card & { card_type: string; account_id: number; currency: string }): PaymentDetail => {
const { number, exp_month, exp_year, card_name, card_type, account_id, currency } = card;
const zeroFillExpMonth = `0${exp_month}`.slice(-2);
@@ -150,6 +164,8 @@ const formatActiveSubscription = (subscription: SubscriptionDetails, expiresAt:
let status = '';
switch (subscription.action_type) {
case 'free-trial':
+ status = 'active_trial';
+ break;
case 'recurrent':
status = 'active';
break;
@@ -166,6 +182,7 @@ const formatActiveSubscription = (subscription: SubscriptionDetails, expiresAt:
return {
subscriptionId: subscription.subscription_id,
offerId: subscription.item_id?.toString(),
+ accessFeeId: `S${subscription.access_fee_id}`,
status,
expiresAt,
nextPaymentAt: subscription.next_rebill_date,
diff --git a/src/stores/AccountController.ts b/src/stores/AccountController.ts
index 4f9b17dc8..d5df15082 100644
--- a/src/stores/AccountController.ts
+++ b/src/stores/AccountController.ts
@@ -442,26 +442,28 @@ export async function reloadActiveSubscription({ delay }: { delay: number } = {
});
}
-export async function exportAccountData() {
+export const exportAccountData = async () => {
return await useAccount(async () => {
return await useService(async ({ accountService }) => {
return await accountService.exportAccountData(undefined, true);
});
});
-}
+};
-export async function getSocialLoginUrls() {
+export const getSocialLoginUrls = async () => {
return await useService(async ({ accountService, config }) => {
return await accountService.getSocialUrls(config);
});
-}
-export async function deleteAccountData(password: string) {
+};
+
+export const deleteAccountData = async (password: string) => {
return await useAccount(async () => {
return await useService(async ({ accountService }) => {
return await accountService.deleteAccount({ password }, true);
});
});
-}
+};
+
export const getReceipt = async (transactionId: string) => {
return await useAccount(async () => {
return await useService(async ({ subscriptionService, sandbox = true }) => {
diff --git a/src/stores/CheckoutController.ts b/src/stores/CheckoutController.ts
index 1cc918eb6..019fbb935 100644
--- a/src/stores/CheckoutController.ts
+++ b/src/stores/CheckoutController.ts
@@ -295,6 +295,16 @@ export const switchSubscription = async (toOfferId: string, switchDirection: 'up
});
};
+export const changeSubscription = async ({ accessFeeId, subscriptionId }: { accessFeeId: string; subscriptionId: string }) => {
+ return await useService(async ({ subscriptionService, sandbox = true }) => {
+ if (!subscriptionService || !('changeSubscription' in subscriptionService)) throw new Error('subscription service is not configured');
+
+ const { responseData } = await subscriptionService.changeSubscription({ accessFeeId, subscriptionId }, sandbox);
+
+ return responseData;
+ });
+};
+
export const updatePayPalPaymentMethod = async (
successUrl: string,
cancelUrl: string,
diff --git a/src/stores/NotificationsController.ts b/src/stores/NotificationsController.ts
index 7f2146821..23c23e476 100644
--- a/src/stores/NotificationsController.ts
+++ b/src/stores/NotificationsController.ts
@@ -26,7 +26,6 @@ export const subscribeToNotifications = async (uuid: string = '') => {
break;
case NotificationsTypes.ACCESS_GRANTED:
await reloadActiveSubscription();
- window.location.href = addQueryParams(window.location.href, { u: 'welcome' });
break;
case NotificationsTypes.ACCESS_REVOKED:
await reloadActiveSubscription();
diff --git a/src/utils/formatting.ts b/src/utils/formatting.ts
index f37511145..97b873e2d 100644
--- a/src/utils/formatting.ts
+++ b/src/utils/formatting.ts
@@ -110,8 +110,8 @@ export const buildLegacySeriesUrlFromMediaItem = (media: PlaylistItem, play: boo
});
};
-export const formatPrice = (price: number, currency: string, country: string) => {
- return new Intl.NumberFormat(country || undefined, {
+export const formatPrice = (price: number, currency: string, country?: string) => {
+ return new Intl.NumberFormat(country || 'en-US', {
style: 'currency',
currency: currency,
}).format(price);
diff --git a/test-e2e/tests/payments/coupons_test.ts b/test-e2e/tests/payments/coupons_test.ts
index babc4f67d..c05cbfbe6 100644
--- a/test-e2e/tests/payments/coupons_test.ts
+++ b/test-e2e/tests/payments/coupons_test.ts
@@ -14,6 +14,7 @@ const jwProps: ProviderProps = {
locale: undefined,
shouldMakePayment: true,
canRenewSubscription: false,
+ hasInlineOfferSwitch: true,
};
const cleengProps: ProviderProps = {
@@ -26,6 +27,7 @@ const cleengProps: ProviderProps = {
locale: 'NL',
shouldMakePayment: false,
canRenewSubscription: true,
+ hasInlineOfferSwitch: false,
};
runTestSuite(jwProps, 'JW Player');
@@ -86,12 +88,12 @@ function runTestSuite(props: ProviderProps, providerName: string) {
);
}
- await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price);
+ await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch);
});
Scenario(`I can cancel a free subscription - ${providerName}`, async ({ I }) => {
couponLoginContext = await I.registerOrLogin(couponLoginContext);
- cancelPlan(I, addYear(today), props.canRenewSubscription);
+ cancelPlan(I, addYear(today), props.canRenewSubscription, providerName);
});
Scenario(`I can renew a free subscription - ${providerName}`, async ({ I }) => {
diff --git a/test-e2e/tests/payments/subscription_test.ts b/test-e2e/tests/payments/subscription_test.ts
index 36f0a7c4d..3b3291ea8 100644
--- a/test-e2e/tests/payments/subscription_test.ts
+++ b/test-e2e/tests/payments/subscription_test.ts
@@ -13,6 +13,7 @@ const jwProps: ProviderProps = {
applicableTax: 0,
canRenewSubscription: false,
fieldWrapper: '',
+ hasInlineOfferSwitch: true,
};
const cleengProps: ProviderProps = {
@@ -24,6 +25,7 @@ const cleengProps: ProviderProps = {
applicableTax: 21,
canRenewSubscription: true,
fieldWrapper: 'iframe',
+ hasInlineOfferSwitch: false,
};
runTestSuite(jwProps, 'JW Player');
@@ -170,7 +172,7 @@ function runTestSuite(props: ProviderProps, providerName: string) {
props.fieldWrapper,
);
- await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price);
+ await finishAndCheckSubscription(I, addYear(today), today, props.yearlyOffer.price, props.hasInlineOfferSwitch);
I.seeAll(cardInfo);
});
@@ -178,7 +180,7 @@ function runTestSuite(props: ProviderProps, providerName: string) {
Scenario(`I can cancel my subscription - ${providerName}`, async ({ I }) => {
paidLoginContext = await I.registerOrLogin(paidLoginContext);
- cancelPlan(I, addYear(today), props.canRenewSubscription);
+ cancelPlan(I, addYear(today), props.canRenewSubscription, providerName);
// Still see payment info
I.seeAll(cardInfo);
diff --git a/test-e2e/utils/payments.ts b/test-e2e/utils/payments.ts
index 13b14978f..8732da225 100644
--- a/test-e2e/utils/payments.ts
+++ b/test-e2e/utils/payments.ts
@@ -35,7 +35,7 @@ export function formatDate(date: Date) {
return new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'long', year: 'numeric' }).format(date);
}
-export async function finishAndCheckSubscription(I: CodeceptJS.I, billingDate: Date, today: Date, yearlyPrice: string) {
+export async function finishAndCheckSubscription(I: CodeceptJS.I, billingDate: Date, today: Date, yearlyPrice: string, hasInlineOfferSwitch: boolean) {
I.click('Continue');
I.waitForLoaderDone(longTimeout);
I.wait(2);
@@ -63,18 +63,30 @@ export async function finishAndCheckSubscription(I: CodeceptJS.I, billingDate: D
I.see(yearlyPrice);
I.see('/year');
I.see('Next billing date is on ' + formatDate(billingDate));
- I.see('Cancel subscription');
- I.waitForElement('[class*="transactionItem"]');
+ if (hasInlineOfferSwitch) {
+ I.waitForElement('[data-testid="change-subscription-button"]', 10);
+ I.click('[data-testid="change-subscription-button"]');
+ }
+
+ I.waitForElement('[data-testid="cancel-subscription-button"]', 10);
+
+ I.waitForElement('[class*="transactionItem"]', 10);
I.see(formatDate(today));
}
-export function cancelPlan(I: CodeceptJS.I, expirationDate: Date, canRenewSubscription: boolean) {
+export function cancelPlan(I: CodeceptJS.I, expirationDate: Date, canRenewSubscription: boolean, providerName?: string) {
I.amOnPage(constants.paymentsUrl);
I.waitForLoaderDone();
- I.click('Cancel subscription');
+ if (providerName?.includes('JW')) {
+ I.waitForElement('[data-testid="change-subscription-button"]', 10);
+ I.click('[data-testid="change-subscription-button"]');
+ }
+
+ I.waitForElement('[data-testid="cancel-subscription-button"]', 10);
+ I.click('[data-testid="cancel-subscription-button"]');
I.see('We are sorry to see you go.');
I.see('You will be unsubscribed from your current plan by clicking the unsubscribe button below.');
I.see('Unsubscribe');
@@ -83,7 +95,8 @@ export function cancelPlan(I: CodeceptJS.I, expirationDate: Date, canRenewSubscr
I.dontSee('This plan will expire');
- I.click('Cancel subscription');
+ I.waitForElement('[data-testid="cancel-subscription-button"]', 10);
+ I.click('[data-testid="cancel-subscription-button"]');
I.click('Unsubscribe');
I.waitForLoaderDone();
I.see('Miss you already.');
@@ -122,7 +135,7 @@ export function renewPlan(I: CodeceptJS.I, billingDate: Date, yearlyPrice: strin
I.see('Annual subscription');
I.see('Next billing date is on');
- I.see('Cancel subscription');
+ I.waitForElement('[data-testid="cancel-subscription-button"]', 10);
}
export function overrideIP(I: CodeceptJS.I) {
diff --git a/test/types.ts b/test/types.ts
index 544188650..14532ef98 100644
--- a/test/types.ts
+++ b/test/types.ts
@@ -27,4 +27,5 @@ export type ProviderProps = {
shouldMakePayment?: boolean;
locale?: string | undefined;
fieldWrapper?: string;
+ hasInlineOfferSwitch: boolean;
};
diff --git a/types/account.d.ts b/types/account.d.ts
index e03e5c119..f462205ff 100644
--- a/types/account.d.ts
+++ b/types/account.d.ts
@@ -312,6 +312,7 @@ export type UpdatePersonalShelvesArgs = {
export type FirstLastNameInput = {
firstName: string;
lastName: string;
+ metadata?: Record;
};
export type EmailConfirmPasswordInput = {
@@ -343,5 +344,5 @@ type ChangePassword = EnvironmentServiceRequest>;
type UpdatePersonalShelves = EnvironmentServiceRequest>;
type GetLocales = EmptyServiceRequest;
-type ExportAccountData = AuthServiceRequest;
+type ExportAccountData = EnvironmentServiceRequest;
type DeleteAccount = EnvironmentServiceRequest;
diff --git a/types/checkout.d.ts b/types/checkout.d.ts
index 79c6c9e82..777420b0e 100644
--- a/types/checkout.d.ts
+++ b/types/checkout.d.ts
@@ -41,6 +41,7 @@ export type Offer = {
contentExternalId: number | null;
contentExternalData: string | null;
contentAgeRestriction: string | null;
+ planSwitchEnabled?: boolean;
};
export type OrderOffer = {
diff --git a/types/subscription.d.ts b/types/subscription.d.ts
index 43908618a..9051255a4 100644
--- a/types/subscription.d.ts
+++ b/types/subscription.d.ts
@@ -1,9 +1,10 @@
-import type { DefaultCreditCardData, SetDefaultCard } from '@inplayer-org/inplayer.js';
+import type { DefaultCreditCardData, SetDefaultCard, ChangeSubscriptionPlanRequestBody, ChangeSubscriptionPlanResponse } from '@inplayer-org/inplayer.js';
// Subscription types
export type Subscription = {
subscriptionId: number | string;
offerId: string;
- status: 'active' | 'cancelled' | 'expired' | 'terminated';
+ accessFeeId?: string;
+ status: 'active' | 'active_trial' | 'cancelled' | 'expired' | 'terminated';
expiresAt: number;
nextPaymentPrice: number;
nextPaymentCurrency: string;
@@ -122,8 +123,15 @@ export type FetchReceiptPayload = {
export type FetchReceiptResponse = string;
+type ChangeSubscriptionPayload = {
+ accessFeeId: string;
+ subscriptionId: string;
+};
+
type GetSubscriptions = CleengRequest;
type UpdateSubscription = CleengRequest;
type GetPaymentDetails = CleengRequest;
type GetTransactions = CleengRequest;
type FetchReceipt = CleengRequest;
+
+type ChangeSubscription = EnvironmentServiceRequest;
diff --git a/yarn.lock b/yarn.lock
index b73d83327..d4acbe708 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1732,10 +1732,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
-"@inplayer-org/inplayer.js@^3.13.12":
- version "3.13.12"
- resolved "https://registry.yarnpkg.com/@inplayer-org/inplayer.js/-/inplayer.js-3.13.12.tgz#6a8ff6516ff92fd1a38ef4e2854de512c29ad44b"
- integrity sha512-NYmpZINBxu/vR4eTFtT84NfN0TcHH1Tplf+j36lQeiSa6Xj0mN6BlTUpZYXVseuOb9SePvGUbPyjXBCIAhALrg==
+"@inplayer-org/inplayer.js@^3.13.13":
+ version "3.13.13"
+ resolved "https://registry.yarnpkg.com/@inplayer-org/inplayer.js/-/inplayer.js-3.13.13.tgz#b64dfee35e488037f64608a8264aecc6e85252da"
+ integrity sha512-LnyriDdasvRy1CUv2Zg0THRs48s+LffhnHO7hEf4K7gOl5DaFmkf16OM1uOBuOjL4xzHGgFR3IfPz7Pay3uzsA==
dependencies:
aws-iot-device-sdk "^2.2.6"
axios "^0.21.2"
@@ -3576,7 +3576,7 @@ compare-func@^2.0.0:
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^2.0.0, concat-stream@~2.0.0:
version "2.0.0"
@@ -3683,9 +3683,9 @@ core-js-pure@^3.25.1, core-js-pure@^3.25.3:
integrity sha512-p/npFUJXXBkCCTIlEGBdghofn00jWG6ZOtdoIXSJmAu2QBvN0IqpZXWweOytcwE6cfx8ZvVUy1vw8zxhe4Y2vg==
core-js@^3.19.2:
- version "3.22.3"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.3.tgz#498c41d997654cb00e81c7a54b44f0ab21ab01d5"
- integrity sha512-1t+2a/d2lppW1gkLXx3pKPVGbBdxXAkqztvWb1EJ8oF8O2gIGiytzflNiFEehYwVK/t2ryUsGBoOFFvNx95mbg==
+ version "3.31.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.0.tgz#4471dd33e366c79d8c0977ed2d940821719db344"
+ integrity sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==
core-util-is@~1.0.0:
version "1.0.3"
@@ -3897,14 +3897,14 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1:
+debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.2.0, debug@^4.3.1:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
dependencies:
ms "2.1.2"
-debug@4.3.4, debug@^4.3.2, debug@^4.3.4:
+debug@4.3.4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -4981,7 +4981,7 @@ fs-tree-diff@^2.0.1:
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
version "2.3.2"
@@ -5013,7 +5013,17 @@ get-func-name@^2.0.0:
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
+get-intrinsic@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
+ integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+
+get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
@@ -5094,7 +5104,7 @@ glob-stream@^6.1.0:
to-absolute-glob "^2.0.0"
unique-stream "^2.0.2"
-glob@7.2.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@7.2.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@@ -5117,6 +5127,18 @@ glob@^6.0.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^7.1.6:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
global-dirs@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
@@ -5226,7 +5248,12 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
+has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
@@ -5507,7 +5534,7 @@ indent-string@^4.0.0:
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
dependencies:
once "^1.3.0"
wrappy "1"
@@ -5857,7 +5884,7 @@ is-wsl@^2.2.0:
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0:
version "2.0.0"
@@ -6645,7 +6672,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-"minimatch@2 || 3", minimatch@5.0.1, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2:
+"minimatch@2 || 3", minimatch@5.0.1, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -6673,12 +6700,12 @@ minimist@1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
-minimist@^1.1.0:
+minimist@^1.1.0, minimist@^1.2.5:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
+minimist@^1.2.0, minimist@^1.2.6:
version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
@@ -6988,11 +7015,16 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-object-inspect@^1.11.0, object-inspect@^1.9.0:
+object-inspect@^1.11.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
+object-inspect@^1.9.0:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+ integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -7051,7 +7083,7 @@ oblivious-set@1.0.0:
once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
@@ -7271,7 +7303,7 @@ path-exists@^4.0.0:
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^2.0.1:
version "2.0.1"
@@ -7971,7 +8003,20 @@ readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stre
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+readable-stream@^2.0.0, readable-stream@^2.3.3:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
+ integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -7984,7 +8029,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
-readable-stream@^3.6.0:
+readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -9326,7 +9371,7 @@ typedarray-to-buffer@^3.1.5:
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
- integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+ integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
typescript@^4.2.4, typescript@^4.3.4:
version "4.6.2"
@@ -9481,7 +9526,7 @@ use-debounce@^7.0.1:
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
- integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
uuid@9.0.0, uuid@^9.0:
version "9.0.0"
@@ -10062,7 +10107,7 @@ wrap-ansi@^7.0.0:
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
write-file-atomic@^3.0.0, write-file-atomic@^3.0.3:
version "3.0.3"