@envive-ai/react-hooks 0.3.46 → 0.3.48

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 (243) hide show
  1. package/dist/application/commerce-api.cjs +73 -3
  2. package/dist/application/commerce-api.d.cts +16 -1
  3. package/dist/application/commerce-api.d.ts +16 -1
  4. package/dist/application/commerce-api.js +73 -3
  5. package/dist/application/models/api/userEvent.d.cts +9 -2
  6. package/dist/application/models/api/userEvent.d.ts +9 -2
  7. package/dist/application/models/featureGates.cjs +2 -1
  8. package/dist/application/models/featureGates.d.cts +2 -1
  9. package/dist/application/models/featureGates.d.ts +2 -1
  10. package/dist/application/models/featureGates.js +2 -1
  11. package/dist/application/models/graphql/queries/getProductOptionsQuery.cjs +33 -0
  12. package/dist/application/models/graphql/queries/getProductOptionsQuery.d.cts +28 -0
  13. package/dist/application/models/graphql/queries/getProductOptionsQuery.d.ts +28 -0
  14. package/dist/application/models/graphql/queries/getProductOptionsQuery.js +32 -0
  15. package/dist/application/models/validators/validateResponse.cjs +2 -1
  16. package/dist/application/models/validators/validateResponse.js +2 -1
  17. package/dist/application/models/validators/validateUserEvent.cjs +3 -3
  18. package/dist/application/models/validators/validateUserEvent.js +3 -3
  19. package/dist/application/utils/index.cjs +2 -0
  20. package/dist/application/utils/index.d.cts +2 -1
  21. package/dist/application/utils/index.d.ts +2 -1
  22. package/dist/application/utils/index.js +2 -1
  23. package/dist/application/utils/messageFromAddToCartEvent.cjs +32 -0
  24. package/dist/application/utils/messageFromAddToCartEvent.d.cts +19 -0
  25. package/dist/application/utils/messageFromAddToCartEvent.d.ts +19 -0
  26. package/dist/application/utils/messageFromAddToCartEvent.js +31 -0
  27. package/dist/atoms/app/index.cjs +1 -1
  28. package/dist/atoms/app/index.d.cts +7 -7
  29. package/dist/atoms/app/index.d.ts +7 -7
  30. package/dist/atoms/app/index.js +1 -1
  31. package/dist/atoms/app/variant.d.cts +6 -6
  32. package/dist/atoms/app/variant.d.ts +6 -6
  33. package/dist/atoms/chat/chatState.d.cts +19 -19
  34. package/dist/atoms/chat/chatState.d.ts +19 -19
  35. package/dist/atoms/chat/form.d.cts +2 -2
  36. package/dist/atoms/chat/form.d.ts +3 -3
  37. package/dist/atoms/chat/index.d.cts +2 -2
  38. package/dist/atoms/chat/index.d.ts +3 -3
  39. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  40. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  41. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  42. package/dist/atoms/chat/messageQueue.d.ts +7 -7
  43. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  44. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  45. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  46. package/dist/atoms/chat/renderedWidgetRefs.d.ts +3 -3
  47. package/dist/atoms/chat/replies.d.cts +1 -1
  48. package/dist/atoms/chat/replies.d.ts +3 -3
  49. package/dist/atoms/chat/suggestions.d.cts +2 -2
  50. package/dist/atoms/chat/suggestions.d.ts +3 -3
  51. package/dist/atoms/envive/enviveConfig.cjs +7 -4
  52. package/dist/atoms/envive/enviveConfig.d.cts +16 -14
  53. package/dist/atoms/envive/enviveConfig.d.ts +16 -14
  54. package/dist/atoms/envive/enviveConfig.js +6 -4
  55. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  56. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  57. package/dist/atoms/org/customerService.d.cts +6 -6
  58. package/dist/atoms/org/customerService.d.ts +6 -6
  59. package/dist/atoms/org/graphqlConfig.cjs +1 -1
  60. package/dist/atoms/org/graphqlConfig.d.cts +6 -6
  61. package/dist/atoms/org/graphqlConfig.d.ts +6 -6
  62. package/dist/atoms/org/graphqlConfig.js +1 -1
  63. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  64. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  65. package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
  66. package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
  67. package/dist/atoms/search/chatSearch.d.cts +17 -17
  68. package/dist/atoms/search/chatSearch.d.ts +17 -17
  69. package/dist/atoms/search/searchAPI.d.cts +13 -13
  70. package/dist/atoms/search/searchAPI.d.ts +13 -13
  71. package/dist/atoms/search/types.d.ts +1 -1
  72. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  73. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  74. package/dist/contexts/enviveConfigContext/enviveConfigContext.cjs +6 -4
  75. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.cts +3 -1
  76. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.ts +3 -1
  77. package/dist/contexts/enviveConfigContext/enviveConfigContext.js +6 -4
  78. package/dist/contexts/enviveContext/enviveContext.cjs +7 -3
  79. package/dist/contexts/enviveContext/enviveContext.d.cts +5 -1
  80. package/dist/contexts/enviveContext/enviveContext.d.ts +5 -1
  81. package/dist/contexts/enviveContext/enviveContext.js +7 -3
  82. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.cjs +2 -97
  83. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.cts +3 -25
  84. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.ts +3 -25
  85. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.js +3 -97
  86. package/dist/contexts/featureFlagServiceContext/index.cjs +2 -1
  87. package/dist/contexts/featureFlagServiceContext/index.d.cts +2 -1
  88. package/dist/contexts/featureFlagServiceContext/index.d.ts +2 -1
  89. package/dist/contexts/featureFlagServiceContext/index.js +2 -1
  90. package/dist/contexts/graphqlContext/mockV3Config.cjs +1 -1
  91. package/dist/contexts/graphqlContext/mockV3Config.js +1 -1
  92. package/dist/contexts/localStorageContext/index.cjs +2 -1
  93. package/dist/contexts/localStorageContext/index.d.cts +2 -1
  94. package/dist/contexts/localStorageContext/index.d.ts +2 -1
  95. package/dist/contexts/localStorageContext/index.js +2 -1
  96. package/dist/contexts/localStorageContext/localStorageContext.cjs +5 -11
  97. package/dist/contexts/localStorageContext/localStorageContext.d.cts +3 -7
  98. package/dist/contexts/localStorageContext/localStorageContext.d.ts +3 -7
  99. package/dist/contexts/localStorageContext/localStorageContext.js +3 -8
  100. package/dist/contexts/localStorageContext/localStorageKeys.cjs +12 -0
  101. package/dist/contexts/localStorageContext/localStorageKeys.d.cts +9 -0
  102. package/dist/contexts/localStorageContext/localStorageKeys.d.ts +9 -0
  103. package/dist/contexts/localStorageContext/localStorageKeys.js +11 -0
  104. package/dist/contexts/salesAgentContext/chatAPI.cjs +13 -2
  105. package/dist/contexts/salesAgentContext/chatAPI.d.cts +4 -1
  106. package/dist/contexts/salesAgentContext/chatAPI.d.ts +4 -1
  107. package/dist/contexts/salesAgentContext/chatAPI.js +14 -3
  108. package/dist/contexts/salesAgentContext/salesAgentContext.cjs +12 -4
  109. package/dist/contexts/salesAgentContext/salesAgentContext.js +12 -4
  110. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  111. package/dist/contexts/types.cjs +1 -1
  112. package/dist/contexts/types.d.cts +3 -1
  113. package/dist/contexts/types.d.ts +3 -1
  114. package/dist/contexts/types.js +1 -1
  115. package/dist/contexts/typesV3.cjs +1 -1
  116. package/dist/contexts/typesV3.d.cts +11 -2
  117. package/dist/contexts/typesV3.d.ts +12 -3
  118. package/dist/contexts/typesV3.js +1 -1
  119. package/dist/hooks/AddToCart/addToCartErrorMessage.cjs +7 -0
  120. package/dist/hooks/AddToCart/addToCartErrorMessage.d.cts +5 -0
  121. package/dist/hooks/AddToCart/addToCartErrorMessage.d.ts +5 -0
  122. package/dist/hooks/AddToCart/addToCartErrorMessage.js +6 -0
  123. package/dist/hooks/AddToCart/createAddToCartUserEvent.cjs +20 -0
  124. package/dist/hooks/AddToCart/createAddToCartUserEvent.d.cts +17 -0
  125. package/dist/hooks/AddToCart/createAddToCartUserEvent.d.ts +17 -0
  126. package/dist/hooks/AddToCart/createAddToCartUserEvent.js +19 -0
  127. package/dist/hooks/AddToCart/index.cjs +9 -0
  128. package/dist/hooks/AddToCart/index.d.cts +5 -0
  129. package/dist/hooks/AddToCart/index.d.ts +5 -0
  130. package/dist/hooks/AddToCart/index.js +6 -0
  131. package/dist/hooks/AddToCart/parseAddToCartFunction.cjs +27 -0
  132. package/dist/hooks/AddToCart/parseAddToCartFunction.d.cts +13 -0
  133. package/dist/hooks/AddToCart/parseAddToCartFunction.d.ts +13 -0
  134. package/dist/hooks/AddToCart/parseAddToCartFunction.js +27 -0
  135. package/dist/hooks/AddToCart/useAddToCart.cjs +24 -0
  136. package/dist/hooks/AddToCart/useAddToCart.d.cts +14 -0
  137. package/dist/hooks/AddToCart/useAddToCart.d.ts +14 -0
  138. package/dist/hooks/AddToCart/useAddToCart.js +23 -0
  139. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  140. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.cjs +6 -5
  141. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.js +3 -2
  142. package/dist/hooks/PageViewedEvent/usePageViewedEvent.cjs +5 -2
  143. package/dist/hooks/PageViewedEvent/usePageViewedEvent.js +5 -2
  144. package/dist/hooks/ProductDetails/index.cjs +9 -0
  145. package/dist/hooks/ProductDetails/index.d.cts +4 -0
  146. package/dist/hooks/ProductDetails/index.d.ts +4 -0
  147. package/dist/hooks/ProductDetails/index.js +4 -0
  148. package/dist/hooks/ProductDetails/useProductDetails.cjs +53 -0
  149. package/dist/hooks/ProductDetails/useProductDetails.d.cts +20 -0
  150. package/dist/hooks/ProductDetails/useProductDetails.d.ts +20 -0
  151. package/dist/hooks/ProductDetails/useProductDetails.js +52 -0
  152. package/dist/hooks/ProductDetails/variantSelection.cjs +42 -0
  153. package/dist/hooks/ProductDetails/variantSelection.d.cts +35 -0
  154. package/dist/hooks/ProductDetails/variantSelection.d.ts +35 -0
  155. package/dist/hooks/ProductDetails/variantSelection.js +38 -0
  156. package/dist/hooks/Search/useSearch.d.cts +1 -1
  157. package/dist/hooks/Search/useSearch.d.ts +1 -1
  158. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +11 -2
  159. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +1 -10
  160. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +1 -10
  161. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +11 -2
  162. package/dist/hooks/WidgetInteraction/types.cjs +12 -2
  163. package/dist/hooks/WidgetInteraction/types.d.cts +28 -3
  164. package/dist/hooks/WidgetInteraction/types.d.ts +28 -3
  165. package/dist/hooks/WidgetInteraction/types.js +12 -2
  166. package/dist/hooks/utils.d.cts +1 -1
  167. package/dist/hooks/utils.d.ts +1 -1
  168. package/dist/services/amplitudeService/amplitudeService.cjs +16 -10
  169. package/dist/services/amplitudeService/amplitudeService.d.cts +5 -2
  170. package/dist/services/amplitudeService/amplitudeService.d.ts +5 -2
  171. package/dist/services/amplitudeService/amplitudeService.js +14 -7
  172. package/dist/services/enviveConfigService/enviveConfigService.cjs +3 -4
  173. package/dist/services/enviveConfigService/enviveConfigService.d.cts +1 -1
  174. package/dist/services/enviveConfigService/enviveConfigService.d.ts +1 -1
  175. package/dist/services/enviveConfigService/enviveConfigService.js +2 -3
  176. package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +8 -7
  177. package/dist/services/enviveConfigService/fetchGraphQLConfig.js +8 -6
  178. package/dist/services/featureFlagService/index.cjs +101 -0
  179. package/dist/services/featureFlagService/index.d.cts +28 -0
  180. package/dist/services/featureFlagService/index.d.ts +28 -0
  181. package/dist/services/featureFlagService/index.js +101 -0
  182. package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +8 -2
  183. package/dist/services/ga4ProjectionService/ga4EventSchema.js +8 -2
  184. package/dist/services/hardcopyService/hardcopyService.d.cts +1 -1
  185. package/dist/services/hardcopyService/hardcopyService.d.ts +1 -1
  186. package/dist/services/urlResolverWarmupService/index.cjs +54 -0
  187. package/dist/services/urlResolverWarmupService/index.d.cts +40 -0
  188. package/dist/services/urlResolverWarmupService/index.d.ts +40 -0
  189. package/dist/services/urlResolverWarmupService/index.js +54 -0
  190. package/dist/types/enviveConfig.d.cts +6 -2
  191. package/dist/types/enviveConfig.d.ts +6 -2
  192. package/dist/types/index.d.cts +2 -2
  193. package/dist/types/index.d.ts +2 -2
  194. package/package.json +17 -1
  195. package/src/application/__tests__/commerceApiAddToCartRehydration.test.ts +126 -0
  196. package/src/application/commerce-api.ts +142 -23
  197. package/src/application/models/api/userEvent.ts +8 -1
  198. package/src/application/models/featureGates.ts +5 -0
  199. package/src/application/models/graphql/queries/getProductOptionsQuery.ts +59 -0
  200. package/src/application/models/validators/__tests__/validateUserEvent.test.ts +41 -0
  201. package/src/application/models/validators/validateResponse.ts +1 -0
  202. package/src/application/models/validators/validateUserEvent.ts +7 -2
  203. package/src/application/utils/__tests__/messageFromAddToCartEvent.test.ts +109 -0
  204. package/src/application/utils/index.ts +1 -0
  205. package/src/application/utils/messageFromAddToCartEvent.ts +32 -0
  206. package/src/atoms/app/index.ts +1 -1
  207. package/src/atoms/envive/enviveConfig.ts +6 -1
  208. package/src/atoms/org/graphqlConfig.ts +1 -1
  209. package/src/contexts/enviveConfigContext/enviveConfigContext.tsx +5 -0
  210. package/src/contexts/enviveContext/enviveContext.tsx +10 -0
  211. package/src/contexts/featureFlagServiceContext/featureFlagServiceContext.tsx +6 -173
  212. package/src/contexts/localStorageContext/localStorageContext.tsx +2 -5
  213. package/src/contexts/localStorageContext/localStorageKeys.ts +8 -0
  214. package/src/contexts/salesAgentContext/__tests__/chatAPI.test.tsx +63 -0
  215. package/src/contexts/salesAgentContext/chatAPI.ts +20 -1
  216. package/src/contexts/salesAgentContext/salesAgentContext.tsx +10 -0
  217. package/src/contexts/types.ts +2 -0
  218. package/src/contexts/typesV3.ts +14 -0
  219. package/src/hooks/AddToCart/__tests__/parseAddToCartFunction.test.ts +31 -0
  220. package/src/hooks/AddToCart/__tests__/useAddToCart.test.tsx +204 -0
  221. package/src/hooks/AddToCart/addToCartErrorMessage.ts +2 -0
  222. package/src/hooks/AddToCart/createAddToCartUserEvent.ts +25 -0
  223. package/src/hooks/AddToCart/index.ts +4 -0
  224. package/src/hooks/AddToCart/parseAddToCartFunction.ts +31 -0
  225. package/src/hooks/AddToCart/useAddToCart.ts +35 -0
  226. package/src/hooks/PageViewedEvent/usePageViewedEvent.ts +4 -0
  227. package/src/hooks/ProductDetails/__tests__/useProductDetails.test.tsx +129 -0
  228. package/src/hooks/ProductDetails/__tests__/variantSelection.test.ts +145 -0
  229. package/src/hooks/ProductDetails/index.ts +2 -0
  230. package/src/hooks/ProductDetails/useProductDetails.ts +62 -0
  231. package/src/hooks/ProductDetails/variantSelection.ts +61 -0
  232. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +15 -0
  233. package/src/hooks/WidgetInteraction/types.ts +42 -1
  234. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +8 -8
  235. package/src/services/amplitudeService/amplitudeService.ts +42 -23
  236. package/src/services/enviveConfigService/enviveConfigService.ts +2 -2
  237. package/src/services/enviveConfigService/fetchGraphQLConfig.ts +17 -6
  238. package/src/services/featureFlagService/index.ts +176 -0
  239. package/src/services/ga4ProjectionService/__tests__/ga4ProjectionService.test.ts +36 -0
  240. package/src/services/ga4ProjectionService/ga4EventSchema.ts +7 -0
  241. package/src/services/urlResolverWarmupService/__tests__/urlResolverWarmupService.test.ts +85 -0
  242. package/src/services/urlResolverWarmupService/index.ts +89 -0
  243. package/src/types/enviveConfig.ts +3 -0
