client/components/mma/switch/SwitchContainer.tsx (116 lines of code) (raw):

import type { Context, ReactNode } from 'react'; import { createContext, useState } from 'react'; import { Navigate, Outlet, useLocation } from 'react-router'; import type { MembersDataApiResponse, MembersDataApiUser, PaidSubscriptionPlan, ProductDetail, } from '../../../../shared/productResponse'; import { getMainPlan, isProduct } from '../../../../shared/productResponse'; import { getBillingPeriodAdjective, PRODUCT_TYPES, } from '../../../../shared/productTypes'; import { LoadingState, useAsyncLoader, } from '../../../utilities/hooks/useAsyncLoader'; import { getBenefitsThreshold } from '../../../utilities/pricingConfig/supporterPlusPricing'; import { createProductDetailFetcher, isNonServiceableCountry, } from '../../../utilities/productUtils'; import { GenericErrorScreen } from '../../shared/GenericErrorScreen'; import { NAV_LINKS } from '../../shared/nav/NavConfig'; import { PageContainer } from '../Page'; import { JsonResponseHandler } from '../shared/asyncComponents/DefaultApiResponseHandler'; import { DefaultLoadingView } from '../shared/asyncComponents/DefaultLoadingView'; export interface SwitchRouterState { productDetail: ProductDetail; user?: MembersDataApiUser; amountPayableToday: number; nextPaymentDate: string; switchHasCompleted?: boolean; } export interface SwitchContextInterface { contributionToSwitch: ProductDetail; isFromApp: boolean; user?: MembersDataApiUser; mainPlan: PaidSubscriptionPlan; monthlyOrAnnual: 'Monthly' | 'Annual'; supporterPlusTitle: string; thresholds: Thresholds; } export interface Thresholds { monthlyThreshold: number; annualThreshold: number; thresholdForBillingPeriod: number; isAboveThreshold: boolean; } export const SwitchContext: Context<SwitchContextInterface | object> = createContext({}); export const SwitchContainer = (props: { isFromApp?: boolean }) => { const location = useLocation(); const routerState = location.state as SwitchRouterState; const contributionToSwitch = routerState?.productDetail; const user = routerState?.user; const [switchHasCompleted, setSwitchHasCompleted] = useState<boolean>(false); if (!switchHasCompleted && routerState?.switchHasCompleted) { setSwitchHasCompleted(true); } if (userIsNavigatingBackFromCompletePage(switchHasCompleted)) { return <Navigate to="/" />; } if (!contributionToSwitch) { return <AsyncLoadedSwitchContainer isFromApp={props.isFromApp} />; } return ( <RenderedPage contributionToSwitch={contributionToSwitch} user={user} isFromApp={props.isFromApp} /> ); }; const SwitchPageContainer = (props: { children: ReactNode }) => { return ( <PageContainer selectedNavItem={NAV_LINKS.accountOverview} pageTitle={'Change your support'} compactTitle minimalFooter > {props.children} </PageContainer> ); }; const AsyncLoadedSwitchContainer = (props: { isFromApp?: boolean }) => { const request = createProductDetailFetcher( PRODUCT_TYPES.contributions.allProductsProductTypeFilterString, ); const { data, loadingState } = useAsyncLoader<MembersDataApiResponse>( request, JsonResponseHandler, ); if (loadingState == LoadingState.HasError) { return ( <SwitchPageContainer> <GenericErrorScreen /> </SwitchPageContainer> ); } if (loadingState == LoadingState.IsLoading) { return ( <SwitchPageContainer> <DefaultLoadingView /> </SwitchPageContainer> ); } if (data == null || data.products.length == 0) { return <Navigate to="/" />; } const contributionToSwitch = data.products.filter(isProduct)[0]; if (isNonServiceableCountry(contributionToSwitch)) { return <Navigate to="/" />; } return ( <RenderedPage contributionToSwitch={contributionToSwitch} user={data.user} isFromApp={props.isFromApp} /> ); }; const RenderedPage = (props: { contributionToSwitch: ProductDetail; user?: MembersDataApiUser; isFromApp?: boolean; }) => { const mainPlan = getMainPlan( props.contributionToSwitch.subscription, ) as PaidSubscriptionPlan; const monthlyOrAnnual = getBillingPeriodAdjective(mainPlan.billingPeriod); return ( <SwitchPageContainer> <SwitchContext.Provider value={{ contributionToSwitch: props.contributionToSwitch, isFromApp: props.isFromApp, user: props.user, mainPlan, monthlyOrAnnual, supporterPlusTitle: PRODUCT_TYPES.supporterplus.productTitle(), thresholds: getThresholds( mainPlan, monthlyOrAnnual == 'Monthly', ), }} > <Outlet /> </SwitchContext.Provider> </SwitchPageContainer> ); }; function userIsNavigatingBackFromCompletePage(hasCompleted: boolean) { return hasCompleted && !location.pathname.includes('complete'); } function getThresholds( mainPlan: PaidSubscriptionPlan, monthly: boolean, ): Thresholds { const monthlyThreshold = getBenefitsThreshold( mainPlan.currencyISO, 'month', ); const annualThreshold = getBenefitsThreshold(mainPlan.currencyISO, 'year'); const thresholdForBillingPeriod = monthly ? monthlyThreshold : annualThreshold; const isAboveThreshold = mainPlan.price >= thresholdForBillingPeriod * 100; return { monthlyThreshold, annualThreshold, thresholdForBillingPeriod, isAboveThreshold, }; }