1+ import type { ABTestAPI } from '@guardian/ab-core' ;
12import type {
23 BrazeArticleContext ,
34 BrazeMessagesInterface ,
@@ -9,12 +10,14 @@ import type { BannerProps } from '@guardian/support-dotcom-components/dist/share
910import { useEffect , useState } from 'react' ;
1011import { getArticleCounts } from '../lib/articleCount' ;
1112import type { ArticleCounts } from '../lib/articleCount' ;
13+ import type { EditionId } from '../lib/edition' ;
1214import type {
1315 CandidateConfig ,
1416 MaybeFC ,
1517 SlotConfig ,
1618} from '../lib/messagePicker' ;
1719import { pickMessage } from '../lib/messagePicker' ;
20+ import { useAB } from '../lib/useAB' ;
1821import { useIsSignedIn } from '../lib/useAuthStatus' ;
1922import { useBraze } from '../lib/useBraze' ;
2023import { useCountryCode } from '../lib/useCountryCode' ;
@@ -50,6 +53,7 @@ type Props = {
5053
5154 pageId : string ;
5255 host ?: string ;
56+ abTestAPI ?: ABTestAPI ; // Optional prop for testing (e.g., Storybook)
5357} ;
5458
5559type BrazeMeta = {
@@ -75,19 +79,43 @@ const DEFAULT_BANNER_TIMEOUT_MILLIS = 2000;
7579const buildCmpBannerConfig = ( ) : CandidateConfig < void > => ( {
7680 candidate : {
7781 id : 'cmpUi' ,
78- canShow : ( ) =>
79- cmp
80- . willShowPrivacyMessage ( )
81- . then ( ( result ) =>
82- result ? { show : true , meta : undefined } : { show : false } ,
83- ) ,
82+ canShow : ( ) => {
83+ // In Storybook environment, CMP APIs may not be available
84+ // Return false immediately to avoid hanging
85+ if (
86+ typeof window !== 'undefined' &&
87+ ( window . location . hostname === 'localhost' ||
88+ window . location . hostname === '127.0.0.1' )
89+ ) {
90+ // eslint-disable-next-line no-console -- Required for debugging CMP issues
91+ console . log (
92+ 'CMP: Detected Storybook/localhost environment, skipping CMP check' ,
93+ ) ;
94+ return Promise . resolve ( { show : false as const } ) ;
95+ }
96+
97+ try {
98+ return cmp
99+ . willShowPrivacyMessage ( )
100+ . then ( ( result ) => {
101+ return result
102+ ? { show : true as const , meta : undefined }
103+ : { show : false as const } ;
104+ } )
105+ . catch ( ( ) => {
106+ return { show : false as const } ;
107+ } ) ;
108+ } catch ( error ) {
109+ return Promise . resolve ( { show : false as const } ) ;
110+ }
111+ } ,
84112 show : ( ) => {
85113 // New CMP is not a react component and is shown outside of react's world
86114 // so render nothing if it will show
87115 return null ;
88116 } ,
89117 } ,
90- timeoutMillis : null ,
118+ timeoutMillis : 1 , // Add 1-millisecond timeout to prevent hanging in problematic environments
91119} ) ;
92120
93121const buildRRBannerConfigWith = ( {
@@ -183,12 +211,23 @@ const buildSignInGateConfig = (
183211 contentType : string ,
184212 sectionId : string ,
185213 tags : TagType [ ] ,
214+ pageId : string ,
215+ contributionsServiceUrl : string ,
216+ editionId : EditionId ,
217+ idUrl : string ,
186218 host ?: string ,
219+ abTestAPI ?: ABTestAPI ,
187220) : CandidateConfig < void > => ( {
188221 candidate : {
189222 id : 'sign-in-gate-portal' ,
190223 canShow : ( ) =>
191- canShowSignInGatePortal ( isSignedIn , isPaidContent , isPreview ) ,
224+ canShowSignInGatePortal (
225+ isSignedIn ,
226+ isPaidContent ,
227+ isPreview ,
228+ pageId ,
229+ abTestAPI ,
230+ ) ,
192231 show : ( ) => ( ) => (
193232 < SignInGatePortal
194233 host = { host }
@@ -197,6 +236,11 @@ const buildSignInGateConfig = (
197236 tags = { tags }
198237 isPaidContent = { isPaidContent }
199238 isPreview = { isPreview }
239+ pageId = { pageId }
240+ contributionsServiceUrl = { contributionsServiceUrl }
241+ editionId = { editionId }
242+ idUrl = { idUrl }
243+ abTestAPI = { abTestAPI }
200244 />
201245 ) ,
202246 } ,
@@ -259,12 +303,17 @@ export const StickyBottomBanner = ({
259303 pageId,
260304 remoteBannerSwitch,
261305 host,
306+ abTestAPI : propAbTestAPI , // Optional prop for testing
262307} : Props & {
263308 remoteBannerSwitch : boolean ;
264309 isSensitive : boolean ;
265310} ) => {
266- const { renderingTarget } = useConfig ( ) ;
311+ const { renderingTarget, editionId } = useConfig ( ) ;
267312 const { brazeMessages } = useBraze ( idApiUrl , renderingTarget ) ;
313+ const hookAbTestAPI = useAB ( ) ?. api ;
314+
315+ // Use prop abTestAPI if provided, otherwise fall back to hook
316+ const abTestAPI = propAbTestAPI ?? hookAbTestAPI ;
268317
269318 const countryCode = useCountryCode ( 'sticky-bottom-banner' ) ;
270319 const isSignedIn = useIsSignedIn ( ) ;
@@ -330,7 +379,12 @@ export const StickyBottomBanner = ({
330379 contentType ,
331380 sectionId ,
332381 tags ,
382+ pageId ,
383+ contributionsServiceUrl ,
384+ editionId ,
385+ idApiUrl , // Using idApiUrl as idUrl
333386 host ,
387+ abTestAPI ,
334388 ) ;
335389
336390 const bannerConfig : SlotConfig = {
@@ -359,6 +413,7 @@ export const StickyBottomBanner = ({
359413 asyncArticleCounts ,
360414 contentType ,
361415 contributionsServiceUrl ,
416+ editionId ,
362417 idApiUrl ,
363418 isMinuteArticle ,
364419 isPaidContent ,
@@ -372,6 +427,7 @@ export const StickyBottomBanner = ({
372427 ophanPageViewId ,
373428 pageId ,
374429 host ,
430+ abTestAPI ,
375431 ] ) ;
376432
377433 if ( SelectedBanner ) {
0 commit comments