Skip to content

Commit

Permalink
Merge pull request #1241 from guardian/ahe/cancellation-save-refactor
Browse files Browse the repository at this point in the history
Refactor: move membership saves into own directory
  • Loading branch information
andrewHEguardian authored Nov 7, 2023
2 parents 1b75665 + 1288788 commit 18c1885
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 159 deletions.
18 changes: 9 additions & 9 deletions client/components/mma/MMAPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,63 +105,63 @@ const ExecuteCancellation = lazy(() =>

const MembershipCancellationLanding = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/MembershipCancellationLanding'
).then(({ MembershipCancellationLanding }) => ({
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/CancellationLanding'
).then(({ CancellationLanding: MembershipCancellationLanding }) => ({
default: MembershipCancellationLanding,
})),
);

const ValueOfSupport = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/ValueOfSupport'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/ValueOfSupport'
).then(({ ValueOfSupport }) => ({
default: ValueOfSupport,
})),
);

const SaveOptions = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/SaveOptions'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/SaveOptions'
).then(({ SaveOptions }) => ({
default: SaveOptions,
})),
);

const MembershipSwitch = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/MembershipSwitch'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/MembershipSwitch'
).then(({ MembershipSwitch }) => ({
default: MembershipSwitch,
})),
);

const SelectReason = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/SelectReason'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/SelectReason'
).then(({ SelectReason }) => ({
default: SelectReason,
})),
);

const ContinueMembershipConfirmation = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/ContinueMembershipConfirmation'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/ContinueMembershipConfirmation'
).then(({ ContinueMembershipConfirmation }) => ({
default: ContinueMembershipConfirmation,
})),
);

const ConfirmMembershipCancellation = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/ConfirmMembershipCancellation'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/ConfirmMembershipCancellation'
).then(({ ConfirmMembershipCancellation }) => ({
default: ConfirmMembershipCancellation,
})),
);

const SwitchThankYou = lazy(() =>
import(
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/SwitchThankYou'
/* webpackChunkName: "Cancellation" */ './cancel/cancellationSaves/membership/SwitchThankYou'
).then(({ SwitchThankYou }) => ({
default: SwitchThankYou,
})),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Meta, StoryObj } from '@storybook/react';
import { rest } from 'msw';
import { ReactRouterDecorator } from '@/.storybook/ReactRouterDecorator';
import { toMembersDataApiResponse } from '@/client/fixtures/mdapiResponse';
import {
digitalPackPaidByDirectDebit,
membershipSupporterCurrencyUSD,
} from '@/client/fixtures/productBuilder/testProducts';
import { PRODUCT_TYPES } from '@/shared/productTypes';
import { CancellationContainer } from '../CancellationContainer';
import { CancellationLanding } from './CancellationLanding';

export default {
title: 'Pages/CancellationLanding',
component: CancellationContainer,
decorators: [ReactRouterDecorator],
parameters: {
layout: 'fullscreen',
},
} as Meta<typeof CancellationContainer>;

export const Membership: StoryObj<typeof CancellationLanding> = {
render: () => {
return <CancellationLanding />;
},

parameters: {
reactRouter: {
state: {
productDetail: membershipSupporterCurrencyUSD(),
user: { email: '[email protected]' },
},
container: (
<CancellationContainer productType={PRODUCT_TYPES.membership} />
),
},
msw: [
rest.get('/api/me/mma', (_req, res, ctx) => {
return res(
ctx.json(
toMembersDataApiResponse(
membershipSupporterCurrencyUSD(),
),
),
);
}),
],
},
};

