Skip to content

Commit 3fbf2d6

Browse files
authored
Merge branch 'main' into skb/tidy-old-designable-banner-code
2 parents e95cb0f + 2383bc8 commit 3fbf2d6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1387
-447
lines changed

dotcom-rendering/.storybook/modes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,8 @@ export const allModes = {
6565
globalColourScheme: 'light',
6666
viewport: breakpoints.desktop,
6767
},
68+
'light leftCol': {
69+
globalColourScheme: 'light',
70+
viewport: breakpoints.leftCol,
71+
},
6872
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
export const mockAuxiaResponseDismissible = {
2+
status: true,
3+
data: {
4+
userTreatment: {
5+
treatmentId: 'auxia-treatment-001',
6+
treatmentTrackingId: 'tracking-001',
7+
surface: 'signin-gate',
8+
treatmentContent: JSON.stringify({
9+
title: 'Register for free and continue reading',
10+
subtitle: "It's still free to read – this is not a paywall",
11+
body: "We're committed to keeping our quality reporting open. By registering and providing us with insight into your preferences, you're helping us to engage with you more deeply.",
12+
first_cta_name: 'Register for free',
13+
first_cta_link: 'https://profile.theguardian.com/register',
14+
second_cta_name: "I'll do it later",
15+
}),
16+
},
17+
},
18+
};
19+
20+
export const mockAuxiaResponseNonDismissible = {
21+
status: true,
22+
data: {
23+
userTreatment: {
24+
treatmentId: 'auxia-treatment-002',
25+
treatmentTrackingId: 'tracking-002',
26+
surface: 'signin-gate',
27+
treatmentContent: JSON.stringify({
28+
title: 'Complete registration to continue reading',
29+
subtitle: 'Registration is required to access this content',
30+
body: 'To continue reading this article and access our quality journalism, please complete your registration.',
31+
first_cta_name: 'Complete registration',
32+
first_cta_link: 'https://profile.theguardian.com/register',
33+
second_cta_name: '', // Empty makes it non-dismissible
34+
}),
35+
},
36+
},
37+
};
38+
39+
export const mockAuxiaResponseLegacy = {
40+
status: true,
41+
data: {
42+
userTreatment: {
43+
treatmentId: 'default-treatment-id', // This triggers legacy gate
44+
treatmentTrackingId: 'legacy-tracking',
45+
surface: 'signin-gate',
46+
treatmentContent: JSON.stringify({
47+
title: 'Register to continue reading',
48+
subtitle: '',
49+
body: '',
50+
first_cta_name: 'Register',
51+
first_cta_link: 'https://profile.theguardian.com/register',
52+
second_cta_name: 'Not now',
53+
}),
54+
},
55+
},
56+
};
57+
58+
export const mockAuxiaResponseNoTreatment = {
59+
status: false,
60+
data: undefined,
61+
};
62+
63+
export const getAuxiaMock = (payload: string): unknown => {
64+
const body = JSON.parse(payload) as Record<string, unknown>;
65+
66+
const mocks = {
67+
dismissable: mockAuxiaResponseDismissible,
68+
'non-dismissable': mockAuxiaResponseNonDismissible,
69+
legacy: mockAuxiaResponseLegacy,
70+
'no-treatment': mockAuxiaResponseNoTreatment,
71+
};
72+
73+
const key = body.sectionId as keyof typeof mocks;
74+
const mock: unknown = mocks[key];
75+
76+
return mock ?? mockAuxiaResponseNoTreatment;
77+
};

dotcom-rendering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@guardian/eslint-config-typescript": "9.0.1",
3838
"@guardian/identity-auth": "6.0.1",
3939
"@guardian/identity-auth-frontend": "8.1.0",
40-
"@guardian/libs": "22.5.0",
40+
"@guardian/libs": "23.0.0",
4141
"@guardian/ophan-tracker-js": "2.2.10",
4242
"@guardian/react-crossword": "6.3.0",
4343
"@guardian/shimport": "1.0.2",

