@envive-ai/react-widgets-v3 0.3.12 → 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.
Files changed (91) hide show
  1. package/dist/CXIntegration/implementations/useEightByEightUnifiedCXButton.cjs +54 -0
  2. package/dist/CXIntegration/implementations/useEightByEightUnifiedCXButton.js +53 -0
  3. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.cjs +65 -0
  4. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +64 -0
  5. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.cjs +64 -0
  6. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.js +63 -0
  7. package/dist/CXIntegration/implementations/useZendeskUnifiedCXButton.cjs +2 -2
  8. package/dist/CXIntegration/implementations/useZendeskUnifiedCXButton.js +2 -2
  9. package/dist/CXIntegration/types.cjs +3 -0
  10. package/dist/CXIntegration/types.js +3 -0
  11. package/dist/CXIntegration/utils/functions.cjs +6 -0
  12. package/dist/CXIntegration/utils/functions.js +6 -0
  13. package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
  14. package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +2 -2
  15. package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
  16. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  17. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
  18. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  19. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
  20. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  21. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  22. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  23. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  24. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
  25. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
  26. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
  27. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -3
  28. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  29. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  30. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +20 -4
  31. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
  32. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  33. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  34. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
  35. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +2 -9
  36. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  37. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  38. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +1 -8
  39. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.cjs +8 -8
  40. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
  41. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  42. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.js +8 -8
  43. package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
  44. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  45. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  46. package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
  47. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
  48. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  49. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  50. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
  51. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.cjs +8 -8
  52. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  53. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  54. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.js +8 -8
  55. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -3
  56. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  57. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  58. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +24 -4
  59. package/dist/widgets/dist/SearchResults/SearchResults.d.cts +2 -2
  60. package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
  61. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  62. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
  63. package/dist/widgets/utils/functions.cjs +9 -0
  64. package/dist/widgets/utils/functions.js +9 -1
  65. package/package.json +1 -1
  66. package/src/CXIntegration/implementations/useEightByEightUnifiedCXButton.ts +91 -0
  67. package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
  68. package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
  69. package/src/CXIntegration/implementations/useZendeskUnifiedCXButton.ts +4 -2
  70. package/src/CXIntegration/types.ts +3 -0
  71. package/src/CXIntegration/utils/functions.ts +12 -0
  72. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
  73. package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
  74. package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
  75. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
  76. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
  77. package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
  78. package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
  79. package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -14
  80. package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
  81. package/src/widgets/SocialProofFlowWidget/SocialProofFlowWidget.tsx +12 -12
  82. package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
  83. package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
  84. package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
  85. package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
  86. package/src/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.tsx +12 -12
  87. package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
  88. package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
  89. package/src/widgets/__tests__/testUtils.tsx +63 -0
  90. package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
  91. package/src/widgets/utils/functions.ts +16 -0
@@ -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
+ });
@@ -17,20 +17,7 @@ import { AnimationSpeed } from '@envive-ai/react-toolkit-v3/PromptCarousel/types
17
17
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
18
18
  import { PromptCarousel } from '@envive-ai/react-toolkit-v3/PromptCarousel';
19
19
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
20
-
21
- type RawWidgetString = { id: string; value: string };
22
- type RawValues = Record<string, RawWidgetString | RawWidgetString[]>;
23
-
24
- /** Finds the string_id (id) from raw widget text values for the given display text */
25
- function getStringIdForText(rawValues: RawValues | undefined, text: string): string | undefined {
26
- if (!rawValues) return undefined;
27
- for (const raw of Object.values(rawValues)) {
28
- const list = Array.isArray(raw) ? raw : [raw];
29
- const found = list.find((s: RawWidgetString) => s.value === text);
30
- if (found) return found.id;
31
- }
32
- return undefined;
33
- }
20
+ import { RawValues, getStringIdForText } from '../utils/functions';
34
21
 
