client/components/mma/cancel/CancellationContainer.tsx (140 lines of code) (raw):
import type { Context, Dispatch, SetStateAction } from 'react';
import { createContext, useState } from 'react';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import type {
MembersDataApiResponse,
MembersDataApiUser,
ProductDetail,
} from '../../../../shared/productResponse';
import { isProduct } from '../../../../shared/productResponse';
import { GROUPED_PRODUCT_TYPES } from '../../../../shared/productTypes';
import type {
ProductType,
ProductTypeWithCancellationFlow,
WithProductType,
} from '../../../../shared/productTypes';
import {
LoadingState,
useAsyncLoader,
} from '../../../utilities/hooks/useAsyncLoader';
import { createProductDetailFetcher } from '../../../utilities/productUtils';
import { GenericErrorScreen } from '../../shared/GenericErrorScreen';
import { NAV_LINKS } from '../../shared/nav/NavConfig';
import type { DeliveryRecordDetail } from '../delivery/records/deliveryRecordsApi';
import type { OutstandingHolidayStop } from '../holiday/HolidayStopApi';
import { PageContainer } from '../Page';
import { JsonResponseHandler } from '../shared/asyncComponents/DefaultApiResponseHandler';
import { DefaultLoadingView } from '../shared/asyncComponents/DefaultLoadingView';
import type {
CancellationReason,
OptionalCancellationReasonId,
} from './cancellationReason';
const renderSingleProductOrReturnToAccountOverview =
(productType: ProductType) => (mdapiResponse: MembersDataApiResponse) => {
const filteredProductDetails = mdapiResponse.products
.filter(isProduct)
.filter((productDetail) => !productDetail.subscription.cancelledAt);
if (filteredProductDetails.length === 1) {
return contextAndOutletContainer(
filteredProductDetails[0],
productType,
);
}
return <Navigate to="/" />;
};
const AsyncLoadedCancellationContainer = (
props: WithProductType<ProductType>,
) => {
const request = createProductDetailFetcher(
props.productType.allProductsProductTypeFilterString,
);
const { data, loadingState } = useAsyncLoader<MembersDataApiResponse>(
request,
JsonResponseHandler,
);
if (loadingState == LoadingState.HasError) {
return <GenericErrorScreen />;
}
if (loadingState == LoadingState.IsLoading) {
return (
<DefaultLoadingView
loadingMessage={`Checking the status of your ${props.productType.friendlyName}...`}
/>
);
}
if (data == null || data.products.length == 0) {
return <Navigate to="/" />;
}
return renderSingleProductOrReturnToAccountOverview(props.productType)(
data,
);
};
export interface CancellationContextInterface {
productDetail: ProductDetail;
productType: ProductTypeWithCancellationFlow;
}
export const CancellationContext: Context<CancellationContextInterface | object> =
createContext({});
const contextAndOutletContainer = (
productDetail: ProductDetail,
productType: ProductType,
) => (
<CancellationContext.Provider value={{ productDetail, productType }}>
<Outlet />
</CancellationContext.Provider>
);
export interface CancellationRouterState {
productDetail: ProductDetail;
productType?: ProductTypeWithCancellationFlow;
user?: MembersDataApiUser;
selectedReasonId?: OptionalCancellationReasonId;
cancellationPolicy?: string;
caseId?: string;
holidayStops?: OutstandingHolidayStop[];
deliveryCredits?: DeliveryRecordDetail[];
updatedContributionAmount?: number;
selectedReason?: CancellationReason;
dontShowOffer?: boolean;
journeyCompleted?: boolean;
}
export interface CancellationPageTitleInterface {
setPageTitle: Dispatch<SetStateAction<string>>;
}
export const CancellationPageTitleContext: Context<
CancellationPageTitleInterface | object
> = createContext({});
export const CancellationContainer = (props: WithProductType<ProductType>) => {
const location = useLocation();
const routerState = location.state as CancellationRouterState;
const productDetail = routerState?.productDetail;
const groupedProductType =
GROUPED_PRODUCT_TYPES[props.productType.groupedProductType];
const [cancellationCompleted, setCancellationCompleted] =
useState<boolean>(false);
if (!cancellationCompleted && routerState?.journeyCompleted) {
setCancellationCompleted(true);
}
const [pageTitle, setPageTitle] = useState<string>(
`Cancel ${
groupedProductType.shortFriendlyName ||
groupedProductType.friendlyName
}`,
);
if (userIsNavigatingBackFromCompletePage(cancellationCompleted)) {
return <Navigate to="/" />;
}
return (
<CancellationPageTitleContext.Provider value={{ setPageTitle }}>
<PageContainer
selectedNavItem={NAV_LINKS.accountOverview}
pageTitle={pageTitle}
>
{productDetail ? (
contextAndOutletContainer(productDetail, props.productType)
) : (
<AsyncLoadedCancellationContainer
productType={props.productType}
/>
)}
</PageContainer>
</CancellationPageTitleContext.Provider>
);
};
function userIsNavigatingBackFromCompletePage(hasCompleted: boolean) {
return (
hasCompleted &&
!location.pathname.includes('reasons') &&
!location.pathname.includes('discount-confirmed') &&
!location.pathname.includes('switch-thank-you')
);
}