Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d792eb9
feat: add identity headers to support banner requests when user is si…
Mar 3, 2026
757303d
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 3, 2026
7c3ea21
feat: use getAuthHeaders for support banner requests to include ident…
Mar 3, 2026
0d25675
Merge branch 'jm/feat-mparticle-headers' of github.com:guardian/dotco…
Mar 3, 2026
f92b557
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 3, 2026
ae51860
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 4, 2026
1c45443
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 4, 2026
032f324
feat: mock contributions API in storybook to prevent hanging on conse…
Mar 4, 2026
a0a25b5
chore: remove commented-out alias assignment in Storybook config
Mar 4, 2026
d19eeee
Merge branch 'main' of github.com:guardian/dotcom-rendering into jm/f…
Mar 5, 2026
5dda22e
fix: webpack server build failing due broken parse5 source maps insid…
Mar 5, 2026
42c7887
refactor: update TopBarSupport to use `useAuthStatus`, add a timeout …
Mar 5, 2026
08cbf93
chore: fix empty line lint rule
Mar 5, 2026
854d94b
chore: better naming header variables
Mar 5, 2026
07a6f71
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 5, 2026
2c89d8e
fix: missing headers in DecideLayout, Gallery and Sport Data storybooks
Mar 5, 2026
8832f4d
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 5, 2026
4d11607
feat: use `useIsSignedIn` hook to determine user sign-in status.
Mar 5, 2026
c0a20e5
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 5, 2026
aab700a
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 6, 2026
f0befca
Merge branch 'main' into jm/feat-mparticle-headers
juabara Mar 10, 2026
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
51 changes: 30 additions & 21 deletions dotcom-rendering/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,24 +103,38 @@ const webpackConfig = (config: Configuration) => {
const rules = config.module?.rules ?? [];

config.resolve ??= {};
config.resolve.alias ??= {};

// Mock JSDOM for storybook - it relies on native node.js packages
// Allows us to use enhancers in stories for better testing of components & full articles
config.resolve.alias['jsdom$'] = path.resolve(
__dirname,
'./mocks/jsdom.ts',
);
config.resolve.alias = {
...config.resolve?.alias,

// log4js tries to call "fs" in storybook -- we can ignore it
config.resolve.alias[
`${path.resolve(__dirname, '../src/server/lib/logging')}$`
] = path.resolve(__dirname, './mocks/log4js.ts');
Buffer: 'buffer',
react: 'react',
'react-dom': 'react-dom',

// Mock BridgetApi for storybook
config.resolve.alias[
`${path.resolve(__dirname, '../src/lib/bridgetApi')}$`
] = path.resolve(__dirname, './mocks/bridgetApi.ts');
// Mock JSDOM for storybook - it relies on native node.js packages
// Allows us to use enhancers in stories for better testing of components & full articles
jsdom$: path.resolve(__dirname, './mocks/jsdom.ts'),

// log4js tries to call "fs" in storybook -- we can ignore it
[`${path.resolve(__dirname, '../src/server/lib/logging')}$`]:
path.resolve(__dirname, './mocks/log4js.ts'),

// Mock BridgetApi for storybook
[`${path.resolve(__dirname, '../src/lib/bridgetApi')}$`]: path.resolve(
__dirname,
'./mocks/bridgetApi.ts',
),

// Mock contributions API so getAuthHeaders does not hang on consent state in Storybook
[`${path.resolve(__dirname, '../src/lib/contributions')}$`]:
path.resolve(__dirname, './mocks/contributions.ts'),

// Mock identity auth frontend to prevent Storybook components from hanging in Pending
'@guardian/identity-auth-frontend': path.resolve(
__dirname,
'./mocks/identityAuthFrontend.ts',
),
};

const webpackLoaders = getLoaders('client.web');

Expand Down Expand Up @@ -149,12 +163,7 @@ const webpackConfig = (config: Configuration) => {
config.resolve.modules = [
...((config && config.resolve && config.resolve.modules) || []),
];
config.resolve.alias = {
...config.resolve.alias,
Buffer: 'buffer',
react: 'react',
'react-dom': 'react-dom',
};

return config;
};

Expand Down
29 changes: 29 additions & 0 deletions dotcom-rendering/.storybook/mocks/contributions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Mock for use in Storybook
* Avoids circular dependencies from the Webpack alias by explicitly mocking
* only the functions required by the components.
*/

export const getAuthHeaders = (): Promise<HeadersInit | undefined> => {
console.log('[Storybook Mock] getAuthHeaders returning undefined');
return Promise.resolve(undefined);
};

export const getPurchaseInfo = (): any => undefined;

export const shouldHideSupportMessaging = (): boolean | 'Pending' => false;

// Additional missing exports required by other components in Storybook
export const useHasOptedOutOfArticleCount = (): boolean | 'Pending' => false;
export const hasOptedOutOfArticleCount = async (): Promise<boolean> => false;
export const hasOptedOutOfWeeklyArticleCount = async (): Promise<boolean> =>
false;
export const hasCmpConsentForWeeklyArticleCount = async (): Promise<boolean> =>
false;
export const recentlyClosedBanner = (): boolean => false;
export const withinLocalNoBannerCachePeriod = (): boolean => false;
export const setLocalNoBannerCachePeriod = (): void => {};
export const getContributionsServiceUrl = (): string => '';
export const hasCmpConsentForBrowserId = async (): Promise<boolean> => false;
export const SUPPORT_ONE_OFF_CONTRIBUTION_COOKIE =
'gu.contributions.contrib-timestamp';
8 changes: 8 additions & 0 deletions dotcom-rendering/.storybook/mocks/identityAuthFrontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const getIdentityAuth = () => ({
isSignedInWithAuthState: () =>
Promise.resolve({
isAuthenticated: false,
accessToken: undefined,
idToken: undefined,
}),
});
63 changes: 51 additions & 12 deletions dotcom-rendering/src/components/TopBarSupport.importable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import { css } from '@emotion/react';
import { getCookie, isUndefined } from '@guardian/libs';
import type { ComponentEvent } from '@guardian/ophan-tracker-js';
import { getHeader } from '@guardian/support-dotcom-components';
import type {
HeaderPayload,
ModuleData,
Expand All @@ -18,11 +17,14 @@ import type {
import { useEffect, useState } from 'react';
import { submitComponentEvent } from '../client/ophan/ophan';
import {
getAuthHeaders,
getPurchaseInfo,
shouldHideSupportMessaging,
} from '../lib/contributions';
import { getOptionsHeaders } from '../lib/identity';
import { getHeader } from '../lib/sdcRequests';
import { useBetaAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useAuthStatus, useIsSignedIn } from '../lib/useAuthStatus';
import { useCountryCode } from '../lib/useCountryCode';
import { usePageViewId } from '../lib/usePageViewId';
import { useConfig } from './ConfigContext';
Expand Down Expand Up @@ -56,18 +58,31 @@ const ReaderRevenueLinksRemote = ({
useState<ModuleData<HeaderProps> | null>(null);
const [SupportHeader, setSupportHeader] =
useState<React.ElementType<HeaderProps> | null>(null);
const authStatus = useAuthStatus();
const isSignedIn = useIsSignedIn();

const { renderingTarget } = useConfig();
const abTests = useBetaAB();

const reportError = (error: unknown, context: string) => {
const msg = `Error importing RR header links for ${context}: ${String(
error,
)}`;
console.log(msg);
window.guardian.modules.sentry.reportError(
new Error(msg),
'rr-header-links',
);
};

useEffect((): void => {
if (isUndefined(countryCode) || isSignedIn === 'Pending') {
return;
}

const hideSupportMessagingForUser =
shouldHideSupportMessaging(isSignedIn);

if (hideSupportMessagingForUser === 'Pending') {
// We don't yet know the user's supporter status
return;
Expand All @@ -90,7 +105,36 @@ const ReaderRevenueLinksRemote = ({
},
};

getHeader(contributionsServiceUrl, requestData)
const fallbackHeaders =
authStatus.kind === 'SignedIn'
? getOptionsHeaders(authStatus).headers
: undefined;

const fetchAuthHeadersWithTimeout = () => {
return Promise.race([
getAuthHeaders(),
new Promise<undefined>((resolve) => {
setTimeout(() => {
resolve(undefined);
}, 2000);
}),
]);
};

void fetchAuthHeadersWithTimeout()
.catch((error: unknown) => {
// Catch any errors from getAuthHeaders itself or the timeout
reportError(error, 'getAuthHeaders');
return undefined;
})
.then((headers) =>
// Fallback to fallbackHeaders if headers are not present
getHeader(
contributionsServiceUrl,
requestData,
headers ?? fallbackHeaders,
),
)
.then((response: ModuleDataResponse<HeaderProps>) => {
if (!response.data) {
return null;
Expand All @@ -102,9 +146,9 @@ const ReaderRevenueLinksRemote = ({
return (
module.name === 'SignInPromptHeader'
? /* webpackChunkName: "sign-in-prompt-header" */
import(`./marketing/header/SignInPromptHeader`)
import('./marketing/header/SignInPromptHeader')
: /* webpackChunkName: "header" */
import(`./marketing/header/Header`)
import('./marketing/header/Header')
).then(
(headerModule: {
[key: string]: React.ElementType<HeaderProps>;
Expand All @@ -116,13 +160,7 @@ const ReaderRevenueLinksRemote = ({
);
})
.catch((error) => {
const msg = `Error importing RR header links: ${String(error)}`;

console.log(msg);
window.guardian.modules.sentry.reportError(
new Error(msg),
'rr-header-links',
);
reportError(error, 'fetching headers or importing component');
});
}, [
countryCode,
Expand All @@ -131,6 +169,7 @@ const ReaderRevenueLinksRemote = ({
pageViewId,
pageUrl,
abTests,
authStatus,
]);

if (SupportHeader !== null && supportHeaderResponse) {
Expand Down
15 changes: 7 additions & 8 deletions dotcom-rendering/src/lib/mockRESTCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,14 @@ export const mockFetch: typeof global.fetch = (
url,
):
return createMockResponse(200, discussion);
case /.*contributions\.(code\.dev-)?guardianapis\.com\/header/.test(
url,
) && requestInit?.method === 'POST':
return createMockResponse(200, contributionsHeaderResponse);
// Get contributions header
case /.*contributions\.(code\.dev-)?guardianapis\.com\/header/.test(
url,
):
// Get contributions header or local Storybook header fetches
case /.*\/header/.test(url):
return createMockResponse(200, contributionsHeaderResponse);
// Catch local Storybook epic fetches
case /.*\/epic/.test(url):
return createMockResponse(200, {
data: { module: { url: '', name: 'Epic', props: {} } },
});
// Get Ophan
case /.*ophan\.theguardian\.com\/img\/.*/.test(url):
return createMockResponse(200);
Expand Down
3 changes: 2 additions & 1 deletion dotcom-rendering/src/lib/sdcRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,6 @@ export const getGutterLiveblog = (
export const getHeader = (
baseUrl: string,
payload: HeaderPayload,
headers?: HeadersInit,
): Promise<ModuleDataResponse<HeaderProps>> =>
getModuleData('header', baseUrl, payload);
getModuleData('header', baseUrl, payload, headers);
2 changes: 2 additions & 0 deletions dotcom-rendering/webpack/webpack.config.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const nodeExternals = require('webpack-node-externals');
const swcConfig = require('./.swcrc.json');
const { svgr } = require('./svg.cjs');
const { transpileExclude } = require('./webpack.config.client.js');

const DEV = process.env.NODE_ENV === 'development';
const nodeVersion = process.versions.node;
Expand Down Expand Up @@ -77,6 +78,7 @@ module.exports = {
rules: [
{
test: /(\.tsx|\.js|\.ts)$/,
exclude: transpileExclude,
use: swcLoader,
},
svgr,
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading