@envive-ai/react-widgets-v3 0.3.39 → 0.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CXIntegration/hooks/useUnifiedCXButton.cjs +1 -1
- package/dist/CXIntegration/hooks/useUnifiedCXButton.js +1 -1
- package/dist/CXIntegration/implementations/useFreshChatUnifiedCXButton.cjs +76 -0
- package/dist/CXIntegration/implementations/useFreshChatUnifiedCXButton.js +75 -0
- package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.cjs +110 -25
- package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +110 -25
- package/dist/CXIntegration/types.cjs +1 -0
- package/dist/CXIntegration/types.js +1 -0
- package/dist/CXIntegration/utils/functions.cjs +2 -0
- package/dist/CXIntegration/utils/functions.js +2 -0
- package/dist/debug/MessageContent.cjs +1 -1
- package/dist/debug/MessageContent.js +1 -1
- package/dist/debug/chatEmbed.cjs +3 -3
- package/dist/debug/chatEmbed.js +2 -2
- package/dist/debug/reportIssue.cjs +2 -2
- package/dist/debug/reportIssue.js +2 -2
- package/dist/hocs/withBaseWidget/types.d.ts +1 -1
- package/dist/hocs/withBaseWidget/withBaseWidget.cjs +1 -1
- package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
- package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
- package/dist/hocs/withBaseWidget/withBaseWidget.js +1 -1
- package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +2 -2
- package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
- package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.cjs +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.js +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.cjs +1 -1
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.js +1 -1
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.cjs +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.js +3 -3
- package/dist/widgets/FloatingChatWidget/FloatingChatOverlay.cjs +8 -0
- package/dist/widgets/FloatingChatWidget/FloatingChatOverlay.js +8 -0
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.cjs +4 -4
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.js +4 -4
- package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.cjs +1 -1
- package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.js +1 -1
- package/dist/widgets/FloatingChatWidget/hooks/useFloatingButtonVisibility.cjs +1 -1
- package/dist/widgets/FloatingChatWidget/hooks/useFloatingButtonVisibility.js +1 -1
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.cjs +1 -1
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.js +1 -1
- package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +3 -3
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.js +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +5 -8
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +6 -9
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +3 -3
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +3 -3
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.cjs +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.js +2 -2
- package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +4 -4
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.js +4 -4
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +3 -3
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +3 -3
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.cjs +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.js +2 -2
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +3 -3
- package/dist/widgets/dist/SearchResults/SearchResults.d.cts +2 -2
- package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
- package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
- package/dist/widgets/hooks/useGetWidgetStatus.cjs +1 -1
- package/dist/widgets/hooks/useGetWidgetStatus.js +1 -1
- package/package.json +1 -1
- package/src/CXIntegration/implementations/useFreshChatUnifiedCXButton.ts +132 -0
- package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +184 -54
- package/src/CXIntegration/types.ts +1 -0
- package/src/CXIntegration/utils/functions.ts +4 -0
- package/src/widgets/FloatingChatWidget/FloatingChatOverlay.tsx +8 -1
- package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +4 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SearchResultsEntryPointWidgetConfig } from "./types.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
3
|
import { SearchResultsHocProps } from "@envive-ai/react-hooks/hooks/Search";
|
|
4
4
|
|
|
5
5
|
//#region ../widgets/dist/SearchResults/SearchResults.d.ts
|
|
@@ -7,7 +7,7 @@ import { SearchResultsHocProps } from "@envive-ai/react-hooks/hooks/Search";
|
|
|
7
7
|
interface SearchResultsProps extends SearchResultsHocProps {
|
|
8
8
|
widgetConfig: SearchResultsEntryPointWidgetConfig;
|
|
9
9
|
}
|
|
10
|
-
declare const SearchResults: (props: Omit<SearchResultsProps, string | number | symbol>) =>
|
|
10
|
+
declare const SearchResults: (props: Omit<SearchResultsProps, string | number | symbol>) => react_jsx_runtime0.JSX.Element;
|
|
11
11
|
//#endregion
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region ../widgets/dist/SearchResults/SearchResultsWidget.d.ts
|
|
4
4
|
//#region src/SearchResults/SearchResultsWidget.d.ts
|
|
5
|
-
declare const SearchResultsWidget: () =>
|
|
5
|
+
declare const SearchResultsWidget: () => react_jsx_runtime1.JSX.Element;
|
|
6
6
|
//#endregion
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SuggestionBarLocationForMetrics } from "./types.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
4
4
|
import { Message } from "postcss";
|
|
5
5
|
|
|
@@ -30,7 +30,7 @@ declare function SuggestionBar({
|
|
|
30
30
|
buttonBorderRadius,
|
|
31
31
|
handleReply,
|
|
32
32
|
dataTestId
|
|
33
|
-
}: Readonly<SuggestionBarProps>):
|
|
33
|
+
}: Readonly<SuggestionBarProps>): react_jsx_runtime2.JSX.Element;
|
|
34
34
|
//#endregion
|
|
35
35
|
//#endregion
|
|
36
36
|
export { SuggestionBar };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
2
2
|
const require_functions = require('../utils/functions.cjs');
|
|
3
|
+
let react = require("react");
|
|
3
4
|
let __envive_ai_react_hooks_atoms_chat = require("@envive-ai/react-hooks/atoms/chat");
|
|
4
5
|
let jotai = require("jotai");
|
|
5
|
-
let react = require("react");
|
|
6
6
|
let __envive_ai_react_hooks_atoms_chat_chatState = require("@envive-ai/react-hooks/atoms/chat/chatState");
|
|
7
7
|
|
|
8
8
|
//#region src/widgets/hooks/useGetWidgetStatus.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isLoading, isProductComparison, userHasInteracted, userHasNotInteracted } from "../utils/functions.js";
|
|
2
|
+
import { useMemo } from "react";
|
|
2
3
|
import { chatAtom, lastAssistantMessageAtom } from "@envive-ai/react-hooks/atoms/chat";
|
|
3
4
|
import { useAtomValue } from "jotai";
|
|
4
|
-
import { useMemo } from "react";
|
|
5
5
|
import { responseStreamingAtom } from "@envive-ai/react-hooks/atoms/chat/chatState";
|
|
6
6
|
|
|
7
7
|
//#region src/widgets/hooks/useGetWidgetStatus.tsx
|
package/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { SelectorFactory } from '@envive-ai/react-hooks/application/utils';
|
|
3
|
+
import { useElementObserver } from '@envive-ai/react-hooks/hooks/ElementObserver';
|
|
4
|
+
import { FLOATING_BUTTON_ID } from '../../widgets/FloatingChatWidget/constants';
|
|
5
|
+
import { CustomerServiceImplProps, UnifiedCXButton } from '../types';
|
|
6
|
+
|
|
7
|
+
const FRESHCHAT_FRAME_ID = 'fc_frame';
|
|
8
|
+
const FRESHCHAT_WIDGET_ID = 'fc_widget';
|
|
9
|
+
const FRESHCHAT_OPEN_CLASS = 'fc-open';
|
|
10
|
+
const FRESHCHAT_LAUNCHER_SELECTOR = '.d-hotline';
|
|
11
|
+
|
|
12
|
+
interface FreshChatApi {
|
|
13
|
+
open: () => void;
|
|
14
|
+
close: () => void;
|
|
15
|
+
show: () => void;
|
|
16
|
+
hide: () => void;
|
|
17
|
+
isOpen?: () => boolean;
|
|
18
|
+
isLoaded?: () => boolean;
|
|
19
|
+
on?: (event: string, callback: () => void) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare global {
|
|
23
|
+
interface Window {
|
|
24
|
+
fcWidget?: FreshChatApi;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface UseFreshChatUnifiedCXButtonProps extends CustomerServiceImplProps {}
|
|
29
|
+
|
|
30
|
+
const isFreshChatOpen = (el: HTMLElement | null): boolean => {
|
|
31
|
+
if (!el) return false;
|
|
32
|
+
if (el.classList.contains(FRESHCHAT_OPEN_CLASS)) return true;
|
|
33
|
+
// Fallback: when open, the iframe expands well beyond launcher dimensions
|
|
34
|
+
const iframe = document.getElementById(FRESHCHAT_WIDGET_ID) as HTMLIFrameElement | null;
|
|
35
|
+
if (!iframe) return false;
|
|
36
|
+
const rect = iframe.getBoundingClientRect();
|
|
37
|
+
return rect.width > 80 && rect.height > 80;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useFreshChatUnifiedCXButton = ({
|
|
41
|
+
onSwitchToAgent,
|
|
42
|
+
onCXClose,
|
|
43
|
+
suppressMerchantButton,
|
|
44
|
+
}: UseFreshChatUnifiedCXButtonProps): UnifiedCXButton => {
|
|
45
|
+
const freshchatFrame = useElementObserver(SelectorFactory.id(FRESHCHAT_FRAME_ID));
|
|
46
|
+
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
47
|
+
// Tracks the previous open/closed state so onChange (which fires on every DOM
|
|
48
|
+
// mutation inside #fc_frame) only acts on real transitions — otherwise the
|
|
49
|
+
// floating button flickers while the chat panel is in use.
|
|
50
|
+
const wasOpenRef = useRef(false);
|
|
51
|
+
|
|
52
|
+
const toggle = () => {
|
|
53
|
+
onSwitchToAgent();
|
|
54
|
+
enviveFloatingButton.hide();
|
|
55
|
+
|
|
56
|
+
if (typeof window.fcWidget !== 'undefined') {
|
|
57
|
+
window.fcWidget.show();
|
|
58
|
+
window.fcWidget.open();
|
|
59
|
+
} else {
|
|
60
|
+
freshchatFrame.show();
|
|
61
|
+
const iframe = document.getElementById(FRESHCHAT_WIDGET_ID) as HTMLIFrameElement | null;
|
|
62
|
+
const launcher = iframe?.contentDocument?.querySelector(
|
|
63
|
+
FRESHCHAT_LAUNCHER_SELECTOR,
|
|
64
|
+
) as HTMLElement | null;
|
|
65
|
+
launcher?.click();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isFreshChatAvailable = () => {
|
|
70
|
+
const frame = document.getElementById(FRESHCHAT_FRAME_ID);
|
|
71
|
+
const hasApi = typeof window.fcWidget !== 'undefined';
|
|
72
|
+
return !!(frame || hasApi);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isSwitchEnabled = () => isFreshChatAvailable();
|
|
76
|
+
|
|
77
|
+
freshchatFrame.onAdd(el => {
|
|
78
|
+
if (!el) return;
|
|
79
|
+
|
|
80
|
+
const isOpen = isFreshChatOpen(el);
|
|
81
|
+
wasOpenRef.current = isOpen;
|
|
82
|
+
|
|
83
|
+
if (isOpen) {
|
|
84
|
+
enviveFloatingButton.hide();
|
|
85
|
+
} else {
|
|
86
|
+
enviveFloatingButton.show();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (suppressMerchantButton) {
|
|
90
|
+
if (isOpen) {
|
|
91
|
+
freshchatFrame.show();
|
|
92
|
+
} else {
|
|
93
|
+
freshchatFrame.hide();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
freshchatFrame.onChange(el => {
|
|
99
|
+
if (!el) return;
|
|
100
|
+
|
|
101
|
+
const isOpen = isFreshChatOpen(el);
|
|
102
|
+
|
|
103
|
+
if (isOpen === wasOpenRef.current) return;
|
|
104
|
+
wasOpenRef.current = isOpen;
|
|
105
|
+
|
|
106
|
+
if (isOpen) {
|
|
107
|
+
enviveFloatingButton.hide();
|
|
108
|
+
if (suppressMerchantButton) {
|
|
109
|
+
freshchatFrame.show();
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
onCXClose?.();
|
|
113
|
+
enviveFloatingButton.show();
|
|
114
|
+
if (suppressMerchantButton) {
|
|
115
|
+
freshchatFrame.hide();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
freshchatFrame.onRemove(() => {
|
|
121
|
+
if (wasOpenRef.current) {
|
|
122
|
+
onCXClose?.();
|
|
123
|
+
}
|
|
124
|
+
wasOpenRef.current = false;
|
|
125
|
+
enviveFloatingButton.show();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
toggle,
|
|
130
|
+
isSwitchEnabled,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
@@ -1,102 +1,232 @@
|
|
|
1
1
|
import { SelectorFactory } from '@envive-ai/react-hooks/application/utils';
|
|
2
2
|
import { useElementObserver } from '@envive-ai/react-hooks/hooks/ElementObserver';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
3
4
|
import { FLOATING_BUTTON_ID } from '../../widgets/FloatingChatWidget/constants';
|
|
4
5
|
import { CustomerServiceImplProps, UnifiedCXButton } from '../types';
|
|
5
6
|
|
|
6
7
|
const BEACON_CONTAINER_ID = 'beacon-container';
|
|
8
|
+
const FAB_BUTTON_SELECTOR = '.BeaconFabButtonFrame';
|
|
9
|
+
const BEACON_WIDGET_SELECTOR = '.BeaconContainer';
|
|
10
|
+
// HelpScout's container may render the widget either as a styled `.BeaconContainer` or
|
|
11
|
+
// (e.g. in the "Instant Answers" view) inside an iframe. We treat either as "open".
|
|
12
|
+
const BEACON_OPEN_CHILD_SELECTOR = '.BeaconContainer, iframe, [class*="Beacon"][class*="ontainer"]';
|
|
7
13
|
|
|
8
14
|
interface UseHelpScoutUnifiedCXButtonProps extends CustomerServiceImplProps {}
|
|
9
15
|
|
|
16
|
+
type BeaconApi = (command: string, ...args: unknown[]) => void;
|
|
17
|
+
|
|
18
|
+
const getBeaconApi = (): BeaconApi | undefined => {
|
|
19
|
+
const beacon = (window as unknown as { Beacon?: BeaconApi }).Beacon;
|
|
20
|
+
return typeof beacon === 'function' ? beacon : undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getBeaconContainer = (): HTMLElement | null => document.getElementById(BEACON_CONTAINER_ID);
|
|
24
|
+
|
|
25
|
+
const getFabButton = (): HTMLElement | null => {
|
|
26
|
+
return (getBeaconContainer()?.querySelector(FAB_BUTTON_SELECTOR) as HTMLElement) ?? null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getFloatingButtonEl = (): HTMLElement | null => document.getElementById(FLOATING_BUTTON_ID);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detects whether the HelpScout beacon is currently visible/open.
|
|
33
|
+
* We use multiple signals because HelpScout has different "views"
|
|
34
|
+
* (chat, instant answers) that may use different class names.
|
|
35
|
+
*/
|
|
36
|
+
const isBeaconCurrentlyOpen = (): boolean => {
|
|
37
|
+
const container = getBeaconContainer();
|
|
38
|
+
if (!container) return false;
|
|
39
|
+
|
|
40
|
+
// Signal 1: a `.BeaconContainer` exists with an enter-* class
|
|
41
|
+
const beaconWidget = container.querySelector(BEACON_WIDGET_SELECTOR);
|
|
42
|
+
if (beaconWidget) {
|
|
43
|
+
const cl = beaconWidget.classList;
|
|
44
|
+
if (
|
|
45
|
+
cl.contains('BeaconContainer-enter-done') ||
|
|
46
|
+
cl.contains('BeaconContainer-enter-active') ||
|
|
47
|
+
cl.contains('BeaconContainer-enter')
|
|
48
|
+
) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
cl.contains('BeaconContainer-exit') ||
|
|
53
|
+
cl.contains('BeaconContainer-exit-active') ||
|
|
54
|
+
cl.contains('BeaconContainer-exit-done')
|
|
55
|
+
) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Signal 2: any non-FAB visible child in the container means the panel is rendered
|
|
61
|
+
const children = Array.from(container.children);
|
|
62
|
+
return children.some(child => {
|
|
63
|
+
if (child.matches(FAB_BUTTON_SELECTOR)) return false;
|
|
64
|
+
const rect = (child as HTMLElement).getBoundingClientRect();
|
|
65
|
+
return rect.width > 50 && rect.height > 50;
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Force-hides the Envive floating button via direct DOM manipulation. This is a
|
|
71
|
+
* safety net for cases where React state alone doesn't unmount the element fast
|
|
72
|
+
* enough (or at all), so we never get a flash of the floating button on top of
|
|
73
|
+
* the merchant's CX UI.
|
|
74
|
+
*/
|
|
75
|
+
const forceHideFloatingButton = () => {
|
|
76
|
+
const el = getFloatingButtonEl();
|
|
77
|
+
if (el) el.style.display = 'none';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Restores the Envive floating button display, letting React's render logic take
|
|
82
|
+
* over again.
|
|
83
|
+
*/
|
|
84
|
+
const forceShowFloatingButton = () => {
|
|
85
|
+
const el = getFloatingButtonEl();
|
|
86
|
+
if (el && el.style.display === 'none') el.style.display = '';
|
|
87
|
+
};
|
|
88
|
+
|
|
10
89
|
export const useHelpScoutUnifiedCXButton = ({
|
|
11
90
|
onSwitchToAgent,
|
|
91
|
+
onCXClose,
|
|
12
92
|
suppressMerchantButton,
|
|
13
93
|
}: UseHelpScoutUnifiedCXButtonProps): UnifiedCXButton => {
|
|
14
94
|
const beaconContainer = useElementObserver(SelectorFactory.id(BEACON_CONTAINER_ID));
|
|
15
95
|
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
16
96
|
|
|
97
|
+
// Track previous open state so we only fire onCXClose on a real open→closed
|
|
98
|
+
// transition, not on initial render or during the open animation.
|
|
99
|
+
const wasOpenRef = useRef(false);
|
|
100
|
+
|
|
101
|
+
// Keep latest callbacks/flags in refs so the Beacon API effect can read them
|
|
102
|
+
// without re-binding listeners on every render.
|
|
103
|
+
const onCXCloseRef = useRef(onCXClose);
|
|
104
|
+
const suppressMerchantButtonRef = useRef(suppressMerchantButton);
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
onCXCloseRef.current = onCXClose;
|
|
107
|
+
suppressMerchantButtonRef.current = suppressMerchantButton;
|
|
108
|
+
}, [onCXClose, suppressMerchantButton]);
|
|
109
|
+
|
|
110
|
+
const handleBeaconOpened = () => {
|
|
111
|
+
wasOpenRef.current = true;
|
|
112
|
+
// Always force-hide the Envive floating button when the merchant CX opens.
|
|
113
|
+
// This is the safety net regardless of suppressMerchantButton.
|
|
114
|
+
forceHideFloatingButton();
|
|
115
|
+
if (suppressMerchantButtonRef.current) {
|
|
116
|
+
const fabButton = getFabButton();
|
|
117
|
+
if (fabButton) fabButton.style.display = '';
|
|
118
|
+
enviveFloatingButton.hide();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleBeaconClosed = () => {
|
|
123
|
+
if (!wasOpenRef.current) return;
|
|
124
|
+
wasOpenRef.current = false;
|
|
125
|
+
onCXCloseRef.current?.();
|
|
126
|
+
forceShowFloatingButton();
|
|
127
|
+
if (suppressMerchantButtonRef.current) {
|
|
128
|
+
const fabButton = getFabButton();
|
|
129
|
+
if (fabButton) fabButton.style.display = 'none';
|
|
130
|
+
enviveFloatingButton.show();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
17
134
|
const toggle = () => {
|
|
18
135
|
onSwitchToAgent();
|
|
136
|
+
// Force hide BEFORE opening HelpScout so there's no flash of the floating
|
|
137
|
+
// button next to the merchant's CX panel.
|
|
138
|
+
wasOpenRef.current = true;
|
|
139
|
+
forceHideFloatingButton();
|
|
140
|
+
if (suppressMerchantButton) {
|
|
141
|
+
enviveFloatingButton.hide();
|
|
142
|
+
}
|
|
19
143
|
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
(
|
|
144
|
+
const beacon = getBeaconApi();
|
|
145
|
+
if (beacon) {
|
|
146
|
+
beacon('open');
|
|
23
147
|
} else {
|
|
24
|
-
// Fallback:
|
|
25
|
-
const
|
|
26
|
-
const fabButton = container?.querySelector('.BeaconFabButtonFrame') as HTMLElement;
|
|
148
|
+
// Fallback: find and click the FAB button
|
|
149
|
+
const fabButton = getFabButton();
|
|
27
150
|
if (fabButton) {
|
|
28
151
|
fabButton.style.display = '';
|
|
29
152
|
fabButton.click();
|
|
30
153
|
}
|
|
31
154
|
}
|
|
32
|
-
|
|
33
|
-
if (suppressMerchantButton) {
|
|
34
|
-
enviveFloatingButton.hide();
|
|
35
|
-
}
|
|
36
155
|
};
|
|
37
156
|
|
|
38
157
|
const isHelpScoutButtonEnabled = () => {
|
|
39
|
-
|
|
40
|
-
const beaconContainerEl = document.getElementById('beacon-container');
|
|
158
|
+
const beaconContainerEl = getBeaconContainer();
|
|
41
159
|
if (!beaconContainerEl) return false;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const fabButton = beaconContainerEl.querySelector('.BeaconFabButtonFrame');
|
|
45
|
-
const hasBeaconAPI = typeof (window as any).Beacon !== 'undefined';
|
|
46
|
-
|
|
160
|
+
const fabButton = beaconContainerEl.querySelector(FAB_BUTTON_SELECTOR);
|
|
161
|
+
const hasBeaconAPI = !!getBeaconApi();
|
|
47
162
|
return !!(fabButton || hasBeaconAPI);
|
|
48
163
|
};
|
|
49
164
|
|
|
50
165
|
const isSwitchEnabled = () => isHelpScoutButtonEnabled();
|
|
51
166
|
|
|
52
|
-
//
|
|
167
|
+
// Use the official HelpScout Beacon API events for the most reliable open/close
|
|
168
|
+
// detection. We use empty deps + refs so this binds exactly once per mount, not
|
|
169
|
+
// every render — important because the Beacon listener registry doesn't dedupe.
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const beacon = getBeaconApi();
|
|
172
|
+
if (!beacon) return undefined;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
beacon('on', 'open', handleBeaconOpened);
|
|
176
|
+
beacon('on', 'close', handleBeaconClosed);
|
|
177
|
+
} catch {
|
|
178
|
+
// Beacon may not be initialized yet — the MutationObserver fallback will
|
|
179
|
+
// still detect the open/close transitions below.
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return () => {
|
|
183
|
+
try {
|
|
184
|
+
beacon('off', 'open', handleBeaconOpened);
|
|
185
|
+
beacon('off', 'close', handleBeaconClosed);
|
|
186
|
+
} catch {
|
|
187
|
+
// ignore — beacon may already be torn down
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
// Hide the merchant's FAB button when the beacon container is added. Do NOT
|
|
194
|
+
// blindly re-show the Envive floating button here — if `Beacon('open')` creates
|
|
195
|
+
// the container in response to a "live support" click, this would un-hide the
|
|
196
|
+
// floating button the user just hid via toggle().
|
|
53
197
|
beaconContainer.onAdd(() => {
|
|
54
198
|
if (suppressMerchantButton) {
|
|
55
|
-
const fabButton =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
199
|
+
const fabButton = getFabButton();
|
|
200
|
+
if (fabButton) fabButton.style.display = 'none';
|
|
201
|
+
}
|
|
202
|
+
// If the beacon happens to mount in an already-open state, treat it like a
|
|
203
|
+
// proper open transition so the floating button is hidden.
|
|
204
|
+
if (isBeaconCurrentlyOpen()) {
|
|
205
|
+
handleBeaconOpened();
|
|
206
|
+
} else if (suppressMerchantButton) {
|
|
61
207
|
enviveFloatingButton.show();
|
|
62
208
|
}
|
|
63
209
|
});
|
|
64
210
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
211
|
+
// MutationObserver fallback for cases where the Beacon API events don't fire
|
|
212
|
+
// (older Beacon versions, opened via direct DOM manipulation, etc.). The logic
|
|
213
|
+
// here is broader than just checking enter/exit classes because the "Instant
|
|
214
|
+
// Answers" view can use different DOM markers.
|
|
67
215
|
beaconContainer.onChange(el => {
|
|
68
|
-
if (el
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const { classList } = beaconWidget;
|
|
75
|
-
|
|
76
|
-
// When beacon is open (has enter-done class)
|
|
77
|
-
if (
|
|
78
|
-
classList.contains('BeaconContainer-enter-done') ||
|
|
79
|
-
classList.contains('BeaconContainer-enter-active')
|
|
80
|
-
) {
|
|
81
|
-
fabButton.style.display = '';
|
|
82
|
-
enviveFloatingButton.hide();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// When beacon is closed (has exit class)
|
|
86
|
-
if (
|
|
87
|
-
classList.contains('BeaconContainer-exit') ||
|
|
88
|
-
classList.contains('BeaconContainer-exit-active')
|
|
89
|
-
) {
|
|
90
|
-
fabButton.style.display = 'none';
|
|
91
|
-
enviveFloatingButton.show();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
216
|
+
if (!el) return;
|
|
217
|
+
const open = isBeaconCurrentlyOpen();
|
|
218
|
+
if (open && !wasOpenRef.current) {
|
|
219
|
+
handleBeaconOpened();
|
|
220
|
+
} else if (!open && wasOpenRef.current) {
|
|
221
|
+
handleBeaconClosed();
|
|
94
222
|
}
|
|
95
223
|
});
|
|
96
224
|
|
|
97
|
-
// Cleanup when container is removed
|
|
225
|
+
// Cleanup when the entire container is removed.
|
|
98
226
|
beaconContainer.onRemove(() => {
|
|
99
|
-
if (
|
|
227
|
+
if (wasOpenRef.current) {
|
|
228
|
+
handleBeaconClosed();
|
|
229
|
+
} else if (suppressMerchantButton) {
|
|
100
230
|
enviveFloatingButton.show();
|
|
101
231
|
}
|
|
102
232
|
});
|
|
@@ -13,6 +13,7 @@ import { useKustomerUnifiedCXButton } from '../implementations/useKustomerUnifie
|
|
|
13
13
|
import { useHelpScoutUnifiedCXButton } from '../implementations/useHelpScoutUnifiedCXButton';
|
|
14
14
|
import { useTalkdeskUnifiedCXButton } from '../implementations/useTalkdeskUnifiedCXButton';
|
|
15
15
|
import { useEightByEightUnifiedCXButton } from '../implementations/useEightByEightUnifiedCXButton';
|
|
16
|
+
import { useFreshChatUnifiedCXButton } from '../implementations/useFreshChatUnifiedCXButton';
|
|
16
17
|
import { useDefaultUnifiedCXButton } from '../implementations/useDefaultUnifiedCXButton';
|
|
17
18
|
|
|
18
19
|
export const findCustomerServiceImpl = (
|
|
@@ -57,6 +58,9 @@ export const findCustomerServiceImpl = (
|
|
|
57
58
|
if (provider === CustomerServiceType.eightByEight) {
|
|
58
59
|
return useEightByEightUnifiedCXButton;
|
|
59
60
|
}
|
|
61
|
+
if (provider === CustomerServiceType.freshchat) {
|
|
62
|
+
return useFreshChatUnifiedCXButton;
|
|
63
|
+
}
|
|
60
64
|
|
|
61
65
|
return useDefaultUnifiedCXButton;
|
|
62
66
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
1
|
+
import { ReactNode, useRef } from 'react';
|
|
2
2
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { useCheckIsMobile } from '@envive-ai/react-toolkit-v3/utils/useCheckIsMobile';
|
|
5
|
+
import { useFocusTrap } from '@envive-ai/react-hooks/hooks/FocusTrap';
|
|
5
6
|
|
|
6
7
|
export interface FloatingChatOverlayProps {
|
|
7
8
|
children?: ReactNode;
|
|
@@ -54,6 +55,8 @@ export const FloatingChatOverlay = ({
|
|
|
54
55
|
previewMode,
|
|
55
56
|
isMobile,
|
|
56
57
|
});
|
|
58
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
59
|
+
useFocusTrap(dialogRef, isOpened && !previewMode);
|
|
57
60
|
|
|
58
61
|
const overlayClasses = classNames(
|
|
59
62
|
previewMode ? 'envive-tw-absolute' : 'envive-tw-fixed',
|
|
@@ -126,6 +129,7 @@ export const FloatingChatOverlay = ({
|
|
|
126
129
|
aria-label="Close floating chat"
|
|
127
130
|
>
|
|
128
131
|
<motion.div
|
|
132
|
+
ref={dialogRef}
|
|
129
133
|
initial={overlayExitAnimation}
|
|
130
134
|
animate={overlayAnimation}
|
|
131
135
|
exit={overlayExitAnimation}
|
|
@@ -137,6 +141,9 @@ export const FloatingChatOverlay = ({
|
|
|
137
141
|
}}
|
|
138
142
|
className="envive-tw-shadow-md"
|
|
139
143
|
onClick={e => e.stopPropagation()}
|
|
144
|
+
role="dialog"
|
|
145
|
+
aria-modal="true"
|
|
146
|
+
aria-label="Envive Chat Window"
|
|
140
147
|
>
|
|
141
148
|
{children}
|
|
142
149
|
</motion.div>
|
package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
VariantTypeEnum,
|
|
13
13
|
} from '@envive-ai/react-hooks/application/models';
|
|
14
14
|
import { variantInfoAtom } from '@envive-ai/react-hooks/atoms/app';
|
|
15
|
-
import { lastAssistantMessageAtom } from '@envive-ai/react-hooks/atoms/chat';
|
|
16
15
|
import {
|
|
17
16
|
EnviveMetricsEventName,
|
|
18
17
|
useAmplitude,
|
|
@@ -27,12 +26,13 @@ import {
|
|
|
27
26
|
PromptButtonCarouselWithImageProps,
|
|
28
27
|
} from '@envive-ai/react-toolkit-v3/PromptButtonCarouselWithImage';
|
|
29
28
|
import { usePromptCarouselAnalytics } from '@envive-ai/react-toolkit-v3/PromptCarousel';
|
|
29
|
+
import { useProductImageUrl } from '@envive-ai/react-hooks/hooks/ProductImageUrl';
|
|
30
30
|
import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
|
|
31
31
|
import { useAtomValue } from 'jotai';
|
|
32
|
-
import { useCallback
|
|
32
|
+
import { useCallback } from 'react';
|
|
33
33
|
import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
|
|
34
34
|
import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
|
|
35
|
-
import { RawValues,
|
|
35
|
+
import { RawValues, getStringIdForText } from '../utils/functions';
|
|
36
36
|
|
|
37
37
|
const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
|
|
38
38
|
const { onTypedMessageSubmitted } = useSalesAgent();
|
|
@@ -127,14 +127,7 @@ const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
|
|
|
127
127
|
const variantInfo = useAtomValue(variantInfoAtom);
|
|
128
128
|
const productId = variantInfo.variant === VariantTypeEnum.Pdp ? variantInfo.productId : undefined;
|
|
129
129
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const productImageUrls = useMemo(
|
|
133
|
-
() => (lastAssistantMessage ? getRecentProductImageUrls(lastAssistantMessage, productId) : []),
|
|
134
|
-
[lastAssistantMessage, productId],
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const productImageUrl = productImageUrls[1] ?? productImageUrls[0] ?? '';
|
|
130
|
+
const productImageUrl = useProductImageUrl(productId);
|
|
138
131
|
|
|
139
132
|
const isDynamic = promptButtonCarouselWithImageWidgetConfig?.isDynamic ?? false;
|
|
140
133
|
|