@envive-ai/react-widgets-v3 0.3.13 → 0.3.14
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/implementations/useHelpScoutUnifiedCXButton.cjs +65 -0
- package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +64 -0
- package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.cjs +64 -0
- package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.js +63 -0
- package/dist/CXIntegration/types.cjs +2 -0
- package/dist/CXIntegration/types.js +2 -0
- package/dist/CXIntegration/utils/functions.cjs +4 -0
- package/dist/CXIntegration/utils/functions.js +4 -0
- package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
- package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
- 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/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
- package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -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 +20 -4
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +2 -9
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +1 -8
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
- package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -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 +24 -4
- package/dist/widgets/dist/SearchResults/SearchResults.d.cts +2 -2
- package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
- package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
- package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
- package/dist/widgets/utils/functions.cjs +9 -0
- package/dist/widgets/utils/functions.js +9 -1
- package/package.json +1 -1
- package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
- package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
- package/src/CXIntegration/types.ts +2 -0
- package/src/CXIntegration/utils/functions.ts +8 -0
- package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
- package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
- package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
- package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
- package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
- package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
- package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
- package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -14
- package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
- package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
- package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
- package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
- package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
- package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
- package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
- package/src/widgets/__tests__/testUtils.tsx +63 -0
- package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
- package/src/widgets/utils/functions.ts +16 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime3 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_runtime3.JSX.Element;
|
|
6
6
|
//#endregion
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region ../widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts
|
|
4
4
|
//#region src/SearchZeroState/SearchZeroStateWidget.d.ts
|
|
@@ -11,7 +11,7 @@ declare const SearchZeroStateWidget: ({
|
|
|
11
11
|
initialIsOpen,
|
|
12
12
|
widgetConfigId,
|
|
13
13
|
entryPointRef
|
|
14
|
-
}: SearchZeroStateWidgetProps) =>
|
|
14
|
+
}: SearchZeroStateWidgetProps) => react_jsx_runtime0.JSX.Element;
|
|
15
15
|
//#endregion
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SuggestionBarLocationForMetrics } from "./types.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime0 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_runtime0.JSX.Element;
|
|
34
34
|
//#endregion
|
|
35
35
|
//#endregion
|
|
36
36
|
export { SuggestionBar };
|
|
@@ -44,11 +44,20 @@ const isProductComparison = (lastMsg) => {
|
|
|
44
44
|
if (lastMsg) return lastMsg?.filter((msg) => msg.type === __envive_ai_react_hooks_application_models.MessageType.Product).length === 2;
|
|
45
45
|
return (0, __envive_ai_react_hooks_atoms_atomStore.getAtomStore)().get(__envive_ai_react_hooks_atoms_chat.lastAssistantMessageAtom)?.filter((msg) => msg.type === __envive_ai_react_hooks_application_models.MessageType.Product).length === 2;
|
|
46
46
|
};
|
|
47
|
+
/** Finds the string_id (id) from raw widget text values for the given display text */
|
|
48
|
+
const getStringIdForText = (rawValues, text) => {
|
|
49
|
+
if (!rawValues) return void 0;
|
|
50
|
+
for (const raw of Object.values(rawValues)) {
|
|
51
|
+
const found = (Array.isArray(raw) ? raw : [raw]).find((s) => s.value === text);
|
|
52
|
+
if (found) return found.id;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
47
55
|
|
|
48
56
|
//#endregion
|
|
49
57
|
exports.getMessageText = getMessageText;
|
|
50
58
|
exports.getProductImageUrl = getProductImageUrl;
|
|
51
59
|
exports.getRecentProductImageUrls = getRecentProductImageUrls;
|
|
60
|
+
exports.getStringIdForText = getStringIdForText;
|
|
52
61
|
exports.isLoading = isLoading;
|
|
53
62
|
exports.isProductComparison = isProductComparison;
|
|
54
63
|
exports.userHasInteracted = userHasInteracted;
|
|
@@ -43,6 +43,14 @@ const isProductComparison = (lastMsg) => {
|
|
|
43
43
|
if (lastMsg) return lastMsg?.filter((msg) => msg.type === MessageType.Product).length === 2;
|
|
44
44
|
return getAtomStore().get(lastAssistantMessageAtom)?.filter((msg) => msg.type === MessageType.Product).length === 2;
|
|
45
45
|
};
|
|
46
|
+
/** Finds the string_id (id) from raw widget text values for the given display text */
|
|
47
|
+
const getStringIdForText = (rawValues, text) => {
|
|
48
|
+
if (!rawValues) return void 0;
|
|
49
|
+
for (const raw of Object.values(rawValues)) {
|
|
50
|
+
const found = (Array.isArray(raw) ? raw : [raw]).find((s) => s.value === text);
|
|
51
|
+
if (found) return found.id;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
46
54
|
|
|
47
55
|
//#endregion
|
|
48
|
-
export { getMessageText, getProductImageUrl, getRecentProductImageUrls, isLoading, isProductComparison, userHasInteracted, userHasNotInteracted };
|
|
56
|
+
export { getMessageText, getProductImageUrl, getRecentProductImageUrls, getStringIdForText, isLoading, isProductComparison, userHasInteracted, userHasNotInteracted };
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { SelectorFactory } from '@envive-ai/react-hooks/application/utils';
|
|
2
|
+
import { useElementObserver } from '@envive-ai/react-hooks/hooks/ElementObserver';
|
|
3
|
+
import { FLOATING_BUTTON_ID } from '../../widgets/FloatingChatWidget/constants';
|
|
4
|
+
import { CustomerServiceImplProps, UnifiedCXButton } from '../types';
|
|
5
|
+
|
|
6
|
+
const BEACON_CONTAINER_ID = 'beacon-container';
|
|
7
|
+
|
|
8
|
+
interface UseHelpScoutUnifiedCXButtonProps extends CustomerServiceImplProps {}
|
|
9
|
+
|
|
10
|
+
export const useHelpScoutUnifiedCXButton = ({
|
|
11
|
+
onSwitchToAgent,
|
|
12
|
+
suppressMerchantButton,
|
|
13
|
+
}: UseHelpScoutUnifiedCXButtonProps): UnifiedCXButton => {
|
|
14
|
+
const beaconContainer = useElementObserver(SelectorFactory.id(BEACON_CONTAINER_ID));
|
|
15
|
+
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
16
|
+
|
|
17
|
+
const toggle = () => {
|
|
18
|
+
onSwitchToAgent();
|
|
19
|
+
|
|
20
|
+
// Try using the Help Scout Beacon API first
|
|
21
|
+
if (typeof (window as any).Beacon !== 'undefined') {
|
|
22
|
+
(window as any).Beacon('open');
|
|
23
|
+
} else {
|
|
24
|
+
// Fallback: Find and click the FAB button
|
|
25
|
+
const container = document.getElementById(BEACON_CONTAINER_ID);
|
|
26
|
+
const fabButton = container?.querySelector('.BeaconFabButtonFrame') as HTMLElement;
|
|
27
|
+
if (fabButton) {
|
|
28
|
+
fabButton.style.display = '';
|
|
29
|
+
fabButton.click();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (suppressMerchantButton) {
|
|
34
|
+
enviveFloatingButton.hide();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const isHelpScoutButtonEnabled = () => {
|
|
39
|
+
// Check for the main Help Scout Beacon container
|
|
40
|
+
const beaconContainerEl = document.getElementById('beacon-container');
|
|
41
|
+
if (!beaconContainerEl) return false;
|
|
42
|
+
|
|
43
|
+
// Check for the FAB button (Floating Action Button) or the global Beacon API
|
|
44
|
+
const fabButton = beaconContainerEl.querySelector('.BeaconFabButtonFrame');
|
|
45
|
+
const hasBeaconAPI = typeof (window as any).Beacon !== 'undefined';
|
|
46
|
+
|
|
47
|
+
return !!(fabButton || hasBeaconAPI);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const isSwitchEnabled = () => isHelpScoutButtonEnabled();
|
|
51
|
+
|
|
52
|
+
// Hide Help Scout FAB button when beacon container is added
|
|
53
|
+
beaconContainer.onAdd(() => {
|
|
54
|
+
if (suppressMerchantButton) {
|
|
55
|
+
const fabButton = document
|
|
56
|
+
.getElementById(BEACON_CONTAINER_ID)
|
|
57
|
+
?.querySelector('.BeaconFabButtonFrame') as HTMLElement;
|
|
58
|
+
if (fabButton) {
|
|
59
|
+
fabButton.style.display = 'none';
|
|
60
|
+
}
|
|
61
|
+
enviveFloatingButton.show();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Observe the beacon container for visibility changes
|
|
66
|
+
// Help Scout Beacon adds/removes classes to show/hide the widget
|
|
67
|
+
beaconContainer.onChange(el => {
|
|
68
|
+
if (el && suppressMerchantButton) {
|
|
69
|
+
// Check if the BeaconContainer widget is open
|
|
70
|
+
const beaconWidget = el.querySelector('.BeaconContainer');
|
|
71
|
+
const fabButton = el.querySelector('.BeaconFabButtonFrame') as HTMLElement;
|
|
72
|
+
|
|
73
|
+
if (beaconWidget && fabButton) {
|
|
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
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Cleanup when container is removed
|
|
98
|
+
beaconContainer.onRemove(() => {
|
|
99
|
+
if (suppressMerchantButton) {
|
|
100
|
+
enviveFloatingButton.show();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
toggle,
|
|
106
|
+
isSwitchEnabled,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SelectorFactory } from '@envive-ai/react-hooks/application/utils';
|
|
2
|
+
import { useElementObserver } from '@envive-ai/react-hooks/hooks/ElementObserver';
|
|
3
|
+
import { FLOATING_BUTTON_ID } from '../../widgets/FloatingChatWidget/constants';
|
|
4
|
+
import { CustomerServiceImplProps, UnifiedCXButton } from '../types';
|
|
5
|
+
|
|
6
|
+
interface UseTalkdeskUnifiedCXButtonProps extends CustomerServiceImplProps {}
|
|
7
|
+
|
|
8
|
+
export const useTalkdeskUnifiedCXButton = ({
|
|
9
|
+
onSwitchToAgent,
|
|
10
|
+
suppressMerchantButton,
|
|
11
|
+
}: UseTalkdeskUnifiedCXButtonProps): UnifiedCXButton => {
|
|
12
|
+
const talkdeskButton = useElementObserver(SelectorFactory.id('talkdesk-chat-widget-trigger'));
|
|
13
|
+
const talkdeskWidget = useElementObserver(SelectorFactory.id('talkdesk-chat-widget'));
|
|
14
|
+
const talkdeskContainer = useElementObserver(SelectorFactory.id('tdWebchat'));
|
|
15
|
+
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
16
|
+
|
|
17
|
+
const toggle = () => {
|
|
18
|
+
onSwitchToAgent();
|
|
19
|
+
if (suppressMerchantButton) {
|
|
20
|
+
enviveFloatingButton.hide();
|
|
21
|
+
}
|
|
22
|
+
talkdeskButton.show();
|
|
23
|
+
talkdeskButton.fire('click');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isTalkdeskButtonEnabled = () => {
|
|
27
|
+
// Check for the main Talkdesk container
|
|
28
|
+
const talkdeskMainContainer = document.getElementById('tdWebchat');
|
|
29
|
+
if (!talkdeskMainContainer) return false;
|
|
30
|
+
|
|
31
|
+
// Check for the trigger button (it's directly in the DOM, not inside an iframe)
|
|
32
|
+
const talkdeskTriggerButton = document.getElementById('talkdesk-chat-widget-trigger');
|
|
33
|
+
return !!talkdeskTriggerButton;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isSwitchEnabled = () => isTalkdeskButtonEnabled();
|
|
37
|
+
|
|
38
|
+
// Hide Talkdesk button when it's added to DOM (if suppressMerchantButton is enabled)
|
|
39
|
+
talkdeskButton.onAdd(() => {
|
|
40
|
+
if (suppressMerchantButton) {
|
|
41
|
+
talkdeskButton.hide();
|
|
42
|
+
enviveFloatingButton.show();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Observe the widget container (NOT the button) to avoid infinite loops
|
|
47
|
+
talkdeskWidget.onAdd(el => {
|
|
48
|
+
if (el && suppressMerchantButton) {
|
|
49
|
+
const ariaHidden = el.getAttribute('aria-hidden');
|
|
50
|
+
|
|
51
|
+
// Widget is open initially (aria-hidden="false")
|
|
52
|
+
if (ariaHidden === 'false') {
|
|
53
|
+
talkdeskButton.show();
|
|
54
|
+
enviveFloatingButton.hide();
|
|
55
|
+
} else {
|
|
56
|
+
// Widget is closed initially
|
|
57
|
+
talkdeskButton.hide();
|
|
58
|
+
enviveFloatingButton.show();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Track widget visibility changes
|
|
64
|
+
talkdeskWidget.onChange(el => {
|
|
65
|
+
if (el && suppressMerchantButton) {
|
|
66
|
+
const ariaHidden = el.getAttribute('aria-hidden');
|
|
67
|
+
|
|
68
|
+
// Widget is open (aria-hidden="false")
|
|
69
|
+
if (ariaHidden === 'false') {
|
|
70
|
+
talkdeskButton.show();
|
|
71
|
+
enviveFloatingButton.hide();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Widget is closed (aria-hidden="true")
|
|
75
|
+
if (ariaHidden === 'true') {
|
|
76
|
+
talkdeskButton.hide();
|
|
77
|
+
enviveFloatingButton.show();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Cleanup when container is removed
|
|
83
|
+
talkdeskContainer.onRemove(() => {
|
|
84
|
+
if (suppressMerchantButton) {
|
|
85
|
+
talkdeskButton.hide();
|
|
86
|
+
enviveFloatingButton.show();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
toggle,
|
|
92
|
+
isSwitchEnabled,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -10,6 +10,8 @@ import { useReDoUnifiedCXButton } from '../implementations/useReDoUnifiedCXButto
|
|
|
10
10
|
import { useRichpanelUnifiedCXButton } from '../implementations/useRichpanelUnifiedCXButton';
|
|
11
11
|
import { useZendeskUnifiedCXButton } from '../implementations/useZendeskUnifiedCXButton';
|
|
12
12
|
import { useKustomerUnifiedCXButton } from '../implementations/useKustomerUnifiedCXButton';
|
|
13
|
+
import { useHelpScoutUnifiedCXButton } from '../implementations/useHelpScoutUnifiedCXButton';
|
|
14
|
+
import { useTalkdeskUnifiedCXButton } from '../implementations/useTalkdeskUnifiedCXButton';
|
|
13
15
|
import { useEightByEightUnifiedCXButton } from '../implementations/useEightByEightUnifiedCXButton';
|
|
14
16
|
import { useDefaultUnifiedCXButton } from '../implementations/useDefaultUnifiedCXButton';
|
|
15
17
|
|
|
@@ -46,6 +48,12 @@ export const findCustomerServiceImpl = (
|
|
|
46
48
|
if (provider === CustomerServiceType.zendesk) {
|
|
47
49
|
return useZendeskUnifiedCXButton;
|
|
48
50
|
}
|
|
51
|
+
if (provider === CustomerServiceType.helpscout) {
|
|
52
|
+
return useHelpScoutUnifiedCXButton;
|
|
53
|
+
}
|
|
54
|
+
if (provider === CustomerServiceType.talkdesk) {
|
|
55
|
+
return useTalkdeskUnifiedCXButton;
|
|
56
|
+
}
|
|
49
57
|
if (provider === CustomerServiceType.eightByEight) {
|
|
50
58
|
return useEightByEightUnifiedCXButton;
|
|
51
59
|
}
|
|
@@ -66,7 +66,7 @@ class MockIntersectionObserver implements IntersectionObserver {
|
|
|
66
66
|
const mockTrackEvent = vi.fn();
|
|
67
67
|
const mockGetHardcopy = vi.fn();
|
|
68
68
|
|
|
69
|
-
vi.mock('
|
|
69
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
70
70
|
useAmplitude: () => ({
|
|
71
71
|
trackEvent: mockTrackEvent,
|
|
72
72
|
isReady: true,
|
|
@@ -77,13 +77,13 @@ vi.mock('src/contexts/amplitudeContext/amplitudeContext', () => ({
|
|
|
77
77
|
},
|
|
78
78
|
}));
|
|
79
79
|
|
|
80
|
-
vi.mock('
|
|
80
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
81
81
|
useHardcopy: () => ({
|
|
82
82
|
getHardcopy: mockGetHardcopy,
|
|
83
83
|
}),
|
|
84
84
|
}));
|
|
85
85
|
|
|
86
|
-
vi.mock('
|
|
86
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
87
87
|
usePage: () => ({
|
|
88
88
|
userEvent: {
|
|
89
89
|
id: 'test-user-event-id',
|
|
@@ -94,6 +94,18 @@ vi.mock('src/contexts/pageContext', () => ({
|
|
|
94
94
|
}),
|
|
95
95
|
}));
|
|
96
96
|
|
|
97
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
98
|
+
useWidgetConfig: () => ({
|
|
99
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
100
|
+
}),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
104
|
+
useUiConfig: () => ({
|
|
105
|
+
getUiConfig: vi.fn().mockResolvedValue({}),
|
|
106
|
+
}),
|
|
107
|
+
}));
|
|
108
|
+
|
|
97
109
|
// Test widget component
|
|
98
110
|
interface TestWidgetProps extends BaseWidgetProps {
|
|
99
111
|
testId?: string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
3
|
+
import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
|
|
4
|
+
import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
5
|
+
import { ChatPreviewWidget } from '../ChatPreviewWidget';
|
|
6
|
+
|
|
7
|
+
const mockTrackEvent = vi.fn();
|
|
8
|
+
const mockGetHardcopy = vi.fn();
|
|
9
|
+
|
|
10
|
+
vi.mock('@envive-ai/react-toolkit-v3/ChatPreview', () => ({
|
|
11
|
+
ChatPreview: () => <div data-testid="chat-preview-mock">Chat Preview</div>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
15
|
+
useAmplitude: () => ({
|
|
16
|
+
trackEvent: mockTrackEvent,
|
|
17
|
+
isReady: true,
|
|
18
|
+
}),
|
|
19
|
+
SpiffyMetricsEventName: {
|
|
20
|
+
ChatComponentVisible: 'Chat Component Visible',
|
|
21
|
+
SearchComponentVisible: 'Search Component Visible',
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
26
|
+
useHardcopy: () => ({
|
|
27
|
+
getHardcopy: mockGetHardcopy,
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
32
|
+
usePage: () => ({
|
|
33
|
+
userEvent: {
|
|
34
|
+
id: 'test-user-event-id',
|
|
35
|
+
category: UserEventCategory.PageVisit,
|
|
36
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
37
|
+
event_id: 'test-event-id',
|
|
38
|
+
} as UserEvent,
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
43
|
+
useWidgetConfig: () => ({
|
|
44
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
49
|
+
useUiConfig: () => ({
|
|
50
|
+
getUiConfig: vi.fn().mockResolvedValue({}),
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
|
|
55
|
+
useSalesAgent: () => ({
|
|
56
|
+
onSuggestionClicked: vi.fn(),
|
|
57
|
+
}),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
|
|
61
|
+
useChatToggle: () => ({
|
|
62
|
+
openChat: vi.fn(),
|
|
63
|
+
}),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
|
|
67
|
+
chatAtom: {},
|
|
68
|
+
lastAssistantMessageAtom: {},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock('jotai', async importOriginal => {
|
|
72
|
+
const actual = await importOriginal();
|
|
73
|
+
return {
|
|
74
|
+
...actual,
|
|
75
|
+
useAtomValue: vi
|
|
76
|
+
.fn()
|
|
77
|
+
.mockReturnValueOnce([])
|
|
78
|
+
.mockReturnValueOnce({ messages: [], suggestions: [] })
|
|
79
|
+
.mockReturnValue([]),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('ChatPreviewWidget analytics', () => {
|
|
84
|
+
const defaultHardcopy = {
|
|
85
|
+
responseId: 'test-response-id',
|
|
86
|
+
language: 'en',
|
|
87
|
+
values: {
|
|
88
|
+
titleLabel: 'Chat Preview',
|
|
89
|
+
textFieldPlaceholderText: 'Type here',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.clearAllMocks();
|
|
95
|
+
mockGetHardcopy.mockResolvedValue(defaultHardcopy);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should track ChatComponentVisible when widget mounts', async () => {
|
|
99
|
+
render(<ChatPreviewWidget widgetConfigId="test-config-1" />);
|
|
100
|
+
|
|
101
|
+
await waitFor(
|
|
102
|
+
() => {
|
|
103
|
+
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
104
|
+
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
105
|
+
eventProps: {
|
|
106
|
+
widget_config_id: 'test-config-1',
|
|
107
|
+
widget_type: WidgetTypeV3.ChatPreviewV3,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
{ timeout: 3000 },
|
|
112
|
+
);
|
|
113
|
+
}, 5000);
|
|
114
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
3
|
+
import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
|
|
4
|
+
import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
5
|
+
import { FloatingChatWidget } from '../FloatingChatWidget';
|
|
6
|
+
|
|
7
|
+
const mockTrackEvent = vi.fn();
|
|
8
|
+
const mockGetHardcopy = vi.fn();
|
|
9
|
+
|
|
10
|
+
vi.mock('@envive-ai/react-toolkit-v3/FloatingButton', () => ({
|
|
11
|
+
FloatingButton: () => <div data-testid="floating-button">Floating Button</div>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
15
|
+
useAmplitude: () => ({
|
|
16
|
+
trackEvent: mockTrackEvent,
|
|
17
|
+
isReady: true,
|
|
18
|
+
}),
|
|
19
|
+
SpiffyMetricsEventName: {
|
|
20
|
+
ChatComponentVisible: 'Chat Component Visible',
|
|
21
|
+
SearchComponentVisible: 'Search Component Visible',
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
26
|
+
useHardcopy: () => ({
|
|
27
|
+
getHardcopy: mockGetHardcopy,
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
32
|
+
usePage: () => ({
|
|
33
|
+
userEvent: {
|
|
34
|
+
id: 'test-user-event-id',
|
|
35
|
+
category: UserEventCategory.PageVisit,
|
|
36
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
37
|
+
event_id: 'test-event-id',
|
|
38
|
+
} as UserEvent,
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
43
|
+
useWidgetConfig: () => ({
|
|
44
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
49
|
+
useUiConfig: () => ({
|
|
50
|
+
getUiConfig: vi.fn().mockResolvedValue({
|
|
51
|
+
floatingButton: { showOption: 'always', position: 'bottom-right' },
|
|
52
|
+
floatingChat: {},
|
|
53
|
+
lookAndFeel: {},
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
|
|
59
|
+
useSalesAgent: () => ({}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
|
|
63
|
+
useChatToggle: () => ({
|
|
64
|
+
isOpen: false,
|
|
65
|
+
openChat: vi.fn(),
|
|
66
|
+
closeChat: vi.fn(),
|
|
67
|
+
}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
vi.mock('../hooks/useFloatingButtonVisibility', () => ({
|
|
71
|
+
useFloatingButtonVisibility: () => ({
|
|
72
|
+
shouldShowFloatingButton: true,
|
|
73
|
+
}),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
vi.mock('../hooks/useAutoPopup', () => ({
|
|
77
|
+
useAutoPopup: () => {},
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
vi.mock('../../hooks/useGetWidgetStatus', () => ({
|
|
81
|
+
default: () => ({
|
|
82
|
+
userHasInteractedValue: false,
|
|
83
|
+
}),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
vi.mock('src/debug/debugBar', () => ({
|
|
87
|
+
DebugBar: () => null,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
vi.mock('../../CXIntegration/hooks/useUnifiedCXButton', () => ({
|
|
91
|
+
useUnifiedCXButton: () => ({
|
|
92
|
+
isSwitchEnabled: () => false,
|
|
93
|
+
toggle: vi.fn(),
|
|
94
|
+
}),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
describe('FloatingChatWidget analytics', () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
vi.clearAllMocks();
|
|
100
|
+
mockGetHardcopy.mockResolvedValue({ language: 'en', values: {} });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should track ChatComponentVisible when floating button is shown', async () => {
|
|
104
|
+
render(<FloatingChatWidget previewButtonOnly />);
|
|
105
|
+
|
|
106
|
+
await waitFor(
|
|
107
|
+
() => {
|
|
108
|
+
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
109
|
+
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
110
|
+
eventProps: {
|
|
111
|
+
widget_config_id: 'floating-button',
|
|
112
|
+
widget_type: WidgetTypeV3.FloatingButtonV3,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
{ timeout: 3000 },
|
|
117
|
+
);
|
|
118
|
+
}, 5000);
|
|
119
|
+
});
|
|
@@ -2,6 +2,7 @@ import { ProductCardWidgetV3Config, WidgetTypeV3 } from '@envive-ai/react-hooks/
|
|
|
2
2
|
import { PromptButtonVariant } from '@envive-ai/react-toolkit-v3/PromptButton/types';
|
|
3
3
|
import { useCallback, useEffect } from 'react';
|
|
4
4
|
import {
|
|
5
|
+
EnviveMetricsEventName,
|
|
5
6
|
SpiffyMetricsEventName,
|
|
6
7
|
useAmplitude,
|
|
7
8
|
} from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
@@ -11,6 +12,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
|
|
|
11
12
|
import { ProductCard } from '@envive-ai/react-toolkit-v3/ProductCard';
|
|
12
13
|
import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
|
|
13
14
|
import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
|
|
15
|
+
import { RawValues, getStringIdForText } from '../utils/functions';
|
|
14
16
|
|
|
15
17
|
const mockPrompts = [
|
|
16
18
|
'Loading prompt 1',
|
|
@@ -63,15 +65,25 @@ const ProductCardWidgetHandler = (props: BaseWidgetProps) => {
|
|
|
63
65
|
}, [trackEvent, widgetConfigId]);
|
|
64
66
|
|
|
65
67
|
const handleSelect = useCallback(
|
|
66
|
-
(
|
|
68
|
+
(text: string) => {
|
|
69
|
+
const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
|
|
70
|
+
const stringId = getStringIdForText(rawValues, text);
|
|
71
|
+
trackEvent({
|
|
72
|
+
eventName: EnviveMetricsEventName.WidgetTextClicked,
|
|
73
|
+
eventProps: {
|
|
74
|
+
response_id: hardcopyContent?.responseId,
|
|
75
|
+
string_id: stringId,
|
|
76
|
+
text,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
67
79
|
onTypedMessageSubmitted({
|
|
68
|
-
query:
|
|
80
|
+
query: text,
|
|
69
81
|
userTyped: false,
|
|
70
82
|
displayLocation: ChatElementDisplayLocationV3.PRODUCT_CARD_PROMPT_BUTTON,
|
|
71
83
|
});
|
|
72
84
|
openChat(ChatElementDisplayLocationV3.PRODUCT_CARD_PROMPT_BUTTON);
|
|
73
85
|
},
|
|
74
|
-
[onTypedMessageSubmitted, openChat],
|
|
86
|
+
[hardcopyContent, onTypedMessageSubmitted, openChat, trackEvent],
|
|
75
87
|
);
|
|
76
88
|
|
|
77
89
|
const handleInputClick = useCallback(() => {
|