From 05e94ade7212410f220bbee21e13faf377e4898d Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Fri, 14 Jul 2023 14:13:58 +0200 Subject: [PATCH 1/7] MSSDK-1480 refactor OfferCard component --- src/components/CurrentPlan/CurrentPlan.js | 98 +---- src/components/OfferCard/OfferCard.js | 350 ------------------ src/components/OfferCard/index.js | 3 - .../OfferMyAccountCard/OfferMyAccountCard.js | 320 ++++++++++++++++ .../OfferMyAccountCardStyled.js | 107 ++++++ src/components/OfferMyAccountCard/index.js | 3 + .../OfferSwitchCard/OfferSwitchCard.js | 174 +++++++++ .../OfferSwitchCardStyled.js} | 8 - src/components/OfferSwitchCard/index.js | 3 + .../SubscriptionSwitchesList.js | 54 +-- .../UpdateSubscription/Unsubscribe.js | 18 +- src/util/eventDispatcher.js | 3 + 12 files changed, 639 insertions(+), 502 deletions(-) delete mode 100644 src/components/OfferCard/OfferCard.js delete mode 100644 src/components/OfferCard/index.js create mode 100644 src/components/OfferMyAccountCard/OfferMyAccountCard.js create mode 100644 src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js create mode 100644 src/components/OfferMyAccountCard/index.js create mode 100644 src/components/OfferSwitchCard/OfferSwitchCard.js rename src/components/{OfferCard/OfferCardStyled.js => OfferSwitchCard/OfferSwitchCardStyled.js} (91%) create mode 100644 src/components/OfferSwitchCard/index.js diff --git a/src/components/CurrentPlan/CurrentPlan.js b/src/components/CurrentPlan/CurrentPlan.js index 497c44304..28a22222d 100644 --- a/src/components/CurrentPlan/CurrentPlan.js +++ b/src/components/CurrentPlan/CurrentPlan.js @@ -6,11 +6,10 @@ import { Trans, useTranslation } from 'react-i18next'; import { getData } from 'util/appConfigHelper'; import { ReactComponent as NoSubscriptionsIcon } from 'assets/images/errors/sad_coupon.svg'; -import { dateFormat, currencyFormat, INFINITE_DATE } from 'util/planHelper'; import { setOfferToSwitch } from 'redux/planDetailsSlice'; import MyAccountError from 'components/MyAccountError'; -import OfferCard from 'components/OfferCard'; +import OfferMyAccountCard from 'components/OfferMyAccountCard'; import SubscriptionManagement from 'components/SubscriptionManagement'; import MessageBox from 'components/MessageBox'; @@ -29,7 +28,7 @@ import { export const SkeletonCard = () => { return ( - + ); }; @@ -79,15 +78,11 @@ const EmptyPlanView = () => { ); }; -const supportedPaymentGateways = ['paypal', 'card', 'adyen']; - const CurrentPlan = () => { const [isMessageBoxOpened, setIsMessageBoxOpened] = useState(false); const [messageBoxType, setMessageBoxType] = useState(null); const [messageBoxText, setMessageBoxText] = useState(''); const [messageSubscriptionId, setMessageSubscriptionId] = useState(null); - const { t } = useTranslation(); - const { pauseOffersIDs } = useSelector(store => store.offers); const { data: subscriptions, loading: isLoading, @@ -96,15 +91,6 @@ const CurrentPlan = () => { const { offerToSwitch } = useSelector(state => state.plan); const dispatch = useDispatch(); - const getInfoBoxType = subscription => { - if (subscription.offerType !== 'S') return ''; - if (subscription.status === 'active' && subscription.pendingSwitchId) - return 'SWITCH'; - if (supportedPaymentGateways.includes(subscription.paymentGateway)) - return ''; - return 'INAPP_SUBSCRIPTION'; - }; - const showMessageBox = (type, text, subscriptionId) => { setIsMessageBoxOpened(true); setMessageBoxType(type); @@ -122,56 +108,6 @@ const CurrentPlan = () => { <> {subscriptions.map(subItem => { - const isPaused = pauseOffersIDs.includes(subItem.offerId); - let description; - let price; - let currency; - let renewalDate; - - switch (subItem.offerType) { - case 'S': - price = subItem.nextPaymentPrice; - currency = currencyFormat[subItem.nextPaymentCurrency]; - renewalDate = dateFormat(subItem.expiresAt); - if (subItem.expiresAt === INFINITE_DATE) - renewalDate = t( - 'currentplan.next-season-start', - 'the next season start' - ); - if (subItem.status === 'active' && !subItem.pendingSwitchId) { - description = `${t( - 'currentplan.subscription.renews-info', - 'Renews automatically on {{renewalDate}}', - { - renewalDate - } - )}`; - } else if (subItem.status === 'cancelled') { - description = `${t( - 'currentplan.subscription.expire-info', - 'This plan will expire on {{renewalDate}}', - { - renewalDate - } - )}`; - } else { - description = ''; - } - - break; - case 'P': - price = subItem.totalPrice; - currency = currencyFormat[subItem.customerCurrency]; - description = `${t( - 'currentplan.pass.expires-on', - 'Expires on' - )} ${dateFormat(subItem.expiresAt)}`; - - break; - default: - break; - } - return ( { offerToSwitch.offerId === subItem.offerId } > - + {isMessageBoxOpened && messageSubscriptionId === subItem.subscriptionId && ( @@ -219,13 +140,12 @@ const CurrentPlan = () => { /> )} - {subItem.offerType === 'S' && - supportedPaymentGateways.includes(subItem.paymentGateway) && ( - - )} + {subItem.offerType === 'S' && !subItem.isExternallyManaged && ( + + )} ); })} diff --git a/src/components/OfferCard/OfferCard.js b/src/components/OfferCard/OfferCard.js deleted file mode 100644 index 04c75e671..000000000 --- a/src/components/OfferCard/OfferCard.js +++ /dev/null @@ -1,350 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import SubscriptionIcon from 'components/SubscriptionIcon'; -import Price from 'components/Price'; -import { ReactComponent as BlockedIcon } from 'assets/images/blocked.svg'; -import { ReactComponent as EditBlockedIcon } from 'assets/images/noEdit.svg'; -import SkeletonWrapper from 'components/SkeletonWrapper'; -import { ReactComponent as DowngradeIcon } from 'assets/images/downgrade_pending.svg'; -import { ReactComponent as UpgradeIcon } from 'assets/images/upgrade_pending.svg'; -import { ReactComponent as PauseIcon } from 'assets/images/pause_noti.svg'; -import { dateFormat, INFINITE_DATE } from 'util/planHelper'; -import { POPUP_TYPES } from 'redux/innerPopupReducer'; -import { showPopup } from 'redux/popupSlice'; - -import { - WrapperStyled, - InnerWrapper, - TitleStyled, - DescriptionStyled, - PriceWrapperStyled, - SubBoxStyled, - BoxTextStyled, - SubBoxButtonStyled, - SubBoxContentStyled -} from './OfferCardStyled'; - -const OfferCard = ({ - period, - offerType, - title, - description, - currency, - price, - showInfoBox, - isDataLoaded, - paymentMethod, - isMyAccount, - pendingSwitchId, - expiresAt, - offerId, - isPriceBoxHidden, - isPaused -}) => { - const { data: currentPlan } = useSelector(state => state.plan.currentPlan); - const { data: switchDetailsStore } = useSelector( - state => state.plan.switchDetails - ); - const { pauseOffersIDs, offers } = useSelector(state => state.offers); - - const switchDetails = switchDetailsStore[pendingSwitchId]; - const isPauseInProgress = pauseOffersIDs?.includes(switchDetails?.toOfferId); - const { t } = useTranslation(); - - const dispatch = useDispatch(); - - const getSwitchCopy = () => { - if (switchDetails) { - const subscription = currentPlan.find( - sub => sub.pendingSwitchId === pendingSwitchId - ); - const subscriptionExpirationDate = - subscription.expiresAt === INFINITE_DATE - ? t('offer-card.next-season-start', 'the next season start') - : dateFormat(subscription.expiresAt); - const { fromOfferId, toOfferId } = switchDetails; - const toOfferIdTitle = offers.find(({ longId }) => longId === toOfferId) - ?.title; - const translatedTitle = t(`offer-title-${fromOfferId}`, title); - const translatedSwitchTitle = t( - `offer-title-${toOfferId}`, - toOfferIdTitle - ); - - // if pause is in progress - if (isPauseInProgress) { - return t( - 'offer-card.info-box.pause-information-text', - 'Your current plan will be paused starting on {{subscriptionExpirationDate}}. While your subscription is paused, you won’t be charged for, and you won’t have access to, {{ translatedTitle }}. You can cancel this pause request at any time.', - { - subscriptionExpirationDate, - translatedTitle - } - ); - } - switch (switchDetails.algorithm) { - case 'IMMEDIATE_WITHOUT_PRORATION': - return t( - 'offer-card.switch-details.immediate-without-proration', - `Your switch is pending and should be completed within few minutes. You will be charged a new price starting {{subscriptionExpirationDate}}.{{translatedSwitchTitle}} renews automatically. You can cancel anytime.`, - { subscriptionExpirationDate, translatedSwitchTitle } - ); - case 'IMMEDIATE_AND_CHARGE_WITH_REFUND': - case 'IMMEDIATE_AND_CHARGE_WITHOUT_PRORATION': - return t( - 'offer-card.switch-details.immediate-and-charge-with-refund-or-without-proration', - `Your switch is pending and should be completed within few minutes. You will be charged a new price immediately and get access to {{translatedSwitchTitle}}. You can cancel anytime.`, - { translatedSwitchTitle } - ); - case 'DEFERRED': - return t( - 'offer-card.switch-details.deferred', - `Your switch is pending. You will have access to {{translatedTitle}} until {{subscriptionExpirationDate}}. From that time you will be charged your new price and will have access to {{translatedSwitchTitle}}. You can cancel this at any time.`, - { - translatedTitle, - subscriptionExpirationDate, - translatedSwitchTitle - } - ); - default: - return ''; - } - } else return ''; - }; - - const getSwitchIcon = () => { - if (switchDetails) { - // if pause is in progress - if (isPauseInProgress) { - return PauseIcon; - } - switch (switchDetails.direction) { - case 'downgrade': - return DowngradeIcon; - case 'upgrade': - return UpgradeIcon; - default: - return null; - } - } else return null; - }; - - const mapCode = { - TO_OFFER_COUNTRY_NOT_ALLOWED: { - text: t( - 'offer-card.error.geo-restriction', - `This plan is currently unavailable in your country or region` - ), - icon: BlockedIcon - }, - ALREADY_HAS_ACCESS: { - text: t( - 'offer-card.error.already-have-access', - 'It looks like you already have access to this offer' - ), - icon: BlockedIcon - }, - TO_FREE_OFFER_NOT_ALLOWED: { - text: t( - 'offer-card.error.to-free-offer', - 'Switching from a paid to a free offer is not possible' - ), - icon: BlockedIcon - }, - SUBSCRIPTION_WITH_COUPON_NOT_ALLOWED: { - text: t( - 'offer-card.error.coupon-applied', - "You can't change your subscription if a coupon was applied. To change plan, please cancel your current subscription and purchase a new one." - ), - icon: BlockedIcon - }, - SWITCH_IN_PROGRESS: { - text: t( - 'offer-card.error.switch-in-progress', - 'Another switch is already in progress. Wait until the process finalization' - ), - icon: BlockedIcon - }, - INAPP_SUBSCRIPTION: { - text: paymentMethod - ? t( - 'offer-card.error.inapp-external', - `${ - paymentMethod - ? `Subscription purchased via ${paymentMethod}. ` - : `` - } Use an external service to edit the plan.`, - { paymentMethod } - ) - : t( - 'offer-card.error.inapp-external-no-pmt-method', - 'Use an external service to edit the plan.' - ), - icon: EditBlockedIcon - }, - SWITCH: { - text: getSwitchCopy(), - icon: getSwitchIcon() - }, - MISSING_PAYMENT_DETAILS: { - text: t( - 'offer-card.error.missing-payment-details', - 'Your payment details are missing. Please add them to proceed with a subscription switch.' - ), - icon: BlockedIcon - } - }; - - const IconComponent = - showInfoBox && mapCode[showInfoBox] && mapCode[showInfoBox].icon - ? mapCode[showInfoBox].icon - : React.Fragment; - - return ( - <> - - - - - - - {t(`offer-title-${offerId}`, title)} - - - {description && ( - - )} - - - {!isPriceBoxHidden && ( - - - {((isMyAccount && offerType === 'S') || !isMyAccount) && ( - - )} - - - )} - - {showInfoBox - ? mapCode[showInfoBox] && - mapCode[showInfoBox].text && ( - - - - - {showInfoBox === 'SWITCH' && - switchDetails.algorithm === 'DEFERRED' && ( - { - window.dispatchEvent( - new CustomEvent( - 'MSSDK:cancel-switch-button-clicked', - { - detail: { - pendingSwitchId, - fromOfferId: switchDetails.fromOfferId, - toOfferId: switchDetails.toOfferId - } - } - ) - ); - - dispatch( - showPopup({ - type: isPauseInProgress - ? POPUP_TYPES.cancelPause - : POPUP_TYPES.cancelSwitch, - data: { - pendingSwitchId, - switchDirection: switchDetails.direction, - switchOfferTitle: - switchDetails && - offers.find( - ({ longId }) => - longId === switchDetails.toOfferId - )?.title, - baseOfferTitle: title, - baseOfferExpirationDate: expiresAt, - baseOfferPrice: `${currency}${price}` - } - }) - ); - }} - > - {isPauseInProgress - ? t('offer-card.cancel-pause-button', 'Cancel pause') - : t('offer-card.cancel-switch', 'Cancel switch')} - - )} - - - ) - : ''} - - ); -}; - -OfferCard.propTypes = { - period: PropTypes.string, - offerType: PropTypes.string, - title: PropTypes.string, - description: PropTypes.string, - currency: PropTypes.string, - price: PropTypes.number, - showInfoBox: PropTypes.string, - isDataLoaded: PropTypes.bool, - paymentMethod: PropTypes.string, - isMyAccount: PropTypes.bool, - pendingSwitchId: PropTypes.string, - expiresAt: PropTypes.number, - offerId: PropTypes.string, - isPriceBoxHidden: PropTypes.bool, - isPaused: PropTypes.bool -}; - -OfferCard.defaultProps = { - period: '', - offerType: '', - title: '', - description: '', - currency: '', - price: null, - showInfoBox: null, - isDataLoaded: true, - paymentMethod: '', - isMyAccount: false, - pendingSwitchId: null, - expiresAt: null, - offerId: '', - isPriceBoxHidden: false, - isPaused: false -}; - -export { OfferCard as PureOfferCard }; - -export default OfferCard; diff --git a/src/components/OfferCard/index.js b/src/components/OfferCard/index.js deleted file mode 100644 index 0f9dcfc74..000000000 --- a/src/components/OfferCard/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import OfferCard from './OfferCard'; - -export default OfferCard; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCard.js b/src/components/OfferMyAccountCard/OfferMyAccountCard.js new file mode 100644 index 000000000..c6000ba73 --- /dev/null +++ b/src/components/OfferMyAccountCard/OfferMyAccountCard.js @@ -0,0 +1,320 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import SubscriptionIcon from 'components/SubscriptionIcon'; +import Price from 'components/Price'; +import { ReactComponent as EditBlockedIcon } from 'assets/images/noEdit.svg'; +import SkeletonWrapper from 'components/SkeletonWrapper'; +import { ReactComponent as DowngradeIcon } from 'assets/images/downgrade_pending.svg'; +import { ReactComponent as UpgradeIcon } from 'assets/images/upgrade_pending.svg'; +import { ReactComponent as PauseIcon } from 'assets/images/pause_noti.svg'; +import { dateFormat, INFINITE_DATE, currencyFormat } from 'util/planHelper'; +import { POPUP_TYPES } from 'redux/innerPopupReducer'; +import { showPopup } from 'redux/popupSlice'; + +import eventDispatcher, { + MSSDK_CANCEL_SWITCH_BUTTON_CLICKED +} from 'util/eventDispatcher'; +import { + WrapperStyled, + InnerWrapper, + TitleStyled, + DescriptionStyled, + PriceWrapperStyled, + SubBoxStyled, + BoxTextStyled, + SubBoxButtonStyled, + SubBoxContentStyled +} from './OfferMyAccountCardStyled'; + +const OfferMyAccountCard = ({ offerId }) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const INFO_BOX_TYPE = { + PENDING_SWITCH: 'PENDING_SWITCH', + EXTERNALLY_MANAGED: 'EXTERNALLY_MANAGED' + }; + + const { data: currentPlan, loading } = useSelector( + state => state.plan.currentPlan + ); + + const subscriptionDetails = + currentPlan.find(sub => sub.offerId === offerId) || {}; + const { + offerType, + offerTitle, + nextPaymentPrice, + totalPrice, + nextPaymentCurrency, + customerCurrency, + paymentMethod, + pendingSwitchId, + expiresAt, + period, + status, + isExternallyManaged + } = subscriptionDetails; + + const currency = currencyFormat[nextPaymentCurrency || customerCurrency]; // use customerCurrency for passes + + const { pauseOffersIDs, offers } = useSelector(state => state.offers); + const isPaused = pauseOffersIDs.includes(offerId); + + const { data: switchDetailsStore } = useSelector( + state => state.plan.switchDetails + ); + const pendingSwitchDetails = switchDetailsStore[pendingSwitchId]; + + const isPauseInProgress = pauseOffersIDs?.includes( + pendingSwitchDetails?.toOfferId + ); + + const generateDescription = () => { + const renewalDate = + expiresAt === INFINITE_DATE + ? t('currentplan.next-season-start', 'the next season start') + : dateFormat(expiresAt); + + if (offerType === 'S' && status === 'active' && !pendingSwitchId) { + return `${t( + 'currentplan.subscription.renews-info', + 'Renews automatically on {{renewalDate}}', + { + renewalDate + } + )}`; + } + if (offerType === 'S' && status === 'cancelled') { + return `${t( + 'currentplan.subscription.expire-info', + 'This plan will expire on {{renewalDate}}', + { + renewalDate + } + )}`; + } + if (offerType === 'P') { + return `${t('currentplan.pass.expires-on', 'Expires on')} ${dateFormat( + expiresAt + )}`; + } + return ''; + }; + + const getPendingSwitchCopy = () => { + if (!pendingSwitchDetails) return null; + + const subscriptionExpirationDate = + expiresAt === INFINITE_DATE + ? t('offer-card.next-season-start', 'the next season start') + : dateFormat(expiresAt); + + const { fromOfferId, toOfferId } = pendingSwitchDetails; + const toOfferIdTitle = offers.find(({ longId }) => longId === toOfferId) + ?.title; + const translatedTitle = t(`offer-title-${fromOfferId}`, offerTitle); + const translatedSwitchTitle = t(`offer-title-${toOfferId}`, toOfferIdTitle); + + // if pause is in progress + if (isPauseInProgress) { + return t( + 'offer-card.info-box.pause-information-text', + 'Your current plan will be paused starting on {{subscriptionExpirationDate}}. While your subscription is paused, you won’t be charged for, and you won’t have access to, {{ translatedTitle }}. You can cancel this pause request at any time.', + { + subscriptionExpirationDate, + translatedTitle + } + ); + } + switch (pendingSwitchDetails.algorithm) { + case 'IMMEDIATE_WITHOUT_PRORATION': + return t( + 'offer-card.switch-details.immediate-without-proration', + `Your switch is pending and should be completed within few minutes. You will be charged a new price starting {{subscriptionExpirationDate}}.{{translatedSwitchTitle}} renews automatically. You can cancel anytime.`, + { subscriptionExpirationDate, translatedSwitchTitle } + ); + case 'IMMEDIATE_AND_CHARGE_WITH_REFUND': + case 'IMMEDIATE_AND_CHARGE_WITHOUT_PRORATION': + return t( + 'offer-card.switch-details.immediate-and-charge-with-refund-or-without-proration', + `Your switch is pending and should be completed within few minutes. You will be charged a new price immediately and get access to {{translatedSwitchTitle}}. You can cancel anytime.`, + { translatedSwitchTitle } + ); + case 'DEFERRED': + return t( + 'offer-card.switch-details.deferred', + `Your switch is pending. You will have access to {{translatedTitle}} until {{subscriptionExpirationDate}}. From that time you will be charged your new price and will have access to {{translatedSwitchTitle}}. You can cancel this at any time.`, + { + translatedTitle, + subscriptionExpirationDate, + translatedSwitchTitle + } + ); + default: + return ''; + } + }; + + const getSwitchIcon = () => { + console.log({ pendingSwitchDetails }); + if (!pendingSwitchDetails) return <>; + if (isPauseInProgress) { + return PauseIcon; + } + if (pendingSwitchDetails.direction === 'downgrade') return DowngradeIcon; + if (pendingSwitchDetails.direction === 'upgrade') return UpgradeIcon; + return <>; + }; + + const mapCode = { + [INFO_BOX_TYPE.EXTERNALLY_MANAGED]: { + text: paymentMethod + ? t( + 'offer-card.error.inapp-external', + `${ + paymentMethod + ? `Subscription purchased via ${paymentMethod}. ` + : `` + } Use an external service to edit the plan.`, + { paymentMethod } + ) + : t( + 'offer-card.error.inapp-external-no-pmt-method', + 'Use an external service to edit the plan.' + ), + icon: EditBlockedIcon + }, + [INFO_BOX_TYPE.PENDING_SWITCH]: { + text: getPendingSwitchCopy(), + icon: getSwitchIcon() + } + }; + + const infoBoxType = () => { + if (offerType === 'S' && status === 'active' && pendingSwitchId) + return INFO_BOX_TYPE.PENDING_SWITCH; + if (isExternallyManaged) return INFO_BOX_TYPE.EXTERNALLY_MANAGED; + return null; + }; + + // const IconComponent = + // infoBoxType() && mapCode[infoBoxType()] && mapCode[infoBoxType()].icon + // ? mapCode[infoBoxType()].icon + // : React.Fragment; + + return ( + <> + + + + + + + {t(`offer-title-${offerId}`, offerTitle)} + + + {generateDescription() && ( + + )} + + + {!isPaused && ( + + + {offerType === 'S' && ( + + )} + + + )} + + + {infoBoxType() ? ( + + {/* */} + + + {infoBoxType() === INFO_BOX_TYPE.PENDING_SWITCH && + pendingSwitchDetails?.algorithm === 'DEFERRED' ? ( + { + eventDispatcher(MSSDK_CANCEL_SWITCH_BUTTON_CLICKED, { + pendingSwitchId, + fromOfferId: pendingSwitchDetails.fromOfferId, + toOfferId: pendingSwitchDetails.toOfferId + }); + + dispatch( + showPopup({ + type: isPauseInProgress + ? POPUP_TYPES.cancelPause + : POPUP_TYPES.cancelSwitch, + data: { + pendingSwitchId, + switchDirection: pendingSwitchDetails.direction, + switchOfferTitle: + pendingSwitchDetails && + offers.find( + ({ longId }) => + longId === pendingSwitchDetails.toOfferId + )?.title, + baseOfferTitle: offerTitle, + baseOfferExpirationDate: expiresAt, + baseOfferPrice: `${currency}${nextPaymentPrice}` + } + }) + ); + }} + > + {isPauseInProgress + ? t('offer-card.cancel-pause-button', 'Cancel pause') + : t('offer-card.cancel-switch', 'Cancel switch')} + + ) : ( + <> + )} + + + ) : ( + <> + )} + + ); +}; + +OfferMyAccountCard.propTypes = { + offerId: PropTypes.string +}; + +OfferMyAccountCard.defaultProps = { + offerId: '' +}; + +export { OfferMyAccountCard as PureOfferMyAccountCard }; + +export default OfferMyAccountCard; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js b/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js new file mode 100644 index 000000000..563648cf8 --- /dev/null +++ b/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js @@ -0,0 +1,107 @@ +import styled from 'styled-components'; +import { mediaFrom } from 'styles/BreakPoints'; +import { + BoldFont, + MediumFont, + MediumFontWeight, + TinyFont, + SmallFont, + MicroFont, + FontColor, + White, + LineColor, + BackgroundColor, + ConfirmColor +} from 'styles/variables'; + +export const WrapperStyled = styled.section` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + width: 100%; +`; + +export const InnerWrapper = styled.div.attrs(() => ({ + className: 'msd__subscription-text__wrapper' +}))` + max-width: 50%; + color: ${FontColor}; + margin-inline-end: 15px; + + ${mediaFrom.small` + max-width: none; + margin-inline-end: 20px; + `} +`; + +export const TitleStyled = styled.h2.attrs(() => ({ + className: 'msd__subscription-text__title' +}))` + margin: 0 auto 3px 0; + + font-weight: ${BoldFont}; + font-size: ${SmallFont}; + line-height: 15px; + + ${mediaFrom.small` + font-size: ${MediumFont}; + line-height: 19px; + `}; +`; +export const DescriptionStyled = styled.p.attrs(() => ({ + className: 'msd__subscription-text__description' +}))` + font-size: ${TinyFont}; + font-weight: ${MediumFontWeight}; + line-height: 17px; + margin: 9px 0; +`; + +export const PriceWrapperStyled = styled.div.attrs(() => ({ + className: 'msd__subscription-price__wrapper' +}))` + display: flex; + flex-direction: column; + margin-inline: auto 0; +`; + +export const SubBoxStyled = styled.div.attrs(() => ({ + className: 'msd__subscription-subcard' +}))` + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-start; + margin: 15px 0; + padding: 12px; + border: 1px solid ${LineColor}; + background: ${BackgroundColor}; + border-radius: 4px; + svg { + min-width: 18px; + width: 18px; + } +`; + +export const BoxTextStyled = styled.p.attrs(() => ({ + className: 'msd__subscription-subcard__title' +}))` + font-size: ${TinyFont}; + color: ${FontColor}; + margin-inline: 10px 0; + line-height: initial; +`; + +export const SubBoxContentStyled = styled.div``; + +export const SubBoxButtonStyled = styled.button` + padding: 0; + margin: 8px 0 0 10px; + background-color: transparent; + color: ${ConfirmColor}; + border: none; + font-size: 13px; + font-weight: 600; + cursor: pointer; +`; diff --git a/src/components/OfferMyAccountCard/index.js b/src/components/OfferMyAccountCard/index.js new file mode 100644 index 000000000..2a4237c5d --- /dev/null +++ b/src/components/OfferMyAccountCard/index.js @@ -0,0 +1,3 @@ +import OfferMyAccountCard from './OfferMyAccountCard'; + +export default OfferMyAccountCard; diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.js b/src/components/OfferSwitchCard/OfferSwitchCard.js new file mode 100644 index 000000000..f63a66859 --- /dev/null +++ b/src/components/OfferSwitchCard/OfferSwitchCard.js @@ -0,0 +1,174 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import SubscriptionIcon from 'components/SubscriptionIcon'; +import Price from 'components/Price'; +import { ReactComponent as BlockedIcon } from 'assets/images/blocked.svg'; +import SkeletonWrapper from 'components/SkeletonWrapper'; +import { currencyFormat, periodMapper } from 'util/planHelper'; +import isPriceTemporaryModified from 'util/isPriceTemporaryModified'; + +import { + WrapperStyled, + InnerWrapper, + TitleStyled, + PriceWrapperStyled, + SubBoxStyled, + BoxTextStyled, + SubBoxContentStyled +} from './OfferSwitchCardStyled'; + +const OfferCard = ({ baseOfferId, toOfferId }) => { + console.log({ baseOfferId }); + console.log({ toOfferId }); + + const { t } = useTranslation(); + + const { + data: allSwitchSettings, + loading: isSwitchSettingsLoading + } = useSelector(state => state.plan.switchSettings); + const switchDetails = + allSwitchSettings[baseOfferId].available.find( + switchData => switchData.toOfferId === toOfferId + ) || + allSwitchSettings[baseOfferId].unavailable.find( + switchData => switchData.toOfferId === toOfferId + ) || + {}; + console.log({ switchDetails }); + const period = periodMapper[switchDetails?.period]?.chargedForEveryText; + const currency = currencyFormat[switchDetails?.nextPaymentPriceCurrency]; + + const mapCode = { + TO_OFFER_COUNTRY_NOT_ALLOWED: { + text: t( + 'offer-card.error.geo-restriction', + `This plan is currently unavailable in your country or region` + ), + icon: BlockedIcon + }, + ALREADY_HAS_ACCESS: { + text: t( + 'offer-card.error.already-have-access', + 'It looks like you already have access to this offer' + ), + icon: BlockedIcon + }, + TO_FREE_OFFER_NOT_ALLOWED: { + text: t( + 'offer-card.error.to-free-offer', + 'Switching from a paid to a free offer is not possible' + ), + icon: BlockedIcon + }, + SUBSCRIPTION_WITH_COUPON_NOT_ALLOWED: { + text: t( + 'offer-card.error.coupon-applied', + "You can't change your subscription if a coupon was applied. To change plan, please cancel your current subscription and purchase a new one." + ), + icon: BlockedIcon + }, + SWITCH_IN_PROGRESS: { + text: t( + 'offer-card.error.switch-in-progress', + 'Another switch is already in progress. Wait until the process finalization' + ), + icon: BlockedIcon + }, + MISSING_PAYMENT_DETAILS: { + text: t( + 'offer-card.error.missing-payment-details', + 'Your payment details are missing. Please add them to proceed with a subscription switch.' + ), + icon: BlockedIcon + } + }; + + const IconComponent = + switchDetails?.reason?.code && mapCode[switchDetails.reason.code]?.icon + ? mapCode[switchDetails.reason.code].icon + : React.Fragment; + + return ( + <> + + + + + + + + {t(`offer-title-${toOfferId}`, switchDetails.title)} + + + + + + + + + + + {switchDetails?.reason?.code + ? mapCode[switchDetails.reason.code] && + mapCode[switchDetails.reason.code].text && ( + + + + + + + ) + : ''} + + ); +}; + +OfferCard.propTypes = { + baseOfferId: PropTypes.string.isRequired, + toOfferId: PropTypes.string.isRequired +}; + +OfferCard.defaultProps = {}; + +export { OfferCard as PureOfferCard }; + +export default OfferCard; diff --git a/src/components/OfferCard/OfferCardStyled.js b/src/components/OfferSwitchCard/OfferSwitchCardStyled.js similarity index 91% rename from src/components/OfferCard/OfferCardStyled.js rename to src/components/OfferSwitchCard/OfferSwitchCardStyled.js index a6dce4d84..39d6d3d46 100644 --- a/src/components/OfferCard/OfferCardStyled.js +++ b/src/components/OfferSwitchCard/OfferSwitchCardStyled.js @@ -49,14 +49,6 @@ export const TitleStyled = styled.h2.attrs(() => ({ line-height: 19px; `}; `; -export const DescriptionStyled = styled.p.attrs(() => ({ - className: 'msd__subscription-text__description' -}))` - font-size: ${TinyFont}; - font-weight: ${MediumFontWeight}; - line-height: 17px; - margin: 9px 0; -`; export const PriceWrapperStyled = styled.div.attrs(() => ({ className: 'msd__subscription-price__wrapper' diff --git a/src/components/OfferSwitchCard/index.js b/src/components/OfferSwitchCard/index.js new file mode 100644 index 000000000..ba57e5ed6 --- /dev/null +++ b/src/components/OfferSwitchCard/index.js @@ -0,0 +1,3 @@ +import OfferSwitchCard from './OfferSwitchCard'; + +export default OfferSwitchCard; diff --git a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js index e72609b8f..a0cf34a2e 100644 --- a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js +++ b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js @@ -4,14 +4,15 @@ import { useSelector, useDispatch } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { SubscriptionStyled } from 'components/CurrentPlan/CurrentPlanStyled'; import { SimpleButtonStyled } from 'components/SubscriptionManagement/SubscriptionManagementStyled'; -import OfferCard from 'components/OfferCard'; +import OfferSwitchCard from 'components/OfferSwitchCard'; import MyAccountError from 'components/MyAccountError'; import { ReactComponent as selectPlanIcon } from 'assets/images/selectPlan.svg'; import { SkeletonCard } from 'components/CurrentPlan/CurrentPlan'; import { POPUP_TYPES } from 'redux/innerPopupReducer'; -import { periodMapper, currencyFormat } from 'util/planHelper'; -import isPriceTemporaryModified from 'util/isPriceTemporaryModified'; import { showPopup } from 'redux/popupSlice'; +import eventDispatcher, { + MSSDK_SWITCH_BUTTON_CLICKED +} from 'util/eventDispatcher'; import { ButtonWrapperStyled } from './SubscriptionSwitchesListStyled'; import mapErrorToText from './helper'; @@ -122,11 +123,6 @@ const SubscriptionSwitchesList = () => { <> {areAvailable && availableFiltered.map(subItem => { - const price = - isPriceTemporaryModified(subItem.toOfferId) && - subItem.algorithm !== 'DEFERRED' - ? subItem.price - : subItem.nextPaymentPrice; return ( { item => item === subItem.toOfferId )} > - { - window.dispatchEvent( - new CustomEvent('MSSDK:switch-button-clicked', { - detail: { - fromOfferId, - toOfferId: subItem.toOfferId, - switchDirection: subItem.switchDirection, - algorithm: subItem.algorithm - } - }) - ); + eventDispatcher(MSSDK_SWITCH_BUTTON_CLICKED, { + fromOfferId, + toOfferId: subItem.toOfferId, + switchDirection: subItem.switchDirection, + algorithm: subItem.algorithm + }); dispatch( showPopup({ type: POPUP_TYPES.switchPlan, @@ -179,21 +167,11 @@ const SubscriptionSwitchesList = () => { })} {areUnAvailable && unavailableFiltered.map(subItem => { - const price = - isPriceTemporaryModified(subItem.toOfferId) && - subItem.algorithm !== 'DEFERRED' - ? subItem.price - : subItem.nextPaymentPrice; return ( - diff --git a/src/components/UpdateSubscription/Unsubscribe.js b/src/components/UpdateSubscription/Unsubscribe.js index 5202b0d8f..6a121f18c 100644 --- a/src/components/UpdateSubscription/Unsubscribe.js +++ b/src/components/UpdateSubscription/Unsubscribe.js @@ -19,7 +19,7 @@ import Button from 'components/Button'; import Checkbox from 'components/Checkbox'; import InnerPopupWrapper from 'components/InnerPopupWrapper'; import Loader from 'components/Loader'; -import OfferCard from 'components/OfferCard'; +import OfferSwitchCard from 'components/OfferSwitchCard'; import { updateList } from 'redux/planDetailsSlice'; import { showPopup, hidePopup } from 'redux/popupSlice'; @@ -331,19 +331,9 @@ const Unsubscribe = ({ } key={downgradeOffer.toOfferId} > - ); diff --git a/src/util/eventDispatcher.js b/src/util/eventDispatcher.js index ece35a0cc..f8a30188e 100644 --- a/src/util/eventDispatcher.js +++ b/src/util/eventDispatcher.js @@ -34,3 +34,6 @@ export const MSSDK_CANCEL_SWITCH_ACTION_FAILED = 'MSSDK:cancel-switch-action-failed'; export const MSSDK_CANCEL_SWITCH_ACTION_CANCELLED = 'MSSDK:cancel-switch-action-cancelled'; +export const MSSDK_CANCEL_SWITCH_BUTTON_CLICKED = + 'MSSDK:cancel-switch-button-clicked'; +export const MSSDK_SWITCH_BUTTON_CLICKED = 'MSSDK:switch-button-clicked'; From 3d3b84660e7622077d9642d3d7fea3d6e4f94c13 Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Mon, 31 Jul 2023 14:15:58 +0200 Subject: [PATCH 2/7] rewrite OfferMyAccountCard to TS --- .../Customer/types/getCustomerOffers.types.ts | 3 +- ...yAccountCard.js => OfferMyAccountCard.tsx} | 128 ++++++++---------- .../OfferMyAccountCard.types.ts | 3 + ...dStyled.js => OfferMyAccountCardStyled.ts} | 2 - .../OfferMyAccountCard/{index.js => index.ts} | 0 src/redux/offersSlice.js | 2 + src/redux/planDetailsSlice.ts | 6 + src/redux/types/offersSlice.types.ts | 34 +++++ src/redux/types/popupSlice.types.ts | 6 +- 9 files changed, 107 insertions(+), 77 deletions(-) rename src/components/OfferMyAccountCard/{OfferMyAccountCard.js => OfferMyAccountCard.tsx} (75%) create mode 100644 src/components/OfferMyAccountCard/OfferMyAccountCard.types.ts rename src/components/OfferMyAccountCard/{OfferMyAccountCardStyled.js => OfferMyAccountCardStyled.ts} (99%) rename src/components/OfferMyAccountCard/{index.js => index.ts} (100%) create mode 100644 src/redux/types/offersSlice.types.ts diff --git a/src/api/Customer/types/getCustomerOffers.types.ts b/src/api/Customer/types/getCustomerOffers.types.ts index 4f7479be8..768a6a051 100644 --- a/src/api/Customer/types/getCustomerOffers.types.ts +++ b/src/api/Customer/types/getCustomerOffers.types.ts @@ -15,8 +15,9 @@ export type CustomerOffer = { nextPaymentPrice: number; paymentGateway: PaymentGateway; paymentMethod: string; - pendingSwitchId: unknown; + pendingSwitchId: string; period: string; subscriptionId?: number; customerCurrency: string; + isExternallyManaged: boolean; }; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCard.js b/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx similarity index 75% rename from src/components/OfferMyAccountCard/OfferMyAccountCard.js rename to src/components/OfferMyAccountCard/OfferMyAccountCard.tsx index c6000ba73..213bdb182 100644 --- a/src/components/OfferMyAccountCard/OfferMyAccountCard.js +++ b/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useDispatch, useSelector } from 'react-redux'; +import { useAppDispatch, useAppSelector } from 'redux/store'; +import { selectCurrentPlan, selectSwitchDetails } from 'redux/planDetailsSlice'; +import { selectOffers } from 'redux/offersSlice'; import { useTranslation } from 'react-i18next'; import SubscriptionIcon from 'components/SubscriptionIcon'; import Price from 'components/Price'; @@ -9,10 +9,15 @@ import SkeletonWrapper from 'components/SkeletonWrapper'; import { ReactComponent as DowngradeIcon } from 'assets/images/downgrade_pending.svg'; import { ReactComponent as UpgradeIcon } from 'assets/images/upgrade_pending.svg'; import { ReactComponent as PauseIcon } from 'assets/images/pause_noti.svg'; -import { dateFormat, INFINITE_DATE, currencyFormat } from 'util/planHelper'; -import { POPUP_TYPES } from 'redux/innerPopupReducer'; -import { showPopup } from 'redux/popupSlice'; - +import { + dateFormat, + INFINITE_DATE, + currencyFormat, + CurrencyFormat +} from 'util/planHelper'; +import { CustomerOffer } from 'api/Customer/types'; +import { showPopup, POPUP_TYPES } from 'redux/popupSlice'; +import { Offer } from 'redux/types/offersSlice.types'; import eventDispatcher, { MSSDK_CANCEL_SWITCH_BUTTON_CLICKED } from 'util/eventDispatcher'; @@ -27,22 +32,14 @@ import { SubBoxButtonStyled, SubBoxContentStyled } from './OfferMyAccountCardStyled'; +import { OfferMyAccountCardProps } from './OfferMyAccountCard.types'; -const OfferMyAccountCard = ({ offerId }) => { +const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { const { t } = useTranslation(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); - const INFO_BOX_TYPE = { - PENDING_SWITCH: 'PENDING_SWITCH', - EXTERNALLY_MANAGED: 'EXTERNALLY_MANAGED' - }; + const { data: currentPlan, loading } = useAppSelector(selectCurrentPlan); - const { data: currentPlan, loading } = useSelector( - state => state.plan.currentPlan - ); - - const subscriptionDetails = - currentPlan.find(sub => sub.offerId === offerId) || {}; const { offerType, offerTitle, @@ -56,16 +53,22 @@ const OfferMyAccountCard = ({ offerId }) => { period, status, isExternallyManaged - } = subscriptionDetails; + } = + currentPlan.find((sub: CustomerOffer) => sub.offerId === offerId) || + ({} as CustomerOffer); - const currency = currencyFormat[nextPaymentCurrency || customerCurrency]; // use customerCurrency for passes + const currency = + currencyFormat[ + (nextPaymentCurrency || customerCurrency) as keyof Record< + CurrencyFormat, + string + > + ]; // use customerCurrency for passes - const { pauseOffersIDs, offers } = useSelector(state => state.offers); + const { pauseOffersIDs, offers } = useAppSelector(selectOffers); const isPaused = pauseOffersIDs.includes(offerId); - const { data: switchDetailsStore } = useSelector( - state => state.plan.switchDetails - ); + const { data: switchDetailsStore } = useAppSelector(selectSwitchDetails); const pendingSwitchDetails = switchDetailsStore[pendingSwitchId]; const isPauseInProgress = pauseOffersIDs?.includes( @@ -113,8 +116,9 @@ const OfferMyAccountCard = ({ offerId }) => { : dateFormat(expiresAt); const { fromOfferId, toOfferId } = pendingSwitchDetails; - const toOfferIdTitle = offers.find(({ longId }) => longId === toOfferId) - ?.title; + const toOfferIdTitle = offers.find( + ({ longId }: Offer) => longId === toOfferId + )?.title; const translatedTitle = t(`offer-title-${fromOfferId}`, offerTitle); const translatedSwitchTitle = t(`offer-title-${toOfferId}`, toOfferIdTitle); @@ -158,20 +162,15 @@ const OfferMyAccountCard = ({ offerId }) => { } }; - const getSwitchIcon = () => { - console.log({ pendingSwitchDetails }); - if (!pendingSwitchDetails) return <>; - if (isPauseInProgress) { - return PauseIcon; - } - if (pendingSwitchDetails.direction === 'downgrade') return DowngradeIcon; - if (pendingSwitchDetails.direction === 'upgrade') return UpgradeIcon; - return <>; + const shouldShowInfoBox = () => { + if (isExternallyManaged || pendingSwitchId) return true; + return false; }; - const mapCode = { - [INFO_BOX_TYPE.EXTERNALLY_MANAGED]: { - text: paymentMethod + const getDescription = () => { + if (pendingSwitchId) return getPendingSwitchCopy(); + if (isExternallyManaged) { + return paymentMethod ? t( 'offer-card.error.inapp-external', `${ @@ -184,26 +183,19 @@ const OfferMyAccountCard = ({ offerId }) => { : t( 'offer-card.error.inapp-external-no-pmt-method', 'Use an external service to edit the plan.' - ), - icon: EditBlockedIcon - }, - [INFO_BOX_TYPE.PENDING_SWITCH]: { - text: getPendingSwitchCopy(), - icon: getSwitchIcon() + ); } + return ''; }; - const infoBoxType = () => { - if (offerType === 'S' && status === 'active' && pendingSwitchId) - return INFO_BOX_TYPE.PENDING_SWITCH; - if (isExternallyManaged) return INFO_BOX_TYPE.EXTERNALLY_MANAGED; - return null; + const getIcon = () => { + if (isPauseInProgress) return PauseIcon; + if (pendingSwitchDetails?.direction === 'downgrade') return DowngradeIcon; + if (pendingSwitchDetails?.direction === 'upgrade') return UpgradeIcon; + return EditBlockedIcon; }; - // const IconComponent = - // infoBoxType() && mapCode[infoBoxType()] && mapCode[infoBoxType()].icon - // ? mapCode[infoBoxType()].icon - // : React.Fragment; + const IconComponent = getIcon(); return ( <> @@ -217,7 +209,9 @@ const OfferMyAccountCard = ({ offerId }) => { width={200} margin="0 10px 10px 10px" > - {t(`offer-title-${offerId}`, offerTitle)} + + <>{t(`offer-title-${offerId}`, offerTitle)} + { )} - {infoBoxType() ? ( + {shouldShowInfoBox() ? ( - {/* */} + - {infoBoxType() === INFO_BOX_TYPE.PENDING_SWITCH && + {pendingSwitchId && pendingSwitchDetails?.algorithm === 'DEFERRED' ? ( { @@ -272,15 +266,15 @@ const OfferMyAccountCard = ({ offerId }) => { dispatch( showPopup({ type: isPauseInProgress - ? POPUP_TYPES.cancelPause - : POPUP_TYPES.cancelSwitch, + ? POPUP_TYPES.CANCEL_PAUSE_POPUP + : POPUP_TYPES.CANCEL_SWITCH_POPUP, data: { pendingSwitchId, switchDirection: pendingSwitchDetails.direction, switchOfferTitle: pendingSwitchDetails && offers.find( - ({ longId }) => + ({ longId }: Offer) => longId === pendingSwitchDetails.toOfferId )?.title, baseOfferTitle: offerTitle, @@ -307,14 +301,4 @@ const OfferMyAccountCard = ({ offerId }) => { ); }; -OfferMyAccountCard.propTypes = { - offerId: PropTypes.string -}; - -OfferMyAccountCard.defaultProps = { - offerId: '' -}; - -export { OfferMyAccountCard as PureOfferMyAccountCard }; - export default OfferMyAccountCard; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCard.types.ts b/src/components/OfferMyAccountCard/OfferMyAccountCard.types.ts new file mode 100644 index 000000000..26d148ede --- /dev/null +++ b/src/components/OfferMyAccountCard/OfferMyAccountCard.types.ts @@ -0,0 +1,3 @@ +export type OfferMyAccountCardProps = { + offerId: string; +}; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js b/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.ts similarity index 99% rename from src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js rename to src/components/OfferMyAccountCard/OfferMyAccountCardStyled.ts index 563648cf8..3d714f823 100644 --- a/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.js +++ b/src/components/OfferMyAccountCard/OfferMyAccountCardStyled.ts @@ -6,9 +6,7 @@ import { MediumFontWeight, TinyFont, SmallFont, - MicroFont, FontColor, - White, LineColor, BackgroundColor, ConfirmColor diff --git a/src/components/OfferMyAccountCard/index.js b/src/components/OfferMyAccountCard/index.ts similarity index 100% rename from src/components/OfferMyAccountCard/index.js rename to src/components/OfferMyAccountCard/index.ts diff --git a/src/redux/offersSlice.js b/src/redux/offersSlice.js index a1d0d0d05..c7b14d1af 100644 --- a/src/redux/offersSlice.js +++ b/src/redux/offersSlice.js @@ -47,4 +47,6 @@ export const offersSlice = createSlice({ } }); +export const selectOffers = state => state.offers; + export default offersSlice.reducer; diff --git a/src/redux/planDetailsSlice.ts b/src/redux/planDetailsSlice.ts index f352dd90f..f516c3b79 100644 --- a/src/redux/planDetailsSlice.ts +++ b/src/redux/planDetailsSlice.ts @@ -162,6 +162,12 @@ export const selectPlanDetails = (state: RootState) => state.plan; export const selectCurrentPlan = (state: RootState) => state.plan.currentPlan; +export const selectCurrentPlanDetails = (state: RootState) => + state.plan.currentPlan; + +export const selectSwitchDetails = (state: RootState) => + state.plan.switchDetails; + export const { setOfferToSwitch, resetOfferToSwitch, diff --git a/src/redux/types/offersSlice.types.ts b/src/redux/types/offersSlice.types.ts new file mode 100644 index 000000000..e022db5ed --- /dev/null +++ b/src/redux/types/offersSlice.types.ts @@ -0,0 +1,34 @@ +export type Offer = { + id: string; + longId: string; + title: string; + active: boolean; + price: { + amount: number; + currency: string; + taxIncluded: boolean; + }; + type: 'subscription' | 'pass' | 'rental' | 'live' | 'single'; + billingCycle?: { + periodUnit: 'week' | 'month' | 'year'; + amount: number; + }; + entitlement?: { + expirationPolicy: 'fixed-date' | 'duration'; + expiresAt: number; + duration: { + amount: number; + periodUnit: string; + }; + }; + externalProperties: []; + customExternalValue: string; + appStoreProductIds: object; + createdAt: number; + updatedAt: number; + imageUrl: string; + tags: string[]; + hasLandingPage: boolean; +}; + +export type Offers = Offer[]; diff --git a/src/redux/types/popupSlice.types.ts b/src/redux/types/popupSlice.types.ts index 2ae1354d8..af0525e13 100644 --- a/src/redux/types/popupSlice.types.ts +++ b/src/redux/types/popupSlice.types.ts @@ -80,16 +80,18 @@ type PauseSubscription = { type CancelSwitch = { pendingSwitchId: string; switchDirection: string; + switchOfferTitle: string; baseOfferTitle: string; - baseOfferExpirationDate: string; + baseOfferExpirationDate: number; baseOfferPrice: string; }; type CancelPause = { pendingSwitchId: string; switchDirection: string; + switchOfferTitle: string; baseOfferTitle: string; - baseOfferExpirationDate: string; + baseOfferExpirationDate: number; baseOfferPrice: string; }; From 49506d099b9a0fe3f805f5aef938d0c9a5dcaeea Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Tue, 1 Aug 2023 07:33:25 +0200 Subject: [PATCH 3/7] rewrite OfferSwtichCard to TS --- .../OfferSwitchCard/OfferSwitchCard.js | 174 ------------------ .../OfferSwitchCard/OfferSwitchCard.tsx | 165 +++++++++++++++++ .../OfferSwitchCard/OfferSwitchCard.types.ts | 4 + ...CardStyled.js => OfferSwitchCardStyled.ts} | 0 .../OfferSwitchCard/{index.js => index.ts} | 0 src/redux/planDetailsSlice.ts | 3 + src/redux/types/planDetailsSlice.types.ts | 6 +- src/util/planHelper.ts | 20 +- 8 files changed, 186 insertions(+), 186 deletions(-) delete mode 100644 src/components/OfferSwitchCard/OfferSwitchCard.js create mode 100644 src/components/OfferSwitchCard/OfferSwitchCard.tsx create mode 100644 src/components/OfferSwitchCard/OfferSwitchCard.types.ts rename src/components/OfferSwitchCard/{OfferSwitchCardStyled.js => OfferSwitchCardStyled.ts} (100%) rename src/components/OfferSwitchCard/{index.js => index.ts} (100%) diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.js b/src/components/OfferSwitchCard/OfferSwitchCard.js deleted file mode 100644 index f63a66859..000000000 --- a/src/components/OfferSwitchCard/OfferSwitchCard.js +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; -import { useTranslation } from 'react-i18next'; -import SubscriptionIcon from 'components/SubscriptionIcon'; -import Price from 'components/Price'; -import { ReactComponent as BlockedIcon } from 'assets/images/blocked.svg'; -import SkeletonWrapper from 'components/SkeletonWrapper'; -import { currencyFormat, periodMapper } from 'util/planHelper'; -import isPriceTemporaryModified from 'util/isPriceTemporaryModified'; - -import { - WrapperStyled, - InnerWrapper, - TitleStyled, - PriceWrapperStyled, - SubBoxStyled, - BoxTextStyled, - SubBoxContentStyled -} from './OfferSwitchCardStyled'; - -const OfferCard = ({ baseOfferId, toOfferId }) => { - console.log({ baseOfferId }); - console.log({ toOfferId }); - - const { t } = useTranslation(); - - const { - data: allSwitchSettings, - loading: isSwitchSettingsLoading - } = useSelector(state => state.plan.switchSettings); - const switchDetails = - allSwitchSettings[baseOfferId].available.find( - switchData => switchData.toOfferId === toOfferId - ) || - allSwitchSettings[baseOfferId].unavailable.find( - switchData => switchData.toOfferId === toOfferId - ) || - {}; - console.log({ switchDetails }); - const period = periodMapper[switchDetails?.period]?.chargedForEveryText; - const currency = currencyFormat[switchDetails?.nextPaymentPriceCurrency]; - - const mapCode = { - TO_OFFER_COUNTRY_NOT_ALLOWED: { - text: t( - 'offer-card.error.geo-restriction', - `This plan is currently unavailable in your country or region` - ), - icon: BlockedIcon - }, - ALREADY_HAS_ACCESS: { - text: t( - 'offer-card.error.already-have-access', - 'It looks like you already have access to this offer' - ), - icon: BlockedIcon - }, - TO_FREE_OFFER_NOT_ALLOWED: { - text: t( - 'offer-card.error.to-free-offer', - 'Switching from a paid to a free offer is not possible' - ), - icon: BlockedIcon - }, - SUBSCRIPTION_WITH_COUPON_NOT_ALLOWED: { - text: t( - 'offer-card.error.coupon-applied', - "You can't change your subscription if a coupon was applied. To change plan, please cancel your current subscription and purchase a new one." - ), - icon: BlockedIcon - }, - SWITCH_IN_PROGRESS: { - text: t( - 'offer-card.error.switch-in-progress', - 'Another switch is already in progress. Wait until the process finalization' - ), - icon: BlockedIcon - }, - MISSING_PAYMENT_DETAILS: { - text: t( - 'offer-card.error.missing-payment-details', - 'Your payment details are missing. Please add them to proceed with a subscription switch.' - ), - icon: BlockedIcon - } - }; - - const IconComponent = - switchDetails?.reason?.code && mapCode[switchDetails.reason.code]?.icon - ? mapCode[switchDetails.reason.code].icon - : React.Fragment; - - return ( - <> - - - - - - - - {t(`offer-title-${toOfferId}`, switchDetails.title)} - - - - - - - - - - - {switchDetails?.reason?.code - ? mapCode[switchDetails.reason.code] && - mapCode[switchDetails.reason.code].text && ( - - - - - - - ) - : ''} - - ); -}; - -OfferCard.propTypes = { - baseOfferId: PropTypes.string.isRequired, - toOfferId: PropTypes.string.isRequired -}; - -OfferCard.defaultProps = {}; - -export { OfferCard as PureOfferCard }; - -export default OfferCard; diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.tsx b/src/components/OfferSwitchCard/OfferSwitchCard.tsx new file mode 100644 index 000000000..603cb2c39 --- /dev/null +++ b/src/components/OfferSwitchCard/OfferSwitchCard.tsx @@ -0,0 +1,165 @@ +import { useAppSelector } from 'redux/store'; +import { useTranslation } from 'react-i18next'; +import SubscriptionIcon from 'components/SubscriptionIcon'; +import Price from 'components/Price'; +import { ReactComponent as BlockedIcon } from 'assets/images/blocked.svg'; +import SkeletonWrapper from 'components/SkeletonWrapper'; +import { + currencyFormat, + periodMapper, + CurrencyFormat, + PeriodProperties, + Period +} from 'util/planHelper'; +import isPriceTemporaryModified from 'util/isPriceTemporaryModified'; +import { selectSwitchSettings } from 'redux/planDetailsSlice'; +import { SwitchSetting } from 'redux/types/planDetailsSlice.types'; +import { OfferSwitchCardProps } from './OfferSwitchCard.types'; +import { + WrapperStyled, + InnerWrapper, + TitleStyled, + PriceWrapperStyled, + SubBoxStyled, + BoxTextStyled, + SubBoxContentStyled +} from './OfferSwitchCardStyled'; + +const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { + const { t } = useTranslation(); + + const { + data: allSwitchSettings, + loading: isSwitchSettingsLoading + } = useAppSelector(selectSwitchSettings); + + const switchDetails = + allSwitchSettings[baseOfferId].available.find( + switchData => switchData.toOfferId === toOfferId + ) || + allSwitchSettings[baseOfferId].unavailable.find( + switchData => switchData.toOfferId === toOfferId + ) || + ({} as SwitchSetting); + + const period = + periodMapper[ + switchDetails?.period as keyof Record + ]?.chargedForEveryText; + + const currency = + currencyFormat[ + switchDetails?.nextPaymentPriceCurrency as keyof Record< + CurrencyFormat, + string + > + ]; + + const getDescription = () => { + switch (switchDetails?.reason?.code) { + case 'TO_OFFER_COUNTRY_NOT_ALLOWED': + return t( + 'offer-card.error.geo-restriction', + `This plan is currently unavailable in your country or region` + ); + case 'ALREADY_HAS_ACCESS': + return t( + 'offer-card.error.already-have-access', + 'It looks like you already have access to this offer' + ); + case 'TO_FREE_OFFER_NOT_ALLOWED': + return t( + 'offer-card.error.to-free-offer', + 'Switching from a paid to a free offer is not possible' + ); + case 'SUBSCRIPTION_WITH_COUPON_NOT_ALLOWED': + return t( + 'offer-card.error.coupon-applied', + "You can't change your subscription if a coupon was applied. To change plan, please cancel your current subscription and purchase a new one." + ); + case 'SWITCH_IN_PROGRESS': + return t( + 'offer-card.error.switch-in-progress', + 'Another switch is already in progress. Wait until the process finalization' + ); + case 'MISSING_PAYMENT_DETAILS': + return t( + 'offer-card.error.missing-payment-details', + 'Your payment details are missing. Please add them to proceed with a subscription switch.' + ); + default: + return ''; + } + }; + + return ( + <> + + + + + + + + {t(`offer-title-${toOfferId}`, switchDetails.title)} + + + + + + + + + + + {switchDetails?.reason?.code && getDescription() ? ( + + + + + + + ) : ( + '' + )} + + ); +}; + +export default OfferSwitchCard; diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.types.ts b/src/components/OfferSwitchCard/OfferSwitchCard.types.ts new file mode 100644 index 000000000..a56eb1e03 --- /dev/null +++ b/src/components/OfferSwitchCard/OfferSwitchCard.types.ts @@ -0,0 +1,4 @@ +export type OfferSwitchCardProps = { + baseOfferId: string; + toOfferId: string; +}; diff --git a/src/components/OfferSwitchCard/OfferSwitchCardStyled.js b/src/components/OfferSwitchCard/OfferSwitchCardStyled.ts similarity index 100% rename from src/components/OfferSwitchCard/OfferSwitchCardStyled.js rename to src/components/OfferSwitchCard/OfferSwitchCardStyled.ts diff --git a/src/components/OfferSwitchCard/index.js b/src/components/OfferSwitchCard/index.ts similarity index 100% rename from src/components/OfferSwitchCard/index.js rename to src/components/OfferSwitchCard/index.ts diff --git a/src/redux/planDetailsSlice.ts b/src/redux/planDetailsSlice.ts index f516c3b79..a356b3e5b 100644 --- a/src/redux/planDetailsSlice.ts +++ b/src/redux/planDetailsSlice.ts @@ -168,6 +168,9 @@ export const selectCurrentPlanDetails = (state: RootState) => export const selectSwitchDetails = (state: RootState) => state.plan.switchDetails; +export const selectSwitchSettings = (state: RootState) => + state.plan.switchSettings; + export const { setOfferToSwitch, resetOfferToSwitch, diff --git a/src/redux/types/planDetailsSlice.types.ts b/src/redux/types/planDetailsSlice.types.ts index d3b94043e..4e0c3bda4 100644 --- a/src/redux/types/planDetailsSlice.types.ts +++ b/src/redux/types/planDetailsSlice.types.ts @@ -1,6 +1,6 @@ import { CustomerOffer } from 'api/Customer/types'; -type SwitchSetting = { +export type SwitchSetting = { toOfferId: string; algorithm: string; switchDirection: string; @@ -12,6 +12,10 @@ type SwitchSetting = { nextPaymentPrice: number; nextPaymentPriceCurrency: string; nextPaymentPriceCurrencySymbol: string; + reason?: { + code: string; + message: string; + }; }; export type SwitchSettings = { diff --git a/src/util/planHelper.ts b/src/util/planHelper.ts index 2b610e04a..e64e21896 100644 --- a/src/util/planHelper.ts +++ b/src/util/planHelper.ts @@ -38,17 +38,15 @@ export const isPeriod = (s: string): s is Period => { return period.some(p => p === s); }; -export const periodMapper: Record< - Period, - { - label: string; - color: string; - bg: string; - border: string; - chargedForEveryText?: string; - accessText?: string; - } -> = { +export type PeriodProperties = { + label: string; + color: string; + bg: string; + border: string; + chargedForEveryText?: string; + accessText?: string; +}; +export const periodMapper: Record = { // Subscription and season pass day: { label: 'D', From 917a190092327f13fcf57236661b073f6843793b Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Tue, 1 Aug 2023 08:42:41 +0200 Subject: [PATCH 4/7] refactor --- .../Customer/types/getCustomerOffers.types.ts | 23 ++++--- .../OfferMyAccountCard/OfferMyAccountCard.tsx | 69 ++++++++----------- .../OfferSwitchCard/OfferSwitchCard.tsx | 5 +- src/redux/types/offersSlice.types.ts | 13 ++-- src/redux/types/planDetailsSlice.types.ts | 26 +++---- 5 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/api/Customer/types/getCustomerOffers.types.ts b/src/api/Customer/types/getCustomerOffers.types.ts index 768a6a051..944dc94d8 100644 --- a/src/api/Customer/types/getCustomerOffers.types.ts +++ b/src/api/Customer/types/getCustomerOffers.types.ts @@ -8,16 +8,17 @@ export type CustomerOffer = { offerType: string; totalPrice: number; offerTitle: string; - externalPaymentId: string; - inTrial: boolean; - nextPaymentAt: number; - nextPaymentCurrency: string; - nextPaymentPrice: number; - paymentGateway: PaymentGateway; - paymentMethod: string; - pendingSwitchId: string; - period: string; - subscriptionId?: number; customerCurrency: string; - isExternallyManaged: boolean; + // optional params exists only for subscriptions + externalPaymentId?: string; + inTrial?: boolean; + nextPaymentAt?: number; + nextPaymentCurrency?: string; + nextPaymentPrice?: number; + paymentGateway?: PaymentGateway; + paymentMethod?: string; + pendingSwitchId?: string; + period?: string; + subscriptionId?: number; + isExternallyManaged?: boolean; }; diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx b/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx index 213bdb182..88e02c7cc 100644 --- a/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx +++ b/src/components/OfferMyAccountCard/OfferMyAccountCard.tsx @@ -1,5 +1,6 @@ import { useAppDispatch, useAppSelector } from 'redux/store'; import { selectCurrentPlan, selectSwitchDetails } from 'redux/planDetailsSlice'; +import { SwitchDetailsObject } from 'redux/types/planDetailsSlice.types'; import { selectOffers } from 'redux/offersSlice'; import { useTranslation } from 'react-i18next'; import SubscriptionIcon from 'components/SubscriptionIcon'; @@ -37,8 +38,9 @@ import { OfferMyAccountCardProps } from './OfferMyAccountCard.types'; const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const { data: currentPlan, loading } = useAppSelector(selectCurrentPlan); + const { pauseOffersIDs, offers } = useAppSelector(selectOffers); + const { data: switchDetailsStore } = useAppSelector(selectSwitchDetails); const { offerType, @@ -65,15 +67,15 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { > ]; // use customerCurrency for passes - const { pauseOffersIDs, offers } = useAppSelector(selectOffers); - const isPaused = pauseOffersIDs.includes(offerId); - - const { data: switchDetailsStore } = useAppSelector(selectSwitchDetails); - const pendingSwitchDetails = switchDetailsStore[pendingSwitchId]; + const pendingSwitchDetails = pendingSwitchId + ? switchDetailsStore[pendingSwitchId] + : ({} as SwitchDetailsObject); - const isPauseInProgress = pauseOffersIDs?.includes( - pendingSwitchDetails?.toOfferId - ); + // PAUSE FEATURE + const isPaused = pauseOffersIDs.includes(offerId); + const isPauseInProgress = pendingSwitchDetails + ? pauseOffersIDs?.includes(pendingSwitchDetails?.toOfferId) + : false; const generateDescription = () => { const renewalDate = @@ -122,7 +124,7 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { const translatedTitle = t(`offer-title-${fromOfferId}`, offerTitle); const translatedSwitchTitle = t(`offer-title-${toOfferId}`, toOfferIdTitle); - // if pause is in progress + // PAUSE FEATURE if (isPauseInProgress) { return t( 'offer-card.info-box.pause-information-text', @@ -162,28 +164,22 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { } }; - const shouldShowInfoBox = () => { - if (isExternallyManaged || pendingSwitchId) return true; - return false; - }; - const getDescription = () => { if (pendingSwitchId) return getPendingSwitchCopy(); if (isExternallyManaged) { - return paymentMethod - ? t( - 'offer-card.error.inapp-external', - `${ - paymentMethod - ? `Subscription purchased via ${paymentMethod}. ` - : `` - } Use an external service to edit the plan.`, - { paymentMethod } - ) - : t( - 'offer-card.error.inapp-external-no-pmt-method', - 'Use an external service to edit the plan.' - ); + if (!paymentMethod) { + return t( + 'offer-card.error.inapp-external-no-pmt-method', + 'Use an external service to edit the plan.' + ); + } + return t( + 'offer-card.error.inapp-external', + `${ + paymentMethod ? `Subscription purchased via ${paymentMethod}. ` : `` + } Use an external service to edit the plan.`, + { paymentMethod } + ); } return ''; }; @@ -228,12 +224,12 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { {!isPaused && ( - {offerType === 'S' && ( + {period && ( { )} - {shouldShowInfoBox() ? ( + {getDescription() && ( @@ -253,8 +249,7 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { __html: getDescription() || '' }} /> - {pendingSwitchId && - pendingSwitchDetails?.algorithm === 'DEFERRED' ? ( + {pendingSwitchId && pendingSwitchDetails?.algorithm === 'DEFERRED' && ( { eventDispatcher(MSSDK_CANCEL_SWITCH_BUTTON_CLICKED, { @@ -289,13 +284,9 @@ const OfferMyAccountCard = ({ offerId }: OfferMyAccountCardProps) => { ? t('offer-card.cancel-pause-button', 'Cancel pause') : t('offer-card.cancel-switch', 'Cancel switch')} - ) : ( - <> )} - ) : ( - <> )} ); diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.tsx b/src/components/OfferSwitchCard/OfferSwitchCard.tsx index 603cb2c39..ce72ded44 100644 --- a/src/components/OfferSwitchCard/OfferSwitchCard.tsx +++ b/src/components/OfferSwitchCard/OfferSwitchCard.tsx @@ -27,7 +27,6 @@ import { const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { const { t } = useTranslation(); - const { data: allSwitchSettings, loading: isSwitchSettingsLoading @@ -144,7 +143,7 @@ const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { - {switchDetails?.reason?.code && getDescription() ? ( + {switchDetails?.reason?.code && getDescription() && ( @@ -155,8 +154,6 @@ const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { /> - ) : ( - '' )} ); diff --git a/src/redux/types/offersSlice.types.ts b/src/redux/types/offersSlice.types.ts index e022db5ed..2daf623d9 100644 --- a/src/redux/types/offersSlice.types.ts +++ b/src/redux/types/offersSlice.types.ts @@ -1,3 +1,6 @@ +import { CurrencyFormat } from 'util/planHelper'; + +// new structure for Offer types export type Offer = { id: string; longId: string; @@ -5,12 +8,12 @@ export type Offer = { active: boolean; price: { amount: number; - currency: string; + currency: CurrencyFormat; taxIncluded: boolean; }; type: 'subscription' | 'pass' | 'rental' | 'live' | 'single'; billingCycle?: { - periodUnit: 'week' | 'month' | 'year'; + periodUnit: 'week' | 'month' | 'year' | 'season'; amount: number; }; entitlement?: { @@ -22,13 +25,15 @@ export type Offer = { }; }; externalProperties: []; - customExternalValue: string; appStoreProductIds: object; createdAt: number; updatedAt: number; imageUrl: string; tags: string[]; - hasLandingPage: boolean; + localizations?: object[]; + hasLandingPage?: boolean; + giftable?: boolean; + seasonGroupId?: string; }; export type Offers = Offer[]; diff --git a/src/redux/types/planDetailsSlice.types.ts b/src/redux/types/planDetailsSlice.types.ts index 4e0c3bda4..10254d0ad 100644 --- a/src/redux/types/planDetailsSlice.types.ts +++ b/src/redux/types/planDetailsSlice.types.ts @@ -25,19 +25,21 @@ export type SwitchSettings = { }; }; +export type SwitchDetailsObject = { + id: string; + customerId: number; + direction: string; + algorithm: string; + fromOfferId: string; + toOfferId: string; + subscriptionId: string; + status: string; + createdAt: number; + updatedAt: number; +}; + export type SwitchDetails = { - [key: string]: { - id: string; - customerId: number; - direction: string; - algorithm: string; - fromOfferId: string; - toOfferId: string; - subscriptionId: string; - status: string; - createdAt: number; - updatedAt: number; - }; + [key: string]: SwitchDetailsObject; }; export type PlanDetailsInitialState = { From af2cfd11c130e758adc67146db7998b00c715335 Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Tue, 1 Aug 2023 10:59:03 +0200 Subject: [PATCH 5/7] fix loaders --- src/components/CurrentPlan/CurrentPlan.js | 5 +-- .../OfferMyAccountCardLoader.tsx | 33 +++++++++++++++++++ .../OfferSwitchCard/OfferSwitchCard.tsx | 6 +++- .../OfferSwitchCard/OfferSwitchCardLoader.tsx | 31 +++++++++++++++++ .../SubscriptionSwitchesList.js | 8 +++-- 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 src/components/OfferMyAccountCard/OfferMyAccountCardLoader.tsx create mode 100644 src/components/OfferSwitchCard/OfferSwitchCardLoader.tsx diff --git a/src/components/CurrentPlan/CurrentPlan.js b/src/components/CurrentPlan/CurrentPlan.js index 28a22222d..b851db912 100644 --- a/src/components/CurrentPlan/CurrentPlan.js +++ b/src/components/CurrentPlan/CurrentPlan.js @@ -10,6 +10,7 @@ import { setOfferToSwitch } from 'redux/planDetailsSlice'; import MyAccountError from 'components/MyAccountError'; import OfferMyAccountCard from 'components/OfferMyAccountCard'; +import OfferMyAccountCardLoader from 'components/OfferMyAccountCard/OfferMyAccountCardLoader'; import SubscriptionManagement from 'components/SubscriptionManagement'; import MessageBox from 'components/MessageBox'; @@ -28,7 +29,7 @@ import { export const SkeletonCard = () => { return ( - + ); }; @@ -130,7 +131,7 @@ const CurrentPlan = () => { offerToSwitch.offerId === subItem.offerId } > - + {isMessageBoxOpened && messageSubscriptionId === subItem.subscriptionId && ( diff --git a/src/components/OfferMyAccountCard/OfferMyAccountCardLoader.tsx b/src/components/OfferMyAccountCard/OfferMyAccountCardLoader.tsx new file mode 100644 index 000000000..c4f3d2c36 --- /dev/null +++ b/src/components/OfferMyAccountCard/OfferMyAccountCardLoader.tsx @@ -0,0 +1,33 @@ +import SkeletonWrapper from 'components/SkeletonWrapper'; +import { + WrapperStyled, + InnerWrapper, + PriceWrapperStyled +} from './OfferMyAccountCardStyled'; + +const OfferMyAccountCardLoader = () => { + return ( + <> + + + + + + + + + + + + ); +}; + +export default OfferMyAccountCardLoader; diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.tsx b/src/components/OfferSwitchCard/OfferSwitchCard.tsx index ce72ded44..132ca437f 100644 --- a/src/components/OfferSwitchCard/OfferSwitchCard.tsx +++ b/src/components/OfferSwitchCard/OfferSwitchCard.tsx @@ -31,7 +31,6 @@ const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { data: allSwitchSettings, loading: isSwitchSettingsLoading } = useAppSelector(selectSwitchSettings); - const switchDetails = allSwitchSettings[baseOfferId].available.find( switchData => switchData.toOfferId === toOfferId @@ -86,6 +85,11 @@ const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { 'offer-card.error.missing-payment-details', 'Your payment details are missing. Please add them to proceed with a subscription switch.' ); + case 'MISSING_PAYMENT_FOR_PRORATION': + return t( + 'offer-card.error.missing-payment-for-proration', + 'The next upgrade will be available after the renewal date. ' + ); default: return ''; } diff --git a/src/components/OfferSwitchCard/OfferSwitchCardLoader.tsx b/src/components/OfferSwitchCard/OfferSwitchCardLoader.tsx new file mode 100644 index 000000000..212c0ca6e --- /dev/null +++ b/src/components/OfferSwitchCard/OfferSwitchCardLoader.tsx @@ -0,0 +1,31 @@ +import SkeletonWrapper from 'components/SkeletonWrapper'; +import { + WrapperStyled, + InnerWrapper, + PriceWrapperStyled +} from './OfferSwitchCardStyled'; + +const OfferSwitchCardLoader = () => { + return ( + + + + + + + + + + + ); +}; + +export default OfferSwitchCardLoader; diff --git a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js index a0cf34a2e..8f1513366 100644 --- a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js +++ b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js @@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next'; import { SubscriptionStyled } from 'components/CurrentPlan/CurrentPlanStyled'; import { SimpleButtonStyled } from 'components/SubscriptionManagement/SubscriptionManagementStyled'; import OfferSwitchCard from 'components/OfferSwitchCard'; +import OfferSwitchCardLoader from 'components/OfferSwitchCard/OfferSwitchCardLoader'; import MyAccountError from 'components/MyAccountError'; import { ReactComponent as selectPlanIcon } from 'assets/images/selectPlan.svg'; -import { SkeletonCard } from 'components/CurrentPlan/CurrentPlan'; import { POPUP_TYPES } from 'redux/innerPopupReducer'; import { showPopup } from 'redux/popupSlice'; import eventDispatcher, { @@ -44,7 +44,11 @@ const SubscriptionSwitchesList = () => { const dispatch = useDispatch(); if (isSwitchSettingsLoading) { - return ; + return ( + + + + ); } if (isAllSwitchSettingsError?.length) { From 5bf8af6c865b1ea5704f317ffdb524b969202337 Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Tue, 1 Aug 2023 12:18:47 +0200 Subject: [PATCH 6/7] fix switches list presentation when there is only pause offer --- .../SubscriptionSwitchesList.js | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js index 8f1513366..ecaee4d28 100644 --- a/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js +++ b/src/components/SubscriptionSwitchesList/SubscriptionSwitchesList.js @@ -68,17 +68,22 @@ const SubscriptionSwitchesList = () => { ); } - const areAvailable = !!( - switchSettings.available && - switchSettings.available.length && - switchSettings.available.filter( - ({ toOfferId }) => !pendingSwtichesToOfferIdsArray.includes(toOfferId) - ).length - ); + const availableSorted = Array.isArray(switchSettings.available) + ? [...switchSettings.available].sort( + (aOffer, bOffer) => bOffer.price - aOffer.price + ) + : []; - const areUnAvailable = !!( - switchSettings.unavailable && switchSettings.unavailable.length + // Filter out the pause subscription + const availableFiltered = availableSorted?.filter( + offer => !pauseOffersIDs.includes(offer.toOfferId) ); + const unavailableFiltered = Array.isArray(switchSettings.unavailable) + ? switchSettings.unavailable.filter( + offer => !pauseOffersIDs.includes(offer.toOfferId) + ) + : []; + const allSwitchesBlocked = switchSettings.unavailableReason; if (allSwitchesBlocked) { const error = mapErrorToText[allSwitchesBlocked.code] @@ -95,7 +100,8 @@ const SubscriptionSwitchesList = () => { /> ); } - if (!areAvailable && !areUnAvailable) { + + if (!availableFiltered?.length && !unavailableFiltered?.length) { return ( { /> ); } - const availableSorted = Array.isArray(switchSettings.available) - ? [...switchSettings.available].sort( - (aOffer, bOffer) => bOffer.price - aOffer.price - ) - : []; - - // Filter out the pause subscription - const availableFiltered = availableSorted?.filter( - offer => !pauseOffersIDs.includes(offer.toOfferId) - ); - const unavailableFiltered = Array.isArray(switchSettings.unavailable) - ? switchSettings.unavailable.filter( - offer => !pauseOffersIDs.includes(offer.toOfferId) - ) - : []; return ( <> - {areAvailable && + {!!availableFiltered.length && availableFiltered.map(subItem => { return ( { ); })} - {areUnAvailable && + {!!unavailableFiltered.length && unavailableFiltered.map(subItem => { return ( From b1a6e10074741d2a8eaa0b8039a8b1d2b8bdf96f Mon Sep 17 00:00:00 2001 From: Iwona Kulacz Date: Tue, 1 Aug 2023 12:53:50 +0200 Subject: [PATCH 7/7] add translation for new copy --- src/components/OfferSwitchCard/OfferSwitchCard.tsx | 2 +- src/translations/en/translations.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/OfferSwitchCard/OfferSwitchCard.tsx b/src/components/OfferSwitchCard/OfferSwitchCard.tsx index 132ca437f..c8a5c6c8f 100644 --- a/src/components/OfferSwitchCard/OfferSwitchCard.tsx +++ b/src/components/OfferSwitchCard/OfferSwitchCard.tsx @@ -88,7 +88,7 @@ const OfferSwitchCard = ({ baseOfferId, toOfferId }: OfferSwitchCardProps) => { case 'MISSING_PAYMENT_FOR_PRORATION': return t( 'offer-card.error.missing-payment-for-proration', - 'The next upgrade will be available after the renewal date. ' + 'The upgrade will be available after the renewal date.' ); default: return ''; diff --git a/src/translations/en/translations.json b/src/translations/en/translations.json index 8bd8b5ad4..e1f9ae83b 100644 --- a/src/translations/en/translations.json +++ b/src/translations/en/translations.json @@ -165,6 +165,7 @@ "offer-card.error.to-free-offer": "Switching from a paid to a free offer is not possible", "offer-card.error.inapp-external": "Subscription purchased via {{ paymentMethod }}. Use an external service to edit the plan.", "offer-card.error.inapp-external-no-pmt-method": "Use an external service to edit the plan.", + "offer-card.error.missing-payment-for-proration": "The upgrade will be available after the renewal date.", "offer-card.info-box.pause-information-text": "Your current plan will be paused starting on {{subscriptionExpirationDate}}. While your subscription is paused, you won’t be charged for, and you won’t have access to, {{ translatedTitle }}. You can cancel this pause request at any time.", "offer-card.next-season-start": "the next season start", "offer-card.switch-details.deferred": "Your switch is pending. You will have access to {{translatedTitle}} until {{subscriptionExpirationDate}}. From that time you will be charged your new price and will have access to {{translatedSwitchTitle}}. You can cancel this at any time.",