Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contribution cancellation journey with pause offer #1369

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { MembersDataApiResponse } from '../../../../shared/productResponse';
import { isProduct } from '../../../../shared/productResponse';
import {
getSpecificProductTypeFromTier,
isProduct,
} from '../../../../shared/productResponse';
import {
LoadingState,
useAsyncLoader,
Expand Down Expand Up @@ -44,9 +47,12 @@ export const SubscriptionInformation = () => {
{data.products.length > 0 ? (
data.products.map((product) => {
if (isProduct(product)) {
const specificProductType =
getSpecificProductTypeFromTier(product.tier);
return (
<li>
{product.tier} - {product.mmaCategory} -{' '}
{product.tier} -{' '}
{specificProductType.groupedProductType} -{' '}
{product.subscription.subscriptionId}
</li>
);
Expand Down
44 changes: 27 additions & 17 deletions client/components/mma/MMAPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,27 +132,27 @@ const ValueOfSupport = lazy(() =>
})),
);

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

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

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

Expand Down Expand Up @@ -706,17 +706,27 @@ const MMARouter = () => {
/>
<Route
path="offer"
element={<SupporterPlusOffer />}
element={<CancelAlternativeOffer />}
/>
<Route
path="pause"
element={<CancelAlternativeOffer />}
/>
<Route
path="pause-review"
element={<CancelAlternativeReview />}
/>
<Route
path="offer-review"
element={<SupporterPlusOfferReview />}
element={<CancelAlternativeReview />}
/>
<Route
path="offer-confirmed"
element={
<SupporterPlusOfferConfirmed />
}
element={<CancelAlternativeConfirmed />}
/>
<Route
path="pause-confirmed"
element={<CancelAlternativeConfirmed />}
/>

<Route
Expand Down
21 changes: 17 additions & 4 deletions client/components/mma/accountoverview/AccountOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
SingleProductDetail,
} from '../../../../shared/productResponse';
import {
getSpecificProductTypeFromTier,
isProduct,
isSpecificProductType,
sortByJoinDate,
Expand Down Expand Up @@ -112,10 +113,16 @@ const AccountOverviewPage = ({ isFromApp }: IsFromAppProps) => {
...allActiveProductDetails,
...allCancelledProductDetails,
].map((product: ProductDetail | CancelledProductDetail) => {
if (product.mmaCategory === 'recurringSupport') {
const specificProductType = getSpecificProductTypeFromTier(
product.tier,
);
if (
specificProductType.groupedProductType ===
'recurringSupportWithBenefits'
) {
return 'subscriptions'; // we want to override the display text in MMA for RC/S+ but not affect functionality
}
return product.mmaCategory;
return specificProductType.groupedProductType;
});

const uniqueProductCategories = [...new Set(allProductCategories)];
Expand Down Expand Up @@ -171,10 +178,16 @@ const AccountOverviewPage = ({ isFromApp }: IsFromAppProps) => {
const visualProductGroupingCategory = (
product: ProductDetail | CancelledProductDetail,
): GroupedProductTypeKeys => {
if (product.mmaCategory === 'recurringSupport') {
const specificProductType = getSpecificProductTypeFromTier(
product.tier,
);
if (
specificProductType.groupedProductType ===
'recurringSupportWithBenefits'
) {
return 'subscriptions';
}
return product.mmaCategory;
return specificProductType.groupedProductType;
};

return (
Expand Down
15 changes: 10 additions & 5 deletions client/components/mma/accountoverview/CancelledProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LinkButton, Stack } from '@guardian/source/react-components';
import { InfoSummary } from '@guardian/source-development-kitchen/react-components';
import { parseDate } from '@/shared/dates';
import type { CancelledProductDetail } from '@/shared/productResponse';
import { getSpecificProductTypeFromTier } from '@/shared/productResponse';
import { GROUPED_PRODUCT_TYPES } from '@/shared/productTypes';
import { wideButtonLayoutCss } from '../../../styles/ButtonStyles';
import { trackEvent } from '../../../utilities/analytics';
Expand All @@ -20,16 +21,20 @@ export const CancelledProductCard = ({
}: {
productDetail: CancelledProductDetail;
}) => {
const groupedProductType = GROUPED_PRODUCT_TYPES[productDetail.mmaCategory];
const specificProductType =
groupedProductType.mapGroupedToSpecific(productDetail);
const specificProductType = getSpecificProductTypeFromTier(
productDetail.tier,
);
const groupedProductType =
GROUPED_PRODUCT_TYPES[specificProductType.groupedProductType];

const cardConfig =
productCardConfiguration[specificProductType.productType];

const showSubscribeAgainButton =
productDetail.mmaCategory !== 'membership' &&
productDetail.mmaCategory !== 'recurringSupport';
specificProductType.groupedProductType !== 'membership' &&
specificProductType.groupedProductType !== 'recurringSupport' &&
specificProductType.groupedProductType !==
'recurringSupportWithBenefits';

return (
<Stack space={4}>
Expand Down
14 changes: 10 additions & 4 deletions client/components/mma/accountoverview/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import type {
ProductDetail,
Subscription,
} from '@/shared/productResponse';
import { getMainPlan, isGift } from '@/shared/productResponse';
import {
getMainPlan,
getSpecificProductTypeFromTier,
isGift,
} from '@/shared/productResponse';
import { GROUPED_PRODUCT_TYPES } from '@/shared/productTypes';
import { wideButtonLayoutCss } from '../../../styles/ButtonStyles';
import { trackEvent } from '../../../utilities/analytics';
Expand Down Expand Up @@ -119,9 +123,11 @@ export const ProductCard = ({
throw new Error('mainPlan does not exist in ProductCard');
}

const groupedProductType = GROUPED_PRODUCT_TYPES[productDetail.mmaCategory];
const specificProductType =
groupedProductType.mapGroupedToSpecific(productDetail);
const specificProductType = getSpecificProductTypeFromTier(
productDetail.tier,
);
const groupedProductType =
GROUPED_PRODUCT_TYPES[specificProductType.groupedProductType];

const isPatron = productDetail.subscription.readerType === 'Patron';

Expand Down
48 changes: 28 additions & 20 deletions client/components/mma/billing/Billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
} from '../../../../shared/productResponse';
import {
getMainPlan,
getSpecificProductTypeFromTier,
isGift,
isProduct,
sortByJoinDate,
Expand Down Expand Up @@ -61,8 +62,8 @@ interface ProductDetailWithInvoice extends ProductDetail {
invoices: InvoiceDataApiItem[];
}

type MMACategoryToProductDetails = {
[mmaCategory in GroupedProductTypeKeys]: ProductDetailWithInvoice[];
type ProductGroupingToProductDetails = {
[productGrouping in GroupedProductTypeKeys]: ProductDetailWithInvoice[];
};

type BillingResponse = [
Expand Down Expand Up @@ -115,42 +116,47 @@ function joinInvoicesWithProductsInCategories(
}
});

const mmaCategoryToProductDetails =
const productGroupingToProductDetails =
organiseProductsIntoCategory(allProductDetails);
return { allProductDetails, mmaCategoryToProductDetails };
return { allProductDetails, productGroupingToProductDetails };
}

function organiseProductsIntoCategory(allProductDetails: ProductDetail[]) {
return allProductDetails.reduce(
(accumulator, productDetail) => ({
return allProductDetails.reduce((accumulator, productDetail) => {
const specificProductType = getSpecificProductTypeFromTier(
productDetail.tier,
);
return {
...accumulator,
[productDetail.mmaCategory]: [
...(accumulator[productDetail.mmaCategory] || []),
[specificProductType.groupedProductType]: [
...(accumulator[specificProductType.groupedProductType] || []),
productDetail,
],
}),
{} as MMACategoryToProductDetails,
);
};
}, {} as ProductGroupingToProductDetails);
}

function renderProductBillingInfo([mmaCategory, productDetails]: [
function renderProductBillingInfo([productGrouping, productDetails]: [
string,
ProductDetailWithInvoice[],
]) {
return (
productDetails.length > 0 && (
<Fragment key={mmaCategory}>
<Fragment key={productGrouping}>
{productDetails.map((productDetail) => {
const mainPlan = getMainPlan(productDetail.subscription);
if (!mainPlan) {
throw new Error(
'mainPlan does not exist for product in billing page',
);
}
const specificProductType = getSpecificProductTypeFromTier(
productDetail.tier,
);
const groupedProductType =
GROUPED_PRODUCT_TYPES[productDetail.mmaCategory];
const specificProductType =
groupedProductType.mapGroupedToSpecific(productDetail);
GROUPED_PRODUCT_TYPES[
specificProductType.groupedProductType
];
const hasCancellationPending =
productDetail.subscription.cancelledAt;
const cancelledCopy =
Expand Down Expand Up @@ -372,11 +378,11 @@ function renderInAppPurchase(subscription: AppSubscription) {
}

function BillingDetailsComponent(props: {
mmaCategoryToProductDetails: MMACategoryToProductDetails;
productGroupingToProductDetails: ProductGroupingToProductDetails;
}) {
return (
<>
{Object.entries(props.mmaCategoryToProductDetails).map(
{Object.entries(props.productGroupingToProductDetails).map(
renderProductBillingInfo,
)}
</>
Expand Down Expand Up @@ -409,7 +415,7 @@ const BillingPage = () => {
isValidAppSubscription,
);

const { allProductDetails, mmaCategoryToProductDetails } =
const { allProductDetails, productGroupingToProductDetails } =
joinInvoicesWithProductsInCategories(mdapiResponse, invoicesResponse);

if (
Expand All @@ -428,7 +434,9 @@ const BillingPage = () => {
appSubscriptions.map(renderInAppPurchase)}

<BillingDetailsComponent
mmaCategoryToProductDetails={mmaCategoryToProductDetails}
productGroupingToProductDetails={
productGroupingToProductDetails
}
/>
</>
);
Expand Down
Loading
Loading