@@ -0,0 +1,32 @@
1
+ import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
2
+ import { Message, MessageRole, MessageType, UserEvent } from 'src/application/models';
3
+
4
+ /**
5
+ * Transforms an AddToCart UserEvent object into a Message object for presentation.
6
+ *
7
+ * @param event The user event object received from the server
8
+ * @param productTitle Resolved product title to display instead of the raw
9
+ * productId. Falls back to `event.attributes.productTitle` (set on freshly
10
+ * created, not-yet-serialized events) and finally to the productId itself.
11
+ *
12
+ * @returns A Message if the event is an AddToCart event, otherwise undefined
13
+ */
14
+ export const messageFromAddToCartEvent = (
15
+ event: UserEvent,
16
+ productTitle?: string,
17
+ ): Message | undefined => {
18
+ if (event.category === UserEventCategory.AddToCart) {
19
+ const label = productTitle ?? event.attributes.productTitle ?? event.attributes.productId;
20
+ return {
21
+ id: event.eventId,
22
+ role: MessageRole.User,
23
+ type: MessageType.QueryTyped,
24
+ createdAt: event.createdAt,
25
+ metadata: {
26
+ content: `Add ${label} to my cart`,
27
+ },
28
+ };
29
+ }
30
+
31
+ return undefined;
32
+ };
@@ -2,7 +2,7 @@ import { ContextEnvEnum, ContextSourceEnum } from '@spiffy-ai/commerce-api-clien
2
2
  import { atom } from 'jotai';
