@envive-ai/react-hooks 0.3.19-alpha-marlo-2 → 0.3.19-alpha-marlo-4

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 (85) hide show
  1. package/dist/application/utils/elementObserver.d.cts +2 -2
  2. package/dist/application/utils/elementObserver.d.ts +2 -2
  3. package/dist/atoms/app/index.d.cts +7 -7
  4. package/dist/atoms/app/index.d.ts +7 -7
  5. package/dist/atoms/app/variant.d.ts +6 -6
  6. package/dist/atoms/chat/chatState.d.cts +17 -17
  7. package/dist/atoms/chat/chatState.d.ts +18 -18
  8. package/dist/atoms/chat/form.d.cts +2 -2
  9. package/dist/atoms/chat/form.d.ts +3 -3
  10. package/dist/atoms/chat/index.d.cts +2 -2
  11. package/dist/atoms/chat/index.d.ts +3 -3
  12. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  13. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  14. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  15. package/dist/atoms/chat/messageQueue.d.ts +7 -7
  16. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  17. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  18. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  19. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  20. package/dist/atoms/chat/replies.d.cts +3 -3
  21. package/dist/atoms/chat/replies.d.ts +3 -3
  22. package/dist/atoms/chat/suggestions.d.cts +2 -2
  23. package/dist/atoms/chat/suggestions.d.ts +3 -3
  24. package/dist/atoms/envive/enviveConfig.d.cts +12 -12
  25. package/dist/atoms/envive/enviveConfig.d.ts +13 -13
  26. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  27. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  28. package/dist/atoms/org/customerService.d.cts +6 -6
  29. package/dist/atoms/org/customerService.d.ts +6 -6
  30. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  31. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  32. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  33. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  34. package/dist/atoms/org/orgAnalyticsConfig.d.cts +5 -5
  35. package/dist/atoms/org/orgAnalyticsConfig.d.ts +5 -5
  36. package/dist/atoms/search/chatSearch.d.cts +17 -17
  37. package/dist/atoms/search/chatSearch.d.ts +17 -17
  38. package/dist/atoms/search/searchAPI.d.cts +13 -13
  39. package/dist/atoms/search/searchAPI.d.ts +13 -13
  40. package/dist/atoms/search/types.d.cts +1 -1
  41. package/dist/atoms/search/types.d.ts +1 -1
  42. package/dist/atoms/search/utils.d.cts +1 -1
  43. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  44. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  45. package/dist/contexts/amplitudeContext/amplitudeContext.cjs +9 -6
  46. package/dist/contexts/amplitudeContext/amplitudeContext.js +9 -6
  47. package/dist/contexts/enviveContext/enviveContext.cjs +3 -3
  48. package/dist/contexts/enviveContext/enviveContext.js +3 -3
  49. package/dist/contexts/enviveContext/types.d.cts +1 -1
  50. package/dist/contexts/pageContext/pageContext.cjs +58 -1
  51. package/dist/contexts/pageContext/pageContext.js +60 -3
  52. package/dist/contexts/pageContext/types.d.ts +1 -1
  53. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  54. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
  55. package/dist/contexts/types.d.cts +1 -1
  56. package/dist/contexts/types.d.ts +1 -1
  57. package/dist/contexts/typesV3.d.ts +1 -1
  58. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
  59. package/dist/hooks/PageViewedEvent/index.cjs +4 -0
  60. package/dist/hooks/PageViewedEvent/index.d.cts +2 -0
  61. package/dist/hooks/PageViewedEvent/index.d.ts +2 -0
  62. package/dist/hooks/PageViewedEvent/index.js +3 -0
  63. package/dist/hooks/PageViewedEvent/usePageViewedEvent.cjs +84 -0
  64. package/dist/hooks/PageViewedEvent/usePageViewedEvent.d.cts +18 -0
  65. package/dist/hooks/PageViewedEvent/usePageViewedEvent.d.ts +18 -0
  66. package/dist/hooks/PageViewedEvent/usePageViewedEvent.js +82 -0
  67. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  68. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.ts +2 -2
  69. package/dist/hooks/WidgetInteraction/types.d.ts +1 -1
  70. package/dist/hooks/WidgetInteraction/utils.d.ts +1 -1
  71. package/dist/hooks/utils.d.ts +1 -1
  72. package/dist/services/amplitudeService/amplitudeService.cjs +5 -10
  73. package/dist/services/amplitudeService/amplitudeService.d.cts +2 -4
  74. package/dist/services/amplitudeService/amplitudeService.d.ts +2 -4
  75. package/dist/services/amplitudeService/amplitudeService.js +5 -10
  76. package/package.json +5 -1
  77. package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +5 -6
  78. package/src/contexts/amplitudeContext/amplitudeContext.tsx +7 -3
  79. package/src/contexts/pageContext/__tests__/pageContext.test.tsx +6 -0
  80. package/src/contexts/pageContext/pageContext.tsx +60 -1
  81. package/src/hooks/PageViewedEvent/__tests__/usePageViewedEvent.test.ts +297 -0
  82. package/src/hooks/PageViewedEvent/index.ts +1 -0
  83. package/src/hooks/PageViewedEvent/usePageViewedEvent.ts +103 -0
  84. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +6 -56
  85. package/src/services/amplitudeService/amplitudeService.ts +4 -14
@@ -10,15 +10,18 @@ import {
10
10
  } from 'react';
11
11
  import {
12
12
  UrlResolverResponse,
13
+ hasParsedVariantInfoAtom,
13
14
  pageUserEventAtom,
14
15
  pageVariantInfoAtom,
15
16
  urlResolverAtom,
17
+ variantInfoAtom,
16
18
  } from 'src/atoms/app/variant';
17
- import { useAtom } from 'jotai';
19
+ import { useAtom, useAtomValue } from 'jotai';
18
20
  import CommerceApiClient from 'src/application/commerce-api';
19
21
  import Logger from 'src/application/logging/logger';
20
22
  import { PageVisitCategory, UserEventCategory } from '@spiffy-ai/commerce-api-client';
21
23
  import { VariantTypeEnum } from 'src/application/models';
24
+ import { EnviveMetricsEventName, useAmplitude } from 'src/contexts/amplitudeContext';
22
25
  import { mapApiUserEventToUserEvent, mapUrlResolverResponseToVariantInfo } from './mapping';
23
26
  import { PageDetails } from './types';
24
27
 
@@ -138,6 +141,62 @@ export const PageProvider: React.FC<{
138
141
  onUrlResolverNotReady,
139
142
  ]);
140
143
 
144
+ const { trackEvent } = useAmplitude();
145
+ const resolvedVariantInfo = useAtomValue(variantInfoAtom);
146
+ const hasParsedVariantInfo = useAtomValue(hasParsedVariantInfoAtom);
147
+ const pageViewedFired = useRef(false);
148
+
149
+ useEffect(() => {
150
+ if (pageViewedFired.current || isLoading || !hasParsedVariantInfo || !variantInfo) {
151
+ return;
152
+ }
153
+
154
+ let pageType: string;
155
+ let pageId: string;
156
+
157
+ switch (variantInfo.variantType) {
158
+ case VariantTypeEnum.Pdp:
159
+ pageType = 'pdp';
160
+ pageId = variantInfo.productId;
161
+ break;
162
+ case VariantTypeEnum.Plp:
163
+ pageType = 'plp';
164
+ pageId = variantInfo.plpId;
165
+ break;
166
+ case VariantTypeEnum.Home:
167
+ pageType = 'homepage';
168
+ pageId = variantInfo.url;
169
+ break;
170
+ case VariantTypeEnum.Other:
171
+ pageType = 'other';
172
+ pageId = variantInfo.url;
173
+ break;
174
+ case VariantTypeEnum.PageVisit:
175
+ pageType = 'other';
176
+ pageId = variantInfo.url;
177
+ break;
178
+ case VariantTypeEnum.FullPageSalesAgent:
179
+ pageType = 'full_page_sales_agent';
180
+ pageId = variantInfo.url;
181
+ break;
182
+ default:
183
+ return;
184
+ }
185
+
186
+ pageViewedFired.current = true;
187
+
188
+ trackEvent({
189
+ eventName: EnviveMetricsEventName.PageViewed,
190
+ eventProps: {
191
+ 'context.page_type': pageType,
192
+ 'context.page_id': pageId,
193
+ 'context.supported': isSupported,
194
+ 'context.ready': isSupported,
195
+ 'context.page_variant_id': resolvedVariantInfo.variantId,
196
+ },
197
+ });
198
+ }, [isLoading, isSupported, hasParsedVariantInfo, variantInfo, resolvedVariantInfo, trackEvent]);
199
+
141
200
  const setPageUrlStable = useCallback((url: string) => {
142
201
  setPageUrl(url);
143
202
  }, []);
