Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions client/components/mma/MMAPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ const AccountOverview = lazyWithRetry(() =>
).then(({ AccountOverview }) => ({ default: AccountOverview })),
);

const ExtraAccounts = lazyWithRetry(() =>
import(
/* webpackChunkName: "ExtraAccounts" */ './extraAccounts/ExtraAccounts'
).then(({ ExtraAccounts }) => ({ default: ExtraAccounts })),
);

const Billing = lazyWithRetry(() =>
import(/* webpackChunkName: "Billing" */ './billing/Billing').then(
({ Billing }) => ({ default: Billing }),
Expand Down Expand Up @@ -588,6 +594,11 @@ const MMARouter = () => {
element={<AccountOverview isFromApp />}
/>

<Route
path="/extra-accounts"
element={<ExtraAccounts />}
/>

<Route path="/billing" element={<Billing />} />
{Object.values(PRODUCT_TYPES).map(
(productType: ProductType) => (
Expand Down
5 changes: 5 additions & 0 deletions client/components/mma/MMAPageSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ const MMALocationObjectArr: LocationObject[] = [
path: '/',
selectedNavItem: NAV_LINKS.accountOverview,
},
{
title: 'Extra accounts',
path: '/extra-accounts',
selectedNavItem: NAV_LINKS.extraAccounts,
},
{
title: 'Billing',
path: '/billing',
Expand Down
7 changes: 7 additions & 0 deletions client/components/mma/accountoverview/AccountOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
GROUPED_PRODUCT_TYPES,
PRODUCT_TYPES,
} from '../../../../shared/productTypes';
import { isExtraAccountsFlagEnabled } from '../../../utilities/extraAccounts';
import { useAccountDataLoader } from '../../../utilities/hooks/useAccountDataLoader';
import { GenericErrorScreen } from '../../shared/GenericErrorScreen';
import { NAV_LINKS } from '../../shared/nav/NavConfig';
Expand All @@ -42,6 +43,7 @@ import { DefaultLoadingView } from '../shared/asyncComponents/DefaultLoadingView
import { DownloadAppCtaVariation1 } from '../shared/DownloadAppCtaVariation1';
import { DownloadEditionsAppCtaWithImage } from '../shared/DownloadEditionsAppCtaWithImage';
import { DownloadFeastAppCtaWithImage } from '../shared/DownloadFeastAppCtaWithImage';
import { ExtraAccountsBanner } from '../shared/ExtraAccountsBanner';
import type { IsFromAppProps } from '../shared/IsFromAppProps';
import { NewspaperArchiveCta } from '../shared/NewspaperArchiveCta';
import { nonServiceableCountries } from '../shared/NonServiceableCountries';
Expand Down Expand Up @@ -103,6 +105,11 @@ export const BenefitsCtas = ({ email, productKeys }: BenefitsCtasProps) => {

return (
<>
{/* TODO: remove the isExtraAccountsFlagEnabled() query-param check
once the Extra accounts feature ships; gate on Digital plus only. */}
{hasDigitalPack && isExtraAccountsFlagEnabled() && (
<ExtraAccountsBanner />
)}
{(hasDigitalPlusPrint ||
isPlusDigitalProduct ||
hasGuardianEmail ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
import { space } from '@guardian/source/foundations';
import { Button } from '@guardian/source/react-components';
import type { ReactNode } from 'react';
import { Modal } from '@/client/components/shared/Modal';
import type { HideFunction } from '@/client/components/shared/Modal';

const modalContainerCss = css`
padding: ${space[5]}px;
padding-bottom: ${space[8]}px;
max-width: 40%;
width: 100%;
`;

interface ExtraAccountCancelInvitationModalProps {
email: string;
title: string;
confirmLabel: string;
onConfirm: (email: string) => Promise<boolean>;
isSubmitting: boolean;
cssOverrides?: SerializedStyles;
instigator: ReactNode;
onSuccess: () => void;
children: ReactNode;
}

export const ExtraAccountCancelInvitationModal = ({
email,
title,
confirmLabel,
onConfirm,
isSubmitting,
cssOverrides,
instigator,
onSuccess,
children,
}: ExtraAccountCancelInvitationModalProps) => {
const confirmButton = (hide: HideFunction) => (
<div
css={css`
display: inline-block;
margin-top: 10px;
margin-right: 10px;
`}
>
<Button
isLoading={isSubmitting}
disabled={isSubmitting}
onClick={() => {
void onConfirm(email).then((ok) => {
if (ok) {
hide();
onSuccess();
}
});
}}
>
{confirmLabel}
</Button>
</div>
);

return (
<div css={cssOverrides}>
<Modal
title={title}
alternateOkText="Cancel"
additionalButton={confirmButton}
instigator={instigator}
containerCssOverrides={modalContainerCss}
hideCloseButton
>
{children}
</Modal>
</div>
);
};
90 changes: 90 additions & 0 deletions client/components/mma/extraAccounts/ExtraAccountInviteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { css } from '@emotion/react';
import { palette, space, textSans17 } from '@guardian/source/foundations';
import { Button, Checkbox, TextInput } from '@guardian/source/react-components';
import { useState } from 'react';

const formCss = css`
margin: ${space[5]}px 0 ${space[4]}px 0;
max-width: 70%;
`;

const introCss = css`
${textSans17};
margin: 0 0 ${space[3]}px 0;
`;

const checkboxBoxCss = css`
background-color: ${palette.neutral[97]};
padding: ${space[3]}px;
margin: ${space[5]}px 0 ${space[5]}px 0;
border-radius: ${space[2]}px;
`;

const actionsCss = css`
display: flex;
gap: ${space[4]}px;
`;

interface ExtraAccountInviteFormProps {
onCancel: () => void;
onSent: (email: string) => void;
sendInvitation: (email: string) => Promise<boolean>;
isSubmitting: boolean;
}

export const ExtraAccountInviteForm = ({
onCancel,
onSent,
sendInvitation,
isSubmitting,
}: ExtraAccountInviteFormProps) => {
const [email, setEmail] = useState('');
const [confirmedConsent, setConfirmedConsent] = useState(false);

const handleSend = async () => {
const ok = await sendInvitation(email);
if (ok) {
onSent(email);
}
};

return (
<div css={formCss}>
<p css={introCss}>
Enter the email address of the persons you'd like to invite.
</p>

<TextInput
label="Recipient's email address"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
Comment thread
j-ruda-guardian marked this conversation as resolved.

<div css={checkboxBoxCss}>
<Checkbox
checked={confirmedConsent}
onChange={(e) => setConfirmedConsent(e.target.checked)}
label="By ticking this box you're confirming that the person receiving this invitation is happy for the Guardian to email them."
name="consentConfirmation"
value="consentConfirmation"
/>
</div>

<div css={actionsCss}>
<Button
priority="primary"
size="small"
isLoading={isSubmitting}
disabled={!email || !confirmedConsent || isSubmitting}
onClick={() => void handleSend()}
>
Send invitation
</Button>
<Button priority="tertiary" size="small" onClick={onCancel}>
Cancel
</Button>
</div>
</div>
);
};
Loading
Loading