client/components/mma/cancel/cancellationSaves/digipack/ConfirmDigiSubCancellation.tsx (260 lines of code) (raw):
import { css } from '@emotion/react';
import {
from,
headlineBold24,
headlineBold34,
palette,
space,
textSans17,
textSansBold20,
} from '@guardian/source/foundations';
import { Button, Stack } from '@guardian/source/react-components';
import { useContext, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import type { CancellationContextInterface } from '@/client/components/mma/cancel/CancellationContainer';
import { CancellationContext } from '@/client/components/mma/cancel/CancellationContainer';
import type { OptionalCancellationReasonId } from '@/client/components/mma/cancel/cancellationReason';
import { JsonResponseHandler } from '@/client/components/mma/shared/asyncComponents/DefaultApiResponseHandler';
import { benefitsCss } from '@/client/components/mma/shared/benefits/BenefitsStyles';
import { GenericErrorScreen } from '@/client/components/shared/GenericErrorScreen';
import { stackedButtonLayoutCss } from '@/client/styles/ButtonStyles';
import { fetchWithDefaultParameters } from '@/client/utilities/fetch';
import { createProductDetailFetcher } from '@/client/utilities/productUtils';
import type {
MembersDataApiResponse,
ProductDetail,
} from '@/shared/productResponse';
import { MDA_TEST_USER_HEADER } from '@/shared/productResponse';
import type { ProductTypeWithCancellationFlow } from '@/shared/productTypes';
import type { DigisubCancellationRouterState } from './DigiSubThankYouOffer';
function GreyBulletpoint() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="17"
viewBox="0 0 16 17"
fill="none"
css={css`
padding-top: 5px;
`}
>
<circle cx="8" cy="8.13672" r="8" fill="#DCDCDC" />
</svg>
);
}
const BenefitsNotAvailable = () => (
<Stack
space={4}
cssOverrides={css`
background-color: #f3f7fe;
border-radius: 4px;
padding: ${space[4]}px;
`}
>
<div>
<div
css={css`
${textSansBold20};
margin-bottom: ${space[2]}px;
`}
>
Extras you'll lose:
</div>
<ul css={benefitsCss}>
<li>
<GreyBulletpoint />
Funding independent journalism
</li>
<li>
<GreyBulletpoint />A regular supporter newsletter
</li>
<li>
<GreyBulletpoint />
Unlimited access in our app
</li>
<li>
<GreyBulletpoint />
Ad-free reading
</li>
<li>
<GreyBulletpoint />
Offline reading
</li>
</ul>
</div>
</Stack>
);
export const ConfirmDigiSubCancellation = () => {
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 DigisubCancellationRouterState;
const eligibleForDiscount = routerState?.eligibleForDiscount;
const reason: OptionalCancellationReasonId = 'mma_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 cancelDigiSub = 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 cancelDigiSub(
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 digisub = (mdapiResponse.products[0] as ProductDetail) || {
subscription: {},
};
const isCancelled =
Object.keys(digisub.subscription).length === 0 ||
digisub.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 (
<section
css={css`
margin-top: ${space[4]}px;
`}
>
<Stack space={1}>
<h1
css={css`
${headlineBold24};
margin-top: 0;
margin-bottom: 0;
${from.tablet} {
${headlineBold34};
}
`}
>
Losing your supporter extras
</h1>
<div
css={css`
${textSans17};
`}
>
Please keep in mind that you will be losing access to your
supporter extras if you cancel today.
</div>
</Stack>
<section
css={css`
margin-top: 32px;
margin-bottom: 32px;
`}
>
<BenefitsNotAvailable />
</section>
<div
css={css`
${textSansBold20};
margin-bottom: ${space[4]}px;
`}
>
Please confirm to cancel your digital subscription
</div>
<div css={stackedButtonLayoutCss}>
<Button
onClick={postCancellation}
isLoading={isSubmitting}
cssOverrides={css`
background-color: ${palette.news['400']};
justify-content: center;
:hover {
background-color: ${palette.news['200']};
}
`}
>
Cancel subscription
</Button>
{eligibleForDiscount && (
<Button
onClick={() =>
navigate('../discount-offer', {
state: { ...routerState },
})
}
cssOverrides={css`
display: flex;
margin-left: ${space[5]}px;
justify-content: center;
`}
priority="subdued"
>
Go back to discount
</Button>
)}
</div>
</section>
);
};