@@ -0,0 +1,297 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { VariantTypeEnum } from 'src/application/models';
3
+ import { EnviveMetricsEventName } from 'src/contexts/amplitudeContext';
4
+ import {
5
+ FullPageSalesAgentVariantInfo,
6
+ HomeVariantInfo,
7
+ OtherVariantInfo,
8
+ PDPPageVariantInfo,
9
+ PLPPageVariantInfo,
10
+ PageVariantInfo,
11
+ VisitPageVariantInfo,
12
+ } from 'src/contexts/pageContext/types';
13
+ import { extractPageViewedContext, usePageViewedEvent } from '../usePageViewedEvent';
14
+
15
+ const mockTrackEvent = vi.fn();
16
+
17
+ vi.mock('src/contexts/amplitudeContext', async () => {
18
+ const actual = await vi.importActual('src/contexts/amplitudeContext');
19
+ return {
20
+ ...actual,
21
+ useAmplitude: () => ({
22
+ trackEvent: mockTrackEvent,
23
+ }),
24
+ };
25
+ });
26
+
27
+ const mockUsePage = vi.fn();
28
+ vi.mock('src/contexts/pageContext', () => ({
29
+ usePage: () => mockUsePage(),
30
+ }));
31
+
32
+ const mockUseAtomValue = vi.fn();
33
+ vi.mock('jotai', async () => {
34
+ const actual = await vi.importActual('jotai');
35
+ return {
36
+ ...actual,
37
+ useAtomValue: () => mockUseAtomValue(),
38
+ };
39
+ });
40
+
41
+ describe('extractPageViewedContext', () => {
42
+ it('should return pdp context', () => {
43
+ const variant: PDPPageVariantInfo = {
44
+ variantType: VariantTypeEnum.Pdp,
45
+ productId: 'product-123',
46
+ collections: [],
47
+ numberOfReviews: 0,
48
+ merchantTags: [],
49
+ url: 'https://example.com/products/product-123',
50
+ };
51
+ expect(extractPageViewedContext(variant)).toEqual({
52
+ page_type: 'pdp',
53
+ page_id: 'product-123',
54
+ });
55
+ });
56
+
57
+ it('should return plp context', () => {
58
+ const variant: PLPPageVariantInfo = {
59
+ variantType: VariantTypeEnum.Plp,
60
+ plpId: 'collection-456',
61
+ topCategory: 'shoes',
62
+ url: 'https://example.com/collections/shoes',
63
+ };
64
+ expect(extractPageViewedContext(variant)).toEqual({
65
+ page_type: 'plp',
66
+ page_id: 'collection-456',
67
+ });
68
+ });
69
+
70
+ it('should return homepage context', () => {
71
+ const variant: HomeVariantInfo = {
72
+ variantType: VariantTypeEnum.Home,
73
+ url: 'https://example.com/',
74
+ };
75
+ expect(extractPageViewedContext(variant)).toEqual({
76
+ page_type: 'homepage',
77
+ page_id: 'https://example.com/',
78
+ });
79
+ });
80
+
81
+ it('should return other context for Other variant', () => {
82
+ const variant: OtherVariantInfo = {
83
+ variantType: VariantTypeEnum.Other,
84
+ url: 'https://example.com/about',
85
+ };
86
+ expect(extractPageViewedContext(variant)).toEqual({
87
+ page_type: 'other',
88
+ page_id: 'https://example.com/about',
89
+ });
90
+ });
91
+
92
+ it('should return other context for PageVisit variant', () => {
93
+ const variant: VisitPageVariantInfo = {
94
+ variantType: VariantTypeEnum.PageVisit,
95
+ url: 'https://example.com/contact',
96
+ };
97
+ expect(extractPageViewedContext(variant)).toEqual({
98
+ page_type: 'other',
99
+ page_id: 'https://example.com/contact',
100
+ });
101
+ });
102
+
103
+ it('should return full_page_sales_agent context', () => {
104
+ const variant: FullPageSalesAgentVariantInfo = {
105
+ variantType: VariantTypeEnum.FullPageSalesAgent,
106
+ url: 'https://example.com/agent',
107
+ };
108
+ expect(extractPageViewedContext(variant)).toEqual({
109
+ page_type: 'full_page_sales_agent',
110
+ page_id: 'https://example.com/agent',
111
+ });
112
+ });
113
+
114
+ it('should return null for unknown variant type', () => {
115
+ const variant = { variantType: 'unknown' } as unknown as PageVariantInfo;
116
+ expect(extractPageViewedContext(variant)).toBeNull();
117
+ });
118
+ });
119
+
120
+ describe('usePageViewedEvent', () => {
121
+ beforeEach(() => {
122
+ vi.clearAllMocks();
123
+ });
124
+
125
+ it('should not fire when still loading', () => {
126
+ mockUsePage.mockReturnValue({
127
+ variantInfo: {
128
+ variantType: VariantTypeEnum.Pdp,
129
+ productId: 'p1',
130
+ collections: [],
131
+ numberOfReviews: 0,
132
+ merchantTags: [],
133
+ url: '',
134
+ },
135
+ isSupported: true,
136
+ isLoading: true,
137
+ });
138
+ mockUseAtomValue
139
+ .mockReturnValueOnce({ variantId: 'v1', variant: 'pdp' })
140
+ .mockReturnValueOnce(true);
141
+
142
+ renderHook(() => usePageViewedEvent());
143
+ expect(mockTrackEvent).not.toHaveBeenCalled();
144
+ });
145
+
146
+ it('should not fire when hasParsedVariantInfo is false', () => {
147
+ mockUsePage.mockReturnValue({
148
+ variantInfo: {
149
+ variantType: VariantTypeEnum.Pdp,
150
+ productId: 'p1',
151
+ collections: [],
152
+ numberOfReviews: 0,
153
+ merchantTags: [],
154
+ url: '',
155
+ },
156
+ isSupported: true,
157
+ isLoading: false,
158
+ });
159
+ mockUseAtomValue
160
+ .mockReturnValueOnce({ variantId: 'v1', variant: 'pdp' })
161
+ .mockReturnValueOnce(false);
162
+
163
+ renderHook(() => usePageViewedEvent());
164
+ expect(mockTrackEvent).not.toHaveBeenCalled();
165
+ });
166
+
167
+ it('should not fire when pageVariantInfo is undefined', () => {
168
+ mockUsePage.mockReturnValue({
169
+ variantInfo: undefined,
170
+ isSupported: false,
171
+ isLoading: false,
172
+ });
173
+ mockUseAtomValue
174
+ .mockReturnValueOnce({ variantId: 'v1', variant: 'pdp' })
175
+ .mockReturnValueOnce(true);
176
+
177
+ renderHook(() => usePageViewedEvent());
178
+ expect(mockTrackEvent).not.toHaveBeenCalled();
179
+ });
180
+
181
+ it('should fire with correct props for pdp page', () => {
182
+ mockUsePage.mockReturnValue({
183
+ variantInfo: {
184
+ variantType: VariantTypeEnum.Pdp,
185
+ productId: 'product-123',
186
+ collections: [],
187
+ numberOfReviews: 5,
188
+ merchantTags: [],
189
+ url: 'https://example.com/products/product-123',
190
+ },
191
+ isSupported: true,
192
+ isLoading: false,
193
+ });
194
+ mockUseAtomValue
195
+ .mockReturnValueOnce({ variantId: 'variant-A', variant: 'pdp' })
196
+ .mockReturnValueOnce(true);
197
+
198
+ renderHook(() => usePageViewedEvent());
199
+
200
+ expect(mockTrackEvent).toHaveBeenCalledTimes(1);
201
+ expect(mockTrackEvent).toHaveBeenCalledWith({
202
+ eventName: EnviveMetricsEventName.PageViewed,
203
+ eventProps: {
204
+ 'context.page_type': 'pdp',
205
+ 'context.page_id': 'product-123',
206
+ 'context.supported': true,
207
+ 'context.ready': true,
208
+ 'context.page_variant_id': 'variant-A',
209
+ },
210
+ });
211
+ });
212
+
213
+ it('should fire only once even if dependencies change', () => {
214
+ mockUsePage.mockReturnValue({
215
+ variantInfo: {
216
+ variantType: VariantTypeEnum.Pdp,
217
+ productId: 'product-123',
218
+ collections: [],
219
+ numberOfReviews: 0,
220
+ merchantTags: [],
221
+ url: '',
222
+ },
223
+ isSupported: true,
224
+ isLoading: false,
225
+ });
226
+ mockUseAtomValue
227
+ .mockReturnValueOnce({ variantId: 'v1', variant: 'pdp' })
228
+ .mockReturnValueOnce(true);
229
+
230
+ const { rerender } = renderHook(() => usePageViewedEvent());
231
+
232
+ mockUseAtomValue
233
+ .mockReturnValueOnce({ variantId: 'v1', variant: 'pdp' })
234
+ .mockReturnValueOnce(true);
235
+
236
+ rerender();
237
+
238
+ expect(mockTrackEvent).toHaveBeenCalledTimes(1);
239
+ });
240
+
241
+ it('should fire with full_page_sales_agent page type', () => {
242
+ mockUsePage.mockReturnValue({
243
+ variantInfo: {
244
+ variantType: VariantTypeEnum.FullPageSalesAgent,
245
+ url: 'https://example.com/agent',
246
+ },
247
+ isSupported: true,
248
+ isLoading: false,
249
+ });
250
+ mockUseAtomValue
251
+ .mockReturnValueOnce({ variantId: 'fpsa-variant', variant: 'full_page' })
252
+ .mockReturnValueOnce(true);
253
+
254
+ renderHook(() => usePageViewedEvent());
255
+
256
+ expect(mockTrackEvent).toHaveBeenCalledTimes(1);
257
+ expect(mockTrackEvent).toHaveBeenCalledWith({
258
+ eventName: EnviveMetricsEventName.PageViewed,
259
+ eventProps: {
260
+ 'context.page_type': 'full_page_sales_agent',
261
+ 'context.page_id': 'https://example.com/agent',
262
+ 'context.supported': true,
263
+ 'context.ready': true,
264
+ 'context.page_variant_id': 'fpsa-variant',
265
+ },
266
+ });
267
+ });
268
+
269
+ it('should set supported and ready to false when isSupported is false', () => {
270
+ mockUsePage.mockReturnValue({
271
+ variantInfo: {
272
+ variantType: VariantTypeEnum.Pdp,
273
+ productId: 'product-456',
274
+ collections: [],
275
+ numberOfReviews: 0,
276
+ merchantTags: [],
277
+ url: '',
278
+ },
279
+ isSupported: false,
280
+ isLoading: false,
281
+ });
282
+ mockUseAtomValue
283
+ .mockReturnValueOnce({ variantId: 'v2', variant: 'pdp' })
284
+ .mockReturnValueOnce(true);
285
+
286
+ renderHook(() => usePageViewedEvent());
287
+
288
+ expect(mockTrackEvent).toHaveBeenCalledWith(
289
+ expect.objectContaining({
290
+ eventProps: expect.objectContaining({
291
+ 'context.supported': false,
292
+ 'context.ready': false,
293
+ }),
294
+ }),
295
+ );
296
+ });
297
+ });
@@ -0,0 +1 @@
1
+ export * from './usePageViewedEvent';
@@ -0,0 +1,103 @@
1
+ import { useAtomValue } from 'jotai';
2
+ import { useEffect, useRef } from 'react';
3
+ import { VariantTypeEnum } from 'src/application/models';
4
+ import { hasParsedVariantInfoAtom, variantInfoAtom } from 'src/atoms/app/variant';
5
+ import { EnviveMetricsEventName, useAmplitude } from 'src/contexts/amplitudeContext';
6
+ import { usePage } from 'src/contexts/pageContext';
7
+ import {
8
+ FullPageSalesAgentVariantInfo,
9
+ HomeVariantInfo,
10
+ OtherVariantInfo,
11
+ PDPPageVariantInfo,
12
+ PLPPageVariantInfo,
13
+ PageVariantInfo,
14
+ VisitPageVariantInfo,
15
+ } from 'src/contexts/pageContext/types';
16
+
17
+ interface PageViewedContext {
18
+ page_type: string;
19
+ page_id: string;
20
+ }
21
+
22
+ export const extractPageViewedContext = (
23
+ pageVariant: PageVariantInfo,
24
+ ): PageViewedContext | null => {
25
+ switch (pageVariant.variantType) {
26
+ case VariantTypeEnum.Pdp:
27
+ return {
28
+ page_type: 'pdp',
29
+ page_id: (pageVariant as PDPPageVariantInfo).productId,
30
+ };
31
+ case VariantTypeEnum.Plp:
32
+ return {
33
+ page_type: 'plp',
34
+ page_id: (pageVariant as PLPPageVariantInfo).plpId,
35
+ };
36
+ case VariantTypeEnum.Home:
37
+ return {
38
+ page_type: 'homepage',
39
+ page_id: (pageVariant as HomeVariantInfo).url,
40
+ };
41
+ case VariantTypeEnum.Other:
42
+ return {
43
+ page_type: 'other',
44
+ page_id: (pageVariant as OtherVariantInfo).url,
45
+ };
46
+ case VariantTypeEnum.PageVisit:
47
+ return {
48
+ page_type: 'other',
49
+ page_id: (pageVariant as VisitPageVariantInfo).url,
50
+ };
51
+ case VariantTypeEnum.FullPageSalesAgent:
52
+ return {
53
+ page_type: 'full_page_sales_agent',
54
+ page_id: (pageVariant as FullPageSalesAgentVariantInfo).url,
55
+ };
56
+ default:
57
+ return null;
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Fires the [Envive] Page Viewed event once per page load when page context data is ready.
63
+ *
64
+ * This event captures page context information and is intended to eventually replace
65
+ * the [Amplitude] Page Viewed event.
66
+ */
67
+ export const usePageViewedEvent = () => {
68
+ const hasFired = useRef(false);
69
+ const { trackEvent } = useAmplitude();
70
+ const { variantInfo: pageVariantInfo, isSupported, isLoading } = usePage();
71
+ const variantInfo = useAtomValue(variantInfoAtom);
72
+ const hasParsedVariantInfo = useAtomValue(hasParsedVariantInfoAtom);
73
+
74
+ useEffect(() => {
75
+ if (hasFired.current) {
76
+ return;
77
+ }
78
+
79
+ if (isLoading || !hasParsedVariantInfo || !pageVariantInfo) {
80
+ return;
81
+ }
82
+
83
+ const pageContext = extractPageViewedContext(pageVariantInfo);
84
+ if (!pageContext) {
85
+ return;
86
+ }
87
+
88
+ hasFired.current = true;
89
+
90
+ trackEvent({
91
+ eventName: EnviveMetricsEventName.PageViewed,
92
+ eventProps: {
93
+ 'context.page_type': pageContext.page_type,
94
+ 'context.page_id': pageContext.page_id,
95
+ // context.supported and context.ready both derive from isSupported (response.ready)
96
+ // because the supportedEventAtom is not currently populated.
97
+ 'context.supported': isSupported,
98
+ 'context.ready': isSupported,
99
+ 'context.page_variant_id': variantInfo.variantId,
100
+ },
101
+ });
102
+ }, [isLoading, isSupported, hasParsedVariantInfo, pageVariantInfo, variantInfo, trackEvent]);
103
+ };
@@ -85,12 +85,10 @@ describe('AmplitudeService', () => {
85
85
  });
86
86
  });
