Skip to content
Draft
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
Expand Up @@ -37,6 +37,7 @@ import {
observerDelivery,
observerVoucherPaidByCard,
patronMembership,
secondaryDigitalPackSharedMembership,
supporterPlus,
supporterPlusAnnualCancelled,
supporterPlusCancelled,
Expand Down Expand Up @@ -134,6 +135,11 @@ export const WithSubscriptions: StoryObj<typeof AccountOverview> = {
http.get('/api/me/one-off-contributions', () => {
return HttpResponse.json([]);
}),
http.get('/api/secondary-user/me', () => {
return HttpResponse.json(
secondaryDigitalPackSharedMembership(),
);
}),
],
},
};
Expand Down
12 changes: 12 additions & 0 deletions client/components/mma/accountoverview/AccountOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { EmptyAccountOverview } from './EmptyAccountOverview';
import { InAppPurchaseCard } from './InAppPurchaseCard';
import { PersonalisedHeader } from './PersonalisedHeader';
import { ProductCard } from './ProductCard';
import { SecondaryAccountProductCard } from './SecondaryAccountCard';
import { SingleContributionCard } from './SingleContributionCard';

export const isDigitalPlusUpgradeBannerFlagEnabled = (): boolean => {
Expand Down Expand Up @@ -161,6 +162,7 @@ const AccountOverviewPage = ({ isFromApp }: IsFromAppProps) => {
mpapiResponse,
singleContributionsResponse,
userSubscriptionsResponse,
maapiResponse,
} = useAccountDataLoader();

useEffect(() => {
Expand Down Expand Up @@ -211,6 +213,8 @@ const AccountOverviewPage = ({ isFromApp }: IsFromAppProps) => {
.filter(isProduct)
.sort(sortByJoinDate);

const secondaryAccountDetails = maapiResponse || null;

const activeProductsNotPendingCancellation = allActiveProductDetails.filter(
(product: ProductDetail) => !product.subscription.cancelledAt,
);
Expand Down Expand Up @@ -445,6 +449,14 @@ const AccountOverviewPage = ({ isFromApp }: IsFromAppProps) => {
/>
)}
{possiblyAffectedByCanadaPostStrike && <CanadaStrike />}
{secondaryAccountDetails && (
<Fragment>
<h2 css={subHeadingCss}>Shared with you</h2>
<SecondaryAccountProductCard
maapiResponse={secondaryAccountDetails}
/>
</Fragment>
)}
{uniqueProductCategories.map((category) => {
const groupedProductType = GROUPED_PRODUCT_TYPES[category];
const activeProductsInCategory = allActiveProductDetails.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ const guardianWeeklyGiftBenefitsCopy = (npd: NextPaymentDetails) =>
const guardianWeeklyBenefitsCopy = (npd: NextPaymentDetails) =>
`You're subscribed to The Guardian Weekly for ${npd.currentPriceValue} per ${npd.paymentInterval} and receive a curated news magazine featuring our best global journalism in print, as well as unlimited access to our full suite of digital benefits.`;

// TODO This not having npd has been quite a problem. Current solution is a bit hacky.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export const secondaryUserBenefitsCopy = () =>
`You're enjoying Digital plus as part of a shared subscription.`; // TODO make Digital plus part dynamic rather than hardcoded.

/**
* In-app purchases have their own dedicated product card component so are not
* included in the configuration object.
Expand Down Expand Up @@ -134,3 +138,8 @@ export const getGuardianWeeklyGiftBenefitsCopy: ProductCardConfiguration = {
colour: productColour.guardianWeekly,
getBenefitsSectionCopy: guardianWeeklyGiftBenefitsCopy,
};

export const getSecondaryUserBenefitsCopy: ProductCardConfiguration = {
colour: productColour.digital,
getBenefitsSectionCopy: secondaryUserBenefitsCopy,
};
50 changes: 47 additions & 3 deletions client/components/mma/accountoverview/ProductCardSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { FetchUpgradePreviewParams } from '@/client/utilities/hooks/useUpgr
import { parseDate } from '@/shared/dates';
import type {
MembersDataApiUser,
MultipleAccountsPrimaryUser,
PaidSubscriptionPlan,
ProductDetail,
SubscriptionPlan,
Expand All @@ -29,6 +30,7 @@ import type { ProductCardConfiguration } from './ProductCardConfiguration';
import {
benefitsSectionBackgroundColour,
benefitsTextCss,
centeredActionCss,
centeredButtonCss,
giftRibbonColour,
giftRibbonCopyColour,
Expand All @@ -37,6 +39,8 @@ import {
productCardTitleCss,
productDetailLayoutCss,
sectionHeadingCss,
sharedMembershipLeaveButtonCss,
sharedMembershipTextCss,
} from './ProductCardStyles';

const NewPriceAlert = () => {
Expand Down Expand Up @@ -92,21 +96,25 @@ export const BenefitsCopyAndToggle = ({
mainPlan,
nextPaymentDetails,
overrideBenefits,
overrideBenefitsText,
}: {
cardConfig: ProductCardConfiguration;
specificProductType: ProductType;
mainPlan: SubscriptionPlan;
nextPaymentDetails?: NextPaymentDetails | undefined;
nextPaymentDetails?: NextPaymentDetails;
overrideBenefits?: ProductBenefit[];
overrideBenefitsText?: string;
}) =>
cardConfig.getBenefitsSectionCopy &&
nextPaymentDetails && (
(nextPaymentDetails || overrideBenefitsText) && (
<Card.Section
backgroundColor={benefitsSectionBackgroundColour}
removeBorders
>
<p css={benefitsTextCss}>
{cardConfig.getBenefitsSectionCopy(nextPaymentDetails)}
{nextPaymentDetails

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be done because secondary account benefits copy text cannot take in a nextPaymentDetails since it is not used for anything in the text. All other subscription product cards show some information about pricing and next payment dates.

? cardConfig.getBenefitsSectionCopy(nextPaymentDetails)
: overrideBenefitsText}
</p>
<BenefitsToggle
productType={specificProductType.productType}
Expand Down Expand Up @@ -135,6 +143,42 @@ export const GuardianAdLiteCopy = ({
</Card.Section>
);

export const SecondaryUserSubscriptionDetails = ({
subscriptionName,
primarySubscriber,
}: {
subscriptionName: string;
primarySubscriber: MultipleAccountsPrimaryUser | undefined;
}) =>
primarySubscriber && (
<Card.Section>
<div css={productDetailLayoutCss}>
<div>
<h4 css={sectionHeadingCss}>Subscription details</h4>
<p css={sharedMembershipTextCss}>
Subscription: {subscriptionName} shared subscription{' '}
<br />
<br />
You’ve been given access by{' '}
{primarySubscriber.firstName}. Your account and activity
are private and not shared with the subscription owner.
</p>
</div>
<div css={centeredActionCss}>
<Button
aria-label={`${subscriptionName} : Leave shared subscription`}
size="small"
priority="tertiary"
cssOverrides={sharedMembershipLeaveButtonCss}
onClick={() => undefined}
>
Manage support
</Button>
</div>
</div>
</Card.Section>
);

const StartDateRow = ({
subscriptionStartDate,
shouldShowStartDate,
Expand Down
15 changes: 15 additions & 0 deletions client/components/mma/accountoverview/ProductCardStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,18 @@ export const giftRibbonColour = (cardConfig: ProductCardConfiguration) =>
cardConfig.invertText ? palette.brand[400] : palette.brandAlt[400];
export const giftRibbonCopyColour = (cardConfig: ProductCardConfiguration) =>
cardConfig.invertText ? palette.brandAlt[400] : palette.brand[400];

export const sharedMembershipTextCss = css`
${textSans17};
margin: 0;
`;

export const centeredActionCss = css`
display: flex;
justify-content: flex-end;
`;

export const sharedMembershipLeaveButtonCss = css`
justify-content: flex-end;
background: transparent;
`;
60 changes: 60 additions & 0 deletions client/components/mma/accountoverview/SecondaryAccountCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { MultipleAccountsApiResponse } from '@/shared/productResponse';
import { getSpecificProductTypeFromProductKey } from '@/shared/productResponse';
import { Card } from '../shared/Card';
import {
getSecondaryUserBenefitsCopy,
secondaryUserBenefitsCopy,
} from './ProductCardConfiguration';
import {
BenefitsCopyAndToggle,
ProductCardHeader,
SecondaryUserSubscriptionDetails,
} from './ProductCardSections';

export const SecondaryAccountProductCard = ({
maapiResponse,
}: {
maapiResponse: MultipleAccountsApiResponse;
}) => {
// TODO: This is a placeholder. We will likely need to adjust this.

// TODO Hard-coded atm
const specificProductType =
getSpecificProductTypeFromProductKey('Digital Pack');
const mainPlan = {
name: specificProductType.friendlyName,
shouldBeVisible: true,
};
if (!mainPlan) {
throw new Error('mainPlan does not exist in SecondaryAccountCard');
}
const productTitle =
specificProductType.productTitle(mainPlan) + ' shared subscription';
const cardConfig = getSecondaryUserBenefitsCopy;
const overrideBenefitsText = secondaryUserBenefitsCopy();

return (
<>
{maapiResponse.primaryUsers.map((primaryUser, index) => (
<Card key={index}>
<ProductCardHeader
cardConfig={cardConfig}
productTitle={productTitle}
/>

<BenefitsCopyAndToggle
cardConfig={cardConfig}
specificProductType={specificProductType}
mainPlan={mainPlan}
overrideBenefitsText={overrideBenefitsText}
/>

<SecondaryUserSubscriptionDetails
subscriptionName={productTitle}
primarySubscriber={primaryUser}
/>
</Card>
))}
</>
);
};
30 changes: 29 additions & 1 deletion client/fixtures/productBuilder/baseProducts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { ProductDetail } from '../../../shared/productResponse';
import type {
MultipleAccountsApiResponse,
ProductDetail,
} from '../../../shared/productResponse';

// Base ProductTypes to support
// | 'membership'
Expand Down Expand Up @@ -261,6 +264,19 @@ export function baseDigitalPack(): ProductDetail {
};
}

// TODO format still to be decided?
export function baseSecondaryDigitalPack(): MultipleAccountsApiResponse {
return {
primaryUsers: [
{
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
},
],
};
}

export function baseContribution(): ProductDetail {
return {
mmaProductKey: 'Contributor',
Expand Down Expand Up @@ -1260,3 +1276,15 @@ export function baseTierThree(): ProductDetail {
isTestUser: false,
};
}

export function baseSecondaryUser(): MultipleAccountsApiResponse {
Comment thread
enesihsa marked this conversation as resolved.
return {
primaryUsers: [
{
firstName: 'John',
lastName: 'Doe',
email: 'j.doe@example.com',
},
],
};
}
5 changes: 5 additions & 0 deletions client/fixtures/productBuilder/testProducts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
baseNationalDeliveryPlus,
baseObserverDelivery,
basePatron,
baseSecondaryUser,
baseSupporterPlus,
baseTierThree,
baseVoucher,
Expand Down Expand Up @@ -69,6 +70,10 @@ export function digitalPackPaidByDirectDebit() {
.getProductDetailObject();
}

export function secondaryDigitalPackSharedMembership() {
return baseSecondaryUser();
}

export function digitalPackWithPaymentFailure() {
return new ProductBuilder(baseDigitalPack())
.payByDirectDebit()
Expand Down
8 changes: 8 additions & 0 deletions client/stores/AccountStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
CancelledProductDetail,
MembersDataApiResponse,
MembersDataApiUser,
MultipleAccountsApiResponse,
SingleProductDetail,
} from '../../shared/productResponse';
import { isProduct } from '../../shared/productResponse';
Expand Down Expand Up @@ -34,6 +35,7 @@ interface AccountState {
mpapiResponse: MPAPIResponse | null;
singleContributionsResponse: SingleProductDetail[] | null;
userSubscriptionsResponse: UserSubscriptionsResponse | null;
maapiResponse: MultipleAccountsApiResponse | null;
loadingState: AccountLoadingState;
error: string | null;
}
Expand All @@ -44,12 +46,14 @@ interface AccountActions {
setMpapiResponse: (response: MPAPIResponse) => void;
setSingleContributionsResponse: (response: SingleProductDetail[]) => void;
setUserSubscriptionsResponse: (response: UserSubscriptionsResponse) => void;
setMaapiResponse: (response: MultipleAccountsApiResponse) => void;
setAllResponses: (responses: {
mdapiResponse: MembersDataApiResponse | null;
cancelledProductsResponse: CancelledProductDetail[] | null;
mpapiResponse: MPAPIResponse | null;
singleContributionsResponse: SingleProductDetail[] | null;
userSubscriptionsResponse: UserSubscriptionsResponse | null;
maapiResponse: MultipleAccountsApiResponse | null;
}) => void;
setLoadingState: (state: AccountLoadingState) => void;
setError: (error: string | null) => void;
Expand All @@ -69,6 +73,7 @@ const initialState: AccountState = {
mpapiResponse: null,
singleContributionsResponse: null,
userSubscriptionsResponse: null,
maapiResponse: null,
loadingState: AccountLoadingState.NotStarted,
error: null,
};
Expand Down Expand Up @@ -99,6 +104,8 @@ export const useAccountStore = create<AccountStore>()(
false,
'setUserSubscriptionsResponse',
),
setMaapiResponse: (response) =>
set({ maapiResponse: response }, false, 'setMaapiResponse'),
setAllResponses: (responses) =>
set(
{
Expand All @@ -110,6 +117,7 @@ export const useAccountStore = create<AccountStore>()(
responses.singleContributionsResponse,
userSubscriptionsResponse:
responses.userSubscriptionsResponse,
maapiResponse: responses.maapiResponse,
loadingState: AccountLoadingState.Loaded,
error: null,
},
Expand Down
Loading
Loading