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
Binary file added .storybook/static/images/globeImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/globeImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { css, Global } from '@emotion/react';
import { useEffect, useMemo } from 'react';
import { ToggleContextProvider } from '#app/contexts/ToggleContext';
import ThemeProvider from '#app/components/ThemeProvider';
import { AccountContext } from '#app/contexts/AccountContext';
import { Close } from '#app/components/icons';
import AccountPromotionalBanner from '.';
import styles from './index.styles';
import { DISPLAY_ACCOUNT_PROMOTIONAL_BANNER_CSS_CLASS } from './utilities';

type AccountPromotionalBannerModalProps = {
onClose: () => void;
signInUrl: string | undefined;
registerUrl: string | undefined;
};

const AccountPromotionalBannerModal = ({
onClose,
signInUrl,
registerUrl,
}: AccountPromotionalBannerModalProps) => {
useEffect(() => {
const modal = document.getElementById(
'account-promotional-banner-modal-container',
);
const reactRootElement = document.getElementById('root');

const handleBackdropClick = (event: MouseEvent | TouchEvent) => {
if (event.target === event.currentTarget) onClose();
};

const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') onClose();
};

if (modal) {
reactRootElement?.setAttribute('inert', 'true');
modal.addEventListener('mousedown', handleBackdropClick);
modal.addEventListener('touchstart', handleBackdropClick);
modal.addEventListener('keydown', handleKeyDown);
}

return () => {
reactRootElement?.removeAttribute('inert');
modal?.removeEventListener('mousedown', handleBackdropClick);
modal?.removeEventListener('touchstart', handleBackdropClick);
modal?.removeEventListener('keydown', handleKeyDown);
};
}, [onClose]);

useEffect(() => {
document
.querySelector('html')
?.classList.add(DISPLAY_ACCOUNT_PROMOTIONAL_BANNER_CSS_CLASS);

return () => {
document
.querySelector('html')
?.classList.remove(DISPLAY_ACCOUNT_PROMOTIONAL_BANNER_CSS_CLASS);
};
}, []);

const accountContextValue = useMemo(
() => ({
isSignedIn: false,
isIdctaAvailable: true,
signInUrl,
registerUrl,
isPersonalizationAvailable: false,
isPersonalizationEnabled: false,
signOutUrl: undefined,
settingsUrl: undefined,
forYouUrl: undefined,
hashedUserId: undefined,
}),
[signInUrl, registerUrl],
);

const handleBackdropKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') onClose();
};

return (
<>
<Global
styles={css`
body {
overflow: hidden;
}
`}
/>
<div
role="dialog"
aria-modal="true"
aria-label="Sign in to BBC"
css={styles.modal}
id="account-promotional-banner-modal-container"
>
<div
role="button"
tabIndex={0}
aria-label="Close modal"
onClick={onClose}
onKeyDown={handleBackdropKeyDown}
css={styles.backdrop}
/>
<div css={styles.modalContent}>
<button
type="button"
onClick={onClose}
css={styles.closeButton}
aria-label="Close modal"
>
<Close />
</button>
<div css={styles.modalImageSide}>
<img
src="/images/globeImage.png"
alt=""
aria-hidden="true"
css={styles.image}
/>
</div>
<ToggleContextProvider>
<ThemeProvider service="ws">
<AccountContext.Provider value={accountContextValue}>
<AccountPromotionalBanner />
</AccountContext.Provider>
</ThemeProvider>
</ToggleContextProvider>
</div>
</div>
</>
);
};

export default AccountPromotionalBannerModal;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AccountPromotionalBanner from '.';
import mockIdctaConfig from '#app/contexts/AccountContext/mocks';
import readme from './README.md';
import { DISPLAY_ACCOUNT_PROMOTIONAL_BANNER_CSS_CLASS } from './utilities';
import AccountPromotionalBannerModal from './AccountPromotionalModal';

export default {
title: 'Account/AccountPromotionalBanner',
Expand Down Expand Up @@ -30,3 +31,11 @@ export const SignedIn = () => <AccountPromotionalBanner />;
SignedIn.globals = {
idctaConfig: { ...mockIdctaConfig, initialIsSignedIn: true },
};

export const SignedOutModal = () => (
<AccountPromotionalBannerModal
onClose={() => {}}
signInUrl="https://example.com/signin"
registerUrl="https://example.com/register"
/>
);
101 changes: 100 additions & 1 deletion src/app/components/Account/AccountPromotionalBanner/index.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default {
display: 'block',
},
}),
callToActionLink: ({ mq }) =>

