@envive-ai/react-hooks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +2 -0
  3. package/dist/GridInsertionService-CEYo9pGj.js +22 -0
  4. package/dist/GridInsertionService-CS_bnPh0.cjs +28 -0
  5. package/dist/bandolier-Ble8jEa8.js +1221 -0
  6. package/dist/bandolier-Bm2xAt_j.cjs +1221 -0
  7. package/dist/carpe-Da7b-LCW.cjs +599 -0
  8. package/dist/carpe-W13mhRRP.js +597 -0
  9. package/dist/cdnService-CZ-aXcY6.cjs +23 -0
  10. package/dist/cdnService-zQfKk3Eb.js +18 -0
  11. package/dist/chatElementDisplayLocation-CX8fuNao.d.cts +239 -0
  12. package/dist/chatElementDisplayLocation-CwptS9tx.d.ts +239 -0
  13. package/dist/chunk-CUT6urMc.cjs +30 -0
  14. package/dist/contexts/index.cjs +13 -0
  15. package/dist/contexts/index.d.cts +65 -0
  16. package/dist/contexts/index.d.ts +66 -0
  17. package/dist/contexts/index.js +7 -0
  18. package/dist/contexts-BRjfVq_k.js +5064 -0
  19. package/dist/contexts-BYArqZtK.cjs +5164 -0
  20. package/dist/coterie-3y0D9ko4.cjs +229 -0
  21. package/dist/coterie-DOWcJAYv.js +229 -0
  22. package/dist/custservice-types-CFIFwZ-r.js +10 -0
  23. package/dist/custservice-types-CkfxZiHY.cjs +16 -0
  24. package/dist/default-C2fEZKXk.js +175 -0
  25. package/dist/default-CBUq6Q6G.cjs +4 -0
  26. package/dist/default-CGIFZK6m.js +4 -0
  27. package/dist/default-D_KPZdPJ.cjs +198 -0
  28. package/dist/divIds-Bss-btao.js +49 -0
  29. package/dist/divIds-DnZNd7rA.cjs +223 -0
  30. package/dist/dreamlandBaby-DCIsuU9R.cjs +338 -0
  31. package/dist/dreamlandBaby-DvSaZGrz.js +338 -0
  32. package/dist/entrypoints-D_JUvkgy.cjs +18 -0
  33. package/dist/entrypoints-YLQsbBRD.js +6 -0
  34. package/dist/enviveConfigContext-CUGLpPGU.js +34 -0
  35. package/dist/enviveConfigContext-Dfr2VH6u.cjs +48 -0
  36. package/dist/fiveCbd-B1SESMCO.js +605 -0
  37. package/dist/fiveCbd-CkOlVby_.cjs +605 -0
  38. package/dist/forLoveAndLemons-CfYPMnKS.cjs +660 -0
  39. package/dist/forLoveAndLemons-DmwYZIk0.js +658 -0
  40. package/dist/greenpan-Bsl3ir59.cjs +389 -0
  41. package/dist/greenpan-BtOi45lf.js +389 -0
  42. package/dist/grooveLife-6_dtYsRk.js +334 -0
  43. package/dist/grooveLife-Cmm1PSCL.cjs +334 -0
  44. package/dist/homegrownCannabis-C-kw-74X.js +400 -0
  45. package/dist/homegrownCannabis-CO0uY_mp.cjs +400 -0
  46. package/dist/hooks/index.cjs +16 -0
  47. package/dist/hooks/index.d.cts +357 -0
  48. package/dist/hooks/index.d.ts +357 -0
  49. package/dist/hooks/index.js +7 -0
  50. package/dist/jackArcher-CLVmwwpI.js +719 -0
  51. package/dist/jackArcher-DdYTIzAV.cjs +719 -0
  52. package/dist/jordanCraig-Am-Oor-O.js +1778 -0
  53. package/dist/jordanCraig-_u3-w4Hp.cjs +1778 -0
  54. package/dist/kindredBravely-CWovIDSc.cjs +482 -0
  55. package/dist/kindredBravely-eWp-ud_E.js +482 -0
  56. package/dist/kutFromTheKloth-BMV4BuGQ.js +361 -0
  57. package/dist/kutFromTheKloth-Q589bAOC.cjs +361 -0
  58. package/dist/larryAndSerges-BMUlTgI-.js +252 -0
  59. package/dist/larryAndSerges-CEau764j.cjs +252 -0
  60. package/dist/leapsAndRebounds-DGMzPO7T.js +352 -0
  61. package/dist/leapsAndRebounds-DHAtRTJD.cjs +352 -0
  62. package/dist/logger-Dln20ans.cjs +25 -0
  63. package/dist/logger-pdEEY8T2.js +19 -0
  64. package/dist/longevityrx-CZW8Hxzi.cjs +312 -0
  65. package/dist/longevityrx-jH2JLhNH.js +312 -0
  66. package/dist/lookOptic-BGXP5P_V.js +274 -0
  67. package/dist/lookOptic-CA6RwLbG.cjs +274 -0
  68. package/dist/mantraBrand-Cm9_PBCT.js +742 -0
  69. package/dist/mantraBrand-DByNqpnL.cjs +742 -0
  70. package/dist/medterra-B0wxj_PV.js +575 -0
  71. package/dist/medterra-DnPN2ksU.cjs +575 -0
  72. package/dist/modells-Bmz8Ag5M.js +476 -0
  73. package/dist/modells-CoYgkLSp.cjs +476 -0
  74. package/dist/models-DHdb7QWn.js +51 -0
  75. package/dist/models-ixxUsGL_.cjs +69 -0
  76. package/dist/pressedFloral-DSKs_oVG.js +653 -0
  77. package/dist/pressedFloral-DjBiSoUl.cjs +653 -0
  78. package/dist/skinPerfection-B_3xzVNS.cjs +326 -0
  79. package/dist/skinPerfection-IDrBuAPt.js +326 -0
  80. package/dist/snapSupplements-BJk5T5ba.js +277 -0
  81. package/dist/snapSupplements-BStTsdOZ.cjs +277 -0
  82. package/dist/socialProofClasses-Bhv2Vulz.js +9 -0
  83. package/dist/socialProofClasses-CrQBWdSA.cjs +39 -0
  84. package/dist/spanx-BYg0LE7R.js +653 -0
  85. package/dist/spanx-LwU1zSzq.cjs +655 -0
  86. package/dist/spanxStaging-CfSmuKYB.js +837 -0
  87. package/dist/spanxStaging-OZLV9qix.cjs +840 -0
  88. package/dist/suggestionBarV2-types-BllzwsBD.js +34 -0
  89. package/dist/suggestionBarV2-types-CaovchMP.cjs +46 -0
  90. package/dist/supergoop-BqPXDnKk.cjs +327 -0
  91. package/dist/supergoop-CIlrHND_.js +325 -0
  92. package/dist/types-C4T5UOIW.cjs +230 -0
  93. package/dist/types-CYNvLeSA.js +176 -0
  94. package/dist/uniqueVintage-B30mOqbH.cjs +1205 -0
  95. package/dist/uniqueVintage-CFueJOhO.js +1203 -0
  96. package/dist/venaCbd-DHGZy49P.cjs +357 -0
  97. package/dist/venaCbd-T0CqVD4k.js +357 -0
  98. package/dist/westonJonBoucher-BdMzs_Yg.cjs +414 -0
  99. package/dist/westonJonBoucher-b4TCQ4ev.js +414 -0
  100. package/dist/wineEnthusiast-BLGlOjgr.cjs +932 -0
  101. package/dist/wineEnthusiast-BqR0i_54.js +932 -0
  102. package/dist/wolfMattress-CyyO-LoC.js +362 -0
  103. package/dist/wolfMattress-DNGZOivg.cjs +362 -0
  104. package/dist/wolfTactical-3Mm2fvVF.js +341 -0
  105. package/dist/wolfTactical-BmXYlFjr.cjs +341 -0
  106. package/package.json +66 -0
  107. package/src/adapters/amplitude/amplitudeAdapter.ts +454 -0
  108. package/src/adapters/amplitude/index.ts +2 -0
  109. package/src/adapters/amplitude/stubAmplitudeAdapter.ts +34 -0
  110. package/src/adapters/spiffy/commerce/api.ts +596 -0
  111. package/src/adapters/spiffy/commerce/exceptions/sessionExceptions.ts +6 -0
  112. package/src/adapters/spiffy/commerce/exceptions/unsupportedProductExceptions.ts +6 -0
  113. package/src/adapters/spiffy/commerce/graphql.ts +184 -0
  114. package/src/application/config/generalStaticConfig.ts +37 -0
  115. package/src/application/logging/logger.ts +29 -0
  116. package/src/application/models/api/context.ts +4 -0
  117. package/src/application/models/api/generationParams.ts +4 -0
  118. package/src/application/models/api/nextMessageRequest.ts +11 -0
  119. package/src/application/models/api/orgAnalyticsConfig.ts +19 -0
  120. package/src/application/models/api/orgConfigResults.ts +40 -0
  121. package/src/application/models/api/organizationConfig.ts +12 -0
  122. package/src/application/models/api/response.ts +132 -0
  123. package/src/application/models/api/responseGenerics.ts +67 -0
  124. package/src/application/models/api/search.ts +26 -0
  125. package/src/application/models/api/suggestion.ts +4 -0
  126. package/src/application/models/api/supportedEventRequest.ts +8 -0
  127. package/src/application/models/api/userEvent.ts +101 -0
  128. package/src/application/models/cachedValue.ts +8 -0
  129. package/src/application/models/chatElementDisplayLocation.ts +22 -0
  130. package/src/application/models/clientDetails.ts +18 -0
  131. package/src/application/models/colorsConfig.ts +28 -0
  132. package/src/application/models/conversationalSearchIds.ts +5 -0
  133. package/src/application/models/dataLayer.ts +45 -0
  134. package/src/application/models/domMutationContinuation.ts +7 -0
  135. package/src/application/models/domObservationStrategy.ts +9 -0
  136. package/src/application/models/events.ts +5 -0
  137. package/src/application/models/featureGates.ts +23 -0
  138. package/src/application/models/frontendConfig.ts +14 -0
  139. package/src/application/models/googleAnalyticsEvents.ts +8 -0
  140. package/src/application/models/graphql/index.ts +2 -0
  141. package/src/application/models/graphql/queries/getMerchantColorsQuery.ts +37 -0
  142. package/src/application/models/graphql/queries/getMerchantFrontendConfigQuery.ts +103 -0
  143. package/src/application/models/graphql/queries/getMerchantOrgIdQuery.ts +11 -0
  144. package/src/application/models/guards/api/index.ts +12 -0
  145. package/src/application/models/guards/api/isApiFormResponse.ts +90 -0
  146. package/src/application/models/guards/api/isApiFormSubmittedResponseAttributes.ts +37 -0
  147. package/src/application/models/guards/api/isApiOrderResponseAttributes.ts +155 -0
  148. package/src/application/models/guards/api/isApiOrgConfigResults.ts +277 -0
  149. package/src/application/models/guards/api/isApiOrganizationConfig.ts +207 -0
  150. package/src/application/models/guards/api/isApiPDPEventAttributes.ts +21 -0
  151. package/src/application/models/guards/api/isApiPLPEventAttributes.ts +41 -0
  152. package/src/application/models/guards/api/isApiPageResponseAttributes.ts +21 -0
  153. package/src/application/models/guards/api/isApiProductResponseAttributes.ts +85 -0
  154. package/src/application/models/guards/api/isApiProductSearchAttributes.ts +23 -0
  155. package/src/application/models/guards/api/isApiProductSearchFilterAttributes.ts +15 -0
  156. package/src/application/models/guards/api/isApiQueryTypedEventAttributes.ts +4 -0
  157. package/src/application/models/guards/api/isApiResponse.ts +39 -0
  158. package/src/application/models/guards/api/isApiReviewResponseAttributes.ts +30 -0
  159. package/src/application/models/guards/api/isApiReviewRichInformation.ts +37 -0
  160. package/src/application/models/guards/api/isApiSearchEventAttributes.ts +28 -0
  161. package/src/application/models/guards/api/isApiSuggestion.ts +36 -0
  162. package/src/application/models/guards/api/isApiSuggestionClickedEventAttributes.ts +9 -0
  163. package/src/application/models/guards/api/isApiTextResponseAttributes.ts +9 -0
  164. package/src/application/models/guards/api/isApiUserEvent.ts +25 -0
  165. package/src/application/models/guards/graphQL/isGraphQLColorsConfig.ts +50 -0
  166. package/src/application/models/guards/isBaseEcommerceEvent.ts +17 -0
  167. package/src/application/models/guards/isGA4EcommerceEvent.ts +17 -0
  168. package/src/application/models/guards/isLegacyUAEcommerceEvent.ts +17 -0
  169. package/src/application/models/guards/isMobilePLPChatPlacementParameter.ts +11 -0
  170. package/src/application/models/guards/isSpanxTakeAQuizCtaParameter.ts +4 -0
  171. package/src/application/models/guards/isVariantInfo.ts +37 -0
  172. package/src/application/models/guards/utils.ts +43 -0
  173. package/src/application/models/index.ts +20 -0
  174. package/src/application/models/localStorageEventListener.ts +4 -0
  175. package/src/application/models/message.ts +146 -0
  176. package/src/application/models/mobilePLPChatPlacementParameter.ts +3 -0
  177. package/src/application/models/orgsEnum.ts +36 -0
  178. package/src/application/models/productExperiment.ts +5 -0
  179. package/src/application/models/spanxTakeAQuizCtaParameter.ts +4 -0
  180. package/src/application/models/spiffyWidgets.ts +16 -0
  181. package/src/application/models/supportedOrgs.ts +137 -0
  182. package/src/application/models/utilityTypes/camelCase.ts +87 -0
  183. package/src/application/models/utilityTypes/camelCasedPropertiesDeep.ts +80 -0
  184. package/src/application/models/utilityTypes/delimiterCase.ts +121 -0
  185. package/src/application/models/utilityTypes/delimiterCasedPropertiesDeep.ts +98 -0
  186. package/src/application/models/utilityTypes/index.ts +1 -0
  187. package/src/application/models/utilityTypes/internal.ts +93 -0
  188. package/src/application/models/utilityTypes/primitive.ts +8 -0
  189. package/src/application/models/utilityTypes/snakeCasedPropertiesDeep.ts +49 -0
  190. package/src/application/models/utilityTypes/splitWords.ts +76 -0
  191. package/src/application/models/utilityTypes/trim.ts +28 -0
  192. package/src/application/models/utilityTypes/unknownArray.ts +25 -0
  193. package/src/application/models/utils/snakeToCamelTransformer.ts +90 -0
  194. package/src/application/models/utils/stringToFulfillmentDisplayStatusEnumValue.ts +68 -0
  195. package/src/application/models/validators/validateGraphQLColorsConfig.ts +29 -0
  196. package/src/application/models/validators/validateGraphQLFrontendConfig.ts +594 -0
  197. package/src/application/models/validators/validateGraphQLOrgId.ts +7 -0
  198. package/src/application/models/validators/validateMobilePLPChatPlacementParameter.ts +14 -0
  199. package/src/application/models/validators/validateOrgConfigResults.ts +47 -0
  200. package/src/application/models/validators/validateOrganizationConfig.ts +37 -0
  201. package/src/application/models/validators/validateResponse.ts +187 -0
  202. package/src/application/models/validators/validateSuggestion.ts +16 -0
  203. package/src/application/models/validators/validateUserEvent.ts +110 -0
  204. package/src/application/models/variantInfo/index.ts +1 -0
  205. package/src/application/models/variantInfo/pageVisitInfo.ts +6 -0
  206. package/src/application/models/variantInfo/plpInfo.ts +3 -0
  207. package/src/application/models/variantInfo/productInfo.ts +5 -0
  208. package/src/application/models/variantInfo/variantInfo.ts +23 -0
  209. package/src/application/service/cachingService.ts +84 -0
  210. package/src/application/service/cdnService.ts +18 -0
  211. package/src/application/service/customerService/index.ts +8 -0
  212. package/src/application/service/customerService/providers/UnsupportedCustomerService.ts +15 -0
  213. package/src/application/service/customerService/types.ts +31 -0
  214. package/src/application/service/domMutationObserver.ts +320 -0
  215. package/src/application/service/domMutations/GridInsertionService.ts +123 -0
  216. package/src/application/service/domMutations/dataLayer/dataLayerEventsListener.ts +99 -0
  217. package/src/application/service/domMutations/domInsertionService.ts +90 -0
  218. package/src/application/service/domMutations/domMutationListener.ts +15 -0
  219. package/src/application/service/domMutations/domMutationListenerState.ts +52 -0
  220. package/src/application/service/domMutations/floatingChat/embeddedChatsPlacementsListener.ts +41 -0
  221. package/src/application/service/domMutations/gladly/gladlyListener.ts +61 -0
  222. package/src/application/service/domMutations/spiffy/orgs/common/kustomerVisibilityListener.ts +41 -0
  223. package/src/application/service/domMutations/spiffy/orgs/common/orgsCommonDataLayerListener.ts +119 -0
  224. package/src/application/service/environmentService.ts +51 -0
  225. package/src/application/service/featureFlagService.ts +130 -0
  226. package/src/application/service/kustomerIntegrationService.ts +111 -0
  227. package/src/application/service/localStorageService.ts +77 -0
  228. package/src/application/service/pageVariantService.ts +779 -0
  229. package/src/application/service/searchService.ts +140 -0
  230. package/src/application/service/sessionStorageService.ts +27 -0
  231. package/src/application/service/shopifyUrlService.ts +63 -0
  232. package/src/application/service/userIdentityService.ts +114 -0
  233. package/src/application/service/windowChatToggleService.ts +71 -0
  234. package/src/application/service/windowDataLayerService.ts +181 -0
  235. package/src/application/service/windowFrontendConfigService.ts +104 -0
  236. package/src/application/utils/__tests__/divideArrays.test.ts +14 -0
  237. package/src/application/utils/analyticsUtils.ts +110 -0
  238. package/src/application/utils/coreContextToApiContext.ts +11 -0
  239. package/src/application/utils/coreUserEventToApiUserEvent.ts +106 -0
  240. package/src/application/utils/divideArray.ts +7 -0
  241. package/src/application/utils/domObserver.ts +96 -0
  242. package/src/application/utils/elementObserver.ts +246 -0
  243. package/src/application/utils/imageFilter.ts +12 -0
  244. package/src/application/utils/index.ts +3 -0
  245. package/src/application/utils/merchantUtils.ts +16 -0
  246. package/src/application/utils/messageFromFormSubmittedEvent.ts +31 -0
  247. package/src/application/utils/messageFromQueryEvent.ts +38 -0
  248. package/src/application/utils/messageFromResponse.ts +133 -0
  249. package/src/application/utils/messageFromSuggestionEvent.ts +32 -0
  250. package/src/application/utils/mouseEventTypes.ts +1 -0
  251. package/src/application/utils/mutationHelper.ts +51 -0
  252. package/src/application/utils/nextMessageRequestToApiRequest.ts +31 -0
  253. package/src/application/utils/nodeSelector.ts +133 -0
  254. package/src/application/utils/overrides.ts +196 -0
  255. package/src/application/utils/stringUtils.ts +55 -0
  256. package/src/application/utils/supportedEventRequestToApiRequest.ts +12 -0
  257. package/src/application/utils/urlsParser.ts +53 -0
  258. package/src/application/utils/validation.ts +5 -0
  259. package/src/atoms/app/index.ts +57 -0
  260. package/src/atoms/app/variant.ts +261 -0
  261. package/src/atoms/atomStore.ts +34 -0
  262. package/src/atoms/chat/chatState.ts +44 -0
  263. package/src/atoms/chat/form.ts +19 -0
  264. package/src/atoms/chat/index.ts +38 -0
  265. package/src/atoms/chat/lastMessage.ts +11 -0
  266. package/src/atoms/chat/messageQueue.ts +65 -0
  267. package/src/atoms/chat/performanceMetrics.ts +84 -0
  268. package/src/atoms/chat/renderedWidgetRefs.ts +28 -0
  269. package/src/atoms/chat/replies.ts +51 -0
  270. package/src/atoms/chat/suggestions.ts +36 -0
  271. package/src/atoms/globalSearch.ts +12 -0
  272. package/src/atoms/index.ts +5 -0
  273. package/src/atoms/org/customerService.ts +13 -0
  274. package/src/atoms/org/graphqlConfig.ts +27 -0
  275. package/src/atoms/org/index.ts +7 -0
  276. package/src/atoms/org/merchantCss.ts +44 -0
  277. package/src/atoms/org/org.ts +256 -0
  278. package/src/atoms/org/orgAnalyticsConfig.ts +28 -0
  279. package/src/atoms/org/orgPageConfig.ts +38 -0
  280. package/src/atoms/org/orgUIConfig.ts +122 -0
  281. package/src/atoms/search/chatSearch.ts +293 -0
  282. package/src/atoms/search/index.ts +2 -0
  283. package/src/atoms/search/productFilters.ts +207 -0
  284. package/src/atoms/search/productSorter.ts +23 -0
  285. package/src/atoms/search/searchAPI.ts +194 -0
  286. package/src/atoms/search/types.ts +55 -0
  287. package/src/atoms/search/utils.ts +18 -0
  288. package/src/config/divIds.ts +27 -0
  289. package/src/config/locators/components/chat/entrypoints.ts +13 -0
  290. package/src/config/locators/components/chat/index.ts +23 -0
  291. package/src/config/locators/components/chat/preview.ts +13 -0
  292. package/src/config/locators/components/chat/variants/index.ts +16 -0
  293. package/src/config/locators/components/common/buttons.ts +6 -0
  294. package/src/config/locators/components/common/cards.ts +18 -0
  295. package/src/config/locators/components/common/links.ts +1 -0
  296. package/src/config/locators/components/common/tables.ts +2 -0
  297. package/src/config/locators/components/floating-button.ts +2 -0
  298. package/src/config/locators/components/index.ts +3 -0
  299. package/src/config/locators/components/report-issue.ts +21 -0
  300. package/src/config/locators/components/search/index.ts +5 -0
  301. package/src/config/locators/components/shadow-dom.ts +1 -0
  302. package/src/config/locators/embedded.ts +21 -0
  303. package/src/config/locators/index.ts +3 -0
  304. package/src/config/socialProofClasses.ts +17 -0
  305. package/src/contexts/chatContext.tsx +451 -0
  306. package/src/contexts/enviveConfigContext.tsx +70 -0
  307. package/src/contexts/index.ts +4 -0
  308. package/src/contexts/systemSettingsContext.tsx +61 -0
  309. package/src/contexts/types.ts +1059 -0
  310. package/src/enabled-features.ts +83 -0
  311. package/src/events/event-types.ts +11 -0
  312. package/src/events/index.ts +52 -0
  313. package/src/events/registerAnalyticsListeners.ts +49 -0
  314. package/src/extension.ts +63 -0
  315. package/src/hooks/index.ts +22 -0
  316. package/src/hooks/useBlockBackButton.ts +29 -0
  317. package/src/hooks/useChatToggle.ts +66 -0
  318. package/src/hooks/useCustomerSupportHandoff.ts +39 -0
  319. package/src/hooks/useDebounce.ts +17 -0
  320. package/src/hooks/useDynamicVariants.ts +210 -0
  321. package/src/hooks/useElementObserver.ts +245 -0
  322. package/src/hooks/useFileUpload.ts +61 -0
  323. package/src/hooks/useGrabAndScroll.ts +133 -0
  324. package/src/hooks/useHideElements.ts +82 -0
  325. package/src/hooks/useHorizontalScrollAnimation.ts +115 -0
  326. package/src/hooks/useImageResolver.ts +51 -0
  327. package/src/hooks/useIntersection.ts +28 -0
  328. package/src/hooks/useIsSmallScreen.ts +23 -0
  329. package/src/hooks/useMessageFilter.ts +49 -0
  330. package/src/hooks/useMessageScrollObserver.ts +47 -0
  331. package/src/hooks/useReducedMotionWithOverride.ts +15 -0
  332. package/src/hooks/useSearch.tsx +433 -0
  333. package/src/hooks/useSnapCalculator.ts +38 -0
  334. package/src/hooks/useSnapControl.ts +155 -0
  335. package/src/hooks/useSystemSettingsContext.ts +12 -0
  336. package/src/hooks/useTrackComponentVisibleEvent.ts +52 -0
  337. package/src/hooks/useUpdateAnalyticsProps.ts +56 -0
  338. package/src/hooks/utils.ts +153 -0
  339. package/src/index.ts +31 -0
  340. package/src/initialize.ts +163 -0
  341. package/src/interceptors/types.ts +6 -0
  342. package/src/interceptors/useFormEscalation.ts +40 -0
  343. package/src/interceptors/useMessageInterceptor.ts +32 -0
  344. package/src/main.ts +85 -0
  345. package/src/main.tsx +123 -0
  346. package/src/merchants/bandolier/bandolier.ts +1389 -0
  347. package/src/merchants/carpe/carpe.ts +656 -0
  348. package/src/merchants/coterie/coterie.ts +280 -0
  349. package/src/merchants/default.ts +193 -0
  350. package/src/merchants/dreamlandBaby/dreamlandBaby.ts +375 -0
  351. package/src/merchants/fiveCbd/fiveCbd.ts +697 -0
  352. package/src/merchants/forLoveAndLemons/forLoveAndLemons.ts +721 -0
  353. package/src/merchants/greenpan/greenpan.ts +440 -0
  354. package/src/merchants/grooveLife/grooveLife.ts +386 -0
  355. package/src/merchants/homegrownCannabis/homegrownCannabis.ts +468 -0
  356. package/src/merchants/init-merchant.sh +53 -0
  357. package/src/merchants/jackArcher/jackArcher.ts +974 -0
  358. package/src/merchants/jordanCraig/jordanCraig.ts +1927 -0
  359. package/src/merchants/kindredBravely/kindredBravely.ts +529 -0
  360. package/src/merchants/kutFromTheKloth/kutFromTheKloth.ts +418 -0
  361. package/src/merchants/larryAndSerges/larryAndSerges.ts +314 -0
  362. package/src/merchants/leapsAndRebounds/leapsAndRebounds.ts +424 -0
  363. package/src/merchants/longevityrx/longevityrx.ts +368 -0
  364. package/src/merchants/lookOptic/lookOptic.ts +323 -0
  365. package/src/merchants/mantraBrand/mantraBrand.ts +838 -0
  366. package/src/merchants/medterra/medterra.ts +670 -0
  367. package/src/merchants/modells/modells.ts +546 -0
  368. package/src/merchants/pressedFloral/pressedFloral.ts +734 -0
  369. package/src/merchants/skinPerfection/skinPerfection.ts +379 -0
  370. package/src/merchants/snapSupplements/snapSupplements.ts +325 -0
  371. package/src/merchants/spanx/spanx.ts +810 -0
  372. package/src/merchants/spanx/spanxStaging.ts +942 -0
  373. package/src/merchants/supergoop/supergoop.ts +376 -0
  374. package/src/merchants/uniqueVintage/uniqueVintage.ts +1314 -0
  375. package/src/merchants/uniqueVintage/views/useUniqueVintageChatSearch.ts +147 -0
  376. package/src/merchants/venaCbd/venaCbd.ts +410 -0
  377. package/src/merchants/westonJonBoucher/westonJonBoucher.ts +473 -0
  378. package/src/merchants/wineEnthusiast/wineEnthusiast.ts +990 -0
  379. package/src/merchants/wolfMattress/wolfMattress.ts +411 -0
  380. package/src/merchants/wolfTactical/wolfTactical.ts +389 -0
  381. package/src/types/custservice-types.ts +28 -0
  382. package/src/types/search-filter-types.ts +111 -0
  383. package/src/types/suggestionBarV2-types.ts +4 -0
  384. package/src/types/test-types.ts +3 -0
  385. package/src/types.ts +66 -0
