Skip to content

Commit 9312ae8

Browse files
committed
Fix ZD not loading after fire
1 parent 7a6f365 commit 9312ae8

3 files changed

Lines changed: 194 additions & 90 deletions

File tree

src/app/page.tsx

Lines changed: 108 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client';
22

3-
import { useCallback, useReducer, useState } from 'react';
4-
import Script from 'next/script';
3+
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
54
import styles from './page.module.css';
65
import PageLoadPixel from '@/components/page-load-pixel/page-load-pixel';
76
import ConsentForm from '@/components/consent-form/consent-form';
@@ -29,20 +28,24 @@ import {
2928
ZENDESK_YES_BUTTON_IDENTIFIER,
3029
ZENDESK_NO_BUTTON_IDENTIFIER,
3130
ZENDESK_SCRIPT_TAG_ID,
32-
ZENDESK_HIDDEN_IFRAME_SELECTOR,
3331
} from '@/constants/zendesk-selectors';
3432
import { ZENDESK_IFRAME_STYLES } from '@/constants/zendesk-styles';
3533
import { getCSSVariable } from '@/utils/get-css-variable';
3634
import { getSlugFromUrl } from '@/utils/get-slug-from-url';
3735
import { widgetReducer, initialWidgetState } from '@/reducers/widget-reducer';
3836
import { setStorageWithExpiry } from '@/utils/set-storage-with-expiry';
3937
import { deleteStorageKeysBySuffix } from '@/utils/delete-storage-keys-by-suffix';
38+
import { cleanupZendesk } from '@/utils/cleanup-zendesk';
4039

4140
export default function Home() {
4241
const [widgetState, dispatch] = useReducer(widgetReducer, initialWidgetState);
4342
const { zendeskReady, loadWidget, firstMessageSent } = widgetState;
4443
const [isBurning, setIsBurning] = useState(false);
4544
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
45+
const scriptLoadedRef = useRef(false);
46+
const initializeZendeskRef = useRef<((retries?: number) => void) | null>(
47+
null,
48+
);
4649

4750
const onContinue = useCallback(() => {
4851
window.firePixelEvent?.('consent');
@@ -121,8 +124,14 @@ export default function Home() {
121124
// clear `ZD-widgetOpen`
122125
sessionStorage.clear();
123126

127+
// Clean up all Zendesk DOM elements, scripts, and globals
128+
cleanupZendesk();
129+
124130
// Reset state to clear UI elements
125131
dispatch({ type: 'RESET_STATE' });
132+
133+
// Reset script loaded flag so script will reload on next mount
134+
scriptLoadedRef.current = false;
126135
});
127136
}, ZENDESK_RESET_DELAY_MS);
128137
}, [dispatch]);
@@ -142,88 +151,112 @@ export default function Home() {
142151
behavior: prefersReducedMotion ? 'auto' : 'smooth',
143152
});
144153

145-
// Clear contents of messaging-container div (Zendesk iframe, etc.)
146-
const messagingContainer = document.getElementById(EMBEDDED_TARGET_ELEMENT);
147-
if (messagingContainer) {
148-
messagingContainer.innerHTML = '';
149-
}
154+
// Stop burn animation
155+
setIsBurning(false);
156+
}, []);
150157