callToActionLink: ({ mq }: Theme) =>
css({
padding: '1rem',
display: 'inline-flex',
Expand Down Expand Up @@ -61,6 +62,7 @@ export default {
fill: 'ButtonText',
},
}),

registerLink: ({ palette }: Theme) =>
css({
height: `${pixelsToRem(44)}rem`,
Expand All @@ -78,4 +80,101 @@ export default {
color: palette.WHITE,
},
}),

modal: css({
position: 'fixed',
inset: 0,
zIndex: 2147483647,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
}),

backdrop: css({
position: 'absolute',
inset: 0,
backgroundColor: 'rgba(20, 20, 20, 0.9)',
backdropFilter: 'blur(0.2rem)',
zIndex: 0,
}),

modalContent: ({ palette, mq }: Theme) =>
css({
position: 'relative',
zIndex: 1,
width: '80%',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
background: `linear-gradient(to bottom left, ${palette.POSTBOX} 0%, ${palette.BLACK} 50%, ${palette.POSTBOX} 100%)`,
'& aside': {
background: 'transparent',
backgroundImage: 'none',
},
'& aside > div > div': {
alignItems: 'center',
textAlign: 'center',
paddingInlineStart: '1rem',
paddingInlineEnd: '1rem',
},
'& aside button[type="button"]:last-child': {
display: 'none',
},
[mq.GROUP_0_MAX_WIDTH]: {
'& aside > div > div:first-child': {
paddingTop: '3rem',
},
},
}),

closeButton: ({ palette }: Theme) =>
css({
position: 'absolute',
top: '0.5rem',
insetInlineEnd: '0.5rem',
background: 'none',
border: 'none',
color: palette.WHITE,
fill: palette.WHITE,
cursor: 'pointer',
zIndex: 2,
width: `${pixelsToRem(44)}rem`,
height: `${pixelsToRem(44)}rem`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}),

modalImageSide: ({ mq }: Theme) =>
css({
width: '100%',
overflow: 'hidden',
flexShrink: 0,
position: 'relative',
alignSelf: 'center',
[mq.GROUP_0_MAX_WIDTH]: {
display: 'none',
},
[mq.GROUP_3_MIN_WIDTH]: {
width: '60%',
},
'& img': {
width: '100%',
height: '100%',
objectFit: 'cover',
display: 'block',
},
}),

image: ({ spacings }: Theme) =>
css({
width: '100%',
maxWidth: `${pixelsToRem(450)}rem`,
height: '100%',
objectFit: 'cover',
display: 'block',
margin: `${spacings.QUADRUPLE}rem ${spacings.DOUBLE}rem ${spacings.DOUBLE}rem`,
}),
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { ServiceContext } from '#contexts/ServiceContext';
import SaveButton from '#app/components/SaveButton';
import { use } from 'react';
import { use, useState } from 'react';
import useHydrationDetection from '#app/hooks/useHydrationDetection';
import { AccountContext } from '#app/contexts/AccountContext';
import AccountPromotionalBannerModal from '#app/components/Account/AccountPromotionalBanner/AccountPromotionalModal';

// TODO: This will contain the guest user experience for the SaveArticleButton,
// which will likely involve prompting the user to sign in or create an account to save articles.
const SaveArticleButtonGuest = () => {
const { translations } = use(ServiceContext);
const { signInUrl, registerUrl } = use(AccountContext);
const isHydrated = useHydrationDetection();
const [isModalOpen, setIsModalOpen] = useState(false);

const getButtonText = () => {
if (!isHydrated) {
return translations.saveArticleButton?.loading;
Expand All @@ -16,15 +19,21 @@ const SaveArticleButtonGuest = () => {
};

return (
<SaveButton
onClick={() => {
// eslint-disable-next-line no-alert
alert('Please sign in to save articles.');
}}
buttonText={getButtonText()}
testId="save-article-btn-guest"
isLoading={!isHydrated}
/>
<>
<SaveButton
onClick={() => setIsModalOpen(true)}
buttonText={getButtonText()}
testId="save-article-btn-guest"
isLoading={!isHydrated}
/>
{isModalOpen && (
<AccountPromotionalBannerModal
onClose={() => setIsModalOpen(false)}
signInUrl={signInUrl}
registerUrl={registerUrl}
/>
)}
</>
);
};

Expand Down
Loading