From 3d2892f86aa29770f5767f744c558e1232ac5314 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 10 Oct 2024 15:47:26 +0100 Subject: [PATCH] Display US marketing card to US users in ab test --- ...andableMarketingCardWrapper.importable.tsx | 82 +++++++++++++++++++ dotcom-rendering/src/components/GridItem.tsx | 13 +++ .../src/components/SignInGate/displayRule.ts | 3 +- dotcom-rendering/src/experiments/ab-tests.ts | 2 + .../tests/usa-expandable-marketing-card.ts | 35 ++++++++ .../src/layouts/CommentLayout.tsx | 57 +++++++++---- .../src/layouts/ImmersiveLayout.tsx | 37 +++++++-- .../src/layouts/InteractiveLayout.tsx | 1 - .../src/layouts/PictureLayout.tsx | 29 ++++++- .../src/layouts/ShowcaseLayout.tsx | 31 +++++-- .../src/layouts/StandardLayout.tsx | 51 ++++++++---- 11 files changed, 289 insertions(+), 52 deletions(-) create mode 100644 dotcom-rendering/src/components/ExpandableMarketingCardWrapper.importable.tsx create mode 100644 dotcom-rendering/src/experiments/tests/usa-expandable-marketing-card.ts diff --git a/dotcom-rendering/src/components/ExpandableMarketingCardWrapper.importable.tsx b/dotcom-rendering/src/components/ExpandableMarketingCardWrapper.importable.tsx new file mode 100644 index 0000000000..cfcb42ddf6 --- /dev/null +++ b/dotcom-rendering/src/components/ExpandableMarketingCardWrapper.importable.tsx @@ -0,0 +1,82 @@ +import { getCookie } from '@guardian/libs'; +import { useEffect, useState } from 'react'; +import type { DailyArticle } from '../lib/dailyArticleCount'; +import { getDailyArticleCount } from '../lib/dailyArticleCount'; +import { getLocaleCode } from '../lib/getCountryCode'; +import { useAB } from '../lib/useAB'; +import { ExpandableMarketingCard } from './ExpandableMarketingCard'; + +interface Props { + guardianBaseURL: string; +} + +const isFirstArticle = () => { + const [dailyCount = {} as DailyArticle] = getDailyArticleCount() ?? []; + return Object.keys(dailyCount).length === 0 || dailyCount.count <= 1; +}; + +const isNewUSUser = async () => { + const isUserInUS = (await getLocaleCode()) === 'US'; + if (!isUserInUS) { + return false; + } + + // Exclude users who have selected a non-US edition. + const editionCookie = getCookie({ name: 'GU_EDITION' }); + const hasUserSelectedNonUSEdition = + !!editionCookie && editionCookie !== 'US'; + + // This check must happen AFTER we've ensured that the user is in the US. + const isNewUser = isFirstArticle(); + + return !hasUserSelectedNonUSEdition && !isNewUser; +}; + +// todo - semantic html accordion-details? +export const ExpandableMarketingCardWrapper = ({ guardianBaseURL }: Props) => { + const [isExpanded, setIsExpanded] = useState(false); + const [isClosed, setIsClosed] = useState(false); + const [isApplicableUser, setIsApplicableUser] = useState(false); + + const abTestAPI = useAB()?.api; + const isInVariantFree = !!abTestAPI?.isUserInVariant( + 'UsaExpandableMarketingCard', + 'variant-free', + ); + const isInVariantBubble = !!abTestAPI?.isUserInVariant( + 'UsaExpandableMarketingCard', + 'variant-bubble', + ); + const isInEitherVariant = isInVariantFree || isInVariantBubble; + + useEffect(() => { + void isNewUSUser().then((show) => { + if (show) { + setIsApplicableUser(true); + } + }); + }, []); + + if (!isInEitherVariant || !isApplicableUser || isClosed) { + return null; + } + + const heading = isInVariantBubble + ? 'Pop your US news bubble' + : 'Yes, this story is free'; + + const kicker = isInVariantBubble + ? 'How the Guardian is different' + : 'Why the Guardian has no paywall'; + + return ( + + ); +}; diff --git a/dotcom-rendering/src/components/GridItem.tsx b/dotcom-rendering/src/components/GridItem.tsx index ac72369de6..224c0164bf 100644 --- a/dotcom-rendering/src/components/GridItem.tsx +++ b/dotcom-rendering/src/components/GridItem.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/react'; +import { from, space } from '@guardian/source/foundations'; import { getZIndex } from '../lib/getZIndex'; type Props = { @@ -28,6 +29,17 @@ const bodyStyles = css` ${getZIndex('bodyArea')} `; +const usCardStyles = css` + ${from.leftCol} { + margin-top: ${space[6]}px; + margin-left: 1px; /* To align with rich links */ + } + + ${from.wide} { + margin-left: 0; + } +`; + const gridArea = css` grid-area: var(--grid-area); `; @@ -41,6 +53,7 @@ export const GridItem = ({ css={[ area === 'body' && bodyStyles, area === 'right-column' && rightColumnStyles, + area === 'uscard' && usCardStyles, gridArea, ]} style={{ diff --git a/dotcom-rendering/src/components/SignInGate/displayRule.ts b/dotcom-rendering/src/components/SignInGate/displayRule.ts index a979e2728c..5ccc743b0c 100644 --- a/dotcom-rendering/src/components/SignInGate/displayRule.ts +++ b/dotcom-rendering/src/components/SignInGate/displayRule.ts @@ -1,7 +1,6 @@ // use the dailyArticleCount from the local storage to see how many articles the user has viewed in a day import { onConsent } from '@guardian/libs'; -import type { ConsentState } from '@guardian/libs'; -import type { CountryCode } from '@guardian/libs'; +import type { ConsentState, CountryCode } from '@guardian/libs'; import type { DailyArticle } from '../../lib/dailyArticleCount'; import { getDailyArticleCount } from '../../lib/dailyArticleCount'; import type { TagType } from '../../types/tag'; diff --git a/dotcom-rendering/src/experiments/ab-tests.ts b/dotcom-rendering/src/experiments/ab-tests.ts index 7a9613eb58..8962426288 100644 --- a/dotcom-rendering/src/experiments/ab-tests.ts +++ b/dotcom-rendering/src/experiments/ab-tests.ts @@ -7,6 +7,7 @@ import { mpuWhenNoEpic } from './tests/mpu-when-no-epic'; import { optimiseSpacefinderInline } from './tests/optimise-spacefinder-inline'; import { signInGateMainControl } from './tests/sign-in-gate-main-control'; import { signInGateMainVariant } from './tests/sign-in-gate-main-variant'; +import { UsaExpandableMarketingCard } from './tests/usa-expandable-marketing-card'; // keep in sync with ab-tests in frontend // https://github.com/guardian/frontend/tree/main/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts @@ -19,4 +20,5 @@ export const tests: ABTest[] = [ mpuWhenNoEpic, adBlockAsk, optimiseSpacefinderInline, + UsaExpandableMarketingCard, ]; diff --git a/dotcom-rendering/src/experiments/tests/usa-expandable-marketing-card.ts b/dotcom-rendering/src/experiments/tests/usa-expandable-marketing-card.ts new file mode 100644 index 0000000000..b6194b3a99 --- /dev/null +++ b/dotcom-rendering/src/experiments/tests/usa-expandable-marketing-card.ts @@ -0,0 +1,35 @@ +import type { ABTest } from '@guardian/ab-core'; + +export const UsaExpandableMarketingCard: ABTest = { + id: 'UsaExpandableMarketingCard', + start: '2024-10-02', + expiry: '2024-12-18', + author: 'dotcom.platform@guardian.co.uk', + description: + 'Test the impact of showing the user a component that highlights the Guardians journalism.', + audience: 0 / 100, + audienceOffset: 0 / 100, + audienceCriteria: 'US-based users that see the US edition.', + successMeasure: 'Users are more likely to engage with the site.', + canRun: () => true, + variants: [ + { + id: 'control', + test: (): void => { + /* no-op */ + }, + }, + { + id: 'variant-free', + test: (): void => { + /* no-op */ + }, + }, + { + id: 'variant-bubble', + test: (): void => { + /* no-op */ + }, + }, + ], +}; diff --git a/dotcom-rendering/src/layouts/CommentLayout.tsx b/dotcom-rendering/src/layouts/CommentLayout.tsx index efd394466e..db6b712dfb 100644 --- a/dotcom-rendering/src/layouts/CommentLayout.tsx +++ b/dotcom-rendering/src/layouts/CommentLayout.tsx @@ -21,6 +21,7 @@ import { Border } from '../components/Border'; import { Carousel } from '../components/Carousel.importable'; import { ContributorAvatar } from '../components/ContributorAvatar'; import { DiscussionLayout } from '../components/DiscussionLayout'; +import { ExpandableMarketingCardWrapper } from '../components/ExpandableMarketingCardWrapper.importable'; import { Footer } from '../components/Footer'; import { GridItem } from '../components/GridItem'; import { HeaderAdSlot } from '../components/HeaderAdSlot'; @@ -96,18 +97,20 @@ const StandardGrid = ({ 'title border headline headline headline' 'lines border headline headline headline' 'meta border standfirst standfirst standfirst' - 'meta border media media media' - '. border body . right-column' - '. border . . right-column'; + 'uscard border standfirst standfirst standfirst' + 'uscard border media media media' + 'uscard border body . right-column' + 'uscard border . . right-column'; ` : css` grid-template-areas: 'title border headline . right-column' 'lines border headline . right-column' 'meta border standfirst . right-column' - 'meta border media . right-column' - '. border body . right-column' - '. border . . right-column'; + 'uscard border standfirst . right-column' + 'uscard border media . right-column' + 'uscard border body . right-column' + 'uscard border . . right-column'; `} } @@ -125,21 +128,23 @@ const StandardGrid = ({ ${display === ArticleDisplay.Showcase ? css` grid-template-areas: - 'title border headline headline' - 'lines border headline headline' - 'meta border standfirst standfirst' - 'meta border media media' - '. border body right-column' - '. border . right-column'; + 'title border headline headline' + 'lines border headline headline' + 'meta border standfirst standfirst' + 'uscard border standfirst standfirst' + 'uscard border media media' + 'uscard border body right-column' + 'uscard border . right-column'; ` : css` grid-template-areas: - 'title border headline right-column' - 'lines border headline right-column' - 'meta border standfirst right-column' - 'meta border media right-column' - '. border body right-column' - '. border . right-column'; + 'title border headline right-column' + 'lines border headline right-column' + 'meta border standfirst right-column' + 'uscard border standfirst right-column' + 'uscard border media right-column' + 'uscard border body right-column' + 'uscard border . right-column'; `} } @@ -547,6 +552,22 @@ export const CommentLayout = (props: WebProps | AppsProps) => { )} + {isWeb && ( + + + + + + + + )}
diff --git a/dotcom-rendering/src/layouts/ImmersiveLayout.tsx b/dotcom-rendering/src/layouts/ImmersiveLayout.tsx index 1432e779cb..12f21c8965 100644 --- a/dotcom-rendering/src/layouts/ImmersiveLayout.tsx +++ b/dotcom-rendering/src/layouts/ImmersiveLayout.tsx @@ -24,6 +24,7 @@ import { Caption } from '../components/Caption'; import { Carousel } from '../components/Carousel.importable'; import { DecideLines } from '../components/DecideLines'; import { DiscussionLayout } from '../components/DiscussionLayout'; +import { ExpandableMarketingCardWrapper } from '../components/ExpandableMarketingCardWrapper.importable'; import { Footer } from '../components/Footer'; import { GridItem } from '../components/GridItem'; import { GuardianLabsLines } from '../components/GuardianLabsLines'; @@ -84,6 +85,8 @@ const ImmersiveGrid = ({ children }: { children: React.ReactNode }) => ( Vertical grey border Main content Right Column + + Duplicate lines are required to ensure the left column does not have extra vertical space. */ ${from.wide} { grid-column-gap: 10px; @@ -95,18 +98,21 @@ const ImmersiveGrid = ({ children }: { children: React.ReactNode }) => ( '. border byline . right-column' 'lines border body . right-column' 'meta border body . right-column' - 'meta border body . right-column' - '. border body . right-column' - '. border . . right-column'; + 'uscard border body . right-column' + 'uscard border . . right-column' + 'uscard border . . right-column' + 'uscard border . . right-column'; } /* Explanation of each unit of grid-template-columns Left Column (220 - 1px border) - Vertical grey border + Vertical grey borders Main content Right Column + + Duplicate lines are required to ensure the left column does not have extra vertical space. */ ${until.wide} { grid-column-gap: 10px; @@ -118,9 +124,10 @@ const ImmersiveGrid = ({ children }: { children: React.ReactNode }) => ( '. border byline right-column' 'lines border body right-column' 'meta border body right-column' - 'meta border body right-column' - '. border body right-column' - '. border . right-column'; + 'uscard border body right-column' + 'uscard border . right-column' + 'uscard border . right-column' + 'uscard border . right-column'; } /* @@ -641,6 +648,22 @@ export const ImmersiveLayout = (props: WebProps | AppProps) => { )}
+ {isWeb && ( + + + + + + + + )} { standfirst={article.standfirst} /> -
diff --git a/dotcom-rendering/src/layouts/PictureLayout.tsx b/dotcom-rendering/src/layouts/PictureLayout.tsx index 16a7d949cd..1f9aea4005 100644 --- a/dotcom-rendering/src/layouts/PictureLayout.tsx +++ b/dotcom-rendering/src/layouts/PictureLayout.tsx @@ -22,6 +22,7 @@ import { Carousel } from '../components/Carousel.importable'; import { ContributorAvatar } from '../components/ContributorAvatar'; import { DecideLines } from '../components/DecideLines'; import { DiscussionLayout } from '../components/DiscussionLayout'; +import { ExpandableMarketingCardWrapper } from '../components/ExpandableMarketingCardWrapper.importable'; import { Footer } from '../components/Footer'; import { GridItem } from '../components/GridItem'; import { HeaderAdSlot } from '../components/HeaderAdSlot'; @@ -67,7 +68,6 @@ const PictureGrid = ({ children }: { children: React.ReactNode }) => ( display: grid; width: 100%; margin-left: 0; - grid-column-gap: 10px; /* @@ -78,6 +78,7 @@ const PictureGrid = ({ children }: { children: React.ReactNode }) => ( Main content Right Column + Duplicate lines are required to ensure the left column does not have extra vertical space. */ ${from.wide} { grid-template-columns: 219px 1px 1020px; @@ -86,7 +87,10 @@ const PictureGrid = ({ children }: { children: React.ReactNode }) => ( '. border standfirst' 'lines border media' 'meta border media' - 'meta border submeta'; + 'uscard border media' + 'uscard border submeta' + 'uscard border .' + 'uscard border .'; } ${until.wide} { @@ -96,7 +100,10 @@ const PictureGrid = ({ children }: { children: React.ReactNode }) => ( '. border standfirst standfirst standfirst' 'lines border media media media' 'meta border media media media' - 'meta border submeta submeta submeta'; + 'uscard border media media media' + 'uscard border submeta submeta submeta' + 'uscard border . . .' + 'uscard border . . .'; } /* @@ -566,6 +573,22 @@ export const PictureLayout = (props: WebProps | AppsProps) => { /> + {isWeb && ( + + + + + + + + )} diff --git a/dotcom-rendering/src/layouts/ShowcaseLayout.tsx b/dotcom-rendering/src/layouts/ShowcaseLayout.tsx index 23e5d6ce26..9e26899fd0 100644 --- a/dotcom-rendering/src/layouts/ShowcaseLayout.tsx +++ b/dotcom-rendering/src/layouts/ShowcaseLayout.tsx @@ -23,6 +23,7 @@ import { Border } from '../components/Border'; import { Carousel } from '../components/Carousel.importable'; import { DecideLines } from '../components/DecideLines'; import { DiscussionLayout } from '../components/DiscussionLayout'; +import { ExpandableMarketingCardWrapper } from '../components/ExpandableMarketingCardWrapper.importable'; import { Footer } from '../components/Footer'; import { GridItem } from '../components/GridItem'; import { HeaderAdSlot } from '../components/HeaderAdSlot'; @@ -90,9 +91,10 @@ const ShowcaseGrid = ({ children }: { children: React.ReactNode }) => ( 'title border headline headline headline' 'lines border media media media' 'meta border media media media' - 'meta border standfirst . right-column' - '. border body . right-column' - '. border . . right-column'; + 'uscard border media media media' + 'uscard border standfirst . right-column' + 'uscard border body . right-column' + 'uscard border . . right-column'; } ${until.wide} { @@ -101,9 +103,10 @@ const ShowcaseGrid = ({ children }: { children: React.ReactNode }) => ( 'title border headline headline' 'lines border media media' 'meta border media media' - 'meta border standfirst right-column' - '. border body right-column' - '. border . right-column'; + 'uscard border media media' + 'uscard border standfirst right-column' + 'uscard border body right-column' + 'uscard border . right-column'; } /* @@ -536,6 +539,22 @@ export const ShowcaseLayout = (props: WebProps | AppsProps) => { )}
+ {isWeb && ( + + + + + + + + )} {
)}
+ {isWeb && ( + + + + + + + + )}