client/components/mma/switch/options/SwitchOptions.tsx (279 lines of code) (raw):

import { css } from '@emotion/react'; import { palette, space, textSans17, textSansBold17, textSansBold20, until, } from '@guardian/source/foundations'; import { Button, Stack, themeButtonReaderRevenueBrand, } from '@guardian/source/react-components'; import { ErrorSummary } from '@guardian/source-development-kitchen/react-components'; import { useContext, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { Link } from 'react-router-dom'; import { buttonCentredCss } from '../../../../styles/ButtonStyles'; import { errorSummaryBlockLinkCss, errorSummaryLinkCss, errorSummaryOverrideCss, } from '../../../../styles/ErrorStyles'; import { productTitleCss, sectionSpacing, smallPrintCss, } from '../../../../styles/GenericStyles'; import { formatAmount } from '../../../../utilities/utils'; import { supporterPlusSwitchBenefits } from '../../shared/benefits/BenefitsConfiguration'; import { BenefitsSection } from '../../shared/benefits/BenefitsSection'; import { Card } from '../../shared/Card'; import { Heading } from '../../shared/Heading'; import type { SwitchContextInterface, SwitchRouterState, } from '../SwitchContainer'; import { SwitchContext } from '../SwitchContainer'; const cardHeaderDivCss = css` display: flex; justify-content: space-between; align-items: flex-end; `; const productSubtitleCss = css` ${textSansBold17}; color: ${palette.neutral[100]}; margin: 0; max-width: 20ch; `; const buttonContainerCss = css` margin-top: ${space[1]}px; padding: ${space[5]}px 0; ${until.tablet} { display: flex; flex-direction: column; position: sticky; bottom: 0; margin-left: -${space[3]}px; margin-right: -${space[3]}px; padding-left: ${space[3]}px; padding-right: ${space[3]}px; } `; const buttonStuckCss = css` ${until.tablet} { background-color: ${palette.neutral[100]}; box-shadow: 0px -1px 16px rgba(0, 0, 0, 0.1); } `; const fromAppHeadingCss = css` ${textSansBold20}; line-height: normal; color: ${palette.brand[500]}; margin-bottom: 0; `; export const SwitchOptions = () => { const switchContext = useContext(SwitchContext) as SwitchContextInterface; const location = useLocation(); const routerState = location.state as SwitchRouterState; const { contributionToSwitch, mainPlan, monthlyOrAnnual, supporterPlusTitle, thresholds, } = switchContext; const { monthlyThreshold, annualThreshold, thresholdForBillingPeriod: threshold, isAboveThreshold, } = thresholds; const currentAmount = mainPlan.price / 100; const buttonContainerRef = useRef(null); const [buttonIsStuck, setButtonIsStuck] = useState(false); // Use IntersectionObserver to detect when button is 'stuck' at the bottom // of the viewport. The bottom of the observable area is set to -1px so that // when the button is stuck it is not considered to be fully visible. The // top edge is similarly extended upwards so the button is considered fully // visible when scrolling off the top of the screen. useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { setButtonIsStuck(entry.intersectionRatio < 1); }, { threshold: [1], rootMargin: '100px 0px -1px 0px' }, ); if (buttonContainerRef.current) { observer.observe(buttonContainerRef.current); } return () => { observer.disconnect(); }; }, [buttonContainerRef]); const navigate = useNavigate(); if (contributionToSwitch.tier === 'Supporter Plus') { return ( <section css={sectionSpacing}> <ErrorSummary cssOverrides={errorSummaryOverrideCss} message="There is a problem with your subscription type" context={ <> Your subscription does not allow you to perform this switch. <Link css={[ errorSummaryLinkCss, errorSummaryBlockLinkCss, ]} to="/" > Return to account overview </Link> </> } /> </section> ); } return ( <> {contributionToSwitch.alertText && ( <section css={sectionSpacing}> <ErrorSummary cssOverrides={errorSummaryOverrideCss} message="There is a problem with your payment method" context={ <> Please update your payment details in order to change your support. <Link css={[ errorSummaryLinkCss, errorSummaryBlockLinkCss, ]} to="/payment/contributions" > Check your payment details </Link> </> } /> </section> )} {switchContext.isFromApp && ( <section css={sectionSpacing}> <h2 css={fromAppHeadingCss}> {isAboveThreshold ? 'Add extras to get full access to our news app today' : 'Change your support to get full access to our news app today'} </h2> <p css={css` ${textSans17}; `} > {isAboveThreshold ? 'Your current payment entitles you to exclusive supporter extras. It takes less than a minute to add them.' : 'It takes less than a minute to change your support type.'}{' '} If this doesn't suit you, no change is needed, but note you will have limited access to our app. </p> </section> )} <section css={sectionSpacing}> <Heading sansSerif cssOverrides={css` margin-bottom: ${space[3]}px; `} > Your current support </Heading> <Card> <Card.Header backgroundColor={palette.brand[600]}> <div css={cardHeaderDivCss}> <h3 css={productTitleCss}>{monthlyOrAnnual}</h3> <p css={productSubtitleCss}> {mainPlan.currency} {formatAmount(currentAmount)}/ {mainPlan.billingPeriod} </p> </div> </Card.Header> <Card.Section> <div css={css` ${textSans17}; `} > You pay {mainPlan.currency} {formatAmount(currentAmount)} on a recurring basis every {mainPlan.billingPeriod} </div> </Card.Section> </Card> </section> <section css={sectionSpacing}> <Stack space={3}> <Heading sansSerif> {isAboveThreshold ? 'Add extras' : 'Change your support'} </Heading> {isAboveThreshold && !switchContext.isFromApp && ( <p css={css` ${textSans17}; margin: 0; `} > Your current payment entitles you to exclusive supporter extras. It takes less than a minute to change your support type and gain access. </p> )} {!isAboveThreshold && !switchContext.isFromApp && ( <p css={css` ${textSans17}; margin: 0; `} > Unlock exclusive supporter extras when you pay a little more </p> )} <Card> <Card.Header backgroundColor={palette.brand[500]}> <div css={cardHeaderDivCss}> <h3 css={productTitleCss}> {supporterPlusTitle} </h3> {!isAboveThreshold && ( <p css={productSubtitleCss}> {mainPlan.currency} {formatAmount(threshold)}/ {mainPlan.billingPeriod} </p> )} </div> </Card.Header> <Card.Section> <BenefitsSection benefits={supporterPlusSwitchBenefits} /> </Card.Section> </Card> </Stack> </section> <section css={[buttonContainerCss, buttonIsStuck && buttonStuckCss]} ref={buttonContainerRef} > <Button theme={themeButtonReaderRevenueBrand} size="small" cssOverrides={buttonCentredCss} onClick={() => navigate('review', { state: routerState, }) } > {isAboveThreshold ? 'Add extras' : `Upgrade to ${mainPlan.currency}${threshold} per ${mainPlan.billingPeriod}`} </Button> </section> <section> <p css={smallPrintCss}> These extras are exclusively available for supporters who give a minimum of {mainPlan.currency} {formatAmount(monthlyThreshold)} per month, or{' '} {mainPlan.currency} {formatAmount(annualThreshold)} per year. </p> </section> </> ); };