@@ -0,0 +1,122 @@
1
+ import { atom } from 'jotai';
2
+ import { FeatureGates, getOrgInfo } from 'src/application/models';
3
+ import { findCustomerServiceImpl } from 'src/application/service/customerService';
4
+ import { CustomerServiceIntegrationMode } from 'src/application/service/customerService/types';
5
+ import { featureFlagServiceAtom, orgShortNameAtom } from 'src/atoms/org/org';
6
+ import { OrgUIConfig } from 'src/contexts/types';
7
+ import { getAtomStore } from 'src/atoms/atomStore';
8
+ import Logger from 'src/application/logging/logger';
9
+
10
+ const getCustomerServiceIntegrationMode = ({
11
+ integrationMode,
12
+ isNewFeatureEnabled,
13
+ }: {
14
+ integrationMode: CustomerServiceIntegrationMode;
15
+ isNewFeatureEnabled: boolean;
16
+ }): CustomerServiceIntegrationMode => {
17
+ if (integrationMode !== CustomerServiceIntegrationMode.full) {
18
+ return integrationMode;
19
+ }
20
+ // Only allow CustomerServiceIntegrationMode.full if `isNewFeatureEnabled` is also true
21
+ return isNewFeatureEnabled ? integrationMode : CustomerServiceIntegrationMode.simple;
22
+ };
23
+
24
+ const internalOrgUIConfigAtom = atom<OrgUIConfig | undefined>(undefined);
25
+
26
+ export const orgUIConfigAtom = atom(
27
+ (get) => {
28
+ const orgUIConfig = get(internalOrgUIConfigAtom);
29
+ if (orgUIConfig) {
30
+ return orgUIConfig;
31
+ }
32
+
33
+ throw new Error('OrgUIConfig is referenced before it is set');
34
+ },
35
+ (_, set, value: OrgUIConfig) => {
36
+ set(internalOrgUIConfigAtom, value);
37
+ },
38
+ );
39
+
40
+ export const getOrgUIConfig = async (
41
+ graphqlOrgUIConfig?: Omit<OrgUIConfig, 'merchantOverrideCss'>,
42
+ graphqlMerchantOverrideCss?: string,
43
+ ) => {
44
+ const atomStore = getAtomStore();
45
+ const orgShortName = atomStore.get(orgShortNameAtom);
46
+ const orgInfo = await getOrgInfo(orgShortName);
47
+ const localOrgUIConfig = orgInfo.orgUIConfig() ?? {};
48
+ const featureFlagService = atomStore.get(featureFlagServiceAtom);
49
+ const isNewFeatureEnabled = featureFlagService.isFeatureGateEnabled(
50
+ FeatureGates.IsNewFeatureEnabled,
51
+ );
52
+ const enableWatermark = featureFlagService.isFeatureGateEnabled(FeatureGates.IsWatermarkEnabled);
53
+ const userQueryAwaysEnabled = featureFlagService.isFeatureGateEnabled(
54
+ FeatureGates.IsUserQueryAlwaysEnabled,
55
+ );
56
+ const isGraphQLUIConfigsEnabled = featureFlagService.isFeatureGateEnabled(
57
+ FeatureGates.IsGraphQLUIConfigsEnabled,
58
+ );
59
+ const isGraphQLComponentConfigsEnabled = featureFlagService.isFeatureGateEnabled(
60
+ FeatureGates.IsGraphQLComponentConfigsEnabled,
61
+ );
62
+
63
+ let orgUIConfig: Partial<OrgUIConfig> = { ...localOrgUIConfig };
64
+
65
+ if (isGraphQLUIConfigsEnabled && graphqlMerchantOverrideCss) {
66
+ orgUIConfig = {
67
+ ...orgUIConfig,
68
+ merchantOverrideCss: graphqlMerchantOverrideCss,
69
+ };
70
+ }
71
+
72
+ if (isGraphQLComponentConfigsEnabled && graphqlOrgUIConfig) {
73
+ orgUIConfig = {
74
+ ...orgUIConfig,
75
+ ...graphqlOrgUIConfig,
76
+ };
77
+ }
78
+
79
+ Logger.logInfo('[spiffy-ai] Feature gate enableWatermark check', {
80
+ enableWatermark,
81
+ floatingChatConfig: orgUIConfig.floatingChatConfig,
82
+ });
83
+
84
+ const updatedOrgUIConfig: OrgUIConfig = {
85
+ ...orgUIConfig,
86
+ conversationalSearch: orgUIConfig.conversationalSearch
87
+ ? {
88
+ ...orgUIConfig.conversationalSearch,
89
+ enabled: isNewFeatureEnabled,
90
+ }
91
+ : undefined,
92
+ customerServiceIntegration: {
93
+ ...orgUIConfig.customerServiceIntegration,
94
+ integrationMode: getCustomerServiceIntegrationMode({
95
+ isNewFeatureEnabled,
96
+ integrationMode:
97
+ orgUIConfig.customerServiceIntegration?.integrationMode ??
98
+ CustomerServiceIntegrationMode.none,
99
+ }),
100
+ },
101
+ floatingChatConfig: {
102
+ ...orgUIConfig.floatingChatConfig,
103
+ // Only enable the watermark if it is enabled in config and through featureFlag.
104
+ // This enables a quick "off-switch" in case a merchant tells us to turn it off
105
+ // quickly. This can be removed once the config is moved to the backend
106
+ enableWatermark: orgUIConfig.floatingChatConfig?.enableWatermark && enableWatermark,
107
+ userQueryInputEnabled:
108
+ orgUIConfig.floatingChatConfig?.userQueryInputEnabled || userQueryAwaysEnabled,
109
+ },
110
+ } as OrgUIConfig;
111
+
112
+ atomStore.set(orgUIConfigAtom, updatedOrgUIConfig);
113
+ };
114
+
115
+ export const orgCustomerServiceConfig = atom(
116
+ (get) => get(orgUIConfigAtom).customerServiceIntegration,
117
+ );
118
+
119
+ export const orgCustomerServiceService = atom((get) => {
120
+ const { provider } = get(orgCustomerServiceConfig);
121
+ return findCustomerServiceImpl(provider);
122
+ });
@@ -0,0 +1,293 @@
1
+ import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
2
+ import { v4 as uuid } from 'uuid';
3
+ import { atom } from 'jotai';
4
+ import {
5
+ Message,
6
+ MessageRole,
7
+ MessageType,
8
+ ProductResponseAttributes,
9
+ } from 'src/application/models';
10
+ import {
11
+ messagesAtom,
12
+ replyEventCategoryAtom,
13
+ userHasRepliedAtom,
14
+ userQueryAtom,
15
+ } from 'src/atoms/chat';
16
+ import { queueUserEventAtom } from '../chat/messageQueue';
17
+ import { orgUIConfigAtom } from '../org';
18
+ import { ProductFilters } from './productFilters';
19
+ import { ProductSorter } from './productSorter';
20
+ import { ChatSearchStateType, ProductSorting } from './types';
21
+
22
+ // NOTE:
23
+ // This is a legacy version of search that needs to be decoupled and removed.
24
+ // the new search.ts file in this directory is likely what you're looking for.
25
+
26
+ export const chatSearchStateAtom = atom<ChatSearchStateType>('entrypoint');
27
+ export const chatSearchHasProductsAtom = atom<boolean>(false);
28
+ export const chatSearchProductSortingAtom = atom<ProductSorting>(ProductSorting.FEATURED);
29
+
30
+ const filterProductList = (message: Message) =>
31
+ message.type === MessageType.Product && message.metadata.isForGrid === true;
32
+ const filterFilterList = (message: Message) => message.type === MessageType.ProductSearchFilter;
33
+
34
+ export type ChatSearchTurn = {
35
+ id: string;
36
+ generatedQuery?: string;
37
+ productCount: number;
38
+ productList: ProductResponseAttributes['attributes'][];
39
+ filterList: string[];
40
+ messages: Message[];
41
+ };
42
+
43
+ export const chatSearchIsLoadingAtom = atom(false);
44
+
45
+ const buildChatSearchTurn = (messages: Message[]): [string, ChatSearchTurn] | null => {
46
+ const queryMessage = messages.find((message) => message.type === MessageType.ProductSearch);
47
+ if (!queryMessage) {
48
+ return null;
49
+ }
50
+ const { generatedQuery, productCount } = queryMessage.metadata ?? {};
51
+ // @ts-expect-error: product has metadata
52
+ const productList = messages.filter(filterProductList).map((message) => message.metadata);
53
+ const filterList = messages
54
+ .filter(filterFilterList)
55
+ .map((message) => message.metadata.filterName);
56
+ return [
57
+ queryMessage.id,
58
+ {
59
+ id: queryMessage.id,
60
+ generatedQuery,
61
+ productCount: productCount || productList.length,
62
+ productList,
63
+ filterList,
64
+ messages,
65
+ },
66
+ ];
67
+ };
68
+
69
+ const chatSearchFilterConfigAtom = atom((get) => {
70
+ const { searchConfig } = get(orgUIConfigAtom);
71
+ return searchConfig.searchFilterConfig;
72
+ });
73
+
74
+ const searchMapAtom = atom((get) => {
75
+ const messages = get(messagesAtom);
76
+ return Object.fromEntries(messages.map(buildChatSearchTurn).filter((v) => v !== null));
77
+ });
78
+
79
+ const internalChatSearchParamsAtom = atom<{
80
+ id: string | null;
81
+ query: string | null;
82
+ }>({
83
+ id: null,
84
+ query: null,
85
+ });
86
+
87
+ // Selected filter options with compound objects for better type safety and display
88
+ export type SelectedChatSearchFilterOption = {
89
+ id: string; // "filterId:filterItemId" - maintains backward compatibility for comparisons
90
+ displayName: string;
91
+ filterId: string; // Filter category
92
+ filterItemId: string; // Filter value
93
+ };
94
+
95
+ export const selectedFilterOptionsAtom = atom<SelectedChatSearchFilterOption[]>([]);
96
+
97
+ export const additiveDynamicFiltersAtom = atom((get) => {
98
+ const { searchConfig } = get(orgUIConfigAtom);
99
+ return searchConfig.additiveDynamicFilters ?? false;
100
+ });
101
+
102
+ export const createChatSearchFilterOption = (
103
+ filterId: string,
104
+ filterItemId: string,
105
+ displayName: string,
106
+ ): SelectedChatSearchFilterOption => ({
107
+ id: `${filterId}:${filterItemId}`,
108
+ displayName,
109
+ filterId,
110
+ filterItemId,
111
+ });
112
+
113
+ const handleSearchAtom = atom(null, (get, set, message: Message) => {
114
+ if (message.type !== MessageType.Search) {
115
+ return;
116
+ }
117
+
118
+ const querySearch = message.metadata.searchTerm;
119
+
120
+ set(replyEventCategoryAtom, UserEventCategory.Search);
121
+ set(userQueryAtom, querySearch);
122
+ set(messagesAtom, [...get(messagesAtom), [message]]);
123
+ set(userHasRepliedAtom, true);
124
+
125
+ set(queueUserEventAtom, {
126
+ eventId: message.id,
127
+ category: UserEventCategory.Search,
128
+ createdAt: message.createdAt,
129
+ attributes: {
130
+ searchTerm: message.metadata.searchTerm,
131
+ selectedFilters: message.metadata.selectedFilters,
132
+ },
133
+ });
134
+ });
135
+
136
+ // This internal atom is used to store the search params
137
+ export const chatSearchParamsAtom = atom(
138
+ (get) => get(internalChatSearchParamsAtom),
139
+ (_, set, value: { id: string | null; query: string | null }) => {
140
+ set(internalChatSearchParamsAtom, value);
141
+ },
142
+ );
143
+
144
+ export const chatSearchQueryAtom = atom(
145
+ (get) => get(chatSearchParamsAtom)?.query ?? null,
146
+ (get, set, value: string | null) => {
147
+ const searchParams = get(chatSearchParamsAtom);
148
+ set(chatSearchParamsAtom, { id: searchParams.id, query: value });
149
+ },
150
+ );
151
+
152
+ export const chatSearchIdAtom = atom(
153
+ (get) => get(chatSearchParamsAtom)?.id ?? null,
154
+ (get, set, value: string | null) => {
155
+ const searchParams = get(chatSearchParamsAtom);
156
+ set(chatSearchParamsAtom, { id: value, query: searchParams.query });
157
+ },
158
+ );
159
+
160
+ export const ChatSearchTurnAtom = atom((get) => {
161
+ const searchFilterConfig = get(chatSearchFilterConfigAtom);
162
+ const searchMap = get(searchMapAtom);
163
+ const chatSearchId = get(chatSearchIdAtom);
164
+ if (!chatSearchId) {
165
+ return null;
166
+ }
167
+ const ChatSearchTurn = searchMap[chatSearchId];
168
+ if (!ChatSearchTurn) {
169
+ return null;
170
+ }
171
+ const productSorting = get(chatSearchProductSortingAtom);
172
+ const selectedFilterOptions = get(selectedFilterOptionsAtom);
173
+ const additiveDynamicFilters = get(additiveDynamicFiltersAtom);
174
+ const filteredProducts = ProductFilters.filterProducts(
175
+ ChatSearchTurn.productList,
176
+ searchFilterConfig,
177
+ selectedFilterOptions,
178
+ additiveDynamicFilters,
179
+ );
180
+
181
+ const filteredAndSortedProducts = ProductSorter.sort(filteredProducts, productSorting);
182
+
183
+ return {
184
+ ...searchMap[chatSearchId],
185
+ productList: filteredAndSortedProducts,
186
+ productCount: filteredAndSortedProducts.length,
187
+ };
188
+ });
189
+
190
+ // TODO: Work on deprecating this atom in favor of using ChatSearchTurnAtom directly
191
+ export const chatSearchProducts = atom((get) => {
192
+ const ChatSearchTurn = get(ChatSearchTurnAtom);
193
+ return ChatSearchTurn?.productList ?? [];
194
+ });
195
+
196
+ export const chatSearchFiltersAtom = atom((get) => {
197
+ const searchFilterConfig = get(chatSearchFilterConfigAtom);
198
+ const searchMap = get(searchMapAtom);
199
+ const chatSearchId = get(chatSearchIdAtom);
200
+
201
+ if (chatSearchId && searchMap[chatSearchId]) {
202
+ const products = searchMap[chatSearchId].productList;
203
+ const selectedFilterOptions = get(selectedFilterOptionsAtom);
204
+ return ProductFilters.getFiltersForProducts(
205
+ products,
206
+ searchFilterConfig,
207
+ selectedFilterOptions,
208
+ );
209
+ }
210
+ return [];
211
+ });
212
+
213
+ // For search we use the following query params:
214
+ // es: true - this indicates that we are in a search state
215
+ // esq: query - this is the current search query
216
+ // esi: search id - this is the unique id for the search results that we are displaying. It is pulled from the message id from the turn that includes the search results
217
+
218
+ export const setSearchParams = (query: string) => {
219
+ const url = new URL(window.location.href);
220
+ url.searchParams.set('es', 'true');
221
+ url.searchParams.set('esq', query);
222
+ window.history.pushState({}, '', url);
223
+ };
224
+
225
+ export const getSearchParamsAtom = atom(() => {
226
+ const params = new URLSearchParams(window.location.search);
227
+ return {
228
+ es: params.get('es'),
229
+ esq: params.get('esq'),
230
+ };
231
+ });
232
+
233
+ export const initiateChatSearchAtom = atom(null, (_, set, query: string) => {
234
+ set(chatSearchIsLoadingAtom, true);
235
+ // Update URL and manually trigger state change (since pushState doesn't trigger popstate)
236
+ const url = new URL(window.location.href);
237
+ url.searchParams.set('es', 'true');
238
+ url.searchParams.set('esq', query);
239
+ window.history.pushState({}, '', url);
240
+
241
+ // Manually trigger page variant re-evaluation by dispatching a popstate event
242
+ // This ensures the page variant system detects the URL change and mounts search results
243
+ window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
244
+
245
+ set(chatSearchParamsAtom, { id: null, query });
246
+ set(handleSearchAtom, {
247
+ id: uuid(),
248
+ role: MessageRole.User,
249
+ type: MessageType.Search,
250
+ createdAt: new Date().toISOString(),
251
+ metadata: {
252
+ searchTerm: query,
253
+ // Enabling this will enable the new backend search
254
+ selectedFilters: [],
255
+ },
256
+ });
257
+ });
258
+
259
+ export const handleSearchResultsAtom = atom(null, (get, set, message: Message | undefined) => {
260
+ if (!message || message.type !== MessageType.ProductSearch) {
261
+ return;
262
+ }
263
+
264
+ const { productCount, generatedQuery } = message.metadata;
265
+ if (productCount !== 0) {
266
+ // Clear the filter options when we get new products
267
+ set(selectedFilterOptionsAtom, []);
268
+ // TODO: Should we still do any of these things?
269
+ set(chatSearchStateAtom, 'product-page');
270
+ const currentSearchParams = get(chatSearchParamsAtom);
271
+ const url = new URL(window.location.href);
272
+ url.searchParams.set('es', 'true');
273
+ url.searchParams.set('esq', generatedQuery);
274
+ url.searchParams.set('esi', message.id);
275
+ window.history.pushState({}, '', url);
276
+ if (currentSearchParams.id === null) {
277
+ // If the id is null, we need to replace the state
278
+ window.history.replaceState(null, '', url);
279
+ } else {
280
+ // If the query is different, we need to push a new state
281
+ window.history.pushState(null, '', url);
282
+ }
283
+ set(chatSearchParamsAtom, { id: message.id, query: generatedQuery });
284
+ }
285
+ });
286
+
287
+ export const setChatSearchParamsAtom = atom(null, (_, set, query: string) => {
288
+ const url = new URL(window.location.href);
289
+ url.searchParams.set('es', 'true');
290
+ url.searchParams.set('esq', query);
291
+ window.history.pushState({}, '', url);
292
+ set(chatSearchParamsAtom, { id: null, query });
293
+ });
@@ -0,0 +1,2 @@
1
+ export * from './chatSearch';
2
+ export * from './searchAPI';
@@ -0,0 +1,207 @@
1
+ import { ProductResponseAttributes } from 'src/application/models';
2
+ import { FilterAttribute } from 'src/contexts/types';
3
+ import { SelectedFilterOption } from 'src/atoms/search/searchAPI';
4
+ import { ChatSearchFilter, ChatSearchFilterItem } from './types';
5
+ import { formatFilterDisplayName } from './utils';
6
+
7
+ const getPriceBucket = (price: number, bucketSize: number) => {
8
+ const bucket = Math.floor(price / bucketSize);
9
+ return bucket * bucketSize;
10
+ };
11
+
12
+ const isStringArray = (value: unknown): value is string[] => {
13
+ if (!Array.isArray(value)) {
14
+ return false;
15
+ }
16
+ return value.every((item) => typeof item === 'string');
17
+ };
18
+
19
+ type FilterSortFunction = (a: ChatSearchFilterItem, b: ChatSearchFilterItem) => number;
20
+
21
+ const sortFilter = (filterConfig: FilterAttribute): FilterSortFunction => {
22
+ if (filterConfig.type === 'price') {
23
+ return (a, b) => Number(a.filterItemId) - Number(b.filterItemId);
24
+ }
25
+ if (filterConfig.type === 'dynamic') {
26
+ if (filterConfig.sorting.type === 'alphabetic') {
27
+ return (a, b) => a.displayName.localeCompare(b.displayName);
28
+ }
29
+ // Sort by the number of products, if they match, sort by alphabetical
30
+ if (filterConfig.sorting.type === 'productCount') {
31
+ return (a, b) =>
32
+ a.productCount === b.productCount
33
+ ? a.displayName.localeCompare(b.displayName)
34
+ : b.productCount - a.productCount;
35
+ }
36
+ if (filterConfig.sorting.type === 'custom') {
37
+ const sortedKeys = filterConfig.sorting.map;
38
+ return (a, b) => {
39
+ if (sortedKeys[a.displayName] && sortedKeys[b.displayName]) {
40
+ // If both are in the sortedKeys, sort by the index
41
+ return sortedKeys[a.displayName] - sortedKeys[b.displayName];
42
+ }
43
+ // If only 'a' is in the sortedKeys, put it before the other
44
+ if (sortedKeys[a.displayName]) {
45
+ return -1;
46
+ }
47
+ // If only 'b' is in the sortedKeys, put it before the other
48
+ if (sortedKeys[b.displayName]) {
49
+ return 1;
50
+ }
51
+ // If neither are in the sortedKeys, sort by productCount
52
+ return b.productCount - a.productCount;
53
+ };
54
+ }
55
+ }
56
+ throw new Error('Invaalid search filter configuration');
57
+ };
58
+
59
+ export class ProductFilters {
60
+ static getFiltersForProducts(
61
+ products: ProductResponseAttributes['attributes'][],
62
+ filterConfigs: FilterAttribute[],
63
+ selectedFilterOptions: SelectedFilterOption[],
64
+ ): ChatSearchFilter[] {
65
+ if (!products || products.length === 0) {
66
+ return [];
67
+ }
68
+
69
+ const filters = filterConfigs.map((filterConfig) =>
70
+ products.reduce(
71
+ (acc, product) => {
72
+ // Price has special handling for the filters
73
+ if (filterConfig.type === 'price') {
74
+ const priceBucket = getPriceBucket(
75
+ product.salePrice ?? product.originalPrice ?? 0,
76
+ filterConfig.bucketSize,
77
+ );
78
+ const current = acc.map[priceBucket] ?? {
79
+ filterItemId: priceBucket,
80
+ displayName: `$${priceBucket}-$${priceBucket + filterConfig.bucketSize}`,
81
+ productCount: 0,
82
+ };
83
+ acc.map[priceBucket] = { ...current, productCount: current.productCount + 1 };
84
+ return acc;
85
+ }
86
+
87
+ // Assume that the "attribute" is an array of values
88
+ const attributeValues =
89
+ (product.filters && product.filters?.[filterConfig.attribute]) ?? [];
90
+ if (isStringArray(attributeValues)) {
91
+ attributeValues.forEach((val) => {
92
+ const normalizedVal = val.toLowerCase();
93
+ const current = acc.map[normalizedVal] ?? {
94
+ filterItemId: normalizedVal,
95
+ displayName: formatFilterDisplayName(val), // Keep original case for display
96
+ productCount: 0,
97
+ };
98
+ acc.map[normalizedVal] = { ...current, productCount: current.productCount + 1 };
99
+ });
100
+ }
101
+
102
+ return acc;
103
+ },
104
+ {
105
+ filterConfig,
106
+ map: {} as Record<string, ChatSearchFilterItem>,
107
+ },
108
+ ),
109
+ );
110
+
111
+ const transformedFilters: ChatSearchFilter[] = filters.map(({ filterConfig, map }) => ({
112
+ filterId: filterConfig.filterId,
113
+ displayName: filterConfig.displayName,
114
+ items: Object.values(map)
115
+ .map((filter) => ({
116
+ filterItemId: filter.filterItemId,
117
+ displayName: filter.displayName,
118
+ productCount: filter.productCount,
119
+ isSelected:
120
+ selectedFilterOptions.some(
121
+ (option) => option.id === `${filterConfig.filterId}:${filter.filterItemId}`,
122
+ ) ?? false,
123
+ }))
124
+ .sort(sortFilter(filterConfig)),
125
+ }));
126
+
127
+ return [...transformedFilters];
128
+ }
129
+
130
+ static filterProducts(
131
+ products: ProductResponseAttributes['attributes'][],
132
+ filterConfigs: FilterAttribute[],
133
+ selectedFilterOptions: SelectedFilterOption[],
134
+ additiveDynamicFilters = false, // Set this to true to restore the original behavior where stacking filters has OR logic.
135
+ ) {
136
+ const selectedFilters = filterConfigs
137
+ .map((filterConfig) => ({
138
+ filterConfig,
139
+ hasSelectedOptions: selectedFilterOptions.some(
140
+ (option) => option.filterId === filterConfig.filterId,
141
+ ),
142
+ }))
143
+ .filter(({ hasSelectedOptions }) => hasSelectedOptions);
144
+ return selectedFilterOptions.length === 0
145
+ ? products
146
+ : products.filter((product) =>
147
+ selectedFilters.reduce((acc, selectedFilter) => {
148
+ // Finding one false removes the product
149
+ if (!acc) {
150
+ return acc;
151
+ }
152
+ // Price is special
153
+ if (selectedFilter.filterConfig.type === 'price') {
154
+ const priceBucket = getPriceBucket(
155
+ product.salePrice ?? product.originalPrice ?? 0,
156
+ selectedFilter.filterConfig.bucketSize,
157
+ );
158
+ const matchesPriceFilter = selectedFilterOptions.some(
159
+ (option) => option.id === `price:${priceBucket}`,
160
+ );
161
+ if (!matchesPriceFilter) {
162
+ return false;
163
+ }
164
+ }
165
+
166
+ if (selectedFilter.filterConfig.type === 'dynamic') {
167
+ // dynamic is misleading here. Its not just dynamic filters, but static filters because the config counts static filters as type: dynamic.
168
+ const { filterId, attribute } = selectedFilter.filterConfig;
169
+ const attributeValues = product.filters?.[attribute];
170
+
171
+ if (!isStringArray(attributeValues)) {
172
+ return false;
173
+ }
174
+
175
+ const isDynamicFilter = selectedFilter.filterConfig.attribute === 'dynamic'; // True dynamic filters (the ones that appear beneath the search input)
176
+ const isSubtractiveLogic = isDynamicFilter && !additiveDynamicFilters;
177
+
178
+ if (isSubtractiveLogic) {
179
+ // AND logic for subtractive dynamic filters and between static filter categories
180
+ const selectedOptionsForThisFilter = selectedFilterOptions
181
+ .filter((option) => option.filterId === filterId)
182
+ .map((option) => option.filterItemId.toLowerCase());
183
+
184
+ const hasAllSelectedValues = selectedOptionsForThisFilter.every((selectedValue) =>
185
+ attributeValues.some((val) => val.toLowerCase() === selectedValue),
186
+ );
187
+
188
+ if (!hasAllSelectedValues) {
189
+ return false;
190
+ }
191
+ } else {
192
+ // OR logic for static filters in the sidebar within a category.
193
+ const matches = attributeValues.some((val) =>
194
+ selectedFilterOptions.some(
195
+ (option) => option.id === `${filterId}:${val.toLowerCase()}`,
196
+ ),
197
+ );
198
+ if (!matches) {
199
+ return false;
200
+ }
201
+ }
202
+ }
203
+ return true;
204
+ }, true),
205
+ );
206
+ }
207
+ }
@@ -0,0 +1,23 @@
1
+ import { ProductResponseAttributes } from 'src/application/models';
2
+ import { ProductSorting } from './types';
3
+
4
+ export class ProductSorter {
5
+ static sort(products: ProductResponseAttributes['attributes'][], sorting: ProductSorting) {
6
+ switch (sorting) {
7
+ case ProductSorting.PRICE_ASC:
8
+ return [...products].sort(
9
+ (p1, p2) =>
10
+ (p1.salePrice ?? p1.originalPrice ?? 0) - (p2.salePrice ?? p2.originalPrice ?? 0),
11
+ );
12
+
13
+ case ProductSorting.PRICE_DESC:
14
+ return [...products].sort(
15
+ (p1, p2) =>
16
+ (p2.salePrice ?? p2.originalPrice ?? 0) - (p1.salePrice ?? p1.originalPrice ?? 0),
17
+ );
18
+
19
+ default:
20
+ return products;
21
+ }
22
+ }
23
+ }