diff --git a/public/images/globeImage.png b/public/images/globeImage.png new file mode 100644 index 00000000000..edd7dcea867 Binary files /dev/null and b/public/images/globeImage.png differ diff --git a/src/app/components/Account/AccountPromotionalBanner/AccountPromotionalModal.tsx b/src/app/components/Account/AccountPromotionalBanner/AccountPromotionalModal.tsx new file mode 100644 index 00000000000..f9bcc547f32 --- /dev/null +++ b/src/app/components/Account/AccountPromotionalBanner/AccountPromotionalModal.tsx @@ -0,0 +1,119 @@ +import { css, Global } from '@emotion/react'; +import { useState, useEffect, useMemo } from 'react'; +import ThemeProvider from '#app/components/ThemeProvider'; +import { ServiceContextProvider } from '#app/contexts/ServiceContext'; +import { AccountContext } from '#app/contexts/AccountContext'; +import { ToggleContextProvider } from '#app/contexts/ToggleContext'; +import AccountPromotionalBanner from '.'; +import styles from './index.styles'; + +type AccountPromotionalBannerModalProps = { + isSignedIn: boolean; + onClose?: () => void; +}; + +const AccountPromotionalBannerModal = ({ + isSignedIn, + onClose, +}: AccountPromotionalBannerModalProps) => { + const [isOpen, setIsOpen] = useState(true); + + const handleClose = () => { + setIsOpen(false); + onClose?.(); + }; + + useEffect(() => { + if (!isOpen) return undefined; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') handleClose(); + }; + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [isOpen]); + + const accountContextValue = useMemo( + () => ({ + isSignedIn, + isIdctaAvailable: true, + signInUrl: 'https://example.com/signin', + registerUrl: 'https://example.com/register', + signOutUrl: undefined, + settingsUrl: undefined, + forYouUrl: undefined, + isAccountPromoBannerVisible: true, + }), + [isSignedIn], + ); + + const handleBackdropKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') handleClose(); + }; + + if (!isOpen) { + return null; + } + + return ( + <> + +
+
+
+
+
+ + +
+
+ + + + + + + + + +
+
+
+
+ + ); +}; + +export default AccountPromotionalBannerModal; diff --git a/src/app/components/Account/AccountPromotionalBanner/index.stories.tsx b/src/app/components/Account/AccountPromotionalBanner/index.stories.tsx index 717029918f4..ba1f18b765d 100644 --- a/src/app/components/Account/AccountPromotionalBanner/index.stories.tsx +++ b/src/app/components/Account/AccountPromotionalBanner/index.stories.tsx @@ -4,6 +4,7 @@ import { AccountContext } from '#app/contexts/AccountContext'; import README from './README.md'; import AccountPromotionalBanner from '.'; import { ToggleContextProvider } from '#app/contexts/ToggleContext'; +import AccountPromotionalBannerModal from './AccountPromotionalModal'; type WithProvidersArgs = { isSignedIn: boolean; @@ -48,3 +49,6 @@ export default { export const SignedOut = withProviders({ isSignedIn: false }); export const SignedInNoRender = withProviders({ isSignedIn: true }); +export const SignedOutModal = () => ( + +); diff --git a/src/app/components/Account/AccountPromotionalBanner/index.styles.tsx b/src/app/components/Account/AccountPromotionalBanner/index.styles.tsx index f01188dc1a1..297b2941588 100644 --- a/src/app/components/Account/AccountPromotionalBanner/index.styles.tsx +++ b/src/app/components/Account/AccountPromotionalBanner/index.styles.tsx @@ -2,7 +2,7 @@ import { Theme, css } from '@emotion/react'; import pixelsToRem from '#app/utilities/pixelsToRem'; export default { - callToActionLink: ({ mq }) => + callToActionLink: ({ mq }: Theme) => css({ padding: '1rem', display: 'inline-flex', @@ -50,6 +50,7 @@ export default { fill: 'ButtonText', }, }), + registerLink: ({ palette }: Theme) => css({ height: `${pixelsToRem(44)}rem`, @@ -67,4 +68,125 @@ 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)', + }), + + modalContent: ({ mq }: Theme) => + css({ + position: 'relative', + zIndex: 1, + width: '45%', + height: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + + [mq.GROUP_4_MIN_WIDTH]: { + width: '60%', + height: '40%', + }, + + '& > aside': { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + + '& > aside > div': { + justifyContent: 'center', + alignItems: 'center', + textAlign: 'center', + }, + + '& > aside > div > div': { + justifyContent: 'center', + alignItems: 'center', + }, + + '& > aside > div > div > div': { + paddingTop: '1rem', + paddingBottom: '1rem', + }, + }), + + modalInner: css({ + position: 'relative', + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + overflow: 'hidden', + }), + + modalBannerSide: css({ + flex: 1, + display: 'flex', + alignItems: 'stretch', + paddingInlineStart: '2rem', + }), + + modalImageSide: ({ mq }: Theme) => + css({ + width: '100%', + height: '12rem', + overflow: 'hidden', + pointerEvents: 'none', + flexShrink: 0, + + '& img': { + width: '100%', + height: '100%', + }, + + [mq.GROUP_4_MIN_WIDTH]: { + position: 'absolute', + insetInlineEnd: '5%', + top: '-20%', + width: '40%', + height: '140%', + zIndex: 2, + }, + }), + + imageHorizontal: ({ mq }: Theme) => + css({ + display: 'block', + width: '100%', + height: '100%', + objectFit: 'contain', + + [mq.GROUP_4_MIN_WIDTH]: { + display: 'none', + }, + }), + + imageVertical: ({ mq }: Theme) => + css({ + display: 'none', + width: '100%', + height: '100%', + objectFit: 'contain', + + [mq.GROUP_4_MIN_WIDTH]: { + display: 'block', + }, + }), }; diff --git a/src/app/components/SaveArticleButton/index.tsx b/src/app/components/SaveArticleButton/index.tsx index ac8fd4daec1..5d48be67e6d 100644 --- a/src/app/components/SaveArticleButton/index.tsx +++ b/src/app/components/SaveArticleButton/index.tsx @@ -1,9 +1,11 @@ import useUASButton, { UASAction } from '#app/hooks/useUASButton'; -import { useContext } from 'react'; -import { ServiceContext } from '#contexts/ServiceContext'; +import { useState, useContext } from 'react'; +import { ServiceContext } from '#app/contexts/ServiceContext'; +import { AccountContext } from '#app/contexts/AccountContext'; import { Article } from '#app/models/types/optimo'; import SaveButton from '../SaveButton'; import styles from './index.styles'; +import AccountPromotionalBannerModal from '../Account/AccountPromotionalBanner/AccountPromotionalModal'; export interface SaveArticleButtonProps { articleId: string; @@ -25,9 +27,11 @@ const SaveArticleButton = ({ const { translations } = useContext(ServiceContext); const { saveArticleButton } = translations || {}; + const { isSignedIn } = useContext(AccountContext); - if (!showButton) return null; + const [isModalOpen, setIsModalOpen] = useState(false); + if (!showButton && isSignedIn) return null; if (!saveArticleButton) return null; if (error) { @@ -45,6 +49,10 @@ const SaveArticleButton = ({ const buttonText = isLoading ? saveArticleButton.saving : buttonLabel; const handleClick = () => { + if (!isSignedIn) { + setIsModalOpen(true); + return; + } handleSaveAction(isSaved ? UASAction.REMOVE : UASAction.SAVE); }; @@ -58,6 +66,12 @@ const SaveArticleButton = ({ buttonText={buttonText} removeText={saveArticleButton.remove} /> + {isModalOpen && ( + setIsModalOpen(false)} + /> + )}
); };