client/components/mma/cancel/cancellationSaves/membership/ConfirmMembershipCancellation.tsx (172 lines of code) (raw):

import { css } from '@emotion/react'; import { palette, space, textSans17 } from '@guardian/source/foundations'; import { Button, Stack } from '@guardian/source/react-components'; import { useContext, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { MDA_TEST_USER_HEADER } from '../../../../../../shared/productResponse'; import type { MembersDataApiResponse, ProductDetail, } from '../../../../../../shared/productResponse'; import type { ProductTypeWithCancellationFlow } from '../../../../../../shared/productTypes'; import { stackedButtonLayoutCss } from '../../../../../styles/ButtonStyles'; import { fetchWithDefaultParameters } from '../../../../../utilities/fetch'; import { createProductDetailFetcher } from '../../../../../utilities/productUtils'; import { GenericErrorScreen } from '../../../../shared/GenericErrorScreen'; import { JsonResponseHandler } from '../../../shared/asyncComponents/DefaultApiResponseHandler'; import { Heading } from '../../../shared/Heading'; import { ProgressStepper } from '../../../shared/ProgressStepper'; import type { CancellationContextInterface, CancellationRouterState, } from '../../CancellationContainer'; import { CancellationContext } from '../../CancellationContainer'; import type { OptionalCancellationReasonId } from '../../cancellationReason'; export const ConfirmMembershipCancellation = () => { const navigate = useNavigate(); const { productDetail, productType } = useContext( CancellationContext, ) as CancellationContextInterface; const [isSubmitting, setIsSubmitting] = useState<boolean>(false); const [loadingFailed, setLoadingFailed] = useState<boolean>(false); const location = useLocation(); const routerState = location.state as CancellationRouterState; const reason: OptionalCancellationReasonId = 'mma_membership_cancellation_default'; //reason needs to be provided as undefined doesn't work. Reason updated if user provides one on next screen. const createCase = ( selectedReasonId: string, productType: ProductTypeWithCancellationFlow, productDetail: ProductDetail, ) => { return fetch('/api/case', { method: 'POST', body: JSON.stringify({ reason: selectedReasonId, product: productType.cancellation.sfCaseProduct, subscriptionName: productDetail.subscription.subscriptionId, gaData: '', }), headers: { 'Content-Type': 'application/json', [MDA_TEST_USER_HEADER]: `${productDetail.isTestUser}`, }, }); }; const cancelMembership = async ( subscriptionId: string, withSubscriptionResponseFetcher: () => Promise<Response>, ) => { await fetchWithDefaultParameters(`/api/cancel/${subscriptionId}`, { method: 'POST', body: JSON.stringify({ reason }), headers: { 'Content-Type': 'application/json' }, }); // response is either empty or 404 - neither is useful so fetch subscription to determine cancellation result... return withSubscriptionResponseFetcher(); }; const postCancellation = async () => { if (isSubmitting) { return; } try { setIsSubmitting(true); const caseResponse = await createCase( reason, productType, productDetail, ); const caseData = await JsonResponseHandler(caseResponse); if (caseData === null) { setIsSubmitting(false); setLoadingFailed(true); return; } const cancelResponse = await cancelMembership( productDetail.subscription.subscriptionId, createProductDetailFetcher( productType.allProductsProductTypeFilterString, productDetail.subscription.subscriptionId, ), ); const cancelData = await JsonResponseHandler(cancelResponse); if (cancelData === null) { setIsSubmitting(false); setLoadingFailed(true); return; } const mdapiResponse = cancelData as MembersDataApiResponse; const membership = (mdapiResponse.products[0] as ProductDetail) || { subscription: {}, }; const isCancelled = Object.keys(membership.subscription).length === 0 || membership.subscription.cancelledAt; if (!isCancelled) { setIsSubmitting(false); setLoadingFailed(true); } else { navigate('../reasons', { state: { ...routerState, journeyCompleted: true }, }); } } catch { setIsSubmitting(false); setLoadingFailed(true); } }; if (loadingFailed) { return ( <GenericErrorScreen loggingMessage="Cancel journey case id api call failed during the cancellation process" /> ); } return ( <> <ProgressStepper steps={[ { title: 'Details' }, { title: 'Options' }, { title: 'Confirmation', isCurrentStep: true }, ]} additionalCSS={css` margin: ${space[5]}px 0 ${space[12]}px; max-width: 350px; `} /> <Stack space={4}> <Heading> Are you sure you want to cancel your Membership? </Heading> <p css={css` ${textSans17}; `} > Please keep in mind that you will be losing access to your supporter benefits.{' '} </p> <p css={css` ${textSans17}; `} > If you cancel you will not be able to rejoin the Guardian Members scheme, as it’s now closed to new members. </p> </Stack> <div css={stackedButtonLayoutCss}> <Button onClick={postCancellation} isLoading={isSubmitting} cssOverrides={css` background-color: ${palette.news['400']}; justify-content: center; :hover { background-color: ${palette.news['200']}; } `} > Confirm Cancellation </Button> <Button priority="tertiary" onClick={() => navigate('../offers', { state: { ...routerState } }) } cssOverrides={css` justify-content: center; `} > Back to options </Button> </div> </> ); };