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)}
+ />
+ )}
);
};