dotcom-rendering/playwright.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export const PORT = isDev ? 3030 : 9000;
1111
*/
1212
export default defineConfig({
1313
testDir: './playwright/tests',
14-
// Don't run tests _within_ files in parallel as this causes flakiness locally - investigating
14+
// Don't run tests _within_ files in parallel locally as this causes flakiness
1515
// Test files still run in parallel as per the number of workers set below
16-
fullyParallel: false,
16+
fullyParallel: !!process.env.CI,
1717
// Fail the build on CI if you accidentally left test.only in the source code
1818
forbidOnly: !!process.env.CI,
1919
// Retry on CI only

dotcom-rendering/src/components/ArticlePage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { AlreadyVisited } from './AlreadyVisited.importable';
1212
import { BrazeMessaging } from './BrazeMessaging.importable';
1313
import { useConfig } from './ConfigContext';
1414
import { DarkModeMessage } from './DarkModeMessage';
15+
import { EnhanceAffiliateLinks } from './EnhanceAffiliateLinks.importable';
1516
import { FocusStyles } from './FocusStyles.importable';
1617
import { Island } from './Island';
1718
import { Lightbox } from './Lightbox';
@@ -130,6 +131,11 @@ export const ArticlePage = (props: WebProps | AppProps) => {
130131
serverSideTests={frontendData.config.abTests}
131132
/>
132133
</Island>
134+
{!!frontendData.affiliateLinksDisclaimer && (
135+
<Island priority="feature" defer={{ until: 'idle' }}>
136+
<EnhanceAffiliateLinks />
137+
</Island>
138+
)}
133139
</>
134140
)}
135141
{renderingTarget === 'Web' ? (

dotcom-rendering/src/components/Card/Card.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export type Props = {
9696
avatarUrl?: string;
9797
showClock?: boolean;
9898
mainMedia?: MainMedia;
99-
/** Note YouTube recommends a minimum width of 480px @see https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-youtube-player-size
99+
/**
100+
* Note YouTube recommends a minimum width of 480px @see https://developers.google.com/youtube/terms/required-minimum-functionality#embedded-youtube-player-size
100101
* At 300px or below, the player will begin to lose functionality e.g. volume controls being omitted.
101102
* Youtube requires a minimum width 200px.
102103
*/
@@ -133,15 +134,20 @@ export type Props = {
133134
isTagPage?: boolean;
134135
/** Allows the consumer to set an aspect ratio on the image of 5:3, 5:4, 4:5 or 1:1 */
135136
aspectRatio?: AspectRatio;
137+
/** The index of the card in a carousel */
136138
index?: number;
137-
/** The Splash card in a flexible container gets a different visual treatment to other cards*/
139+
/**
140+
* Useful for videos. Has the form: collection-{collection ID}-{card grouping type}-{card index}
141+
* For example, the first splash card in the second collection would be: "collection-1-splash-0"
142+
*/
143+
uniqueId?: string;
144+
/** The Splash card in a flexible container gets a different visual treatment to other cards */
138145
isFlexSplash?: boolean;
139146
showTopBarDesktop?: boolean;
140147
showTopBarMobile?: boolean;
141148
trailTextSize?: TrailTextSize;
142149
/** A kicker image is seperate to the main media and renders as part of the kicker */
143150
showKickerImage?: boolean;
144-
isInHideTrailsAbTest?: boolean;
145151
};
146152

147153
const starWrapper = (cardHasImage: boolean) => css`
@@ -403,12 +409,12 @@ export const Card = ({
403409
isTagPage = false,
404410
aspectRatio,
405411
index = 0,
412+
uniqueId = '',
406413
isFlexSplash,
407414
showTopBarDesktop = true,
408415
showTopBarMobile = true,
409416
trailTextSize,
410417
showKickerImage = false,
411-
isInHideTrailsAbTest = false,
412418
}: Props) => {
413419
const hasSublinks = supportingContent && supportingContent.length > 0;
414420
const sublinkPosition = decideSublinkPosition(
@@ -898,7 +904,6 @@ export const Card = ({
898904
src={media.mainMedia.videoId}
899905
height={media.mainMedia.height}
900906
width={media.mainMedia.width}
901-
videoId={media.mainMedia.videoId}
902907
thumbnailImage={
903908
media.mainMedia.thumbnailImage ?? ''
904909
}
@@ -911,6 +916,7 @@ export const Card = ({
911916
aspectRatio={aspectRatio}
912917
/>
913918
}
919+
uniqueId={uniqueId}
914920
/>
915921
</Island>
916922
)}
@@ -1175,7 +1181,6 @@ export const Card = ({
11751181
trailTextSize={trailTextSize}
11761182
padTop={headlinePosition === 'inner'}
11771183
hideUntil={hideTrailTextUntil()}
1178-
isInHideTrailsAbTest={isInHideTrailsAbTest}
11791184
/>
11801185
)}
11811186

dotcom-rendering/src/components/Card/components/TrailText.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
space,
55
textSans14,
66
textSans17,
7-
until,
87
} from '@guardian/source/foundations';
98
import { Hide } from '@guardian/source/react-components';
109
import { palette } from '../../../palette';
@@ -34,12 +33,6 @@ const fontStyles = (trailTextSize: TrailTextSize) => css`
3433
}
3534
`;
3635

37-
const isInHideTrailsAbTestStyles = css`
38-
${until.tablet} {
39-
display: none;
40-
}
41-
`;
42-
4336
type Props = {
4437
trailText: string;
4538
trailTextSize?: TrailTextSize;
@@ -51,7 +44,6 @@ type Props = {
5144
padBottom?: boolean;
5245
/** Adds padding to the top of the trail text */
5346
padTop?: boolean;
54-
isInHideTrailsAbTest?: boolean;
5547
};
5648

5749
export const TrailText = ({
@@ -61,7 +53,6 @@ export const TrailText = ({
6153
hideUntil,
6254
padBottom = true,
6355
padTop = false,
64-
isInHideTrailsAbTest = false,
6556
}: Props) => {
6657
const trailText = (
6758
<div
@@ -73,7 +64,6 @@ export const TrailText = ({
7364
fontStyles(trailTextSize),
7465
padBottom && bottomPadding,
7566
padTop && topPadding,
76-
isInHideTrailsAbTest && isInHideTrailsAbTestStyles,
7767
]}
7868
>
7969
<div

dotcom-rendering/src/components/DecideContainer.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type Props = {
4848
frontId?: string;
4949
collectionId: number;
5050
containerLevel?: DCRContainerLevel;
51-
isInHideTrailsAbTest?: boolean;
5251
};
5352

5453
export const DecideContainer = ({
@@ -64,9 +63,7 @@ export const DecideContainer = ({
6463
frontId,
6564
collectionId,
6665
containerLevel,
67-
isInHideTrailsAbTest,
6866
}: Props) => {
69-
// If you add a new container type which contains an MPU, you must also add it to
7067
switch (containerType) {
7168
case 'dynamic/fast':
7269
return (
@@ -257,7 +254,7 @@ export const DecideContainer = ({
257254
absoluteServerTimes={absoluteServerTimes}
258255
imageLoading={imageLoading}
259256
aspectRatio={aspectRatio}
260-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
257+
collectionId={collectionId}
261258
/>
262259
);
263260
case 'flexible/general':
@@ -271,7 +268,6 @@ export const DecideContainer = ({
271268
aspectRatio={aspectRatio}
272269
containerLevel={containerLevel}
273270
collectionId={collectionId}
274-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
275271
/>
276272
);
277273
case 'scrollable/small':
@@ -286,7 +282,6 @@ export const DecideContainer = ({
286282
absoluteServerTimes={absoluteServerTimes}
287283
aspectRatio={aspectRatio}
288284
sectionId={sectionId}
289-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
290285
/>
291286
</Island>
292287
);
@@ -302,7 +297,6 @@ export const DecideContainer = ({
302297
absoluteServerTimes={absoluteServerTimes}
303298
aspectRatio={aspectRatio}
304299
sectionId={sectionId}
305-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
306300
/>
307301
</Island>
308302
);
@@ -315,7 +309,6 @@ export const DecideContainer = ({
315309
absoluteServerTimes={absoluteServerTimes}
316310
imageLoading={imageLoading}
317311
aspectRatio={aspectRatio}
318-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
319312
/>
320313
);
321314
case 'scrollable/feature':
@@ -328,7 +321,6 @@ export const DecideContainer = ({
328321
absoluteServerTimes={absoluteServerTimes}
329322
aspectRatio={aspectRatio}
330323
collectionId={collectionId}
331-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
332324
/>
333325
</Island>
334326
);
@@ -341,7 +333,6 @@ export const DecideContainer = ({
341333
imageLoading={imageLoading}
342334
aspectRatio={aspectRatio}
343335
collectionId={collectionId}
344-
isInHideTrailsAbTest={!!isInHideTrailsAbTest}
345336
/>
346337
);
347338
default:
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useEffect } from 'react';
2+
import { getSkimlinksAccountId, isSkimlink } from '../lib/affiliateLinksUtils';
3+
4+
/**
5+
* Add custom parameters to skimlink URLs:
6+
* - referrer
7+
* - Skimlinks account ID
8+
*
9+
* ## Why does this need to be an Island?
10+
*
11+
* Code needs to be client side to get the referrer and AB test participation
12+
*
13+
* ---
14+
*
15+
* (No visual story exists as this does not render anything)
16+
*/
17+
export const EnhanceAffiliateLinks = () => {
18+
useEffect(() => {
19+
const allLinksOnPage = [...document.querySelectorAll('a')];
20+
21+
for (const link of allLinksOnPage) {
22+
if (isSkimlink(link.href)) {
23+
const referrerDomain =
24+
document.referrer === ''
25+
? 'none'
26+
: new URL(document.referrer).hostname;
27+
28+
const skimlinksAccountId = getSkimlinksAccountId(link.href);
29+
30+
// Skimlinks treats xcust as one long string, so we use | to separate values
31+
const xcustValue = `referrer|${referrerDomain}|accountId|${skimlinksAccountId}`;
32+
33+
link.href = `${link.href}&xcust=${encodeURIComponent(
34+
xcustValue,
35+
)}`;
36+
}
37+
}
38+
});
39+
40+
// We don’t render anything
41+
return null;
42+
};

0 commit comments

Comments
 (0)