35
22
  const mockButtonTexts = [
36
23
  'Loading button 1',
@@ -0,0 +1,150 @@
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 { PromptCarouselWidget } from '../PromptCarouselWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/PromptCarousel', () => ({
14
+ PromptCarousel: ({
15
+ promptButtonTexts,
16
+ handleButtonClick,
17
+ }: {
18
+ promptButtonTexts: string[];
19
+ handleButtonClick: (text: string) => void;
20
+ }) => (
21
+ <div data-testid="prompt-carousel-mock">
22
+ {promptButtonTexts.map(text => (
23
+ <button
24
+ key={text}
25
+ type="button"
26
+ onClick={() => handleButtonClick(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
+ describe('PromptCarouselWidget analytics', () => {
91
+ const defaultHardcopy = {
92
+ responseId: 'test-response-id',
93
+ language: 'en',
94
+ values: {
95
+ promptButtonTexts: ['Button 1', 'Button 2', 'Button 3'],
96
+ },
97
+ rawValues: {
98
+ promptButtonTexts: [
99
+ { id: 'id-1', value: 'Button 1' },
100
+ { id: 'id-2', value: 'Button 2' },
101
+ ],
102
+ },
103
+ };
104
+
105
+ beforeEach(() => {
106
+ vi.clearAllMocks();
107
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
108
+ });
109
+
110
+ it('should track ChatComponentVisible when widget mounts', async () => {
111
+ render(<PromptCarouselWidget widgetConfigId="test-config-1" />);
112
+
113
+ await waitFor(
114
+ () => {
115
+ expect(mockTrackEvent).toHaveBeenCalledWith({
116
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
117
+ eventProps: {
118
+ widget_config_id: 'test-config-1',
119
+ widget_type: WidgetTypeV3.PromptCarouselV3,
120
+ },
121
+ });
122
+ },
123
+ { timeout: 3000 },
124
+ );
125
+ }, 5000);
126
+
127
+ it('should track WidgetTextClicked when button is clicked', async () => {
128
+ render(<PromptCarouselWidget widgetConfigId="test-config-2" />);
129
+
130
+ await waitFor(
131
+ () => {
132
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
133
+ },
134
+ { timeout: 3000 },
135
+ );
136
+
137
+ mockTrackEvent.mockClear();
138
+
139
+ screen.getByText('Button 1').click();
140
+
141
+ expect(mockTrackEvent).toHaveBeenCalledWith({
142
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
143
+ eventProps: {
144
+ response_id: 'test-response-id',
145
+ string_id: 'id-1',
146
+ text: 'Button 1',
147
+ },
148
+ });
149
+ }, 5000);
150
+ });
@@ -19,38 +19,38 @@ export const SocialProofFlowWidget = ({ widgetConfigId }: SocialProofFlowWidgetP
19
19
  isProductComparisonValue,
20
20
  } = useGetWidgetStatus();
21
21
 
22
- if (userHasInteractedValue) {
22
+ if (userHasInteractedValue && isLoadingValue) {
23
23
  return (
24
- <ChatPreviewWidgetWithBaseWidget
24
+ <ChatPreviewLoadingWidgetWithBaseWidget
25
25
  widgetConfigId={widgetConfigId}
26
- widgetType={WidgetTypeV3.ChatPreviewV3}
26
+ widgetType={WidgetTypeV3.ChatPreviewLoadingV3}
27
27
  />
28
28
  );
29
29
  }
30
30
 
31
- if (userHasNotInteractedValue) {
31
+ if (userHasInteractedValue && isProductComparisonValue) {
32
32
  return (
33
- <SocialProofWidgetWithBaseWidget
33
+ <ChatPreviewComparisonWidgetWithBaseWidget
34
34
  widgetConfigId={widgetConfigId}
35
- widgetType={WidgetTypeV3.SocialProofV3}
35
+ widgetType={WidgetTypeV3.ChatPreviewComparisonV3}
36
36
  />
37
37
  );
38
38
  }
39
39
 
40
- if (isLoadingValue) {
40
+ if (userHasInteractedValue) {
41
41
  return (
42
- <ChatPreviewLoadingWidgetWithBaseWidget
42
+ <ChatPreviewWidgetWithBaseWidget
43
43
  widgetConfigId={widgetConfigId}
44
- widgetType={WidgetTypeV3.ChatPreviewLoadingV3}
44
+ widgetType={WidgetTypeV3.ChatPreviewV3}
45
45
  />
46
46
  );
47
47
  }
48
48
 
49
- if (isProductComparisonValue) {
49
+ if (userHasNotInteractedValue) {
50
50
  return (
51
- <ChatPreviewComparisonWidgetWithBaseWidget
51
+ <SocialProofWidgetWithBaseWidget
52
52
  widgetConfigId={widgetConfigId}
53
- widgetType={WidgetTypeV3.ChatPreviewComparisonV3}
53
+ widgetType={WidgetTypeV3.SocialProofV3}
54
54
  />
55
55
  );
56
56
  }
@@ -17,6 +17,7 @@ import {
17
17
  } from '@envive-ai/react-hooks/application/models';
18
18
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
19
19
  import {
20
+ EnviveMetricsEventName,
20
21
  SpiffyMetricsEventName,
21
22
  useAmplitude,
22
23
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -25,7 +26,7 @@ import { chatPreviewLoadingDataAtom } from '@envive-ai/react-hooks/atoms/widget'
25
26
  import { ChatPreviewLoading } from '@envive-ai/react-toolkit-v3/ChatPreviewLoading';
26
27
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
27
28
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
28
- import { getProductImageUrl } from '../utils/functions';
29
+ import { RawValues, getProductImageUrl, getStringIdForText } from '../utils/functions';
29
30
 
30
31
  const SocialProofWidgetHandler = (props: BaseWidgetProps) => {
31
32
  const setChatPreviewLoadingData = useSetAtom(chatPreviewLoadingDataAtom);
@@ -102,6 +103,16 @@ const SocialProofWidgetHandler = (props: BaseWidgetProps) => {
102
103
 
103
104
  const handlePrimaryButtonClick = useCallback(
104
105
  (text: string) => {
106
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
107
+ const stringId = getStringIdForText(rawValues, text);
108
+ trackEvent({
109
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
110
+ eventProps: {
111
+ response_id: hardcopyContent?.responseId,
112
+ string_id: stringId,
113
+ text,
114
+ },
115
+ });
105
116
  onTypedMessageSubmitted({
106
117
  query: text,
107
118
  userTyped: false,
@@ -0,0 +1,184 @@
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 { SocialProofWidget } from '../SocialProofWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/SocialProof', async importOriginal => {
14
+ const actual = await importOriginal();
15
+ const MockSocialProof = ({
16
+ widgetContentProps,
17
+ widgetEventProps,
18
+ }: {
19
+ widgetContentProps: { primaryButtonText?: string };
20
+ widgetEventProps: { handlePrimaryButtonClick: (text: string) => void };
21
+ }) => (
22
+ <div data-testid="social-proof-mock">
23
+ <button
24
+ type="button"
25
+ onClick={() =>
26
+ widgetEventProps.handlePrimaryButtonClick(
27
+ widgetContentProps.primaryButtonText || 'Primary',
28
+ )
29
+ }
30
+ >
31
+ {widgetContentProps.primaryButtonText || 'Primary'}
32
+ </button>
33
+ </div>
34
+ );
35
+ return {
36
+ ...actual,
37
+ SocialProof: MockSocialProof,
38
+ };
39
+ });
40
+
41
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
42
+ useAmplitude: () => ({
43
+ trackEvent: mockTrackEvent,
44
+ isReady: true,
45
+ }),
46
+ SpiffyMetricsEventName: {
47
+ ChatComponentVisible: 'Chat Component Visible',
48
+ SearchComponentVisible: 'Search Component Visible',
49
+ },
50
+ EnviveMetricsEventName: {
51
+ WidgetTextClicked: 'Widget Text Clicked',
52
+ },
53
+ }));
54
+
55
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
56
+ useHardcopy: () => ({
57
+ getHardcopy: mockGetHardcopy,
58
+ }),
59
+ }));
60
+
61
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
62
+ usePage: () => ({
63
+ userEvent: {
64
+ id: 'test-user-event-id',
65
+ category: UserEventCategory.PageVisit,
66
+ created_at: '2025-01-01T00:00:00Z',
67
+ event_id: 'test-event-id',
68
+ } as UserEvent,
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
73
+ useWidgetConfig: () => ({
74
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
79
+ useUiConfig: () => ({
80
+ getUiConfig: vi.fn().mockResolvedValue({}),
81
+ }),
82
+ }));
83
+
84
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
85
+ useSalesAgent: () => ({
86
+ onTypedMessageSubmitted: vi.fn(),
87
+ }),
88
+ }));
89
+
90
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
91
+ useChatToggle: () => ({
92
+ openChat: vi.fn(),
93
+ }),
94
+ }));
95
+
96
+ vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
97
+ lastAssistantMessageAtom: {},
98
+ }));
99
+
100
+ vi.mock('@envive-ai/react-hooks/atoms/app', () => ({
101
+ variantInfoAtom: {},
102
+ }));
103
+
104
+ vi.mock('@envive-ai/react-hooks/atoms/widget', () => ({
105
+ chatPreviewLoadingDataAtom: {},
106
+ }));
107
+
108
+ vi.mock('jotai', async importOriginal => {
109
+ const actual = await importOriginal();
110
+ const variantInfo = {
111
+ variant: 'PageVisit',
112
+ productId: undefined,
113
+ plpId: undefined,
114
+ url: '',
115
+ pageVisitCategory: '',
116
+ };
117
+ const useAtomValueMock = vi.fn();
118
+ useAtomValueMock.mockReturnValueOnce([]).mockReturnValueOnce(variantInfo).mockReturnValue([]);
119
+ return {
120
+ ...actual,
121
+ useAtomValue: useAtomValueMock,
122
+ useSetAtom: vi.fn(() => vi.fn()),
123
+ };
124
+ });
125
+
126
+ describe('SocialProofWidget analytics', () => {
127
+ const defaultHardcopy = {
128
+ responseId: 'test-response-id',
129
+ language: 'en',
130
+ values: {
131
+ primaryButtonText: 'Primary Button',
132
+ titleLabel: 'Title',
133
+ },
134
+ rawValues: {
135
+ primaryButtonText: { id: 'id-1', value: 'Primary Button' },
136
+ },
137
+ };
138
+
139
+ beforeEach(() => {
140
+ vi.clearAllMocks();
141
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
142
+ });
143
+
144
+ it('should track ChatComponentVisible when widget mounts', async () => {
145
+ render(<SocialProofWidget widgetConfigId="test-config-1" />);
146
+
147
+ await waitFor(
148
+ () => {
149
+ expect(mockTrackEvent).toHaveBeenCalledWith({
150
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
151
+ eventProps: {
152
+ widget_config_id: 'test-config-1',
153
+ widget_type: WidgetTypeV3.SocialProofV3,
154
+ },
155
+ });
156
+ },
157
+ { timeout: 3000 },
158
+ );
159
+ }, 5000);
160
+
161
+ it('should track WidgetTextClicked when primary button is clicked', async () => {
162
+ render(<SocialProofWidget widgetConfigId="test-config-2" />);
163
+
164
+ await waitFor(
165
+ () => {
166
+ expect(screen.getByText('Primary Button')).toBeInTheDocument();
167
+ },
168
+ { timeout: 3000 },
169
+ );
170
+
171
+ mockTrackEvent.mockClear();
172
+
173
+ screen.getByText('Primary Button').click();
174
+
175
+ expect(mockTrackEvent).toHaveBeenCalledWith({
176
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
177
+ eventProps: {
178
+ response_id: 'test-response-id',
179
+ string_id: 'id-1',
180
+ text: 'Primary Button',
181
+ },
182
+ });
183
+ }, 5000);
184
+ });
@@ -7,6 +7,7 @@ import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application
7
7
  import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
8
8
  import { useCallback, useEffect } from 'react';
9
9
  import {
10
+ EnviveMetricsEventName,
10
11
  SpiffyMetricsEventName,
11
12
  useAmplitude,
12
13
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -15,6 +16,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
15
16
  import { TitledPromptCarousel } from '@envive-ai/react-toolkit-v3/TitledPromptCarousel';
16
17
  import { AnimationSpeed, PromptCarouselRows } from '@envive-ai/react-toolkit-v3/PromptCarousel';
17
18
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
19
+ import { RawValues, getStringIdForText } from '../utils/functions';
18
20
 
19
21
  const mockButtonTexts = [
20
22
  'Loading button 1',
@@ -67,6 +69,16 @@ const TitledPromptCarouselWidgetHandler = (props: BaseWidgetProps) => {
67
69
 
68
70
  const handleButtonClick = useCallback(
69
71
  (text: string) => {
72
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
73
+ const stringId = getStringIdForText(rawValues, text);
74
+ trackEvent({
75
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
76
+ eventProps: {
77
+ response_id: hardcopyContent?.responseId,
78
+ string_id: stringId,
79
+ text,
80
+ },
81
+ });
70
82
  onTypedMessageSubmitted({
71
83
  query: text,
72
84
  userTyped: false,