diff --git a/src/components/Payment/Payment.tsx b/src/components/Payment/Payment.tsx index e9b59573a..05e455e40 100644 --- a/src/components/Payment/Payment.tsx +++ b/src/components/Payment/Payment.tsx @@ -21,7 +21,6 @@ import type { Offer } from '#types/checkout'; import OfferSwitch from '#components/OfferSwitch/OfferSwitch'; import Alert from '#components/Alert/Alert'; import { useSubscriptionChange } from '#src/hooks/useSubscriptionChange'; -import useOffers from '#src/hooks/useOffers'; const VISIBLE_TRANSACTIONS = 4; @@ -43,6 +42,8 @@ type Props = { canUpdatePaymentMethod: boolean; canRenewSubscription?: boolean; canShowReceipts?: boolean; + offers?: Offer[]; + pendingDowngradeOfferId?: string; }; const Payment = ({ @@ -63,9 +64,9 @@ const Payment = ({ canUpdatePaymentMethod, onUpgradeSubscriptionClick, offerSwitchesAvailable, + offers = [], + pendingDowngradeOfferId = '', }: Props): JSX.Element => { - const { offers } = useOffers(); - const { t, i18n } = useTranslation(['user', 'account']); const hiddenTransactionsCount = transactions ? transactions?.length - VISIBLE_TRANSACTIONS : 0; const hasMoreTransactions = hiddenTransactionsCount > 0; @@ -100,8 +101,6 @@ const Payment = ({ const changeSubscriptionPlan = useSubscriptionChange(isUpgradeOffer ?? false, selectedOfferId, customer, activeSubscription?.subscriptionId); - const pendingDowngradeOfferId = (customer.metadata?.[`${activeSubscription?.subscriptionId}_pending_downgrade`] as string) || ''; - const onChangePlanClick = async () => { if (selectedOfferId && activeSubscription?.subscriptionId) { changeSubscriptionPlan.mutate({ diff --git a/src/containers/PaymentContainer/PaymentContainer.tsx b/src/containers/PaymentContainer/PaymentContainer.tsx new file mode 100644 index 000000000..cc2c7673c --- /dev/null +++ b/src/containers/PaymentContainer/PaymentContainer.tsx @@ -0,0 +1,114 @@ +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'; + +const PaymentContainer = () => { + const { accessModel } = useConfigStore( + (s) => ({ + accessModel: s.accessModel, + favoritesList: s.config.features?.favoritesList, + }), + shallow, + ); + const navigate = useNavigate(); + const [showAllTransactions, setShowAllTransactions] = useState(false); + const [isLoadingReceipt, setIsLoadingReceipt] = useState(false); + + const { + user: customer, + subscription: activeSubscription, + transactions, + activePayment, + pendingOffer, + loading, + canRenewSubscription, + canUpdatePaymentMethod, + canShowReceipts, + } = useAccountStore(); + 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(); + + 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} + /> + ); +}; + +export default PaymentContainer; diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 673da1a43..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(); - 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} - /> - ) : ( - - ) - } - /> + : } /> } />