11'use client' ;
22
33import { useEffect } from 'react' ;
4+ import Script from 'next/script' ;
45import { useCookieConsent } from '@/contexts/CookieConsentContext' ;
56
67type WatsonAssistantInstance = {
78 render : ( ) => Promise < void > ;
89 destroy : ( ) => void ;
910} ;
1011
11- // Extend Window interface for WatsonX
1212declare global {
1313 interface Window {
1414 watsonAssistantChatOptions ?: {
@@ -21,7 +21,6 @@ declare global {
2121 }
2222}
2323
24- // West Midlands locations that should have WatsonX chat
2524const WATSON_X_LOCATIONS = [
2625 'birmingham' ,
2726 'sandwell' ,
@@ -32,15 +31,14 @@ const WATSON_X_LOCATIONS = [
3231 'solihull'
3332] ;
3433
34+ const WATSON_SCRIPT_URL =
35+ 'https://web-chat.global.assistant.watson.appdomain.cloud/versions/latest/WatsonAssistantChatEntry.js' ;
36+
3537interface WatsonXChatProps {
3638 locationSlug ?: string ;
3739}
3840
39- // Module-level state — survives React Strict Mode unmount/remount cycles.
40- // The Watson SDK script is a one-shot global resource that cannot be safely
41- // removed and re-added, so we track it outside the component lifecycle.
4241let watsonInstance : WatsonAssistantInstance | null = null ;
43- let scriptAdded = false ;
4442let pendingCleanup : ReturnType < typeof setTimeout > | null = null ;
4543
4644function doCleanup ( ) {
@@ -53,33 +51,17 @@ function doCleanup() {
5351 }
5452 watsonInstance = null ;
5553 }
56- scriptAdded = false ;
5754 delete window . watsonAssistantChatOptions ;
58- const existing = document . querySelector (
59- 'script[src*="WatsonAssistantChatEntry.js"]'
60- ) ;
61- if ( existing ) {
62- existing . remove ( ) ;
63- }
6455}
6556
66- /**
67- * WatsonX Chat component for West Midlands locations
68- * Injects the IBM WatsonX Assistant chat widget
69- *
70- * @param locationSlug - Optional location slug to determine if chat should be shown
71- * If not provided, chat is always shown (for west-midlands hub page)
72- */
7357export default function WatsonXChat ( { locationSlug } : WatsonXChatProps ) {
7458 const { hasConsent } = useCookieConsent ( ) ;
7559
76- // Determine if we should show the chat widget (requires functional consent)
7760 const isWatsonLocation = ! locationSlug || WATSON_X_LOCATIONS . includes ( locationSlug ) ;
7861 const hasFunctionalConsent = hasConsent ( 'functional' ) ;
7962 const shouldShowChat = isWatsonLocation && hasFunctionalConsent ;
8063
8164 useEffect ( ( ) => {
82- // Cancel any scheduled cleanup (e.g. from Strict Mode unmount/remount)
8365 if ( pendingCleanup ) {
8466 clearTimeout ( pendingCleanup ) ;
8567 pendingCleanup = null ;
@@ -90,36 +72,29 @@ export default function WatsonXChat({ locationSlug }: WatsonXChatProps) {
9072 return ;
9173 }
9274
93- // Already have a live instance or the script is loading — nothing to do
94- if ( watsonInstance || scriptAdded ) return ;
95-
96- scriptAdded = true ;
97-
98- // Set up Watson Assistant options
99- window . watsonAssistantChatOptions = {
100- integrationID : '83b099b7-08a1-4118-bba3-341fbec366d1' ,
101- region : 'eu-gb' ,
102- serviceInstanceID : 'a3a6beaa-5967-4039-8390-d48ace365d86' ,
103- onLoad : async ( instance ) => {
104- watsonInstance = instance ;
105- await instance . render ( ) ;
106- }
107- } ;
108-
109- // Load the Watson Assistant script
110- const script = document . createElement ( 'script' ) ;
111- script . src =
112- 'https://web-chat.global.assistant.watson.appdomain.cloud/versions/latest/WatsonAssistantChatEntry.js' ;
113- script . async = true ;
114- document . head . appendChild ( script ) ;
75+ if ( ! watsonInstance ) {
76+ window . watsonAssistantChatOptions = {
77+ integrationID : '83b099b7-08a1-4118-bba3-341fbec366d1' ,
78+ region : 'eu-gb' ,
79+ serviceInstanceID : 'a3a6beaa-5967-4039-8390-d48ace365d86' ,
80+ onLoad : async ( instance ) => {
81+ watsonInstance = instance ;
82+ await instance . render ( ) ;
83+ }
84+ } ;
85+ }
11586
11687 return ( ) => {
117- // Defer cleanup so a Strict Mode remount can cancel it.
118- // If no remount follows (real unmount), cleanup fires after the timeout.
11988 pendingCleanup = setTimeout ( doCleanup , 0 ) ;
12089 } ;
12190 } , [ shouldShowChat ] ) ;
12291
123- // This component doesn't render anything visible
124- return null ;
92+ if ( ! shouldShowChat ) return null ;
93+
94+ return (
95+ < Script
96+ src = { WATSON_SCRIPT_URL }
97+ strategy = "afterInteractive"
98+ />
99+ ) ;
125100}
0 commit comments