@envive-ai/react-widgets-v3 0.3.13 → 0.3.15-beta.0

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.
Files changed (143) hide show
  1. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.cjs +65 -0
  2. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +64 -0
  3. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.cjs +64 -0
  4. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.js +63 -0
  5. package/dist/CXIntegration/types.cjs +2 -0
  6. package/dist/CXIntegration/types.js +2 -0
  7. package/dist/CXIntegration/utils/functions.cjs +4 -0
  8. package/dist/CXIntegration/utils/functions.js +4 -0
  9. package/dist/hocs/withBaseWidget/types.d.cts +3 -3
  10. package/dist/hocs/withBaseWidget/types.d.ts +5 -3
  11. package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
  12. package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
  13. package/dist/hooks/dist/application/models/api/widgetText.d.cts +8 -0
  14. package/dist/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.cts +12 -0
  15. package/dist/hooks/dist/contexts/types.d.cts +38 -0
  16. package/dist/hooks/dist/contexts/typesV3.d.cts +239 -0
  17. package/dist/hooks/dist/services/amplitudeService/eventNames.d.cts +43 -0
  18. package/dist/hooks/dist/types/customerService.d.cts +21 -0
  19. package/dist/packages/hooks/dist/application/models/api/orgConfigResults.d.ts +1 -0
  20. package/dist/packages/hooks/dist/application/models/api/widgetText.d.ts +8 -0
  21. package/dist/packages/hooks/dist/application/models/frontendConfig.d.ts +1 -0
  22. package/dist/packages/hooks/dist/contexts/amplitudeContext/amplitudeContext.d.ts +2 -0
  23. package/dist/packages/hooks/dist/contexts/amplitudeContext/index.d.ts +2 -0
  24. package/dist/packages/hooks/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.ts +2 -0
  25. package/dist/packages/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.ts +14 -0
  26. package/dist/packages/hooks/dist/contexts/hardcopyContext/index.d.ts +1 -0
  27. package/dist/packages/hooks/dist/contexts/types.d.ts +42 -0
  28. package/dist/packages/hooks/dist/contexts/typesV3.d.ts +239 -0
  29. package/dist/packages/hooks/dist/services/amplitudeService/amplitudeService.d.ts +1 -0
  30. package/dist/packages/hooks/dist/services/amplitudeService/eventNames.d.ts +43 -0
  31. package/dist/packages/hooks/dist/types/customerService.d.ts +21 -0
  32. package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +3 -2
  33. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  34. package/dist/packages/widgets/dist/SearchZeroState/index.d.ts +2 -1
  35. package/dist/packages/widgets/dist/SearchZeroState/types.d.ts +2 -2
  36. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +3 -3
  37. package/dist/packages/widgets/dist/SuggestionButtonContainer/index.d.ts +2 -0
  38. package/dist/packages/widgets/dist/SuggestionButtonContainer/types.d.ts +3 -2
  39. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/api/response.d.ts +14 -0
  40. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/api/search.d.ts +15 -0
  41. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCase.d.ts +73 -0
  42. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCasedPropertiesDeep.d.ts +61 -0
  43. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/internal.d.ts +25 -0
  44. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/splitWords.d.ts +35 -0
  45. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/trim.d.ts +32 -0
  46. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/unknownArray.d.ts +32 -0
  47. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/variantInfo/variantInfo.d.ts +1 -0
  48. package/dist/packages/widgets/dist/packages/hooks/dist/atoms/search/searchAPI.d.ts +15 -0
  49. package/dist/packages/widgets/dist/packages/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.ts +1 -0
  50. package/dist/packages/widgets/dist/packages/hooks/dist/contexts/types.d.ts +63 -0
  51. package/dist/packages/widgets/dist/packages/hooks/dist/hooks/Search/useSearch.d.ts +60 -0
  52. package/dist/packages/widgets/dist/packages/hooks/dist/hooks/utils.d.ts +13 -0
  53. package/dist/packages/widgets/dist/packages/hooks/dist/types/OrgInfo.d.ts +1 -0
  54. package/dist/packages/widgets/dist/packages/hooks/dist/types/index.d.ts +1 -0
  55. package/dist/packages/widgets/dist/packages/hooks/dist/types/search-filter-types.d.ts +28 -0
  56. package/dist/packages/widgets/dist/packages/hooks/dist/types/test-types.d.ts +10 -0
  57. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  58. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  59. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
  60. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  61. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  62. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.cjs +29 -5
  63. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  64. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
  65. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.js +30 -6
  66. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
  67. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
  68. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -3
  69. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  70. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  71. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +20 -4
  72. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
  73. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  74. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  75. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
  76. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +15 -10
  77. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  78. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  79. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +16 -11
  80. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
  81. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  82. package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
  83. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  84. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  85. package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
  86. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
  87. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  88. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  89. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
  90. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  91. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  92. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -3
  93. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  94. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  95. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +24 -4
  96. package/dist/widgets/dist/SearchResults/SearchResults.d.cts +3 -2
  97. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  98. package/dist/widgets/dist/SearchZeroState/types.d.cts +2 -2
  99. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +3 -3
  100. package/dist/widgets/dist/SuggestionButtonContainer/types.d.cts +3 -2
  101. package/dist/widgets/dist/packages/hooks/dist/application/models/api/response.d.cts +14 -0
  102. package/dist/widgets/dist/packages/hooks/dist/application/models/api/search.d.cts +15 -0
  103. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCase.d.cts +73 -0
  104. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCasedPropertiesDeep.d.cts +61 -0
  105. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/internal.d.cts +25 -0
  106. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/splitWords.d.cts +35 -0
  107. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/trim.d.cts +32 -0
  108. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/unknownArray.d.cts +32 -0
  109. package/dist/widgets/dist/packages/hooks/dist/atoms/search/searchAPI.d.cts +14 -0
  110. package/dist/widgets/dist/packages/hooks/dist/contexts/types.d.cts +61 -0
  111. package/dist/widgets/dist/packages/hooks/dist/hooks/Search/useSearch.d.cts +60 -0
  112. package/dist/widgets/dist/packages/hooks/dist/hooks/utils.d.cts +12 -0
  113. package/dist/widgets/dist/packages/hooks/dist/types/search-filter-types.d.cts +28 -0
  114. package/dist/widgets/dist/packages/hooks/dist/types/test-types.d.cts +10 -0
  115. package/dist/widgets/utils/functions.cjs +9 -0
  116. package/dist/widgets/utils/functions.js +9 -1
  117. package/dist/widgets-v2/SearchZeroState/index.d.cts +2 -1
  118. package/dist/widgets-v2/SearchZeroState/index.d.ts +2 -1
  119. package/dist/widgets-v2/SuggestionButtonContainer/index.d.ts +1 -0
  120. package/package.json +1 -1
  121. package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
  122. package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
  123. package/src/CXIntegration/types.ts +2 -0
  124. package/src/CXIntegration/utils/functions.ts +8 -0
  125. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
  126. package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
  127. package/src/widgets/FloatingChatWidget/FloatingChatWidget.tsx +56 -15
  128. package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
  129. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
  130. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
  131. package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
  132. package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
  133. package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +33 -23
  134. package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
  135. package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
  136. package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
  137. package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
  138. package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
  139. package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
  140. package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
  141. package/src/widgets/__tests__/testUtils.tsx +63 -0
  142. package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
  143. package/src/widgets/utils/functions.ts +16 -0