export const DigiPack: StoryObj<typeof CancellationLanding> = {
render: () => {
return <CancellationLanding />;
},

parameters: {
reactRouter: {
state: {
productDetail: digitalPackPaidByDirectDebit(),
user: { email: '[email protected]' },
},
container: (
<CancellationContainer productType={PRODUCT_TYPES.digipack} />
),
},
msw: [
rest.get('/api/me/mma', (_req, res, ctx) => {
return res(
ctx.json(
toMembersDataApiResponse(
digitalPackPaidByDirectDebit(),
),
),
);
}),
],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { textSans } from '@guardian/source-foundations';
import { Button, Stack } from '@guardian/source-react-components';
import { useContext } from 'react';
import { Navigate, useNavigate } from 'react-router';
import { featureSwitches } from '../../../../../shared/featureSwitches';
import type {
MembersDataApiResponse,
PaidSubscriptionPlan,
Expand All @@ -17,7 +16,6 @@ import {
LoadingState,
useAsyncLoader,
} from '../../../../utilities/hooks/useAsyncLoader';
import { getNewMembershipPrice } from '../../../../utilities/membershipPriceRise';
import { allRecurringProductsDetailFetcher } from '../../../../utilities/productUtils';
import type { PhoneRegionKey } from '../../../shared/CallCenterEmailAndNumbers';
import { CallCentreEmailAndNumbers } from '../../../shared/CallCenterEmailAndNumbers';
Expand All @@ -27,36 +25,7 @@ import { DefaultLoadingView } from '../../shared/asyncComponents/DefaultLoadingV
import { Heading } from '../../shared/Heading';
import type { CancellationContextInterface } from '../CancellationContainer';
import { CancellationContext } from '../CancellationContainer';

function ineligibleForSave(
products: ProductDetail[],
membershipToCancel: ProductDetail,
) {
const inPaymentFailure = products.find((product) => product.alertText);

const hasOtherProduct = products.find(
(product) =>
product.mmaCategory != 'membership' &&
!product.subscription.cancelledAt,
);

const membershipTierIsNotSupporter =
membershipToCancel.tier !== 'Supporter';

const mainPlan = getMainPlan(
membershipToCancel.subscription,
) as PaidSubscriptionPlan;

const hasBeenPriceRisen =
getNewMembershipPrice(mainPlan) === mainPlan.price / 100;

return (
inPaymentFailure ||
hasOtherProduct ||
membershipTierIsNotSupporter ||
hasBeenPriceRisen
);
}
import { ineligibleForSave } from './eligibilityCheck';

function getPhoneRegion(currencyIso: CurrencyIso): PhoneRegionKey {
switch (currencyIso) {
Expand All @@ -70,14 +39,13 @@ function getPhoneRegion(currencyIso: CurrencyIso): PhoneRegionKey {
}
}

export const MembershipCancellationLanding = () => {
export const CancellationLanding = () => {
const navigate = useNavigate();
const cancellationContext = useContext(
const { productDetail: productToCancel } = useContext(
CancellationContext,
) as CancellationContextInterface;
const membership = cancellationContext.productDetail;

if (!membership) {
if (!productToCancel) {
return <Navigate to="/" />;
}

Expand All @@ -99,10 +67,7 @@ export const MembershipCancellationLanding = () => {
return <GenericErrorScreen />;
}

if (
featureSwitches.membershipSave &&
ineligibleForSave(data.products as ProductDetail[], membership)
) {
if (ineligibleForSave(data.products as ProductDetail[], productToCancel)) {
return (
<Navigate
to="../"
Expand All @@ -114,7 +79,7 @@ export const MembershipCancellationLanding = () => {
}

const mainPlan = getMainPlan(
membership.subscription,
productToCancel.subscription,
) as PaidSubscriptionPlan;

const phoneRegion = getPhoneRegion(mainPlan.currencyISO as CurrencyIso);
Expand Down Expand Up @@ -149,7 +114,7 @@ export const MembershipCancellationLanding = () => {
<CallCentreEmailAndNumbers
hideEmailAddress={true}
phoneRegionFilterKeys={
membership.selfServiceCancellation
productToCancel.selfServiceCancellation
.phoneRegionsToDisplay
}
openPhoneRegion={phoneRegion}
Expand All @@ -168,6 +133,7 @@ export const MembershipCancellationLanding = () => {
</p>
<div css={buttonLayoutCss}>
<Button
// ToDo: navigate to different routes depending on the product journey
onClick={() =>
navigate('../details', {
state: { user: data.user },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import type { Meta, StoryFn, StoryObj } from '@storybook/react';
import { rest } from 'msw';
import type { Meta, StoryFn } from '@storybook/react';
import { ReactRouterDecorator } from '../../../../../.storybook/ReactRouterDecorator';
import { PRODUCT_TYPES } from '../../../../../shared/productTypes';
import { toMembersDataApiResponse } from '../../../../fixtures/mdapiResponse';
import { membershipSupporterCurrencyUSD } from '../../../../fixtures/productBuilder/testProducts';
import { CancellationContainer } from '../CancellationContainer';
import { ConfirmMembershipCancellation } from './ConfirmMembershipCancellation';
import { ContinueMembershipConfirmation } from './ContinueMembershipConfirmation';
import { MembershipCancellationLanding } from './MembershipCancellationLanding';
import { MembershipSwitch } from './MembershipSwitch';
import { SaveOptions } from './SaveOptions';
import { SelectReason } from './SelectReason';
import { SwitchThankYou } from './SwitchThankYou';
import { ValueOfSupport } from './ValueOfSupport';
import { ConfirmMembershipCancellation } from './membership/ConfirmMembershipCancellation';
import { ContinueMembershipConfirmation } from './membership/ContinueMembershipConfirmation';
import { MembershipSwitch } from './membership/MembershipSwitch';
import { SaveOptions } from './membership/SaveOptions';
import { SelectReason } from './membership/SelectReason';
import { SwitchThankYou } from './membership/SwitchThankYou';
import { ValueOfSupport } from './membership/ValueOfSupport';

export default {
title: 'Pages/CancellationSave',
Expand Down Expand Up @@ -40,26 +37,6 @@ export const SwitchReview: StoryFn<typeof MembershipSwitch> = () => {
return <MembershipSwitch />;
};

export const LandingPage: StoryObj<typeof MembershipCancellationLanding> = {
render: () => {
return <MembershipCancellationLanding />;
},

parameters: {
msw: [
rest.get('/api/me/mma', (_req, res, ctx) => {
return res(
ctx.json(
toMembersDataApiResponse(
membershipSupporterCurrencyUSD(),
),
),
);
}),
],
},
};

export const SwitchOptions: StoryFn<typeof SaveOptions> = () => {
return <SaveOptions />;
};
Expand Down
44 changes: 44 additions & 0 deletions client/components/mma/cancel/cancellationSaves/eligibilityCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getNewMembershipPrice } from '@/client/utilities/membershipPriceRise';
import type {
PaidSubscriptionPlan,
ProductDetail,
} from '@/shared/productResponse';
import { getMainPlan } from '@/shared/productResponse';

export function ineligibleForSave(
products: ProductDetail[],
productToCancel: ProductDetail,
) {
if (productToCancel.mmaCategory === 'membership') {
return isMembershipIneligible(products, productToCancel);
}
}

function isMembershipIneligible(
products: ProductDetail[],
productToCancel: ProductDetail,
) {
const inPaymentFailure = products.find((product) => product.alertText);

const hasOtherProduct = products.find(
(product) =>
product.mmaCategory != 'membership' &&
!product.subscription.cancelledAt,
);

const membershipTierIsNotSupporter = productToCancel.tier !== 'Supporter';

const mainPlan = getMainPlan(
productToCancel.subscription,
) as PaidSubscriptionPlan;

const hasBeenPriceRisen =
getNewMembershipPrice(mainPlan) === mainPlan.price / 100;

return (
inPaymentFailure ||
hasOtherProduct ||
membershipTierIsNotSupporter ||
hasBeenPriceRisen
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ import { palette, space, textSans } 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 { 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';
} 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';
} from '../../CancellationContainer';
import { CancellationContext } from '../../CancellationContainer';
import type { OptionalCancellationReasonId } from '../../cancellationReason';

export const ConfirmMembershipCancellation = () => {
const navigate = useNavigate();
Expand Down
Loading

0 comments on commit 18c1885

Please sign in to comment.