87
87
 
88
- const validApiKey = 'abcdef1234567890abcdef1234567890';
89
-
90
88
  const createService = (overrides?: Partial<AmplitudeServiceConfig>) => {
91
89
  return new AmplitudeService({
92
90
  userId: 'test-user-id',
93
- amplitudeApiKey: validApiKey,
91
+ amplitudeApiKey: 'test-api-key',
94
92
  dataResidency: 'US',
95
93
  env: 'test',
96
94
  orgId: 'test-org-id',
@@ -109,7 +107,7 @@ describe('AmplitudeService', () => {
109
107
  createService();
110
108
 
111
109
  expect(mockInit).toHaveBeenCalledWith(
112
- validApiKey,
110
+ 'test-api-key',
113
111
  'test-user-id',
114
112
  expect.objectContaining({
115
113
  serverZone: 'US',
@@ -123,7 +121,7 @@ describe('AmplitudeService', () => {
123
121
  it('should not be ready if userId is missing', () => {
124
122
  const service = new AmplitudeService({
125
123
  userId: '',
126
- amplitudeApiKey: validApiKey,
124
+ amplitudeApiKey: 'test-api-key',
127
125
  dataResidency: 'US',
128
126
  env: 'test',
129
127
  orgId: 'test-org-id',
@@ -159,7 +157,7 @@ describe('AmplitudeService', () => {
159
157
  it('should not be ready if featureFlagService is missing', () => {
160
158
  const service = new AmplitudeService({
161
159
  userId: 'test-user-id',
162
- amplitudeApiKey: validApiKey,
160
+ amplitudeApiKey: 'test-api-key',
163
161
  dataResidency: 'US',
164
162
  env: 'test',
165
163
  orgId: 'test-org-id',
@@ -179,54 +177,6 @@ describe('AmplitudeService', () => {
179
177
 
180
178
  expect(service.isReady).toBe(true);
181
179
  });
182
-
183
- it('should not set isMockMode when all required config is provided', () => {
184
- const service = createService();
185
-
186
- expect(service.isMockMode).toBe(false);
187
- });
188
- });
189
-
190
- describe('Mock mode', () => {
191
- it('should set isMockMode and not initialize the client when API key is not a valid hex string', () => {
192
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
193
-
194
- const service = createService({ amplitudeApiKey: 'not-a-valid-key' });
195
-
196
- expect(service.isReady).toBe(false);
197
- expect(service.isMockMode).toBe(true);
198
- expect(mockInit).not.toHaveBeenCalled();
199
-
200
- consoleSpy.mockRestore();
201
- });
202
-
203
- it('should print a console.log message when a mock API key is detected', () => {
204
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
205
-
206
- createService({ amplitudeApiKey: 'not-a-valid-key' });
207
-
208
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Mock API key detected'));
209
-
210
- consoleSpy.mockRestore();
211
- });
212
-
213
- it('should not set isMockMode when API key is empty — service is simply not ready', () => {
214
- const service = createService({ amplitudeApiKey: '' });
215
-
216
- expect(service.isReady).toBe(false);
217
- expect(service.isMockMode).toBe(false);
218
- });
219
-
220
- it('should not call the Amplitude client when trackEvent is called in mock mode', async () => {
221
- const service = createService({ amplitudeApiKey: 'not-a-valid-key' });
222
-
223
- await service.trackEvent({
224
- eventName: SpiffyMetricsEventName.ChatUserMessageInput,
225
- eventProps: { message: 'test' },
226
- });
227
-
228
- expect(mockTrack).not.toHaveBeenCalled();
229
- });
230
180
  });
231
181
 
232
182
  describe('trackEvent', () => {
@@ -319,7 +269,7 @@ describe('AmplitudeService', () => {
319
269
  it('should not track if client is not initialized', async () => {
320
270
  const service = new AmplitudeService({
321
271
  userId: '',
322
- amplitudeApiKey: validApiKey,
272
+ amplitudeApiKey: 'test-api-key',
323
273
  dataResidency: 'US',
324
274
  env: 'test',
325
275
  orgId: 'test-org-id',
@@ -469,7 +419,7 @@ describe('AmplitudeService', () => {
469
419
  it('should fall back to window.localStorage when getLocalStorageItem is not provided', async () => {
470
420
  const service = new AmplitudeService({
471
421
  userId: 'test-user-id',
472
- amplitudeApiKey: validApiKey,
422
+ amplitudeApiKey: 'test-api-key',
473
423
  dataResidency: 'US',
474
424
  env: 'test',
475
425
  orgId: 'test-org-id',
@@ -51,8 +51,6 @@ export class AmplitudeService {
51
51
 
52
52
  private config: AmplitudeServiceConfig;
53
53
 
54
- private _isMockMode: boolean = false;
55
-
56
54
  constructor(config: AmplitudeServiceConfig) {
57
55
  this.config = config;
58
56
  this.initialize();
@@ -67,10 +65,6 @@ export class AmplitudeService {
67
65
  );
68
66
  }
69
67
 
70
- get isMockMode(): boolean {
71
- return this._isMockMode;
72
- }
73
-
74
68
  private getLocalStorageItem(key: string): string | null {
75
69
  if (this.config.getLocalStorageItem) {
76
70
  return this.config.getLocalStorageItem(key);
@@ -219,8 +213,8 @@ export class AmplitudeService {
219
213
  return enrichment;
220
214
  }
221
215
 
222
- private static isValidAmplitudeApiKey(apiKey: string): boolean {
223
- return /^[0-9a-f]{32}$/i.test(apiKey);
216
+ static isDummyApiKey(apiKey: string): boolean {
217
+ return !/^[0-9a-f]{32}$/i.test(apiKey);
224
218
  }
225
219
 
226
220
  private initialize(): void {
@@ -238,12 +232,8 @@ export class AmplitudeService {
238
232
  return;
239
233
  }
240
234
 
241
- if (!AmplitudeService.isValidAmplitudeApiKey(this.config.amplitudeApiKey)) {
242
- this._isMockMode = true;
243
- // eslint-disable-next-line no-console
244
- console.log(
245
- '[AmplitudeService] Mock API key detected — running in mock mode, no events will be sent to Amplitude.',
246
- );
235
+ if (AmplitudeService.isDummyApiKey(this.config.amplitudeApiKey)) {
236
+ logger.logDebug('AmplitudeService is using a dummy API key — analytics will not be tracked');
247
237
  return;
248
238
  }
249
239