@@ -1,15 +1,22 @@
1
- import { ReactNode, Suspense, lazy, useEffect, useMemo, useRef, useState } from 'react';
1
+ import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application/models';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ useAmplitude,
6
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
7
+ import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
2
8
  import {
3
9
  FloatingChatConfig,
4
10
  LookAndFeelConfig,
5
11
  WidgetTypeV3,
6
12
  } from '@envive-ai/react-hooks/contexts/typesV3';
13
+ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
14
+ import { useWidgetInteraction } from '@envive-ai/react-hooks/hooks/WidgetInteraction';
7
15
  import {
8
- SpiffyMetricsEventName,
9
- useAmplitude,
10
- } from '@envive-ai/react-hooks/contexts/amplitudeContext';
11
- import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
12
- import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
16
+ Collapse,
17
+ WidgetInteractionComponent,
18
+ WidgetInteractionType,
19
+ } from '@envive-ai/react-hooks/hooks/WidgetInteraction/types';
13
20
  import {
14
21
  FloatingButton,
15
22
  FloatingButtonBackgroundColor,
@@ -18,15 +25,15 @@ import {
18
25
  FloatingButtonShow,
19
26
  FloatingButtonVariant,
20
27
  } from '@envive-ai/react-toolkit-v3/FloatingButton';
21
- import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
22
- import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application/models';
28
+ import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
29
+ import { ReactNode, Suspense, lazy, useEffect, useMemo, useRef, useState } from 'react';
23
30
  import { DebugBar } from 'src/debug/debugBar';
31
+ import { useUnifiedCXButton } from '../../CXIntegration/hooks/useUnifiedCXButton';
32
+ import { CustomerServiceType } from '../../CXIntegration/types';
24
33
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
25
- import { FloatingChatOverlay } from './FloatingChatOverlay';
26
34
  import useGetWidgetStatus from '../hooks/useGetWidgetStatus';
27
- import { useUnifiedCXButton } from '../../CXIntegration/hooks/useUnifiedCXButton';
28
35
  import { FLOATING_BUTTON_ID } from './constants';
29
- import { CustomerServiceType } from '../../CXIntegration/types';
36
+ import { FloatingChatOverlay } from './FloatingChatOverlay';
30
37
  import { useFloatingButtonVisibility } from './hooks/useFloatingButtonVisibility';
31
38
  import { useAutoPopup } from './hooks/useAutoPopup';
32
39
 
@@ -74,7 +81,7 @@ interface FloatingChatWidgetHandlerProps extends BaseWidgetProps {
74
81
  const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
75
82
  const { previewButtonOnly, previewChatAlwaysOpen } = props;
76
83
  const salesAgentData = useSalesAgent();
77
-
84
+ const { trackWidgetInteraction } = useWidgetInteraction();
78
85
  const { userHasInteractedValue } = useGetWidgetStatus();
79
86
 
80
87
  // TODO: Get hardcopy
@@ -86,7 +93,7 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
86
93
  CustomerServiceType.unsupported;
87
94
 
88
95
  // TODO: Get hardcopy content
89
- const { isOpen, openChat, closeChat } = useChatToggle();
96
+ const { isOpen, openChat, closeChat, onHover } = useChatToggle();
90
97
 
91
98
  const [isCXOpen, setIsCXOpen] = useState(false);
92
99
 
@@ -95,6 +102,19 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
95
102
  if (isOpen) setIsCXOpen(false);
96
103
  }, [isOpen]);