3
3
  import { atomWithStorage } from 'jotai/utils';
4
4
  import { VariantInfo } from 'src/application/models';
5
- import { UserIdentityContextType } from 'src/contexts/userIdentityContext/userIdentityContext';
5
+ import type { UserIdentityContextType } from 'src/contexts/userIdentityContext/userIdentityContext';
6
6
  import { v4 as uuid } from 'uuid';
7
7
  import { contextSourceAtom, envAtom, orgIdAtom, orgShortNameAtom } from '../envive/enviveConfig';
8
8
 
@@ -2,7 +2,7 @@ import Logger from 'src/application/logging/logger';
2
2
  import { atom } from 'jotai';
3
3
  import { EnviveConfig } from 'src/contexts/types';
4
4
  import { ContextSourceEnum } from '@spiffy-ai/commerce-api-client';
5
- import { LocalStorageKeys } from 'src/contexts/localStorageContext';
5
+ import { LocalStorageKeys } from 'src/contexts/localStorageContext/localStorageKeys';
6
6
 
7
7
  const logger = new Logger('enviveConfig');
8
8
 
@@ -49,6 +49,11 @@ export const orgIdAtom = atom(get => {
49
49
  return config?.orgId;
50
50
  });
51
51
 
52
+ export const addToCartCallbackAtom = atom(get => {
53
+ const config = get(enviveConfigAtom);
54
+ return config?.addToCart;
55
+ });
56
+
52
57
  export const contextSourceAtom = atom(get => {
53
58
  const config = get(enviveConfigAtom);
54
59
 
@@ -1,7 +1,7 @@
1
1
  import { atom } from 'jotai';
2
2
  import { ColorMapping } from 'src/application/models/colorsConfig';
3
3
  import { FrontendConfig } from 'src/application/models/frontendConfig';
4
- import { FeatureFlagContextType } from 'src/contexts/featureFlagServiceContext/featureFlagServiceContext'; // Import FeatureFlagContextType from new context
4
+ import type { FeatureFlagContextType } from 'src/contexts/featureFlagServiceContext/featureFlagServiceContext';
5
5
 
6
6
  export const internalGraphQLColorsConfigAtom = atom<ColorMapping | undefined>(undefined);
7
7
  const internalGraphQLFrontendConfigAtom = atom<FrontendConfig | undefined>(undefined);
@@ -3,6 +3,7 @@ import { useSetAtom } from 'jotai';
3
3
  import { enviveConfigAtom } from 'src/atoms/envive/enviveConfig';
4
4
  import Logger from 'src/application/logging/logger';
5
5
  import { OrgConfigFeatureGate } from 'src/application/models';
6
+ import { AddToCartCallback } from 'src/types/enviveConfig';
6
7
 
7
8
  const logger = new Logger('enviveConfigContext');
8
9
 
@@ -24,6 +25,7 @@ interface EnviveConfigProviderProps {
24
25
  publicKey?: string;
25
26
  featureOverrides?: Record<string, boolean>;
26
27
  featureGates?: OrgConfigFeatureGate[];
28
+ addToCart?: AddToCartCallback;
27
29
  }
28
30
 
29
31
  export const EnviveConfigProvider: React.FC<EnviveConfigProviderProps> = ({
@@ -44,6 +46,7 @@ export const EnviveConfigProvider: React.FC<EnviveConfigProviderProps> = ({
44
46
  publicKey,
45
47
  featureOverrides,
46
48
  featureGates,
49
+ addToCart,
47
50
  }) => {
48
51
  const setEnviveConfig = useSetAtom(enviveConfigAtom);
49
52
 
@@ -65,6 +68,7 @@ export const EnviveConfigProvider: React.FC<EnviveConfigProviderProps> = ({
65
68
  publicKey,
66
69
  featureOverrides,
67
70
  featureGates,
71
+ addToCart,
68
72
  }),
69
73
  [
70
74
  identifyingPrefix,
@@ -83,6 +87,7 @@ export const EnviveConfigProvider: React.FC<EnviveConfigProviderProps> = ({
83
87
  publicKey,
84
88
  featureOverrides,
85
89
  featureGates,
90
+ addToCart,
86
91
  ],
87
92
  );
88
93
 
@@ -7,6 +7,7 @@ import { resolvedBaseConfigVersionAtom } from 'src/atoms/envive/resolvedBaseConf
7
7
  import { IHardcopyService } from 'src/services/hardcopyService';
8
8
  import { IUserIdentityService } from 'src/services/userIdentityService';
9
9
  import Logger from 'src/application/logging/logger';
10
+ import { AddToCartCallback } from 'src/types/enviveConfig';
10
11
  import { AmplitudeProvider } from '../amplitudeContext';
11
12
  import { EnviveConfigProvider } from '../enviveConfigContext';
12
13
  import { EnviveCssProvider } from '../enviveCssContext';
@@ -51,8 +52,11 @@ interface EnviveProviderProps extends AgentWrapperProps {
51
52
  enviveOn?: boolean;
52
53
  cdnUrl?: string;
53
54
  reactAppName?: string;
55
+ env?: string;
56
+ contextSource?: string;
54
57
  previewMode?: boolean;
55
58
  mockSalesAgentData?: any;
59
+ addToCart?: AddToCartCallback;
56
60
  }
57
61
 
58
62
  const SearchAgentWrapper: React.FC<AgentWrapperProps> = ({ children, enabledAgents }) => {
@@ -100,7 +104,10 @@ export const EnviveProvider: React.FC<EnviveProviderProps> = ({
100
104
  enviveOn,
101
105
  cdnUrl,
102
106
  reactAppName,
107
+ env,
108
+ contextSource,
103
109
  identifyingPrefix,
110
+ addToCart,
104
111
  }) => {
105
112
  const { extensionService } = services;
106
113
  const [enviveServiceConfig, setEnviveServiceConfig] = useState<EnviveServiceConfig | null>(null);
@@ -196,6 +203,9 @@ export const EnviveProvider: React.FC<EnviveProviderProps> = ({
196
203
  enviveOn={enviveOn}
197
204
  cdnUrl={cdnUrl}
198
205
  reactAppName={reactAppName}
206
+ env={env}
207
+ contextSource={contextSource}
208
+ addToCart={addToCart}
199
209
  >
200
210
  <NewOrgConfigProvider config={enviveServiceConfig.orgConfig ?? {}}>
201
211
  <LocalStorageProvider>
@@ -1,178 +1,11 @@
1
1
  import React, { ReactNode, createContext, useContext, useMemo } from 'react';
2
- import { OrgConfigFeatureGate } from 'src/application/models/api/orgConfigResults';
3
- import { FeatureGates } from 'src/application/models/featureGates';
4
- import Logger from 'src/application/logging/logger';
2
+ import { FeatureFlagService, FullFlagValue } from 'src/services/featureFlagService';
5
3
 
6
- const FEATURE_FLAGS_STORAGE_KEY = 'spiffy-feature-flags';
7
-
8
- const logger = new Logger('featureFlagServiceContext');
9
-
10
- export interface FullFlagValue {
11
- resolved: boolean;
12
- initial: boolean | undefined;
13
- query_param_override: boolean | undefined;
14
- window_param_override: boolean | undefined;
15
- stored_override: boolean | undefined;
16
- }
17
-
18
- // This is the class that was previously implicitly used or defined elsewhere
19
- export class FeatureFlagService {
20
- private featureGates: OrgConfigFeatureGate[];
21
-
22
- constructor(featureGates: OrgConfigFeatureGate[]) {
23
- this.featureGates = featureGates;
24
- }
25
-
26
- static getQueryParamFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
27
- const url = window.location?.href ?? '';
28
- if (url.includes('?')) {
29
- const urlObj = new URL(url);
30
- const params = new URLSearchParams(urlObj.search);
31
- const paramsObj = Object.fromEntries(params.entries());
32
- const value = paramsObj[featureGate];
33
-
34
- if (value != null) {
35
- return value === 'true';
36
- }
37
- }
38
-
39
- return undefined;
40
- }
41
-
42
- static getWindowFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
43
- if (typeof window === 'undefined') {
44
- return undefined;
45
- }
46
- const windowOverride = window as Window & {
47
- _envive?: { featureOverrides?: Record<string, boolean> };
48
- _spiffy?: { featureOverrides?: Record<string, boolean> };
49
- };
50
- const featureOverrides =
51
- windowOverride?._envive?.featureOverrides ?? windowOverride?._spiffy?.featureOverrides;
52
- const value = featureOverrides?.[String(featureGate)];
53
- if (value != null) {
54
- return value;
55
- }
56
- return undefined;
57
- }
58
-
59
- static getStoredFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
60
- if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
61
- return undefined;
62
- }
63
- try {
64
- const featureFlags = window.localStorage.getItem(FEATURE_FLAGS_STORAGE_KEY);
65
- if (!featureFlags) {
66
- return undefined;
67
- }
68
- const featureFlagsObj = JSON.parse(featureFlags) as Record<string, boolean>;
69
- return featureFlagsObj[featureGate];
70
- } catch (err) {
71
- logger.logError('getStoredFeatureGateOverrides: error parsing feature flags', err);
72
- return undefined;
73
- }
74
- }
75
-
76
- static getFeatureFlagOverride(featureGate: FeatureGates): boolean | undefined {
77
- const queryOverride = FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate);
78
- if (queryOverride != null) {
79
- logger.logDebug(
80
- `isFeatureGateEnabled featureGate:${featureGate} overridden by URL param. New value: ${queryOverride}`,
81
- );
82
- return queryOverride;
83
- }
84
-
85
- const windowOverride = FeatureFlagService.getWindowFeatureGateOverrides(featureGate);
86
- if (windowOverride != null) {
87
- logger.logDebug(
88
- `isFeatureGateEnabled featureGate:${featureGate} overridden by window param. New value: ${windowOverride}`,
89
- );
90
- return windowOverride;
91
- }
92
-
93
- const storedOverride = FeatureFlagService.getStoredFeatureGateOverrides(featureGate);
94
- if (storedOverride != null) {
95
- logger.logDebug(
96
- `isFeatureGateEnabled featureGate:${featureGate} overridden by stored value. New value: ${storedOverride}`,
97
- );
98
- return storedOverride;
99
- }
100
-
101
- return undefined;
102
- }
103
-
104
- isFeatureGateEnabled = (featureGate: FeatureGates): boolean => {
105
- const gateValue = this.featureGates.find(gate => gate.name === featureGate);
106
-
107
- const featureFlagOverride = FeatureFlagService.getFeatureFlagOverride(featureGate);
108
- if (featureFlagOverride !== undefined) {
109
- return featureFlagOverride;
110
- }
111
-
112
- if (gateValue == null || gateValue.value == null) {
113
- logger.logDebug(
114
- `isFeatureGateEnabled featureGate:${featureGate} value is undefined - returning false`,
115
- );
116
- return false;
117
- }
118
- return gateValue.value;
119
- };
120
-
121
- // Ensure that standard overrides work as expected
122
- isClientSessionEnabled = (): boolean =>
123
- this.isFeatureGateEnabled(FeatureGates.IsClientSessionEnabled);
124
-
125
- getFeatureFlags = (): Record<string, boolean> => {
126
- return Object.fromEntries(
127
- Object.values(FeatureGates).map((featureGate: FeatureGates) => [
128
- featureGate,
129
- this.isFeatureGateEnabled(featureGate),
130
- ]),
131
- );
132
- };
133
-
134
- getFullFlagValue = (featureGate: FeatureGates): FullFlagValue => {
135
- const gateValue = this.featureGates.find(gate => gate.name === featureGate);
136
- return {
137
- resolved: this.isFeatureGateEnabled(featureGate),
138
- initial: gateValue?.value,
139
- query_param_override: FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate),
140
- window_param_override: FeatureFlagService.getWindowFeatureGateOverrides(featureGate),
141
- stored_override: FeatureFlagService.getStoredFeatureGateOverrides(featureGate),
142
- };
143
- };
144
-
145
- getFullFlagValues = (): Record<string, FullFlagValue> => {
146
- const featureGates = Object.values(FeatureGates).map((featureGate: FeatureGates) => [
147
- featureGate,
148
- this.getFullFlagValue(featureGate),
149
- ]);
150
-
151
- return featureGates.length > 0
152
- ? featureGates.reduce<Record<string, FullFlagValue>>((acc, entry) => {
153
- const [name, value] = entry as [FeatureGates, FullFlagValue];
154
- if (name && value != null) {
155
- return { ...acc, [`feature_gate.${name}`]: value };
156
- }
157
- return acc;
158
- }, {})
159
- : {};
160
- };
161
-
162
- static persistFeatureGateOverrides(): void {
163
- if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
164
- return;
165
- }
166
- const overrides = Object.values(FeatureGates)
167
- .map(
168
- featureGate =>
169
- [featureGate, FeatureFlagService.getFeatureFlagOverride(featureGate)] as const,
170
- )
171
- .filter(([, value]) => value !== undefined)
172
- .reduce<Record<string, boolean>>((acc, [key, value]) => ({ ...acc, [key]: value! }), {});
173
- window.localStorage.setItem(FEATURE_FLAGS_STORAGE_KEY, JSON.stringify(overrides));
174
- }
175
- }
4
+ // The service class lives in src/services/featureFlagService (a React-free
5
+ // module the injection bundle can import pre-config); re-export it here so
6
+ // existing imports keep working.
7
+ export { FeatureFlagService };
8
+ export type { FullFlagValue };
176
9
 
177
10
  export interface FeatureFlagContextType {
178
11
  featureFlagService: FeatureFlagService | undefined;
@@ -1,12 +1,9 @@
1
1
  import React, { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
2
2
  import Logger from 'src/application/logging/logger';
3
3
  import { LocalStorageEventListener } from 'src/application/models/localStorageEventListener';
4
+ import { LocalStorageKeys } from './localStorageKeys';
4
5
 
5
- export enum LocalStorageKeys {
6
- ChatId = 'v1-spiffy-chat-session-id',
7
- SpiffyOnOverride = 'spiffy_on',
8
- EnviveOnOverride = 'envive_on',
9
- }
6
+ export { LocalStorageKeys };
10
7
 
11
8
  interface LocalStorageContextType {
12
9
  setItem: (key: string, value: string) => void;
@@ -0,0 +1,8 @@
1
+ // Kept React-free: pre-config injection-bundle code (atoms/envive) needs
2
+ // these keys, and importing them through localStorageContext.tsx would pull
3
+ // React into the eager critical chunk.
4
+ export enum LocalStorageKeys {
5
+ ChatId = 'v1-spiffy-chat-session-id',
6
+ SpiffyOnOverride = 'spiffy_on',
7
+ EnviveOnOverride = 'envive_on',
8
+ }
@@ -0,0 +1,63 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { Provider, createStore } from 'jotai';
3
+ import React from 'react';
4
+ import { Message, MessageRole, MessageType } from 'src/application/models/message';
5
+ import { messagesAtom } from 'src/atoms/chat/chatState';
6
+ import { useSalesAgentChatAPI } from '../chatAPI';
7
+
8
+ const mockTrackEvent = vi.fn();
9
+ vi.mock('../../amplitudeContext', async () => {
10
+ const actual = await vi.importActual('../../amplitudeContext');
11
+ return {
12
+ ...actual,
13
+ useAmplitude: () => ({ trackEvent: mockTrackEvent }),
14
+ };
15
+ });
16
+
17
+ vi.mock('src/hooks/WidgetInteraction', () => ({
18
+ useWidgetInteraction: () => ({ trackWidgetInteraction: vi.fn() }),
19
+ }));
20
+
21
+ const asTextMessage = (message: Message) => message as Extract<Message, { type: MessageType.Text }>;
22
+
23
+ const renderChatAPI = (store: ReturnType<typeof createStore>) => {
24
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
25
+ // The jotai Provider is typed against the root @types/react (18) while this
26
+ // package resolves @types/react 19; the cast bridges the two ReactNode types.
27
+ <Provider store={store}>{children as React.ReactElement<unknown, string>}</Provider>
28
+ );
29
+ return renderHook(() => useSalesAgentChatAPI(), { wrapper });
30
+ };
31
+
32
+ describe('useSalesAgentChatAPI.injectAssistantMessage', () => {
33
+ it('appends an assistant text message turn onto messagesAtom', () => {
34
+ const store = createStore();
35
+ const { result } = renderChatAPI(store);
36
+
37
+ result.current.injectAssistantMessage("I'm sorry, something went wrong.");
38
+
39
+ const messages = store.get(messagesAtom);
40
+ expect(messages).toHaveLength(1);
41
+ expect(messages[0]).toHaveLength(1);
42
+ expect(messages[0][0]).toMatchObject({
43
+ role: MessageRole.Assistant,
44
+ type: MessageType.Text,
45
+ metadata: { content: "I'm sorry, something went wrong." },
46
+ });
47
+ expect(messages[0][0].id).toEqual(expect.any(String));
48
+ expect(messages[0][0].createdAt).toEqual(expect.any(String));
49
+ });
50
+
51
+ it('appends subsequent messages as additional turns without clobbering prior ones', () => {
52
+ const store = createStore();
53
+ const { result } = renderChatAPI(store);
54
+
55
+ result.current.injectAssistantMessage('first');
56
+ result.current.injectAssistantMessage('second');
57
+
58
+ const messages = store.get(messagesAtom);
59
+ expect(messages).toHaveLength(2);
60
+ expect(asTextMessage(messages[0][0]).metadata).toMatchObject({ content: 'first' });
61
+ expect(asTextMessage(messages[1][0]).metadata).toMatchObject({ content: 'second' });
62
+ });
63
+ });
@@ -6,6 +6,7 @@ import Logger from 'src/application/logging/logger';
6
6
  import {
7
7
  ChatElementDisplayLocationV3,
8
8
  FormSubmittedAttributes,
9
+ Message,
9
10
  MessageRole,
10
11
  MessageType,
11
12
  Suggestion,
@@ -14,7 +15,7 @@ import {
14
15
  import { hasParsedVariantInfoAtom } from 'src/atoms/app';
15
16
  import { analyticsContextAtom } from 'src/atoms/app/variant';
16
17
  import { formSubmitAtom, replyEventCategoryAtom } from 'src/atoms/chat';
17
- import { suggestionsAtom } from 'src/atoms/chat/chatState';
18
+ import { messagesAtom, suggestionsAtom } from 'src/atoms/chat/chatState';
18
19
  import { queueUserEventAtom } from 'src/atoms/chat/messageQueue';
19
20
  import { useWidgetInteraction } from 'src/hooks/WidgetInteraction';
20
21
  import {
@@ -49,6 +50,8 @@ export interface SalesAgentChatAPI {
49
50
  displayLocation: ChatElementDisplayLocationV3;
50
51
  }) => void;
51
52
  onFormResponseSubmitted: (form: FormSubmittedAttributes) => void;
53
+ /** Injects a synthetic assistant text message into the chat transcript, e.g. to surface a failure. */
54
+ injectAssistantMessage: (content: string) => void;
52
55
  }
53
56
 
54
57
  export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
@@ -60,6 +63,7 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
60
63
  const setSuggestions = useSetAtom(suggestionsAtom);
61
64
  const setReplyEventCategory = useSetAtom(replyEventCategoryAtom);
62
65
  const setFormSubmit = useSetAtom(formSubmitAtom);
66
+ const setMessages = useSetAtom(messagesAtom);
63
67
  const { trackEvent } = useAmplitude();
64
68
  const { trackWidgetInteraction } = useWidgetInteraction();
65
69
  const contextWidget = widget || WidgetInteractionComponent.FLOATING_CHAT;
@@ -260,11 +264,26 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
260
264
  [setReplyEventCategory, setFormSubmit, trackEvent, context, queueUserEvent],
261
265
  );
262
266
 
267
+ const injectAssistantMessage = useCallback(
268
+ (content: string) => {
269
+ const message: Message = {
270
+ id: uuid(),
271
+ role: MessageRole.Assistant,
272
+ type: MessageType.Text,
273
+ createdAt: new Date().toISOString(),
274
+ metadata: { content },
275
+ };
276
+ setMessages(current => [...current, [message]]);
277
+ },
278
+ [setMessages],
279
+ );
280
+
263
281
  return {
264
282
  logPageVisit,
265
283
  logUserEvent,
266
284
  onSuggestionClicked,
267
285
  onTypedMessageSubmitted,
268
286
  onFormResponseSubmitted,
287
+ injectAssistantMessage,
269
288
  };
270
289
  };
@@ -4,6 +4,7 @@ import { ReactNode, createContext, useCallback, useEffect, useMemo, useRef, useS
4
4
  import Logger from 'src/application/logging/logger';
5
5
  import { NextMessageRequest, Suggestion, UserEvent } from 'src/application/models';
6
6
  import { Message, MessageRole, MessageType } from 'src/application/models/message';
7
+ import { messageFromAddToCartEvent } from 'src/application/utils';
7
8
  import {
8
9
  initializedAtom,
9
10
  messagesAtom,
@@ -153,6 +154,12 @@ export const SalesAgentProvider: React.FC<SalesAgentProviderProps> = ({
153
154
  };
154
155
  setMessages(current => [...current, [newMessage]]);
155
156
  }
157
+ if (userEvent && userEvent.category === UserEventCategory.AddToCart) {
158
+ const newMessage = messageFromAddToCartEvent(userEvent);
159
+ if (newMessage) {
160
+ setMessages(current => [...current, [newMessage]]);
161
+ }
162
+ }
156
163
  if (userEvent && userEvent.category === UserEventCategory.FormSubmitted) {
157
164
  const { formResponseId, filledSchema } = userEvent.attributes;
158
165
  setMessages(prevMessagesState => {
@@ -239,6 +246,7 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
239
246
  onSuggestionClicked,
240
247
  onTypedMessageSubmitted,
241
248
  onFormResponseSubmitted,
249
+ injectAssistantMessage,
242
250
  } = useSalesAgentChatAPI(contextWidget);
243
251
 
244
252
  const salesAgent: SalesAgent = useMemo(() => {
@@ -255,6 +263,7 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
255
263
  onSuggestionClicked,
256
264
  onTypedMessageSubmitted,
257
265
  onFormResponseSubmitted,
266
+ injectAssistantMessage,
258
267
  };
259
268
  }, [
260
269
  messages,
@@ -269,6 +278,7 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
269
278
  onSuggestionClicked,
270
279
  onTypedMessageSubmitted,
271
280
  onFormResponseSubmitted,
281
+ injectAssistantMessage,
272
282
  ]);
273
283
  return salesAgent;
274
284
  };
@@ -13,6 +13,7 @@ import { ChatState } from 'src/types/custservice-types';
13
13
  import { SuggestionBarLocationForMetrics } from 'src/types/suggestionBarV2-types';
14
14
  import { TestProps } from 'src/types/test-types';
15
15
  import { OrgConfigFeatureGate } from 'src/application/models/api/orgConfigResults';
16
+ import type { AddToCartCallback } from 'src/types/enviveConfig';
16
17
  import { WidgetConfigV3, WidgetTypeV3 } from './typesV3';
17
18
  import type { HardcopyResponse } from './hardcopyContext/hardcopyContext';
18
19
 
@@ -939,6 +940,7 @@ export interface EnviveConfig {
939
940
  publicKey?: string;
940
941
  featureGates?: OrgConfigFeatureGate[];
941
942
  hardcopyOverride?: Partial<Record<WidgetTypeV3, HardcopyResponse>>;
943
+ addToCart?: AddToCartCallback;
942
944
  }
943
945
 
944
946
  export type {
@@ -111,6 +111,17 @@ export const enum ButtonColorsVars {
111
111
 
112
112
  type ImageAspectRatio = '1/1' | '3/4';
113
113
 
114
+ type AddToCartConfig = {
115
+ enabled: boolean;
116
+ // Colors are CSS variable strings, e.g. 'var(--envive-colors-background-dark)'
117
+ backgroundColor?: string;
118
+ textColor?: string;
119
+ ctaText?: string;
120
+ // Merchant-supplied JS function, as a string, used to invoke their cart.
121
+ // Ignored when window._spiffy/_envive.addToCart is set - the window variable always takes precedence.
122
+ addToCartFunction?: string;
123
+ };
124
+
114
125
  type ButtonColors = {
115
126
  lightButtonBackgroundColor: string;
116
127
  lightButtonBorderColor?: string;
@@ -193,6 +204,7 @@ type LookAndFeelConfig = {
193
204
  imageAspectRatio: ImageAspectRatio;
194
205
  buttonColors: ButtonColors;
195
206
  typography: Typography;
207
+ addToCart?: AddToCartConfig;
196
208
  };
197
209
 
198
210
  export enum ConfigVersionEnum {
@@ -385,6 +397,7 @@ interface FullPageSalesAgentWidgetV3Config extends BaseWidgetConfig<WidgetTypeV3
385
397
  mobileBackgroundImage?: string;
386
398
  desktopBackgroundImage?: string;
387
399
  backgroundColor?: string;
400
+ chatBackgroundColor?: string;
388
401
  suggestionButtonType?: PromptButtonVariant;
389
402
  }
390
403
 
@@ -402,6 +415,7 @@ type WidgetConfigV3 =
402
415
  | FullPageSalesAgentWidgetV3Config;
403
416
 
404
417
  export type {
418
+ AddToCartConfig,
405
419
  ChatPreviewComparisonWidgetV3Config,
406
420
  ChatPreviewLoadingWidgetV3Config,
407
421
  ChatPreviewWidgetV3Config,
@@ -0,0 +1,31 @@
1
+ import { parseAddToCartFunction } from '../parseAddToCartFunction';
2
+
3
+ describe('parseAddToCartFunction', () => {
4
+ it('returns undefined when no string is provided', () => {
5
+ expect(parseAddToCartFunction(undefined)).toBeUndefined();
6
+ });
7
+
8
+ it('parses a function-expression string into a callable', async () => {
9
+ const fn = parseAddToCartFunction('function(params) { globalThis.__testCalls = [params]; }');
10
+
11
+ expect(fn).toBeInstanceOf(Function);
12
+ fn?.({ product_id: 'abc' });
13
+ expect((globalThis as Record<string, unknown>).__testCalls).toEqual([{ product_id: 'abc' }]);
14
+ });
15
+
16
+ it('parses an arrow-function string into a callable', () => {
17
+ const fn = parseAddToCartFunction('(params) => { globalThis.__testCalls = [params]; }');
18
+
19
+ expect(fn).toBeInstanceOf(Function);
20
+ fn?.({ product_id: 'xyz' });
21
+ expect((globalThis as Record<string, unknown>).__testCalls).toEqual([{ product_id: 'xyz' }]);
22
+ });
23
+
24
+ it('returns undefined when the string has a syntax error', () => {
25
+ expect(parseAddToCartFunction('function( { broken')).toBeUndefined();
26
+ });
27
+
28
+ it('returns undefined when the string evaluates to a non-function value', () => {
29
+ expect(parseAddToCartFunction('42')).toBeUndefined();
30
+ });
31
+ });