151-
// Remove hidden Zendesk iframe (data-product="web_widget")
152-
const hiddenIframe = document.querySelector(ZENDESK_HIDDEN_IFRAME_SELECTOR);
153-
if (hiddenIframe) {
154-
hiddenIframe.remove();
155-
}
158+
const initializeZendesk = useCallback(
159+
(retries = 5) => {
160+
// Wait a bit to ensure zE is available and messaging-container exists
161+
setTimeout(() => {
162+
try {
163+
// Check if messaging-container exists
164+
const messagingContainer = document.getElementById(
165+
EMBEDDED_TARGET_ELEMENT,
166+
);
167+
if (!messagingContainer) {
168+
if (retries > 0) {
169+
initializeZendeskRef.current?.(retries - 1);
170+
}
171+
return;
172+
}
156173

157-
// Remove ze-snippet script element
158-
const zeSnippet = document.getElementById(ZENDESK_SCRIPT_TAG_ID);
159-
if (zeSnippet) {
160-
zeSnippet.remove();
161-
}
174+
// Check if zE exists (script might not have initialized yet)
175+
if (typeof zE === 'undefined') {
176+
if (retries > 0) {
177+
initializeZendeskRef.current?.(retries - 1);
178+
}
179+
return;
180+
}
162181

163-
// Clean up Zendesk global objects
164-
if (typeof window !== 'undefined') {
165-
// Remove zE function if it exists
166-
try {
167-
delete (window as unknown as { zE?: unknown }).zE;
168-
} catch {
169-
// Ignore errors if zE is not configurable
170-
}
182+
// Set cookies and theme customization first
183+
zE('messenger:set', 'cookies', 'functional');
184+
zE('messenger:set', 'customization', {
185+
common: {
186+
hideHeader: true,
187+
},
188+
theme: {
189+
message: getCSSVariable('--sds-color-palette-blue-60'),
190+
action: getCSSVariable('--sds-color-palette-blue-60'),
191+
onAction: '#FAFAFA',
192+
businessMessage: '#F2F2F2', // Chat response background
193+
onBusinessMessage: getCSSVariable('--sds-color-text-01'),
194+
background: 'transparent', // Chat window background
195+
onBackground: '#666666',
196+
error: '#FF1744',
197+
onError: '#FFFFFF',
198+
notify: '#FF007F',
199+
onNotify: '#FFFFFF',
200+
},
201+
});
171202

172-
// Remove zEMessenger if it exists
173-
try {
174-
delete (window as unknown as { zEMessenger?: unknown }).zEMessenger;
175-
} catch {
176-
// Ignore errors if zEMessenger is not configurable
177-
}
203+
// Render the embedded messenger
204+
zE('messenger', 'render', {
205+
mode: 'embedded',
206+
widget: {
207+
targetElement: `#${EMBEDDED_TARGET_ELEMENT}`,
208+
},
209+
});
210+
211+
// Set ready after a delay to allow widget to render
212+
setTimeout(() => {
213+
dispatch({ type: 'SET_ZENDESK_READY' });
214+
}, ZENDESK_READY_DELAY_MS);
215+
} catch (error) {
216+
window.fireJse?.(error);
217+
}
218+
}, 50);
219+
},
220+
[dispatch],
221+
);
222+
223+
// Store initializeZendesk in a ref to enable recursive calls
224+
useEffect(() => {
225+
initializeZendeskRef.current = initializeZendesk;
226+
}, [initializeZendesk]);
227+
228+
// Manually load Zendesk script when loadWidget becomes true
229+
useEffect(() => {
230+
if (!loadWidget || scriptLoadedRef.current) {
231+
return;
178232
}
179233

180-
// Stop burn animation
181-
setIsBurning(false);
182-
}, []);
234+
// Check if script already exists
235+
const existingScript = document.getElementById(ZENDESK_SCRIPT_TAG_ID);
236+
if (existingScript) {
237+
// Script already exists, just initialize
238+
initializeZendesk();
239+
scriptLoadedRef.current = true;
240+
return;
241+
}
183242

184-
const handleOnError = useCallback((e: Error) => {
185-
window.fireJse?.(e);
186-
}, []);
243+
// Create and inject script tag manually
244+
const script = document.createElement('script');
245+
script.id = ZENDESK_SCRIPT_TAG_ID;
246+
script.src = ZENDESK_SCRIPT_URL;
247+
script.async = true;
187248

188-
const handleOnLoad = useCallback(() => {
189-
try {
190-
// Set cookies and theme customization first
191-
zE('messenger:set', 'cookies', 'functional');
192-
zE('messenger:set', 'customization', {
193-
common: {
194-
hideHeader: true,
195-
},
196-
theme: {
197-
message: getCSSVariable('--sds-color-palette-blue-60'),
198-
action: getCSSVariable('--sds-color-palette-blue-60'),
199-
onAction: '#FAFAFA',
200-
businessMessage: '#F2F2F2', // Chat response background
201-
onBusinessMessage: getCSSVariable('--sds-color-text-01'),
202-
background: 'transparent', // Chat window background
203-
onBackground: '#666666',
204-
error: '#FF1744',
205-
onError: '#FFFFFF',
206-
notify: '#FF007F',
207-
onNotify: '#FFFFFF',
208-
},
209-
});
249+
script.onload = () => {
250+
scriptLoadedRef.current = true;
251+
initializeZendesk();
252+
};
210253

211-
// Render the embedded messenger
212-
zE('messenger', 'render', {
213-
mode: 'embedded',
214-
widget: {
215-
targetElement: `#${EMBEDDED_TARGET_ELEMENT}`,
216-
},
217-
});
254+
script.onerror = () => {
255+
window.fireJse?.(new Error('Failed to load Zendesk script'));
256+
};
218257

219-
// Set ready after a delay to allow widget to render
220-
setTimeout(() => {
221-
dispatch({ type: 'SET_ZENDESK_READY' });
222-
}, ZENDESK_READY_DELAY_MS);
223-
} catch (error) {
224-
window.fireJse?.(error);
225-
}
226-
}, [dispatch]);
258+
document.head.appendChild(script);
259+
}, [loadWidget, initializeZendesk]);
227260