97
104
 
105
+ const handleClose = (type: Collapse) => {
106
+ trackWidgetInteraction({
107
+ eventName: EnviveMetricsEventName.WidgetInteraction,
108
+ trigger: {
109
+ widget: WidgetInteractionComponent.FLOATING_CHAT,
110
+ widget_interaction: WidgetInteractionType.WIDGET_COLLAPSED,
111
+ widget_interaction_data: {
112
+ widget_collapsed: type,
113
+ },
114
+ },
115
+ });
116
+ };
117
+
98
118
  const { shouldShowFloatingButton } = useFloatingButtonVisibility({
99
119
  floatingButtonShowConfig: floatingButton?.showOption as FloatingButtonShow,
100
120
  isChatOpen: isOpen,
@@ -152,7 +172,12 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
152
172
  onClose={
153
173
  previewChatAlwaysOpen
154
174
  ? () => {}
155
- : () => closeChat(ChatElementDisplayLocationV3.FLOATING_CHAT_OVERLAY)
175
+ : () => {
176
+ closeChat(ChatElementDisplayLocationV3.FLOATING_CHAT_OVERLAY);
177
+ handleClose({
178
+ collapse_source: 'body_click',
179
+ });
180
+ }
156
181
  }
157
182
  previewMode={!!previewChatAlwaysOpen}
158
183
  >
@@ -167,10 +192,25 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
167
192
  isFloatingChatOpen={effectiveIsOpen}
168
193
  onToggleCXButton={toggle}
169
194
  debugBar={<DebugBar />}
195
+ onSwipeClose={
196
+ previewChatAlwaysOpen
197
+ ? () => {}
198
+ : () => {
199
+ closeChat(ChatElementDisplayLocationV3.FLOATING_CHAT_CLOSE_BUTTON);
200
+ handleClose({
201
+ collapse_source: 'swipe',
202
+ });
203
+ }
204
+ }
170
205
  onClose={
171
206
  previewChatAlwaysOpen
172
207
  ? () => {}
173
- : () => closeChat(ChatElementDisplayLocationV3.FLOATING_CHAT_CLOSE_BUTTON)
208
+ : () => {
209
+ closeChat(ChatElementDisplayLocationV3.FLOATING_CHAT_CLOSE_BUTTON);
210
+ handleClose({
211
+ collapse_source: 'close_button',
212
+ });
213
+ }
174
214
  }
175
215
  />
176
216
  </Suspense>
@@ -189,6 +229,7 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
189
229
  ? () => {}
190
230
  : () => openChat(ChatElementDisplayLocationV3.FLOATING_BUTTON)
191
231
  }
232
+ onMouseOver={onHover}
192
233
  customIcon={floatingButton?.iconSVGSrc}
193
234
  show={floatingButton?.showOption as FloatingButtonShow}
194
235
  location={floatingButton?.position as FloatingButtonLocation}
@@ -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
- (prompt: string) => {
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: prompt,
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(() => {
@@ -0,0 +1,144 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
6
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
7
+ import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
8
+ import { ProductCardWidget } from '../ProductCardWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/ProductCard', () => ({
14
+ ProductCard: ({ prompts, onSelect }: { prompts: string[]; onSelect: (text: string) => void }) => (
15
+ <div data-testid="product-card-mock">
16
+ {prompts.map(text => (
17
+ <button
18
+ key={text}
19
+ type="button"
20
+ onClick={() => onSelect(text)}
21
+ >
22
+ {text}
23
+ </button>
24
+ ))}
25
+ </div>
26
+ ),
27
+ }));
28
+
29
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
30
+ useAmplitude: () => ({
31
+ trackEvent: mockTrackEvent,
32
+ isReady: true,
33
+ }),
34
+ SpiffyMetricsEventName: {
35
+ ChatComponentVisible: 'Chat Component Visible',
36
+ SearchComponentVisible: 'Search Component Visible',
37
+ },
38
+ EnviveMetricsEventName: {
39
+ WidgetTextClicked: 'Widget Text Clicked',
40
+ },
41
+ }));
42
+
43
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
44
+ useHardcopy: () => ({
45
+ getHardcopy: mockGetHardcopy,
46
+ }),
47
+ }));
48
+
49
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
50
+ usePage: () => ({
51
+ userEvent: {
52
+ id: 'test-user-event-id',
53
+ category: UserEventCategory.PageVisit,
54
+ created_at: '2025-01-01T00:00:00Z',
55
+ event_id: 'test-event-id',
56
+ } as UserEvent,
57
+ }),
58
+ }));
59
+
60
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
61
+ useWidgetConfig: () => ({
62
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
63
+ }),
64
+ }));
65
+
66
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
67
+ useUiConfig: () => ({
68
+ getUiConfig: vi.fn().mockResolvedValue({}),
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
73
+ useSalesAgent: () => ({
74
+ onTypedMessageSubmitted: vi.fn(),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
79
+ useChatToggle: () => ({
80
+ openChat: vi.fn(),
81
+ }),
82
+ }));
83
+
84
+ describe('ProductCardWidget analytics', () => {
85
+ const defaultHardcopy = {
86
+ responseId: 'test-response-id',
87
+ language: 'en',
88
+ values: {
89
+ prompts: ['Prompt 1', 'Prompt 2', 'Prompt 3'],
90
+ },
91
+ rawValues: {
92
+ prompts: [
93
+ { id: 'id-1', value: 'Prompt 1' },
94
+ { id: 'id-2', value: 'Prompt 2' },
95
+ ],
96
+ },
97
+ };
98
+
99
+ beforeEach(() => {
100
+ vi.clearAllMocks();
101
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
102
+ });
103
+
104
+ it('should track ChatComponentVisible when widget mounts', async () => {
105
+ render(<ProductCardWidget widgetConfigId="test-config-1" />);
106
+
107
+ await waitFor(
108
+ () => {
109
+ expect(mockTrackEvent).toHaveBeenCalledWith({
110
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
111
+ eventProps: {
112
+ widget_config_id: 'test-config-1',
113
+ widget_type: WidgetTypeV3.ProductCardV3,
114
+ },
115
+ });
116
+ },
117
+ { timeout: 3000 },
118
+ );
119
+ }, 5000);
120
+
121
+ it('should track WidgetTextClicked when prompt is selected', async () => {
122
+ render(<ProductCardWidget widgetConfigId="test-config-2" />);
123
+
124
+ await waitFor(
125
+ () => {
126
+ expect(screen.getByText('Prompt 1')).toBeInTheDocument();
127
+ },
128
+ { timeout: 3000 },
129
+ );
130
+
131
+ mockTrackEvent.mockClear();
132
+
133
+ screen.getByText('Prompt 1').click();
134
+
135
+ expect(mockTrackEvent).toHaveBeenCalledWith({
136
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
137
+ eventProps: {
138
+ response_id: 'test-response-id',
139
+ string_id: 'id-1',
140
+ text: 'Prompt 1',
141
+ },
142
+ });
143
+ }, 5000);
144
+ });
@@ -8,6 +8,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
8
8
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
9
9
  import { useCallback, useEffect, useMemo } from 'react';
10
10
  import {
11
+ EnviveMetricsEventName,
11
12
  SpiffyMetricsEventName,
12
13
  useAmplitude,
13
14
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -24,7 +25,7 @@ import { useAtomValue } from 'jotai';
24
25
  import { variantInfoAtom } from '@envive-ai/react-hooks/atoms/app';
25
26
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
26
27
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
27
- import { getRecentProductImageUrls } from '../utils/functions';
28
+ import { RawValues, getRecentProductImageUrls, getStringIdForText } from '../utils/functions';
28
29
 
29
30
  const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
30
31
  const { onTypedMessageSubmitted } = useSalesAgent();
@@ -60,6 +61,16 @@ const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
60
61
 
61
62
  const handlePromptButtonClick = useCallback(
62
63
  (text: string) => {
64
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
65
+ const stringId = getStringIdForText(rawValues, text);
66
+ trackEvent({
67
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
68
+ eventProps: {
69
+ response_id: hardcopyContent?.responseId,
70
+ string_id: stringId,
71
+ text,
72
+ },
73
+ });
63
74
  onTypedMessageSubmitted({
64
75
  query: text,
65
76
  userTyped: false,
@@ -0,0 +1,179 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
6
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
7
+ import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
8
+ import { PromptButtonCarouselWithImageWidget } from '../PromptButtonCarouselWithImageWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/PromptButtonCarouselWithImage', () => ({
14
+ PromptButtonCarouselWithImage: ({
15
+ promptButtonsTexts = [],
16
+ handlePromptButtonClick,
17
+ }: {
18
+ promptButtonsTexts?: string[];
19
+ handlePromptButtonClick: (text: string) => void;
20
+ }) => (
21
+ <div data-testid="prompt-button-carousel-mock">
22
+ {(promptButtonsTexts ?? []).map(text => (
23
+ <button
24
+ key={text}
25
+ type="button"
26
+ onClick={() => handlePromptButtonClick(text)}
27
+ >
28
+ {text}
29
+ </button>
30
+ ))}
31
+ </div>
32
+ ),
33
+ }));
34
+
35
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
36
+ useAmplitude: () => ({
37
+ trackEvent: mockTrackEvent,
38
+ isReady: true,
39
+ }),
40
+ SpiffyMetricsEventName: {
41
+ ChatComponentVisible: 'Chat Component Visible',
42
+ SearchComponentVisible: 'Search Component Visible',
43
+ },
44
+ EnviveMetricsEventName: {
45
+ WidgetTextClicked: 'Widget Text Clicked',
46
+ },
47
+ }));
48
+
49
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
50
+ useHardcopy: () => ({
51
+ getHardcopy: mockGetHardcopy,
52
+ }),
53
+ }));
54
+
55
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
56
+ usePage: () => ({
57
+ userEvent: {
58
+ id: 'test-user-event-id',
59
+ category: UserEventCategory.PageVisit,
60
+ created_at: '2025-01-01T00:00:00Z',
61
+ event_id: 'test-event-id',
62
+ } as UserEvent,
63
+ }),
64
+ }));
65
+
66
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
67
+ useWidgetConfig: () => ({
68
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
73
+ useUiConfig: () => ({
74
+ getUiConfig: vi.fn().mockResolvedValue({}),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
79
+ useSalesAgent: () => ({
80
+ onTypedMessageSubmitted: vi.fn(),
81
+ }),
82
+ }));
83
+
84
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
85
+ useChatToggle: () => ({
86
+ openChat: vi.fn(),
87
+ }),
88
+ }));
89
+
90
+ vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
91
+ lastAssistantMessageAtom: {},
92
+ }));
93
+
94
+ vi.mock('@envive-ai/react-hooks/atoms/app', () => ({
95
+ variantInfoAtom: {},
96
+ }));
97
+
98
+ vi.mock('jotai', async importOriginal => {
99
+ const actual = await importOriginal();
100
+ const variantInfo = {
101
+ variant: 'PageVisit',
102
+ productId: undefined,
103
+ plpId: undefined,
104
+ url: '',
105
+ pageVisitCategory: '',
106
+ };
107
+ const useAtomValueMock = vi.fn();
108
+ let callCount = 0;
109
+ useAtomValueMock.mockImplementation(() => {
110
+ callCount += 1;
111
+ return callCount % 2 === 0 ? [] : variantInfo;
112
+ });
113
+ return {
114
+ ...actual,
115
+ useAtomValue: useAtomValueMock,
116
+ };
117
+ });
118
+
119
+ describe('PromptButtonCarouselWithImageWidget analytics', () => {
120
+ const defaultHardcopy = {
121
+ responseId: 'test-response-id',
122
+ language: 'en',
123
+ values: {
124
+ promptButtonsTexts: ['Button 1', 'Button 2', 'Button 3'],
125
+ },
126
+ rawValues: {
127
+ promptButtonsTexts: [
128
+ { id: 'id-1', value: 'Button 1' },
129
+ { id: 'id-2', value: 'Button 2' },
130
+ ],
131
+ },
132
+ };
133
+
134
+ beforeEach(() => {
135
+ vi.clearAllMocks();
136
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
137
+ });
138
+
139
+ it('should track ChatComponentVisible when widget mounts', async () => {
140
+ render(<PromptButtonCarouselWithImageWidget widgetConfigId="test-config-1" />);
141
+
142
+ await waitFor(
143
+ () => {
144
+ expect(mockTrackEvent).toHaveBeenCalledWith({
145
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
146
+ eventProps: {
147
+ widget_config_id: 'test-config-1',
148
+ widget_type: WidgetTypeV3.PromptButtonCarouselWithImageV3,
149
+ },
150
+ });
151
+ },
152
+ { timeout: 3000 },
153
+ );
154
+ }, 5000);
155
+
156
+ it('should track WidgetTextClicked when button is clicked', async () => {
157
+ render(<PromptButtonCarouselWithImageWidget widgetConfigId="test-config-2" />);
158
+
159
+ await waitFor(
160
+ () => {
161
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
162
+ },
163
+ { timeout: 3000 },
164
+ );
165
+
166
+ mockTrackEvent.mockClear();
167
+
168
+ screen.getByText('Button 1').click();
169
+
170
+ expect(mockTrackEvent).toHaveBeenCalledWith({
171
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
172
+ eventProps: {
173
+ response_id: 'test-response-id',
174
+ string_id: 'id-1',
175
+ text: 'Button 1',
176
+ },
177
+ });
178
+ }, 5000);
179
+ });