client/components/mma/cancel/CancellationSummary.tsx (235 lines of code) (raw):

import { css } from '@emotion/react'; import { palette, space, textEgyptianBold17, } from '@guardian/source/foundations'; import { Link } from 'react-router-dom'; import type { CurrencyIso } from '@/client/utilities/currencyIso'; import { cancellationFormatDate } from '../../../../shared/dates'; import type { PaidSubscriptionPlan, ProductDetail, Subscription, } from '../../../../shared/productResponse'; import { getMainPlan } from '../../../../shared/productResponse'; import type { ProductType } from '../../../../shared/productTypes'; import { measure } from '../../../styles/typography'; import { hasDeliveryRecordsFlow } from '../../../utilities/productUtils'; import { GenericErrorScreen } from '../../shared/GenericErrorScreen'; import { SupportTheGuardianButton } from '../../shared/SupportTheGuardianButton'; import { WithStandardTopMargin } from '../../shared/WithStandardTopMargin'; import { Heading } from '../shared/Heading'; import { hrefStyle } from './cancellationConstants'; import { CancellationContributionReminder } from './cancellationContributionReminder'; import type { CancellationReasonId } from './cancellationReason'; import { ResubscribeThrasher } from './ResubscribeThrasher'; const actuallyCancelled = ( productType: ProductType, productDetail: ProductDetail, productDetailBeforeCancelling: ProductDetail, eligableForOffer?: boolean, eligibleForPause?: boolean, cancellationReasonId?: CancellationReasonId, ) => { const isSupportPlus = productType.productType === 'supporterplus'; const isContribution = productType.productType === 'contributions'; const isGuardianAdLite = productType.productType === 'guardianadlite'; let showReminder: boolean = !!productType.cancellation?.shouldShowReminder; if (isSupportPlus && eligableForOffer) { showReminder = false; } const deliveryRecordsLink: string = `/delivery/${productType.urlPart}/records`; let currencySymbol: undefined | CurrencyIso; let contributionheadingCopy = ''; if ( productDetailBeforeCancelling && Object.keys(productDetailBeforeCancelling.subscription).length ) { const mainPlan = getMainPlan( productDetailBeforeCancelling.subscription, ) as PaidSubscriptionPlan; currencySymbol = mainPlan.currencyISO; if (isContribution) { contributionheadingCopy = `Your ${ mainPlan ? `${mainPlan.billingPeriod}ly ` : '' }support has been cancelled`; } } return ( <> <WithStandardTopMargin> <Heading borderless cssOverrides={[ measure.heading, css` margin-bottom: ${space[6]}px; `, ]} > {(isSupportPlus || isGuardianAdLite) && 'Your subscription has been cancelled'} {isContribution && contributionheadingCopy} {!isSupportPlus && !isContribution && !isGuardianAdLite && `Your ${productType.friendlyName} is cancelled`} </Heading> {productType.cancellation && !productType.cancellation.shouldHideSummaryMainPara && ( <p> {productType.cancellation ?.alternateSummaryMainPara || (productDetail.subscription.end ? ( <> You will continue to receive the benefits of your{' '} {productType.friendlyName} until{' '} <b> {cancellationFormatDate( productDetail.subscription .cancellationEffectiveDate, )} </b> . You will not be charged again. If you think you’re owed a refund, please contact us at{' '} <a css={hrefStyle} href="mailto:customer.help@theguardian.com" > customer.help@theguardian.com </a> . </> ) : ( 'Your cancellation is effective immediately.' ))} </p> )} {isContribution && eligibleForPause && ( <p>This is immediate and you will not be charged again.</p> )} </WithStandardTopMargin> {showReminder && <CancellationContributionReminder />} {!productType.cancellation?.shouldHideThrasher && ( <ResubscribeThrasher usageContext={`${productType.urlPart}_cancellation_summary`} > <WithStandardTopMargin> {hasDeliveryRecordsFlow(productType) && ( <p> You can still{' '} <Link css={css` color: ${palette.brand[500]}; text-decoration: underline; :visited { color: ${palette.brand[500]}; } `} to={deliveryRecordsLink} state={{ productDetail }} > view your previous deliveries </Link>{' '} and{' '} <Link css={css` color: ${palette.brand[500]}; text-decoration: underline; :visited { color: ${palette.brand[500]}; } `} to={deliveryRecordsLink} state={{ productDetail }} > report a delivery problem </Link> . </p> )} {(!productType.cancellation || !productType.cancellation .onlyShowSupportSectionIfAlternateText || productType.cancellation.summaryReasonSpecificPara?.( cancellationReasonId, )) && ( <> <h4 css={css` ${textEgyptianBold17}; margin-bottom: ${space[3]}px; `} > Support us another way </h4> <p> {productType?.cancellation?.summaryReasonSpecificPara?.( cancellationReasonId, currencySymbol, ) || 'If you are interested in supporting our journalism in other ways, ' + 'please consider either a contribution or a subscription.'} </p> <div css={{ marginBottom: '30px' }}> <SupportTheGuardianButton urlSuffix={ productType.cancellation && productType.cancellation .alternateSupportButtonUrlSuffix && productType.cancellation.alternateSupportButtonUrlSuffix( cancellationReasonId, ) } alternateButtonText={ productType.cancellation && productType.cancellation .alternateSupportButtonText && productType.cancellation.alternateSupportButtonText( cancellationReasonId, ) } supportReferer={ productType.urlPart + '_cancellation_summary' } theme="brand" size="small" /> </div> </> )} </WithStandardTopMargin> </ResubscribeThrasher> )} </> ); }; export const isCancelled = (subscription: Subscription) => Object.keys(subscription).length === 0 || subscription.cancelledAt; export const getCancellationSummary = ( productType: ProductType, productDetail: ProductDetail, productDetailBeforeCancelling: ProductDetail, eligableForOffer?: boolean, eligibleForPause?: boolean, cancellationReasonId?: CancellationReasonId, ) => { return isCancelled(productDetail.subscription) ? ( actuallyCancelled( productType, productDetail, productDetailBeforeCancelling, eligableForOffer, eligibleForPause, cancellationReasonId, ) ) : ( <GenericErrorScreen loggingMessage={`${productType.friendlyName} cancellation call succeeded but subsequent product detail doesn't show as cancelled`} /> ); };