228261
return (
229262
<>
@@ -272,14 +305,6 @@ export default function Home() {
272305

273306
{!loadWidget && <ConsentForm onContinue={onContinue} />}
274307
</main>
275-
{loadWidget && (
276-
<Script
277-
id={ZENDESK_SCRIPT_TAG_ID}
278-
src={ZENDESK_SCRIPT_URL}
279-
onLoad={handleOnLoad}
280-
onError={handleOnError}
281-
/>
282-
)}
283308
<PageLoadPixel />
284309
</>
285310
);

src/hooks/use-zendesk-iframe-styles.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,12 @@ export function useZendeskIframeStyles({
166166
}, [injectStyles]);
167167

168168
useEffect(() => {
169+
// Reset injection state when widget becomes unavailable
170+
// This ensures styles are re-injected when widget reloads
169171
if (!zendeskReady) {
172+
injectedRef.current = false;
173+
styleElementRef.current = null;
174+
replacedButtonsRef.current = new WeakSet<HTMLElement>();
170175
return;
171176
}
172177

@@ -177,13 +182,12 @@ export function useZendeskIframeStyles({
177182
let observerTimeout: ReturnType<typeof setTimeout> | null = null;
178183

179184
// Inject styles when widget becomes ready
180-
if (!injectedRef.current) {
181-
injectStylesTimeout = setTimeout(() => {
182-
if (isMountedRef.current) {
183-
injectStyles();
184-
}
185-
}, INITIAL_RENDER_DELAY_MS);
186-
}
185+
// Always inject (or re-inject) when zendeskReady becomes true
186+
injectStylesTimeout = setTimeout(() => {
187+
if (isMountedRef.current) {
188+
injectStyles();
189+
}
190+
}, INITIAL_RENDER_DELAY_MS);
187191

188192
// Set up MutationObserver to re-inject styles if iframe reloads
189193
let observerCleanup: (() => void) | null = null;

src/utils/cleanup-zendesk.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { EMBEDDED_TARGET_ELEMENT } from '@/config/common';
2+
import { ZENDESK_SCRIPT_TAG_ID } from '@/constants/zendesk-selectors';
3+
4+
/**
5+
* Zendesk global objects that need to be cleaned up.
6+
*/
7+
const ZENDESK_GLOBALS = [
8+
'zE',
9+
'zEMessenger',
10+
'zEmbed',
11+
'zEACLoaded',
12+
'zEWebpackACJsonp',
13+
] as const;
14+
15+
/**
16+
* Cleans up all Zendesk DOM elements, scripts, and global objects.
17+
*
18+
* This function removes:
19+
* - All Zendesk iframes (messaging iframe, hidden iframe, etc.)
20+
* - All Zendesk script tags
21+
* - All Zendesk global objects (zE, zEMessenger, zEmbed, etc.)
22+
* - Contents of the messaging-container div
23+
*
24+
* After cleanup, recreates zEMessenger with fresh config to ensure clean state
25+
* for potential reload.
26+
*
27+
* @function cleanupZendesk
28+
*
29+
* @returns {void}
30+
*/
31+
export function cleanupZendesk(): void {
32+
// Clear contents of messaging-container div (Zendesk iframe, etc.)
33+
const messagingContainer = document.getElementById(EMBEDDED_TARGET_ELEMENT);
34+
if (messagingContainer) {
35+
messagingContainer.innerHTML = '';
36+
}
37+
38+
// Remove ALL Zendesk iframes (not just the hidden one)
39+
const allZendeskIframes = document.querySelectorAll(
40+
'iframe[id*="zendesk"], iframe[data-product="web_widget"]',
41+
);
42+
allZendeskIframes.forEach((iframe) => iframe.remove());
43+
44+
// Remove ze-snippet script element
45+
const zeSnippet = document.getElementById(ZENDESK_SCRIPT_TAG_ID);
46+
if (zeSnippet) {
47+
zeSnippet.remove();
48+
}
49+
50+
// Remove any other Zendesk script tags
51+
const zendeskScripts = document.querySelectorAll(
52+
'script[src*="zdassets.com"], script[src*="zendesk"]',
53+
);
54+
zendeskScripts.forEach((script) => script.remove());
55+
56+
// Clean up Zendesk global objects
57+
if (typeof window !== 'undefined') {
58+
ZENDESK_GLOBALS.forEach((globalName) => {
59+
try {
60+
const win = window as unknown as Record<string, unknown>;
61+
if (globalName in win) {
62+
delete win[globalName];
63+
}
64+
} catch {
65+
// Ignore errors if property is not configurable
66+
}
67+
});
68+
69+
// Recreate zEMessenger with fresh config (layout.tsx will also set this,
70+
// but we need it here before state resets to ensure clean state)
71+
(window as unknown as { zEMessenger?: unknown }).zEMessenger = {
72+
autorender: false,
73+
};
74+
}
75+
}

0 commit comments

Comments
 (0)