@envive-ai/react-hooks 0.3.22 → 0.3.24

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 (84) hide show
  1. package/dist/application/models/featureGates.cjs +2 -1
  2. package/dist/application/models/featureGates.d.cts +2 -1
  3. package/dist/application/models/featureGates.d.ts +2 -1
  4. package/dist/application/models/featureGates.js +2 -1
  5. package/dist/atoms/app/index.d.ts +7 -7
  6. package/dist/atoms/app/variant.d.cts +6 -6
  7. package/dist/atoms/app/variant.d.ts +6 -6
  8. package/dist/atoms/chat/chatState.cjs +3 -1
  9. package/dist/atoms/chat/chatState.d.cts +22 -19
  10. package/dist/atoms/chat/chatState.d.ts +22 -19
  11. package/dist/atoms/chat/chatState.js +3 -2
  12. package/dist/atoms/chat/form.d.cts +3 -3
  13. package/dist/atoms/chat/form.d.ts +2 -2
  14. package/dist/atoms/chat/index.cjs +1 -0
  15. package/dist/atoms/chat/index.d.cts +2 -2
  16. package/dist/atoms/chat/index.d.ts +4 -4
  17. package/dist/atoms/chat/index.js +2 -2
  18. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  19. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  20. package/dist/atoms/chat/messageQueue.d.ts +6 -6
  21. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  22. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  23. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  24. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  25. package/dist/atoms/chat/replies.d.cts +3 -3
  26. package/dist/atoms/chat/replies.d.ts +2 -2
  27. package/dist/atoms/chat/suggestions.d.cts +2 -2
  28. package/dist/atoms/chat/suggestions.d.ts +2 -2
  29. package/dist/atoms/envive/enviveConfig.d.cts +13 -13
  30. package/dist/atoms/envive/enviveConfig.d.ts +13 -13
  31. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  32. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  33. package/dist/atoms/org/customerService.d.cts +6 -6
  34. package/dist/atoms/org/customerService.d.ts +6 -6
  35. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  36. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  37. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  38. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  39. package/dist/atoms/org/orgAnalyticsConfig.d.cts +5 -5
  40. package/dist/atoms/org/orgAnalyticsConfig.d.ts +5 -5
  41. package/dist/atoms/search/chatSearch.d.cts +17 -17
  42. package/dist/atoms/search/chatSearch.d.ts +17 -17
  43. package/dist/atoms/search/searchAPI.d.cts +13 -13
  44. package/dist/atoms/search/searchAPI.d.ts +13 -13
  45. package/dist/atoms/search/types.d.cts +1 -1
  46. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  47. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  48. package/dist/contexts/graphqlContext/graphqlContext.cjs +4 -4
  49. package/dist/contexts/graphqlContext/graphqlContext.js +4 -4
  50. package/dist/contexts/hardcopyContext/hardcopyContext.cjs +5 -3
  51. package/dist/contexts/hardcopyContext/hardcopyContext.js +5 -3
  52. package/dist/contexts/salesAgentContext/chatAPI.cjs +12 -5
  53. package/dist/contexts/salesAgentContext/chatAPI.js +13 -6
  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.cjs +1 -1
  58. package/dist/contexts/typesV3.d.cts +2 -1
  59. package/dist/contexts/typesV3.d.ts +2 -1
  60. package/dist/contexts/typesV3.js +1 -1
  61. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
  62. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +8 -2
  63. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +8 -2
  64. package/dist/hooks/WidgetInteraction/types.cjs +6 -2
  65. package/dist/hooks/WidgetInteraction/types.d.cts +8 -3
  66. package/dist/hooks/WidgetInteraction/types.d.ts +8 -3
  67. package/dist/hooks/WidgetInteraction/types.js +6 -2
  68. package/dist/hooks/utils.d.ts +1 -1
  69. package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +31 -27
  70. package/dist/services/ga4ProjectionService/ga4EventSchema.js +31 -27
  71. package/dist/services/ga4ProjectionService/ga4ProjectionService.cjs +31 -5
  72. package/dist/services/ga4ProjectionService/ga4ProjectionService.js +31 -5
  73. package/package.json +1 -1
  74. package/src/application/models/featureGates.ts +1 -0
  75. package/src/atoms/chat/chatState.ts +1 -0
  76. package/src/contexts/graphqlContext/graphqlContext.tsx +6 -8
  77. package/src/contexts/hardcopyContext/hardcopyContext.tsx +10 -2
  78. package/src/contexts/salesAgentContext/chatAPI.ts +6 -2
  79. package/src/contexts/typesV3.ts +1 -0
  80. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +10 -2
  81. package/src/hooks/WidgetInteraction/types.ts +10 -1
  82. package/src/services/ga4ProjectionService/__tests__/ga4ProjectionService.test.ts +110 -49
  83. package/src/services/ga4ProjectionService/ga4EventSchema.ts +35 -27
  84. package/src/services/ga4ProjectionService/ga4ProjectionService.ts +60 -6
@@ -8,18 +8,39 @@ const filterToSchema = (eventProps, allowedFields) => {
8
8
  for (const field of allowedFields) if (field in eventProps) result[field] = eventProps[field];
9
9
  return result;
10
10
  };
11
+ const flattenOneLevel = (obj) => {
12
+ const result = {};
13
+ for (const [key, value] of Object.entries(obj)) if (!key.includes(".") && value !== null && typeof value === "object" && !Array.isArray(value)) for (const [subKey, subValue] of Object.entries(value)) result[`${key}.${subKey}`] = subValue;
14
+ else result[key] = value;
15
+ return result;
16
+ };
17
+ const filterWithProjections = (eventProps, projections) => {
18
+ const result = {};
19
+ for (const [ga4Key, sourceKey] of Object.entries(projections)) if (sourceKey in eventProps) result[ga4Key] = eventProps[sourceKey];
20
+ return result;
21
+ };
11
22
  const flattenDotKeys = (obj) => {
12
23
  const result = {};
13
24
  for (const [key, value] of Object.entries(obj)) result[key.replace(/\./g, "_")] = value;
14
25
  return result;
15
26
  };
16
27
  const sanitizePageId = (filtered) => {
17
- const pageType = filtered["context.page_type"];
28
+ const pageType = filtered["page_type"] ?? filtered["context.page_type"];
18
29
  if (pageType === "pdp" || pageType === "plp") return filtered;
19
30
  const rest = { ...filtered };
31
+ delete rest["page_id"];
20
32
  delete rest["context.page_id"];
21
33
  return rest;
22
34
  };
35
+ const getNestedValue = (obj, path) => {
36
+ const parts = path.split(".");
37
+ let current = obj;
38
+ for (const part of parts) {
39
+ if (current === null || typeof current !== "object") return void 0;
40
+ current = current[part];
41
+ }
42
+ return current;
43
+ };
23
44
  const projectWidgetInteractionData = (eventProps, config) => {
24
45
  if (!config.widgetInteractionDataProjections) return {};
25
46
  const interaction = eventProps["trigger.widget_interaction"];
@@ -30,7 +51,10 @@ const projectWidgetInteractionData = (eventProps, config) => {
30
51
  if (interactionData === null || interactionData === void 0 || typeof interactionData !== "object") return {};
31
52
  const data = interactionData;
32
53
  const result = {};
33
- for (const [gaKey, sourceField] of Object.entries(projectionMap)) if (sourceField in data) result[gaKey] = data[sourceField];
54
+ for (const [gaKey, sourcePath] of Object.entries(projectionMap)) {
55
+ const value = getNestedValue(data, sourcePath);
56
+ if (value !== void 0) result[gaKey] = value;
57
+ }
34
58
  return result;
35
59
  };
36
60
  const truncateString = (value) => {
@@ -50,8 +74,10 @@ const projectToGA4 = (eventName, eventProps) => {
50
74
  const schemaEntry = GA4_EVENT_SCHEMA[eventName];
51
75
  if (schemaEntry.gaEventName === null) return;
52
76
  const config = schemaEntry;
53
- const props = eventProps ?? {};
54
- let filtered = filterToSchema(props, config.allowedFields);
77
+ const props = flattenOneLevel(eventProps ?? {});
78
+ let filtered;
79
+ if (config.fieldProjections) filtered = filterWithProjections(props, config.fieldProjections);
80
+ else filtered = filterToSchema(props, config.allowedFields);
55
81
  filtered = sanitizePageId(filtered);
56
82
  const interactionFields = projectWidgetInteractionData(props, config);
57
83
  const truncatedParams = truncateValues({
@@ -69,4 +95,4 @@ const projectToGA4 = (eventName, eventProps) => {
69
95
 
70
96
  //#endregion
71
97
  export { projectToGA4 };
72
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2E0UHJvamVjdGlvblNlcnZpY2UuanMiLCJuYW1lcyI6WyJMb2dnZXIiLCJyZXN1bHQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+Il0sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZpY2VzL2dhNFByb2plY3Rpb25TZXJ2aWNlL2dhNFByb2plY3Rpb25TZXJ2aWNlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBMb2dnZXIgZnJvbSAnc3JjL2FwcGxpY2F0aW9uL2xvZ2dpbmcvbG9nZ2VyJztcbmltcG9ydCB7IEVudml2ZU1ldHJpY3NFdmVudE5hbWUgfSBmcm9tICcuLi9hbXBsaXR1ZGVTZXJ2aWNlL2V2ZW50TmFtZXMnO1xuaW1wb3J0IHsgR0E0UHJvamVjdGVkRXZlbnRDb25maWcsIEdBNF9FVkVOVF9TQ0hFTUEgfSBmcm9tICcuL2dhNEV2ZW50U2NoZW1hJztcblxuY29uc3QgbG9nZ2VyID0gbmV3IExvZ2dlcignZ2E0UHJvamVjdGlvblNlcnZpY2UnKTtcblxuY29uc3QgZmlsdGVyVG9TY2hlbWEgPSAoXG4gIGV2ZW50UHJvcHM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LFxuICBhbGxvd2VkRmllbGRzOiByZWFkb25seSBzdHJpbmdbXSxcbik6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0+IHtcbiAgY29uc3QgcmVzdWx0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICBmb3IgKGNvbnN0IGZpZWxkIG9mIGFsbG93ZWRGaWVsZHMpIHtcbiAgICBpZiAoZmllbGQgaW4gZXZlbnRQcm9wcykge1xuICAgICAgcmVzdWx0W2ZpZWxkXSA9IGV2ZW50UHJvcHNbZmllbGRdO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufTtcblxuLy8gXCJjb250ZXh0LnBhZ2VfdHlwZVwiIOKGkiBcImNvbnRleHRfcGFnZV90eXBlXCJcbmNvbnN0IGZsYXR0ZW5Eb3RLZXlzID0gKG9iajogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9PiB7XG4gIGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcbiAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2YgT2JqZWN0LmVudHJpZXMob2JqKSkge1xuICAgIHJlc3VsdFtrZXkucmVwbGFjZSgvXFwuL2csICdfJyldID0gdmFsdWU7XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbi8vIE9taXQgY29udGV4dC5wYWdlX2lkIGZvciBub24tcGRwL3BscCBwYWdlIHR5cGVzLiBUaGUgY3VycmVudCBpbXBsZW1lbnRhdGlvbiBmb3IgY29udGV4dC5wYWdlX2lkIGlzOlxuLy8gUERQOiBwcm9kdWN0X2lkXG4vLyBQTFA6IHBscF9pZFxuLy8gU2VhcmNoOiBzZWFyY2ggcXVlcnlcbi8vIE90aGVyOiBwYWdlIHVybFxuLy8gV2Ugd2FudCB0byBvbWl0IGFsbCBidXQgcGRwIGFuZCBwbHAgcGFnZSB0eXBlcyB0byBwcm92aWRlIGEgY2xlYXIsIGNvbnNpc3RlbnQgaW50ZXJmYWNlIGZvciBtZXJjaGFudHMuXG5jb25zdCBzYW5pdGl6ZVBhZ2VJZCA9IChmaWx0ZXJlZDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9PiB7XG4gIGNvbnN0IHBhZ2VUeXBlID0gZmlsdGVyZWRbJ2NvbnRleHQucGFnZV90eXBlJ107XG4gIGlmIChwYWdlVHlwZSA9PT0gJ3BkcCcgfHwgcGFnZVR5cGUgPT09ICdwbHAnKSB7XG4gICAgcmV0dXJuIGZpbHRlcmVkO1xuICB9XG4gIGNvbnN0IHJlc3QgPSB7IC4uLmZpbHRlcmVkIH07XG4gIGRlbGV0ZSByZXN0Wydjb250ZXh0LnBhZ2VfaWQnXTtcbiAgcmV0dXJuIHJlc3Q7XG59O1xuXG4vLyBFeHRyYWN0IHdoaXRlbGlzdGVkIHN1Yi1maWVsZHMgZnJvbSB0cmlnZ2VyLndpZGdldF9pbnRlcmFjdGlvbl9kYXRhXG5jb25zdCBwcm9qZWN0V2lkZ2V0SW50ZXJhY3Rpb25EYXRhID0gKFxuICBldmVudFByb3BzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbiAgY29uZmlnOiBHQTRQcm9qZWN0ZWRFdmVudENvbmZpZyxcbik6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0+IHtcbiAgaWYgKCFjb25maWcud2lkZ2V0SW50ZXJhY3Rpb25EYXRhUHJvamVjdGlvbnMpIHtcbiAgICByZXR1cm4ge307XG4gIH1cblxuICBjb25zdCBpbnRlcmFjdGlvbiA9IGV2ZW50UHJvcHNbJ3RyaWdnZXIud2lkZ2V0X2ludGVyYWN0aW9uJ107XG4gIGlmICh0eXBlb2YgaW50ZXJhY3Rpb24gIT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgcHJvamVjdGlvbk1hcCA9IGNvbmZpZy53aWRnZXRJbnRlcmFjdGlvbkRhdGFQcm9qZWN0aW9uc1tpbnRlcmFjdGlvbl07XG4gIGlmICghcHJvamVjdGlvbk1hcCkge1xuICAgIHJldHVybiB7fTtcbiAgfVxuXG4gIGNvbnN0IGludGVyYWN0aW9uRGF0YSA9IGV2ZW50UHJvcHNbJ3RyaWdnZXIud2lkZ2V0X2ludGVyYWN0aW9uX2RhdGEnXTtcbiAgaWYgKFxuICAgIGludGVyYWN0aW9uRGF0YSA9PT0gbnVsbCB8fFxuICAgIGludGVyYWN0aW9uRGF0YSA9PT0gdW5kZWZpbmVkIHx8XG4gICAgdHlwZW9mIGludGVyYWN0aW9uRGF0YSAhPT0gJ29iamVjdCdcbiAgKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgZGF0YSA9IGludGVyYWN0aW9uRGF0YSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgY29uc3QgcmVzdWx0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICBmb3IgKGNvbnN0IFtnYUtleSwgc291cmNlRmllbGRdIG9mIE9iamVjdC5lbnRyaWVzKHByb2plY3Rpb25NYXApKSB7XG4gICAgaWYgKHNvdXJjZUZpZWxkIGluIGRhdGEpIHtcbiAgICAgIHJlc3VsdFtnYUtleV0gPSBkYXRhW3NvdXJjZUZpZWxkXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbmNvbnN0IHRydW5jYXRlU3RyaW5nID0gKHZhbHVlOiB1bmtub3duKTogdW5rbm93biA9PiB7XG4gIGlmICh0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnICYmIHZhbHVlLmxlbmd0aCA+IDEwMCkge1xuICAgIHJldHVybiBgJHt2YWx1ZS5zdWJzdHJpbmcoMCwgOTcpfS4uLmA7XG4gIH1cbiAgcmV0dXJuIHZhbHVlO1xufTtcblxuY29uc3QgdHJ1bmNhdGVWYWx1ZXMgPSAob2JqOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0+IHtcbiAgY29uc3QgcmVzdWx0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICBmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhvYmopKSB7XG4gICAgcmVzdWx0W2tleV0gPSB0cnVuY2F0ZVN0cmluZyh2YWx1ZSk7XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbmNvbnN0IHB1c2hUb0RhdGFMYXllciA9IChnYUV2ZW50OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHZvaWQgPT4ge1xuICBpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93LmRhdGFMYXllcikge1xuICAgIHdpbmRvdy5kYXRhTGF5ZXIucHVzaChnYUV2ZW50KTtcbiAgfVxufTtcblxuZXhwb3J0IGNvbnN0IHByb2plY3RUb0dBNCA9IChcbiAgZXZlbnROYW1lOiBFbnZpdmVNZXRyaWNzRXZlbnROYW1lLFxuICBldmVudFByb3BzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4pOiB2b2lkID0+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBzY2hlbWFFbnRyeSA9IEdBNF9FVkVOVF9TQ0hFTUFbZXZlbnROYW1lXTtcblxuICAgIGlmIChzY2hlbWFFbnRyeS5nYUV2ZW50TmFtZSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGNvbmZpZyA9IHNjaGVtYUVudHJ5O1xuICAgIGNvbnN0IHByb3BzID0gZXZlbnRQcm9wcyA/PyB7fTtcblxuICAgIGxldCBmaWx0ZXJlZCA9IGZpbHRlclRvU2NoZW1hKHByb3BzLCBjb25maWcuYWxsb3dlZEZpZWxkcyk7XG4gICAgZmlsdGVyZWQgPSBzYW5pdGl6ZVBhZ2VJZChmaWx0ZXJlZCk7XG5cbiAgICBjb25zdCBpbnRlcmFjdGlvbkZpZWxkcyA9IHByb2plY3RXaWRnZXRJbnRlcmFjdGlvbkRhdGEocHJvcHMsIGNvbmZpZyk7XG5cbiAgICBjb25zdCBmbGF0UGFyYW1zID0ge1xuICAgICAgLi4uZmxhdHRlbkRvdEtleXMoZmlsdGVyZWQpLFxuICAgICAgLi4uaW50ZXJhY3Rpb25GaWVsZHMsXG4gICAgfTtcblxuICAgIGNvbnN0IHRydW5jYXRlZFBhcmFtcyA9IHRydW5jYXRlVmFsdWVzKGZsYXRQYXJhbXMpO1xuXG4gICAgcHVzaFRvRGF0YUxheWVyKHtcbiAgICAgIGV2ZW50OiBjb25maWcuZ2FFdmVudE5hbWUsXG4gICAgICAuLi50cnVuY2F0ZWRQYXJhbXMsXG4gICAgfSk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci5sb2dFcnJvcignRXJyb3IgcHJvamVjdGluZyBldmVudCB0byBHQTQnLCBlcnIsIHtcbiAgICAgIGV2ZW50TmFtZSxcbiAgICB9KTtcbiAgfVxufTtcbiJdLCJtYXBwaW5ncyI6Ijs7OztBQUlBLE1BQU0sU0FBUyxJQUFJQSxlQUFPLHVCQUF1QjtBQUVqRCxNQUFNLGtCQUNKLFlBQ0Esa0JBQzRCO0NBQzVCLE1BQU1DLFNBQWtDLEVBQUU7QUFDMUMsTUFBSyxNQUFNLFNBQVMsY0FDbEIsS0FBSSxTQUFTLFdBQ1gsUUFBTyxTQUFTLFdBQVc7QUFHL0IsUUFBTzs7QUFJVCxNQUFNLGtCQUFrQixRQUEwRDtDQUNoRixNQUFNQSxTQUFrQyxFQUFFO0FBQzFDLE1BQUssTUFBTSxDQUFDLEtBQUssVUFBVSxPQUFPLFFBQVEsSUFBSSxDQUM1QyxRQUFPLElBQUksUUFBUSxPQUFPLElBQUksSUFBSTtBQUVwQyxRQUFPOztBQVNULE1BQU0sa0JBQWtCLGFBQStEO0NBQ3JGLE1BQU0sV0FBVyxTQUFTO0FBQzFCLEtBQUksYUFBYSxTQUFTLGFBQWEsTUFDckMsUUFBTztDQUVULE1BQU0sT0FBTyxFQUFFLEdBQUcsVUFBVTtBQUM1QixRQUFPLEtBQUs7QUFDWixRQUFPOztBQUlULE1BQU0sZ0NBQ0osWUFDQSxXQUM0QjtBQUM1QixLQUFJLENBQUMsT0FBTyxpQ0FDVixRQUFPLEVBQUU7Q0FHWCxNQUFNLGNBQWMsV0FBVztBQUMvQixLQUFJLE9BQU8sZ0JBQWdCLFNBQ3pCLFFBQU8sRUFBRTtDQUdYLE1BQU0sZ0JBQWdCLE9BQU8saUNBQWlDO0FBQzlELEtBQUksQ0FBQyxjQUNILFFBQU8sRUFBRTtDQUdYLE1BQU0sa0JBQWtCLFdBQVc7QUFDbkMsS0FDRSxvQkFBb0IsUUFDcEIsb0JBQW9CLFVBQ3BCLE9BQU8sb0JBQW9CLFNBRTNCLFFBQU8sRUFBRTtDQUdYLE1BQU0sT0FBTztDQUNiLE1BQU1BLFNBQWtDLEVBQUU7QUFDMUMsTUFBSyxNQUFNLENBQUMsT0FBTyxnQkFBZ0IsT0FBTyxRQUFRLGNBQWMsQ0FDOUQsS0FBSSxlQUFlLEtBQ2pCLFFBQU8sU0FBUyxLQUFLO0FBR3pCLFFBQU87O0FBR1QsTUFBTSxrQkFBa0IsVUFBNEI7QUFDbEQsS0FBSSxPQUFPLFVBQVUsWUFBWSxNQUFNLFNBQVMsSUFDOUMsUUFBTyxHQUFHLE1BQU0sVUFBVSxHQUFHLEdBQUcsQ0FBQztBQUVuQyxRQUFPOztBQUdULE1BQU0sa0JBQWtCLFFBQTBEO0NBQ2hGLE1BQU1BLFNBQWtDLEVBQUU7QUFDMUMsTUFBSyxNQUFNLENBQUMsS0FBSyxVQUFVLE9BQU8sUUFBUSxJQUFJLENBQzVDLFFBQU8sT0FBTyxlQUFlLE1BQU07QUFFckMsUUFBTzs7QUFHVCxNQUFNLG1CQUFtQixZQUEyQztBQUNsRSxLQUFJLE9BQU8sV0FBVyxlQUFlLE9BQU8sVUFDMUMsUUFBTyxVQUFVLEtBQUssUUFBUTs7QUFJbEMsTUFBYSxnQkFDWCxXQUNBLGVBQ1M7QUFDVCxLQUFJO0VBQ0YsTUFBTSxjQUFjLGlCQUFpQjtBQUVyQyxNQUFJLFlBQVksZ0JBQWdCLEtBQzlCO0VBR0YsTUFBTSxTQUFTO0VBQ2YsTUFBTSxRQUFRLGNBQWMsRUFBRTtFQUU5QixJQUFJLFdBQVcsZUFBZSxPQUFPLE9BQU8sY0FBYztBQUMxRCxhQUFXLGVBQWUsU0FBUztFQUVuQyxNQUFNLG9CQUFvQiw2QkFBNkIsT0FBTyxPQUFPO0VBT3JFLE1BQU0sa0JBQWtCLGVBTEw7R0FDakIsR0FBRyxlQUFlLFNBQVM7R0FDM0IsR0FBRztHQUNKLENBRWlEO0FBRWxELGtCQUFnQjtHQUNkLE9BQU8sT0FBTztHQUNkLEdBQUc7R0FDSixDQUFDO1VBQ0ssS0FBSztBQUNaLFNBQU8sU0FBUyxpQ0FBaUMsS0FBSyxFQUNwRCxXQUNELENBQUMifQ==
98
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2E0UHJvamVjdGlvblNlcnZpY2UuanMiLCJuYW1lcyI6WyJMb2dnZXIiLCJyZXN1bHQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+IiwiY3VycmVudDogdW5rbm93biIsImZpbHRlcmVkOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9nYTRQcm9qZWN0aW9uU2VydmljZS9nYTRQcm9qZWN0aW9uU2VydmljZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgTG9nZ2VyIGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9sb2dnaW5nL2xvZ2dlcic7XG5pbXBvcnQgeyBFbnZpdmVNZXRyaWNzRXZlbnROYW1lIH0gZnJvbSAnLi4vYW1wbGl0dWRlU2VydmljZS9ldmVudE5hbWVzJztcbmltcG9ydCB7IEdBNFByb2plY3RlZEV2ZW50Q29uZmlnLCBHQTRfRVZFTlRfU0NIRU1BIH0gZnJvbSAnLi9nYTRFdmVudFNjaGVtYSc7XG5cbmNvbnN0IGxvZ2dlciA9IG5ldyBMb2dnZXIoJ2dhNFByb2plY3Rpb25TZXJ2aWNlJyk7XG5cbmNvbnN0IGZpbHRlclRvU2NoZW1hID0gKFxuICBldmVudFByb3BzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbiAgYWxsb3dlZEZpZWxkczogcmVhZG9ubHkgc3RyaW5nW10sXG4pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9PiB7XG4gIGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcbiAgZm9yIChjb25zdCBmaWVsZCBvZiBhbGxvd2VkRmllbGRzKSB7XG4gICAgaWYgKGZpZWxkIGluIGV2ZW50UHJvcHMpIHtcbiAgICAgIHJlc3VsdFtmaWVsZF0gPSBldmVudFByb3BzW2ZpZWxkXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbi8vIEZsYXR0ZW4gb25seSB0cnVlIHRvcC1sZXZlbCBuZXN0ZWQgb2JqZWN0cyAoa2V5cyB3aXRoIG5vIGRvdHMpIG9uZSBsZXZlbCBkZWVwLlxuLy8gS2V5cyB0aGF0IGFscmVhZHkgY29udGFpbiBkb3RzIGFyZSBsZWZ0IGludGFjdCBzbyB0aGF0IGUuZy5cbi8vIGB0cmlnZ2VyLndpZGdldF9pbnRlcmFjdGlvbl9kYXRhOiB7IC4uLiB9YCBpcyBwcmVzZXJ2ZWQgZm9yIGRvd25zdHJlYW0gZXh0cmFjdGlvbi5cbmNvbnN0IGZsYXR0ZW5PbmVMZXZlbCA9IChvYmo6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPT4ge1xuICBjb25zdCByZXN1bHQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKG9iaikpIHtcbiAgICBpZiAoXG4gICAgICAha2V5LmluY2x1ZGVzKCcuJykgJiZcbiAgICAgIHZhbHVlICE9PSBudWxsICYmXG4gICAgICB0eXBlb2YgdmFsdWUgPT09ICdvYmplY3QnICYmXG4gICAgICAhQXJyYXkuaXNBcnJheSh2YWx1ZSlcbiAgICApIHtcbiAgICAgIGZvciAoY29uc3QgW3N1YktleSwgc3ViVmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHZhbHVlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSkge1xuICAgICAgICByZXN1bHRbYCR7a2V5fS4ke3N1YktleX1gXSA9IHN1YlZhbHVlO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICByZXN1bHRba2V5XSA9IHZhbHVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufTtcblxuLy8gQXBwbHkgYSBHQTQgcHJvamVjdGlvbiBtYXAgeyBnYTRLZXk6IHNvdXJjZUtleSB9IHRvIGV4dHJhY3QgYW5kIHJlbmFtZSBmaWVsZHMuXG5jb25zdCBmaWx0ZXJXaXRoUHJvamVjdGlvbnMgPSAoXG4gIGV2ZW50UHJvcHM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LFxuICBwcm9qZWN0aW9uczogUmVjb3JkPHN0cmluZywgc3RyaW5nPixcbik6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0+IHtcbiAgY29uc3QgcmVzdWx0OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9O1xuICBmb3IgKGNvbnN0IFtnYTRLZXksIHNvdXJjZUtleV0gb2YgT2JqZWN0LmVudHJpZXMocHJvamVjdGlvbnMpKSB7XG4gICAgaWYgKHNvdXJjZUtleSBpbiBldmVudFByb3BzKSB7XG4gICAgICByZXN1bHRbZ2E0S2V5XSA9IGV2ZW50UHJvcHNbc291cmNlS2V5XTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbi8vIFwiY29udGV4dC5wYWdlX3R5cGVcIiDihpIgXCJjb250ZXh0X3BhZ2VfdHlwZVwiXG5jb25zdCBmbGF0dGVuRG90S2V5cyA9IChvYmo6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPT4ge1xuICBjb25zdCByZXN1bHQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKG9iaikpIHtcbiAgICByZXN1bHRba2V5LnJlcGxhY2UoL1xcLi9nLCAnXycpXSA9IHZhbHVlO1xuICB9XG4gIHJldHVybiByZXN1bHQ7XG59O1xuXG4vLyBPbWl0IGNvbnRleHQucGFnZV9pZCBmb3Igbm9uLXBkcC9wbHAgcGFnZSB0eXBlcy4gVGhlIGN1cnJlbnQgaW1wbGVtZW50YXRpb24gZm9yIGNvbnRleHQucGFnZV9pZCBpczpcbi8vIFBEUDogcHJvZHVjdF9pZFxuLy8gUExQOiBwbHBfaWRcbi8vIFNlYXJjaDogc2VhcmNoIHF1ZXJ5XG4vLyBPdGhlcjogcGFnZSB1cmxcbi8vIFdlIHdhbnQgdG8gb21pdCBhbGwgYnV0IHBkcCBhbmQgcGxwIHBhZ2UgdHlwZXMgdG8gcHJvdmlkZSBhIGNsZWFyLCBjb25zaXN0ZW50IGludGVyZmFjZSBmb3IgbWVyY2hhbnRzLlxuLy8gSGFuZGxlcyBib3RoIGRvdC1ub3RhdGlvbiBrZXlzIChhbGxvd2VkRmllbGRzIHBhdGgpIGFuZCByZW5hbWVkIEdBNCBrZXlzIChmaWVsZFByb2plY3Rpb25zIHBhdGgpLlxuY29uc3Qgc2FuaXRpemVQYWdlSWQgPSAoZmlsdGVyZWQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPT4ge1xuICBjb25zdCBwYWdlVHlwZSA9IGZpbHRlcmVkWydwYWdlX3R5cGUnXSA/PyBmaWx0ZXJlZFsnY29udGV4dC5wYWdlX3R5cGUnXTtcbiAgaWYgKHBhZ2VUeXBlID09PSAncGRwJyB8fCBwYWdlVHlwZSA9PT0gJ3BscCcpIHtcbiAgICByZXR1cm4gZmlsdGVyZWQ7XG4gIH1cbiAgY29uc3QgcmVzdCA9IHsgLi4uZmlsdGVyZWQgfTtcbiAgZGVsZXRlIHJlc3RbJ3BhZ2VfaWQnXTtcbiAgZGVsZXRlIHJlc3RbJ2NvbnRleHQucGFnZV9pZCddO1xuICByZXR1cm4gcmVzdDtcbn07XG5cbmNvbnN0IGdldE5lc3RlZFZhbHVlID0gKG9iajogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sIHBhdGg6IHN0cmluZyk6IHVua25vd24gPT4ge1xuICBjb25zdCBwYXJ0cyA9IHBhdGguc3BsaXQoJy4nKTtcbiAgbGV0IGN1cnJlbnQ6IHVua25vd24gPSBvYmo7XG4gIGZvciAoY29uc3QgcGFydCBvZiBwYXJ0cykge1xuICAgIGlmIChjdXJyZW50ID09PSBudWxsIHx8IHR5cGVvZiBjdXJyZW50ICE9PSAnb2JqZWN0JykgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICBjdXJyZW50ID0gKGN1cnJlbnQgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pW3BhcnRdO1xuICB9XG4gIHJldHVybiBjdXJyZW50O1xufTtcblxuLy8gRXh0cmFjdCB3aGl0ZWxpc3RlZCBzdWItZmllbGRzIGZyb20gdHJpZ2dlci53aWRnZXRfaW50ZXJhY3Rpb25fZGF0YVxuY29uc3QgcHJvamVjdFdpZGdldEludGVyYWN0aW9uRGF0YSA9IChcbiAgZXZlbnRQcm9wczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4gIGNvbmZpZzogR0E0UHJvamVjdGVkRXZlbnRDb25maWcsXG4pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9PiB7XG4gIGlmICghY29uZmlnLndpZGdldEludGVyYWN0aW9uRGF0YVByb2plY3Rpb25zKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG5cbiAgY29uc3QgaW50ZXJhY3Rpb24gPSBldmVudFByb3BzWyd0cmlnZ2VyLndpZGdldF9pbnRlcmFjdGlvbiddO1xuICBpZiAodHlwZW9mIGludGVyYWN0aW9uICE9PSAnc3RyaW5nJykge1xuICAgIHJldHVybiB7fTtcbiAgfVxuXG4gIGNvbnN0IHByb2plY3Rpb25NYXAgPSBjb25maWcud2lkZ2V0SW50ZXJhY3Rpb25EYXRhUHJvamVjdGlvbnNbaW50ZXJhY3Rpb25dO1xuICBpZiAoIXByb2plY3Rpb25NYXApIHtcbiAgICByZXR1cm4ge307XG4gIH1cblxuICBjb25zdCBpbnRlcmFjdGlvbkRhdGEgPSBldmVudFByb3BzWyd0cmlnZ2VyLndpZGdldF9pbnRlcmFjdGlvbl9kYXRhJ107XG4gIGlmIChcbiAgICBpbnRlcmFjdGlvbkRhdGEgPT09IG51bGwgfHxcbiAgICBpbnRlcmFjdGlvbkRhdGEgPT09IHVuZGVmaW5lZCB8fFxuICAgIHR5cGVvZiBpbnRlcmFjdGlvbkRhdGEgIT09ICdvYmplY3QnXG4gICkge1xuICAgIHJldHVybiB7fTtcbiAgfVxuXG4gIGNvbnN0IGRhdGEgPSBpbnRlcmFjdGlvbkRhdGEgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcbiAgZm9yIChjb25zdCBbZ2FLZXksIHNvdXJjZVBhdGhdIG9mIE9iamVjdC5lbnRyaWVzKHByb2plY3Rpb25NYXApKSB7XG4gICAgY29uc3QgdmFsdWUgPSBnZXROZXN0ZWRWYWx1ZShkYXRhLCBzb3VyY2VQYXRoKTtcbiAgICBpZiAodmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgcmVzdWx0W2dhS2V5XSA9IHZhbHVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufTtcblxuY29uc3QgdHJ1bmNhdGVTdHJpbmcgPSAodmFsdWU6IHVua25vd24pOiB1bmtub3duID0+IHtcbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycgJiYgdmFsdWUubGVuZ3RoID4gMTAwKSB7XG4gICAgcmV0dXJuIGAke3ZhbHVlLnN1YnN0cmluZygwLCA5Nyl9Li4uYDtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59O1xuXG5jb25zdCB0cnVuY2F0ZVZhbHVlcyA9IChvYmo6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPT4ge1xuICBjb25zdCByZXN1bHQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge307XG4gIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKG9iaikpIHtcbiAgICByZXN1bHRba2V5XSA9IHRydW5jYXRlU3RyaW5nKHZhbHVlKTtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufTtcblxuY29uc3QgcHVzaFRvRGF0YUxheWVyID0gKGdhRXZlbnQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogdm9pZCA9PiB7XG4gIGlmICh0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJyAmJiB3aW5kb3cuZGF0YUxheWVyKSB7XG4gICAgd2luZG93LmRhdGFMYXllci5wdXNoKGdhRXZlbnQpO1xuICB9XG59O1xuXG5leHBvcnQgY29uc3QgcHJvamVjdFRvR0E0ID0gKFxuICBldmVudE5hbWU6IEVudml2ZU1ldHJpY3NFdmVudE5hbWUsXG4gIGV2ZW50UHJvcHM/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbik6IHZvaWQgPT4ge1xuICB0cnkge1xuICAgIGNvbnN0IHNjaGVtYUVudHJ5ID0gR0E0X0VWRU5UX1NDSEVNQVtldmVudE5hbWVdO1xuXG4gICAgaWYgKHNjaGVtYUVudHJ5LmdhRXZlbnROYW1lID09PSBudWxsKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgY29uZmlnID0gc2NoZW1hRW50cnk7XG4gICAgY29uc3QgcHJvcHMgPSBmbGF0dGVuT25lTGV2ZWwoZXZlbnRQcm9wcyA/PyB7fSk7XG5cbiAgICBsZXQgZmlsdGVyZWQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICAgIGlmIChjb25maWcuZmllbGRQcm9qZWN0aW9ucykge1xuICAgICAgZmlsdGVyZWQgPSBmaWx0ZXJXaXRoUHJvamVjdGlvbnMocHJvcHMsIGNvbmZpZy5maWVsZFByb2plY3Rpb25zKTtcbiAgICB9IGVsc2Uge1xuICAgICAgZmlsdGVyZWQgPSBmaWx0ZXJUb1NjaGVtYShwcm9wcywgY29uZmlnLmFsbG93ZWRGaWVsZHMpO1xuICAgIH1cbiAgICBmaWx0ZXJlZCA9IHNhbml0aXplUGFnZUlkKGZpbHRlcmVkKTtcblxuICAgIGNvbnN0IGludGVyYWN0aW9uRmllbGRzID0gcHJvamVjdFdpZGdldEludGVyYWN0aW9uRGF0YShwcm9wcywgY29uZmlnKTtcblxuICAgIGNvbnN0IGZsYXRQYXJhbXMgPSB7XG4gICAgICAuLi5mbGF0dGVuRG90S2V5cyhmaWx0ZXJlZCksXG4gICAgICAuLi5pbnRlcmFjdGlvbkZpZWxkcyxcbiAgICB9O1xuXG4gICAgY29uc3QgdHJ1bmNhdGVkUGFyYW1zID0gdHJ1bmNhdGVWYWx1ZXMoZmxhdFBhcmFtcyk7XG5cbiAgICBwdXNoVG9EYXRhTGF5ZXIoe1xuICAgICAgZXZlbnQ6IGNvbmZpZy5nYUV2ZW50TmFtZSxcbiAgICAgIC4uLnRydW5jYXRlZFBhcmFtcyxcbiAgICB9KTtcbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nZ2VyLmxvZ0Vycm9yKCdFcnJvciBwcm9qZWN0aW5nIGV2ZW50IHRvIEdBNCcsIGVyciwge1xuICAgICAgZXZlbnROYW1lLFxuICAgIH0pO1xu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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envive-ai/react-hooks",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "React hooks for connecting to Envive AI services.",
5
5
  "keywords": [
6
6
  "react",
@@ -27,4 +27,5 @@ export enum FeatureGates {
27
27
  IsAiSuggestionsVariantEnabled = 'is_ai_suggestions_variant_enabled',
28
28
  IsAiSuggestionsVariantAEnabled = 'is_ai_suggestions_variant_a_enabled',
29
29
  IsAiSuggestionsVariantBEnabled = 'is_ai_suggestions_variant_b_enabled',
30
+ IsVoiceInputEnabled = 'is_voice_input_enabled',
30
31
  }
@@ -26,6 +26,7 @@ export const initializedAtom = atom<boolean>(false);
26
26
  export const chatIsOpenAtom = atom<boolean>(false);
27
27
  export const requestFailureAtom = atom<boolean>(false);
28
28
  export const formSubmitAtom = atom<FormSubmittedAttributes>();
29
+ export const listeningToSpeechAtom = atom<'start' | 'stop' | 'abort' | undefined>(undefined);
29
30
  export const chatOnToggleAtom = atom(
30
31
  null,
31
32
  (
@@ -254,21 +254,19 @@ export const GraphQLProvider = ({
254
254
  const isSemanticColors = !window.location.href.includes('globals=merchant');
255
255
  const isStorybook = window.top?.location.href.includes('?path=');
256
256
 
257
- // If the v3 root config is not found and we are configured to return the deprecated config, we will return the mocked config
258
- if ((!v3RootConfig && mockV3ConfigToDeprecatedConfig) || (isStorybook && isSemanticColors)) {
257
+ // If no v3 colors are found, return the default mock config to allow unconfigured merchants to work by default
258
+ if (!v3RootConfig?.colors || (isStorybook && isSemanticColors)) {
259
259
  logger.logDebug('GraphQLContext | Returning mock v3 config', {
260
- colorsConfig: mockV3ColorsConfig as ColorsConfigV3Response,
260
+ colorsConfig: mockV3ColorsConfig,
261
261
  frontendConfig: mockV3FrontendConfig as FrontendConfigV3Response,
262
262
  });
263
263
  const colorsConfig = mockV3ColorsConfig;
264
264
  const frontendConfig = mockV3FrontendConfig;
265
265
  return {
266
- colorsConfig: isSemanticColors
267
- ? (colorsConfig as ColorsConfigV3Response)
268
- : (v3ColorsConfig as ColorsConfigV3Response),
266
+ colorsConfig,
269
267
  frontendConfig: frontendConfig as FrontendConfigV3Response,
270
268
  orgPageConfig: {
271
- pageVariants: [],
269
+ pageVariants: DEFAULT_PAGE_VARIANTS,
272
270
  widgetConfigs: {},
273
271
  mountingConfigs: {},
274
272
  },
@@ -277,7 +275,7 @@ export const GraphQLProvider = ({
277
275
 
278
276
  logger.logDebug('GraphQLContext | Returning v3 config', {
279
277
  colorsConfig: v3ColorsConfig as ColorsConfigV3Response,
280
- frontendConfig: v3FrontendConfig as FrontendConfigV3Response,
278
+ frontendConfig: v3FrontendConfig,
281
279
  orgPageConfig: {
282
280
  pageVariants: v3pageVariants,
283
281
  widgetConfigs: v3WidgetConfigs,
@@ -219,10 +219,18 @@ export const HardcopyProvider: React.FC<HardcopyProviderProps> = ({
219
219
  const { featureFlagService } = useFeatureFlagService();
220
220
  const getHardcopyFromBackend = useCallback(
221
221
  async (request: HardcopyRequest): Promise<HardcopyResponse> => {
222
- if (hardcopyOverride?.[request.widgetType]) {
222
+ const fallbackWidgetType =
223
+ request.widgetType === WidgetTypeV3.ProductCardV3
224
+ ? WidgetTypeV3.ImagePromptCardV3
225
+ : request.widgetType;
226
+
227
+ const overrideEntry =
228
+ hardcopyOverride?.[request.widgetType] ?? hardcopyOverride?.[fallbackWidgetType];
229
+ if (overrideEntry) {
223
230
  logger.logDebug('using hardcopy override', request.widgetType);
224
- return hardcopyOverride[request.widgetType]!;
231
+ return overrideEntry;
225
232
  }
233
+
226
234
  const overrideConfigVersion =
227
235
  getQueryParam('spiffy_config_version') ||
228
236
  getQueryParam('envive_config_version') ||
@@ -14,6 +14,7 @@ import {
14
14
  import { hasParsedVariantInfoAtom } from 'src/atoms/app';
15
15
  import { analyticsContextAtom } from 'src/atoms/app/variant';
16
16
  import { formSubmitAtom, replyEventCategoryAtom } from 'src/atoms/chat';
17
+ import { suggestionsAtom } from 'src/atoms/chat/chatState';
17
18
  import { queueUserEventAtom } from 'src/atoms/chat/messageQueue';
18
19
  import { useWidgetInteraction } from 'src/hooks/WidgetInteraction';
19
20
  import {
@@ -56,6 +57,7 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
56
57
  const context = useAtomValue(analyticsContextAtom);
57
58
  const hasParsedVariantInfo = useAtomValue(hasParsedVariantInfoAtom);
58
59
  const queueUserEvent = useSetAtom(queueUserEventAtom);
60
+ const setSuggestions = useSetAtom(suggestionsAtom);
59
61
  const setReplyEventCategory = useSetAtom(replyEventCategoryAtom);
60
62
  const setFormSubmit = useSetAtom(formSubmitAtom);
61
63
  const { trackEvent } = useAmplitude();
@@ -164,9 +166,10 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
164
166
  content: suggestion.content,
165
167
  },
166
168
  };
169
+ setSuggestions([]);
167
170
  queueUserEvent(event);
168
171
  },
169
- [queueUserEvent, trackEvent, context],
172
+ [queueUserEvent, setSuggestions, trackEvent, context],
170
173
  );
171
174
  const onTypedMessageSubmitted = useCallback(
172
175
  ({
@@ -229,9 +232,10 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
229
232
  userTyped,
230
233
  },
231
234
  };
235
+ setSuggestions([]);
232
236
  queueUserEvent(event);
233
237
  },
234
- [queueUserEvent, trackEvent, context],
238
+ [queueUserEvent, setSuggestions, trackEvent, context],
235
239
  );
236
240
  const onFormResponseSubmitted = useCallback(
237
241
  (form: FormSubmittedAttributes) => {
@@ -156,6 +156,7 @@ type LookAndFeelConfig = {
156
156
  chatHeaderLogoLightSrc: string;
157
157
  widgetLogoSrc: string;
158
158
  hideWidgetLogo: boolean;
159
+ voiceInputEnabled?: boolean;
159
160
  elementRadius: number;
160
161
  imageAspectRatio: ImageAspectRatio;
161
162
  buttonColors: ButtonColors;
@@ -1,6 +1,10 @@
1
+ import { useAtomValue } from 'jotai';
1
2
  import { RefObject, useEffect, useRef } from 'react';
2
- import { useIntersection } from 'src/hooks/Intersection/useIntersection';
3
+ import { pageVariantInfoAtom } from 'src/atoms/app';
3
4
  import { useAmplitude } from 'src/contexts/amplitudeContext/amplitudeContext';
5
+ import { PageVariantInfo } from 'src/contexts/pageContext/types';
6
+ import { useIntersection } from 'src/hooks/Intersection/useIntersection';
7
+ import { extractPageContext } from 'src/hooks/WidgetInteraction/utils';
4
8
  import {
5
9
  EnviveMetricsEventName,
6
10
  SpiffyMetricsEventName,
@@ -23,6 +27,7 @@ export const useTrackComponentVisibleEvent = (
23
27
  const isVisible = useIntersection(element, rootMargin);
24
28
  const hasTrackedEvent = useRef(false);
25
29
  const { trackEvent } = useAmplitude();
30
+ const variantInfo = useAtomValue(pageVariantInfoAtom);
26
31
 
27
32
  useEffect(() => {
28
33
  if (!enabled || hasTrackedEvent.current) {
@@ -37,7 +42,10 @@ export const useTrackComponentVisibleEvent = (
37
42
  eventName: EnviveMetricsEventName.WidgetRendered,
38
43
  eventProps: {
39
44
  ...eventProps,
40
- 'trigger.widget': eventProps?.widget_type,
45
+ trigger: {
46
+ widget: eventProps?.widget_type,
47
+ },
48
+ context: variantInfo ? extractPageContext(variantInfo as PageVariantInfo) : null,
41
49
  },
42
50
  });
43
51
  hasTrackedEvent.current = true;
@@ -62,6 +62,8 @@ export enum WidgetInteractionType {
62
62
  REVIEW_CARD_CLICKED = 'review_card_clicked',
63
63
  MESSAGE_SUBMITTED = 'message_submitted',
64
64
  MANUAL_SCROLL_TO_BOTTOM = 'manual_scroll_to_bottom',
65
+ VOICE_TRANSCRIPTION_STARTED = 'voice_transcription_started',
66
+ VOICE_TRANSCRIPTION_COMPLETED = 'voice_transcription_completed',
65
67
  }
66
68
 
67
69
  export enum InteractionClass {
@@ -85,6 +87,8 @@ export const INTERACTION_TYPE_CLASS: Record<WidgetInteractionType, InteractionCl
85
87
  [WidgetInteractionType.REVIEW_CARD_CLICKED]: InteractionClass.INTENTIONAL,
86
88
  [WidgetInteractionType.MESSAGE_SUBMITTED]: InteractionClass.INTENTIONAL,
87
89
  [WidgetInteractionType.MANUAL_SCROLL_TO_BOTTOM]: InteractionClass.PASSIVE,
90
+ [WidgetInteractionType.VOICE_TRANSCRIPTION_STARTED]: InteractionClass.INTENTIONAL,
91
+ [WidgetInteractionType.VOICE_TRANSCRIPTION_COMPLETED]: InteractionClass.INTENTIONAL,
88
92
  };
89
93
 
90
94
  export type URL = {
@@ -151,6 +155,10 @@ export type MessageSubmitted = {
151
155
  message_submitted: Request;
152
156
  };
153
157
 
158
+ export type VoiceTranscription = {
159
+ transcription: string;
160
+ };
161
+
154
162
  export type WidgetInteractionData =
155
163
  | SuggestionScrolled
156
164
  | SuggestionClicked
@@ -161,4 +169,5 @@ export type WidgetInteractionData =
161
169
  | ArticleLinkClicked
162
170
  | ProductCardClicked
163
171
  | ReviewCardClicked
164
- | MessageSubmitted;
172
+ | MessageSubmitted
173
+ | VoiceTranscription;
@@ -28,34 +28,46 @@ describe('projectToGA4', () => {
28
28
  });
29
29
 
30
30
  describe('Widget Rendered', () => {
31
- it('should push filtered and flattened props', () => {
31
+ it('should push renamed GA4 fields from nested input', () => {
32
32
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
33
- 'context.page_type': 'pdp',
34
- 'context.page_id': 'product-123',
35
- 'trigger.widget': 'floating_button',
36
- 'trigger.interaction_id': 'abc-123',
33
+ trigger: { widget: 'floating_button' },
34
+ context: { page_type: 'pdp', page_id: 'product-123' },
37
35
  });
38
36
 
39
37
  expect(window.dataLayer).toHaveLength(1);
40
38
  expect(window.dataLayer[0]).toEqual({
41
39
  event: 'envive_widget_rendered',
42
- context_page_type: 'pdp',
43
- context_page_id: 'product-123',
44
- trigger_widget: 'floating_button',
40
+ page_type: 'pdp',
41
+ page_id: 'product-123',
42
+ widget: 'floating_button',
45
43
  });
46
44
  });
47
45
 
48
- it('should exclude fields not in allowedFields', () => {
46
+ it('should also accept flat dot-notation input', () => {
49
47
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
50
48
  'context.page_type': 'pdp',
51
49
  'context.page_id': 'product-123',
52
50
  'trigger.widget': 'floating_button',
53
- 'trigger.interaction_id': 'should-be-dropped',
51
+ 'trigger.interaction_id': 'abc-123',
52
+ });
53
+
54
+ expect(window.dataLayer[0]).toEqual({
55
+ event: 'envive_widget_rendered',
56
+ page_type: 'pdp',
57
+ page_id: 'product-123',
58
+ widget: 'floating_button',
59
+ });
60
+ });
61
+
62
+ it('should exclude fields not in fieldProjections', () => {
63
+ projectToGA4(EnviveMetricsEventName.WidgetRendered, {
64
+ trigger: { widget: 'floating_button', interaction_id: 'should-be-dropped' },
65
+ context: { page_type: 'pdp', page_id: 'product-123' },
54
66
  'some.random_field': 'also-dropped',
55
67
  });
56
68
 
57
69
  const pushed = window.dataLayer[0];
58
- expect(pushed).not.toHaveProperty('trigger_interaction_id');
70
+ expect(pushed).not.toHaveProperty('interaction_id');
59
71
  expect(pushed).not.toHaveProperty('some_random_field');
60
72
  });
61
73
  });
@@ -63,97 +75,146 @@ describe('projectToGA4', () => {
63
75
  describe('page_id sanitization', () => {
64
76
  it('should keep page_id for pdp page type', () => {
65
77
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
66
- 'context.page_type': 'pdp',
67
- 'context.page_id': 'product-123',
68
- 'trigger.widget': 'floating_button',
78
+ trigger: { widget: 'floating_button' },
79
+ context: { page_type: 'pdp', page_id: 'product-123' },
69
80
  });
70
81
 
71
- expect(window.dataLayer[0]).toHaveProperty('context_page_id', 'product-123');
82
+ expect(window.dataLayer[0]).toHaveProperty('page_id', 'product-123');
72
83
  });
73
84
 
74
85
  it('should keep page_id for plp page type', () => {
75
86
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
76
- 'context.page_type': 'plp',
77
- 'context.page_id': 'category-456',
78
- 'trigger.widget': 'floating_button',
87
+ trigger: { widget: 'floating_button' },
88
+ context: { page_type: 'plp', page_id: 'category-456' },
79
89
  });
80
90
 
81
- expect(window.dataLayer[0]).toHaveProperty('context_page_id', 'category-456');
91
+ expect(window.dataLayer[0]).toHaveProperty('page_id', 'category-456');
82
92
  });
83
93
 
84
94
  it('should omit page_id for search page type', () => {
85
95
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
86
- 'context.page_type': 'search',
87
- 'context.page_id': 'some search query',
88
- 'trigger.widget': 'floating_button',
96
+ trigger: { widget: 'floating_button' },
97
+ context: { page_type: 'search', page_id: 'some search query' },
89
98
  });
90
99
 
91
- expect(window.dataLayer[0]).not.toHaveProperty('context_page_id');
100
+ expect(window.dataLayer[0]).not.toHaveProperty('page_id');
92
101
  });
93
102
 
94
103
  it('should omit page_id for homepage page type', () => {
95
104
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
96
- 'context.page_type': 'homepage',
97
- 'context.page_id': 'https://example.com',
98
- 'trigger.widget': 'floating_button',
105
+ trigger: { widget: 'floating_button' },
106
+ context: { page_type: 'homepage', page_id: 'https://example.com' },
99
107
  });
100
108
 
101
- expect(window.dataLayer[0]).not.toHaveProperty('context_page_id');
109
+ expect(window.dataLayer[0]).not.toHaveProperty('page_id');
102
110
  });
103
111
 
104
112
  it('should omit page_id for other page type', () => {
105
113
  projectToGA4(EnviveMetricsEventName.WidgetRendered, {
106
- 'context.page_type': 'other',
107
- 'context.page_id': 'https://example.com/about',
108
- 'trigger.widget': 'floating_button',
114
+ trigger: { widget: 'floating_button' },
115
+ context: { page_type: 'other', page_id: 'https://example.com/about' },
109
116
  });
110
117
 
111
- expect(window.dataLayer[0]).not.toHaveProperty('context_page_id');
118
+ expect(window.dataLayer[0]).not.toHaveProperty('page_id');
112
119
  });
113
120
  });
114
121
 
115
122
  describe('Widget Interaction', () => {
116
- it('should push base fields and flatten dot keys', () => {
123
+ it('should push renamed GA4 fields (flat dot-notation input)', () => {
117
124
  projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
118
125
  'context.page_type': 'pdp',
119
126
  'context.page_id': 'product-123',
120
127
  'trigger.widget': 'chat_overlay',
121
128
  'trigger.widget_interaction': 'link_clicked',
129
+ 'trigger.interaction_class': 'intentional',
122
130
  });
123
131
 
124
132
  expect(window.dataLayer[0]).toEqual({
125
133
  event: 'envive_widget_interaction',
126
- context_page_type: 'pdp',
127
- context_page_id: 'product-123',
128
- trigger_widget: 'chat_overlay',
129
- trigger_widget_interaction: 'link_clicked',
134
+ page_type: 'pdp',
135
+ page_id: 'product-123',
136
+ widget: 'chat_overlay',
137
+ interaction_type: 'link_clicked',
138
+ interaction_class: 'intentional',
130
139
  });
131
140
  });
132
141
 
133
- it('should extract collapse_source from widget_interaction_data for widget_collapsed', () => {
142
+ it('should project from real Amplitude nested-object props', () => {
143
+ projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
144
+ trigger: {
145
+ widget: 'floating_button',
146
+ widget_interaction: 'widget_clicked',
147
+ interaction_id: '604a2e2c-9848-41dd-a2b1-cb5b8612ec08',
148
+ interaction_class: 'intentional',
149
+ },
150
+ context: {
151
+ page_id: 'hsa-fsa-eligible',
152
+ page_type: 'plp',
153
+ },
154
+ 'environment.execution_context': 'bundle',
155
+ 'org.short_name': 'dermalogica',
156
+ });
157
+
158
+ expect(window.dataLayer[0]).toEqual({
159
+ event: 'envive_widget_interaction',
160
+ page_type: 'plp',
161
+ page_id: 'hsa-fsa-eligible',
162
+ widget: 'floating_button',
163
+ interaction_type: 'widget_clicked',
164
+ interaction_class: 'intentional',
165
+ });
166
+ });
167
+
168
+ it('should omit page_id for non-pdp/plp page types (nested input)', () => {
169
+ projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
170
+ trigger: { widget: 'floating_button', widget_interaction: 'widget_clicked' },
171
+ context: { page_type: 'homepage', page_id: 'https://example.com' },
172
+ });
173
+
174
+ expect(window.dataLayer[0]).not.toHaveProperty('page_id');
175
+ expect(window.dataLayer[0]).toHaveProperty('page_type', 'homepage');
176
+ });
177
+
178
+ it('should extract interaction_collapse_source from widget_interaction_data for widget_collapsed', () => {
134
179
  projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
135
180
  'context.page_type': 'pdp',
136
181
  'context.page_id': 'product-123',
137
182
  'trigger.widget': 'chat_overlay',
138
183
  'trigger.widget_interaction': 'widget_collapsed',
139
- 'trigger.widget_interaction_data': { collapse_source: 'close_button' },
184
+ 'trigger.widget_interaction_data': {
185
+ widget_collapsed: { collapse_source: 'close_button' },
186
+ },
187
+ });
188
+
189
+ expect(window.dataLayer[0]).toHaveProperty('interaction_collapse_source', 'close_button');
190
+ });
191
+
192
+ it('should extract interaction_collapse_source from nested real-data trigger object', () => {
193
+ projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
194
+ trigger: {
195
+ widget: 'floating_button',
196
+ widget_interaction: 'widget_collapsed',
197
+ interaction_class: 'navigational',
198
+ widget_interaction_data: { widget_collapsed: { collapse_source: 'swipe' } },
199
+ },
200
+ context: { page_type: 'pdp', page_id: 'product-123' },
140
201
  });
141
202
 
142
- expect(window.dataLayer[0]).toHaveProperty('trigger_collapse_source', 'close_button');
203
+ expect(window.dataLayer[0]).toHaveProperty('interaction_collapse_source', 'swipe');
204
+ expect(window.dataLayer[0]).toHaveProperty('interaction_class', 'navigational');
143
205
  });
144
206
 
145
- it('should extract product_id from widget_interaction_data for product_card_clicked', () => {
207
+ it('should extract interaction_product_id from widget_interaction_data for product_card_clicked', () => {
146
208
  projectToGA4(EnviveMetricsEventName.WidgetInteraction, {
147
209
  'context.page_type': 'pdp',
148
210
  'context.page_id': 'product-123',
149
211
  'trigger.widget': 'chat_overlay',
150
212
  'trigger.widget_interaction': 'product_card_clicked',
151
- 'trigger.widget_interaction_data': { product_id: 'sku-789', url: 'https://example.com' },
213
+ 'trigger.widget_interaction_data': { product_card_clicked: { product_id: 'sku-789' } },
152
214
  });
153
215
 
154
216
  const pushed = window.dataLayer[0];
155
- expect(pushed).toHaveProperty('trigger_product_id', 'sku-789');
156
- expect(pushed).not.toHaveProperty('url');
217
+ expect(pushed).toHaveProperty('interaction_product_id', 'sku-789');
157
218
  });
158
219
 
159
220
  it('should extract suggestion_id from widget_interaction_data for suggestion_scrolled', () => {
@@ -162,10 +223,10 @@ describe('projectToGA4', () => {
162
223
  'context.page_id': 'product-123',
163
224
  'trigger.widget': 'chat_overlay',
164
225
  'trigger.widget_interaction': 'suggestion_scrolled',
165
- 'trigger.widget_interaction_data': { suggestion_id: 'sug-456' },
226
+ 'trigger.widget_interaction_data': { suggestion_scrolled: { suggestion_id: 'sug-456' } },
166
227
  });
167
228
 
168
- expect(window.dataLayer[0]).toHaveProperty('trigger_suggestion_id', 'sug-456');
229
+ expect(window.dataLayer[0]).toHaveProperty('interaction_suggestion_id', 'sug-456');
169
230
  });
170
231
 
171
232
  it('should drop widget_interaction_data for unwhitelisted interactions', () => {
@@ -281,8 +342,8 @@ describe('projectToGA4', () => {
281
342
 
282
343
  expect(window.dataLayer[0]).toEqual({
283
344
  event: 'envive_page_context_evaluated',
284
- context_page_type: 'pdp',
285
- context_page_id: 'product-123',
345
+ page_type: 'pdp',
346
+ page_id: 'product-123',
286
347
  context_supported: true,
287
348
  context_ready: true,
288
349
  context_page_variant_id: 'variant-A',
@@ -300,8 +361,8 @@ describe('projectToGA4', () => {
300
361
  });
301
362
 
302
363
  const pushed = window.dataLayer[0];
303
- expect(pushed).not.toHaveProperty('context_page_id');
304
- expect(pushed).toHaveProperty('context_page_type', 'search');
364
+ expect(pushed).not.toHaveProperty('page_id');
365
+ expect(pushed).toHaveProperty('page_type', 'search');
305
366
  });
306
367
  });
307
368