@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.
- package/LICENSE +2 -0
- package/README.md +2 -0
- package/dist/GridInsertionService-CEYo9pGj.js +22 -0
- package/dist/GridInsertionService-CS_bnPh0.cjs +28 -0
- package/dist/bandolier-Ble8jEa8.js +1221 -0
- package/dist/bandolier-Bm2xAt_j.cjs +1221 -0
- package/dist/carpe-Da7b-LCW.cjs +599 -0
- package/dist/carpe-W13mhRRP.js +597 -0
- package/dist/cdnService-CZ-aXcY6.cjs +23 -0
- package/dist/cdnService-zQfKk3Eb.js +18 -0
- package/dist/chatElementDisplayLocation-CX8fuNao.d.cts +239 -0
- package/dist/chatElementDisplayLocation-CwptS9tx.d.ts +239 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/contexts/index.cjs +13 -0
- package/dist/contexts/index.d.cts +65 -0
- package/dist/contexts/index.d.ts +66 -0
- package/dist/contexts/index.js +7 -0
- package/dist/contexts-BRjfVq_k.js +5064 -0
- package/dist/contexts-BYArqZtK.cjs +5164 -0
- package/dist/coterie-3y0D9ko4.cjs +229 -0
- package/dist/coterie-DOWcJAYv.js +229 -0
- package/dist/custservice-types-CFIFwZ-r.js +10 -0
- package/dist/custservice-types-CkfxZiHY.cjs +16 -0
- package/dist/default-C2fEZKXk.js +175 -0
- package/dist/default-CBUq6Q6G.cjs +4 -0
- package/dist/default-CGIFZK6m.js +4 -0
- package/dist/default-D_KPZdPJ.cjs +198 -0
- package/dist/divIds-Bss-btao.js +49 -0
- package/dist/divIds-DnZNd7rA.cjs +223 -0
- package/dist/dreamlandBaby-DCIsuU9R.cjs +338 -0
- package/dist/dreamlandBaby-DvSaZGrz.js +338 -0
- package/dist/entrypoints-D_JUvkgy.cjs +18 -0
- package/dist/entrypoints-YLQsbBRD.js +6 -0
- package/dist/enviveConfigContext-CUGLpPGU.js +34 -0
- package/dist/enviveConfigContext-Dfr2VH6u.cjs +48 -0
- package/dist/fiveCbd-B1SESMCO.js +605 -0
- package/dist/fiveCbd-CkOlVby_.cjs +605 -0
- package/dist/forLoveAndLemons-CfYPMnKS.cjs +660 -0
- package/dist/forLoveAndLemons-DmwYZIk0.js +658 -0
- package/dist/greenpan-Bsl3ir59.cjs +389 -0
- package/dist/greenpan-BtOi45lf.js +389 -0
- package/dist/grooveLife-6_dtYsRk.js +334 -0
- package/dist/grooveLife-Cmm1PSCL.cjs +334 -0
- package/dist/homegrownCannabis-C-kw-74X.js +400 -0
- package/dist/homegrownCannabis-CO0uY_mp.cjs +400 -0
- package/dist/hooks/index.cjs +16 -0
- package/dist/hooks/index.d.cts +357 -0
- package/dist/hooks/index.d.ts +357 -0
- package/dist/hooks/index.js +7 -0
- package/dist/jackArcher-CLVmwwpI.js +719 -0
- package/dist/jackArcher-DdYTIzAV.cjs +719 -0
- package/dist/jordanCraig-Am-Oor-O.js +1778 -0
- package/dist/jordanCraig-_u3-w4Hp.cjs +1778 -0
- package/dist/kindredBravely-CWovIDSc.cjs +482 -0
- package/dist/kindredBravely-eWp-ud_E.js +482 -0
- package/dist/kutFromTheKloth-BMV4BuGQ.js +361 -0
- package/dist/kutFromTheKloth-Q589bAOC.cjs +361 -0
- package/dist/larryAndSerges-BMUlTgI-.js +252 -0
- package/dist/larryAndSerges-CEau764j.cjs +252 -0
- package/dist/leapsAndRebounds-DGMzPO7T.js +352 -0
- package/dist/leapsAndRebounds-DHAtRTJD.cjs +352 -0
- package/dist/logger-Dln20ans.cjs +25 -0
- package/dist/logger-pdEEY8T2.js +19 -0
- package/dist/longevityrx-CZW8Hxzi.cjs +312 -0
- package/dist/longevityrx-jH2JLhNH.js +312 -0
- package/dist/lookOptic-BGXP5P_V.js +274 -0
- package/dist/lookOptic-CA6RwLbG.cjs +274 -0
- package/dist/mantraBrand-Cm9_PBCT.js +742 -0
- package/dist/mantraBrand-DByNqpnL.cjs +742 -0
- package/dist/medterra-B0wxj_PV.js +575 -0
- package/dist/medterra-DnPN2ksU.cjs +575 -0
- package/dist/modells-Bmz8Ag5M.js +476 -0
- package/dist/modells-CoYgkLSp.cjs +476 -0
- package/dist/models-DHdb7QWn.js +51 -0
- package/dist/models-ixxUsGL_.cjs +69 -0
- package/dist/pressedFloral-DSKs_oVG.js +653 -0
- package/dist/pressedFloral-DjBiSoUl.cjs +653 -0
- package/dist/skinPerfection-B_3xzVNS.cjs +326 -0
- package/dist/skinPerfection-IDrBuAPt.js +326 -0
- package/dist/snapSupplements-BJk5T5ba.js +277 -0
- package/dist/snapSupplements-BStTsdOZ.cjs +277 -0
- package/dist/socialProofClasses-Bhv2Vulz.js +9 -0
- package/dist/socialProofClasses-CrQBWdSA.cjs +39 -0
- package/dist/spanx-BYg0LE7R.js +653 -0
- package/dist/spanx-LwU1zSzq.cjs +655 -0
- package/dist/spanxStaging-CfSmuKYB.js +837 -0
- package/dist/spanxStaging-OZLV9qix.cjs +840 -0
- package/dist/suggestionBarV2-types-BllzwsBD.js +34 -0
- package/dist/suggestionBarV2-types-CaovchMP.cjs +46 -0
- package/dist/supergoop-BqPXDnKk.cjs +327 -0
- package/dist/supergoop-CIlrHND_.js +325 -0
- package/dist/types-C4T5UOIW.cjs +230 -0
- package/dist/types-CYNvLeSA.js +176 -0
- package/dist/uniqueVintage-B30mOqbH.cjs +1205 -0
- package/dist/uniqueVintage-CFueJOhO.js +1203 -0
- package/dist/venaCbd-DHGZy49P.cjs +357 -0
- package/dist/venaCbd-T0CqVD4k.js +357 -0
- package/dist/westonJonBoucher-BdMzs_Yg.cjs +414 -0
- package/dist/westonJonBoucher-b4TCQ4ev.js +414 -0
- package/dist/wineEnthusiast-BLGlOjgr.cjs +932 -0
- package/dist/wineEnthusiast-BqR0i_54.js +932 -0
- package/dist/wolfMattress-CyyO-LoC.js +362 -0
- package/dist/wolfMattress-DNGZOivg.cjs +362 -0
- package/dist/wolfTactical-3Mm2fvVF.js +341 -0
- package/dist/wolfTactical-BmXYlFjr.cjs +341 -0
- package/package.json +66 -0
- package/src/adapters/amplitude/amplitudeAdapter.ts +454 -0
- package/src/adapters/amplitude/index.ts +2 -0
- package/src/adapters/amplitude/stubAmplitudeAdapter.ts +34 -0
- package/src/adapters/spiffy/commerce/api.ts +596 -0
- package/src/adapters/spiffy/commerce/exceptions/sessionExceptions.ts +6 -0
- package/src/adapters/spiffy/commerce/exceptions/unsupportedProductExceptions.ts +6 -0
- package/src/adapters/spiffy/commerce/graphql.ts +184 -0
- package/src/application/config/generalStaticConfig.ts +37 -0
- package/src/application/logging/logger.ts +29 -0
- package/src/application/models/api/context.ts +4 -0
- package/src/application/models/api/generationParams.ts +4 -0
- package/src/application/models/api/nextMessageRequest.ts +11 -0
- package/src/application/models/api/orgAnalyticsConfig.ts +19 -0
- package/src/application/models/api/orgConfigResults.ts +40 -0
- package/src/application/models/api/organizationConfig.ts +12 -0
- package/src/application/models/api/response.ts +132 -0
- package/src/application/models/api/responseGenerics.ts +67 -0
- package/src/application/models/api/search.ts +26 -0
- package/src/application/models/api/suggestion.ts +4 -0
- package/src/application/models/api/supportedEventRequest.ts +8 -0
- package/src/application/models/api/userEvent.ts +101 -0
- package/src/application/models/cachedValue.ts +8 -0
- package/src/application/models/chatElementDisplayLocation.ts +22 -0
- package/src/application/models/clientDetails.ts +18 -0
- package/src/application/models/colorsConfig.ts +28 -0
- package/src/application/models/conversationalSearchIds.ts +5 -0
- package/src/application/models/dataLayer.ts +45 -0
- package/src/application/models/domMutationContinuation.ts +7 -0
- package/src/application/models/domObservationStrategy.ts +9 -0
- package/src/application/models/events.ts +5 -0
- package/src/application/models/featureGates.ts +23 -0
- package/src/application/models/frontendConfig.ts +14 -0
- package/src/application/models/googleAnalyticsEvents.ts +8 -0
- package/src/application/models/graphql/index.ts +2 -0
- package/src/application/models/graphql/queries/getMerchantColorsQuery.ts +37 -0
- package/src/application/models/graphql/queries/getMerchantFrontendConfigQuery.ts +103 -0
- package/src/application/models/graphql/queries/getMerchantOrgIdQuery.ts +11 -0
- package/src/application/models/guards/api/index.ts +12 -0
- package/src/application/models/guards/api/isApiFormResponse.ts +90 -0
- package/src/application/models/guards/api/isApiFormSubmittedResponseAttributes.ts +37 -0
- package/src/application/models/guards/api/isApiOrderResponseAttributes.ts +155 -0
- package/src/application/models/guards/api/isApiOrgConfigResults.ts +277 -0
- package/src/application/models/guards/api/isApiOrganizationConfig.ts +207 -0
- package/src/application/models/guards/api/isApiPDPEventAttributes.ts +21 -0
- package/src/application/models/guards/api/isApiPLPEventAttributes.ts +41 -0
- package/src/application/models/guards/api/isApiPageResponseAttributes.ts +21 -0
- package/src/application/models/guards/api/isApiProductResponseAttributes.ts +85 -0
- package/src/application/models/guards/api/isApiProductSearchAttributes.ts +23 -0
- package/src/application/models/guards/api/isApiProductSearchFilterAttributes.ts +15 -0
- package/src/application/models/guards/api/isApiQueryTypedEventAttributes.ts +4 -0
- package/src/application/models/guards/api/isApiResponse.ts +39 -0
- package/src/application/models/guards/api/isApiReviewResponseAttributes.ts +30 -0
- package/src/application/models/guards/api/isApiReviewRichInformation.ts +37 -0
- package/src/application/models/guards/api/isApiSearchEventAttributes.ts +28 -0
- package/src/application/models/guards/api/isApiSuggestion.ts +36 -0
- package/src/application/models/guards/api/isApiSuggestionClickedEventAttributes.ts +9 -0
- package/src/application/models/guards/api/isApiTextResponseAttributes.ts +9 -0
- package/src/application/models/guards/api/isApiUserEvent.ts +25 -0
- package/src/application/models/guards/graphQL/isGraphQLColorsConfig.ts +50 -0
- package/src/application/models/guards/isBaseEcommerceEvent.ts +17 -0
- package/src/application/models/guards/isGA4EcommerceEvent.ts +17 -0
- package/src/application/models/guards/isLegacyUAEcommerceEvent.ts +17 -0
- package/src/application/models/guards/isMobilePLPChatPlacementParameter.ts +11 -0
- package/src/application/models/guards/isSpanxTakeAQuizCtaParameter.ts +4 -0
- package/src/application/models/guards/isVariantInfo.ts +37 -0
- package/src/application/models/guards/utils.ts +43 -0
- package/src/application/models/index.ts +20 -0
- package/src/application/models/localStorageEventListener.ts +4 -0
- package/src/application/models/message.ts +146 -0
- package/src/application/models/mobilePLPChatPlacementParameter.ts +3 -0
- package/src/application/models/orgsEnum.ts +36 -0
- package/src/application/models/productExperiment.ts +5 -0
- package/src/application/models/spanxTakeAQuizCtaParameter.ts +4 -0
- package/src/application/models/spiffyWidgets.ts +16 -0
- package/src/application/models/supportedOrgs.ts +137 -0
- package/src/application/models/utilityTypes/camelCase.ts +87 -0
- package/src/application/models/utilityTypes/camelCasedPropertiesDeep.ts +80 -0
- package/src/application/models/utilityTypes/delimiterCase.ts +121 -0
- package/src/application/models/utilityTypes/delimiterCasedPropertiesDeep.ts +98 -0
- package/src/application/models/utilityTypes/index.ts +1 -0
- package/src/application/models/utilityTypes/internal.ts +93 -0
- package/src/application/models/utilityTypes/primitive.ts +8 -0
- package/src/application/models/utilityTypes/snakeCasedPropertiesDeep.ts +49 -0
- package/src/application/models/utilityTypes/splitWords.ts +76 -0
- package/src/application/models/utilityTypes/trim.ts +28 -0
- package/src/application/models/utilityTypes/unknownArray.ts +25 -0
- package/src/application/models/utils/snakeToCamelTransformer.ts +90 -0
- package/src/application/models/utils/stringToFulfillmentDisplayStatusEnumValue.ts +68 -0
- package/src/application/models/validators/validateGraphQLColorsConfig.ts +29 -0
- package/src/application/models/validators/validateGraphQLFrontendConfig.ts +594 -0
- package/src/application/models/validators/validateGraphQLOrgId.ts +7 -0
- package/src/application/models/validators/validateMobilePLPChatPlacementParameter.ts +14 -0
- package/src/application/models/validators/validateOrgConfigResults.ts +47 -0
- package/src/application/models/validators/validateOrganizationConfig.ts +37 -0
- package/src/application/models/validators/validateResponse.ts +187 -0
- package/src/application/models/validators/validateSuggestion.ts +16 -0
- package/src/application/models/validators/validateUserEvent.ts +110 -0
- package/src/application/models/variantInfo/index.ts +1 -0
- package/src/application/models/variantInfo/pageVisitInfo.ts +6 -0
- package/src/application/models/variantInfo/plpInfo.ts +3 -0
- package/src/application/models/variantInfo/productInfo.ts +5 -0
- package/src/application/models/variantInfo/variantInfo.ts +23 -0
- package/src/application/service/cachingService.ts +84 -0
- package/src/application/service/cdnService.ts +18 -0
- package/src/application/service/customerService/index.ts +8 -0
- package/src/application/service/customerService/providers/UnsupportedCustomerService.ts +15 -0
- package/src/application/service/customerService/types.ts +31 -0
- package/src/application/service/domMutationObserver.ts +320 -0
- package/src/application/service/domMutations/GridInsertionService.ts +123 -0
- package/src/application/service/domMutations/dataLayer/dataLayerEventsListener.ts +99 -0
- package/src/application/service/domMutations/domInsertionService.ts +90 -0
- package/src/application/service/domMutations/domMutationListener.ts +15 -0
- package/src/application/service/domMutations/domMutationListenerState.ts +52 -0
- package/src/application/service/domMutations/floatingChat/embeddedChatsPlacementsListener.ts +41 -0
- package/src/application/service/domMutations/gladly/gladlyListener.ts +61 -0
- package/src/application/service/domMutations/spiffy/orgs/common/kustomerVisibilityListener.ts +41 -0
- package/src/application/service/domMutations/spiffy/orgs/common/orgsCommonDataLayerListener.ts +119 -0
- package/src/application/service/environmentService.ts +51 -0
- package/src/application/service/featureFlagService.ts +130 -0
- package/src/application/service/kustomerIntegrationService.ts +111 -0
- package/src/application/service/localStorageService.ts +77 -0
- package/src/application/service/pageVariantService.ts +779 -0
- package/src/application/service/searchService.ts +140 -0
- package/src/application/service/sessionStorageService.ts +27 -0
- package/src/application/service/shopifyUrlService.ts +63 -0
- package/src/application/service/userIdentityService.ts +114 -0
- package/src/application/service/windowChatToggleService.ts +71 -0
- package/src/application/service/windowDataLayerService.ts +181 -0
- package/src/application/service/windowFrontendConfigService.ts +104 -0
- package/src/application/utils/__tests__/divideArrays.test.ts +14 -0
- package/src/application/utils/analyticsUtils.ts +110 -0
- package/src/application/utils/coreContextToApiContext.ts +11 -0
- package/src/application/utils/coreUserEventToApiUserEvent.ts +106 -0
- package/src/application/utils/divideArray.ts +7 -0
- package/src/application/utils/domObserver.ts +96 -0
- package/src/application/utils/elementObserver.ts +246 -0
- package/src/application/utils/imageFilter.ts +12 -0
- package/src/application/utils/index.ts +3 -0
- package/src/application/utils/merchantUtils.ts +16 -0
- package/src/application/utils/messageFromFormSubmittedEvent.ts +31 -0
- package/src/application/utils/messageFromQueryEvent.ts +38 -0
- package/src/application/utils/messageFromResponse.ts +133 -0
- package/src/application/utils/messageFromSuggestionEvent.ts +32 -0
- package/src/application/utils/mouseEventTypes.ts +1 -0
- package/src/application/utils/mutationHelper.ts +51 -0
- package/src/application/utils/nextMessageRequestToApiRequest.ts +31 -0
- package/src/application/utils/nodeSelector.ts +133 -0
- package/src/application/utils/overrides.ts +196 -0
- package/src/application/utils/stringUtils.ts +55 -0
- package/src/application/utils/supportedEventRequestToApiRequest.ts +12 -0
- package/src/application/utils/urlsParser.ts +53 -0
- package/src/application/utils/validation.ts +5 -0
- package/src/atoms/app/index.ts +57 -0
- package/src/atoms/app/variant.ts +261 -0
- package/src/atoms/atomStore.ts +34 -0
- package/src/atoms/chat/chatState.ts +44 -0
- package/src/atoms/chat/form.ts +19 -0
- package/src/atoms/chat/index.ts +38 -0
- package/src/atoms/chat/lastMessage.ts +11 -0
- package/src/atoms/chat/messageQueue.ts +65 -0
- package/src/atoms/chat/performanceMetrics.ts +84 -0
- package/src/atoms/chat/renderedWidgetRefs.ts +28 -0
- package/src/atoms/chat/replies.ts +51 -0
- package/src/atoms/chat/suggestions.ts +36 -0
- package/src/atoms/globalSearch.ts +12 -0
- package/src/atoms/index.ts +5 -0
- package/src/atoms/org/customerService.ts +13 -0
- package/src/atoms/org/graphqlConfig.ts +27 -0
- package/src/atoms/org/index.ts +7 -0
- package/src/atoms/org/merchantCss.ts +44 -0
- package/src/atoms/org/org.ts +256 -0
- package/src/atoms/org/orgAnalyticsConfig.ts +28 -0
- package/src/atoms/org/orgPageConfig.ts +38 -0
- package/src/atoms/org/orgUIConfig.ts +122 -0
- package/src/atoms/search/chatSearch.ts +293 -0
- package/src/atoms/search/index.ts +2 -0
- package/src/atoms/search/productFilters.ts +207 -0
- package/src/atoms/search/productSorter.ts +23 -0
- package/src/atoms/search/searchAPI.ts +194 -0
- package/src/atoms/search/types.ts +55 -0
- package/src/atoms/search/utils.ts +18 -0
- package/src/config/divIds.ts +27 -0
- package/src/config/locators/components/chat/entrypoints.ts +13 -0
- package/src/config/locators/components/chat/index.ts +23 -0
- package/src/config/locators/components/chat/preview.ts +13 -0
- package/src/config/locators/components/chat/variants/index.ts +16 -0
- package/src/config/locators/components/common/buttons.ts +6 -0
- package/src/config/locators/components/common/cards.ts +18 -0
- package/src/config/locators/components/common/links.ts +1 -0
- package/src/config/locators/components/common/tables.ts +2 -0
- package/src/config/locators/components/floating-button.ts +2 -0
- package/src/config/locators/components/index.ts +3 -0
- package/src/config/locators/components/report-issue.ts +21 -0
- package/src/config/locators/components/search/index.ts +5 -0
- package/src/config/locators/components/shadow-dom.ts +1 -0
- package/src/config/locators/embedded.ts +21 -0
- package/src/config/locators/index.ts +3 -0
- package/src/config/socialProofClasses.ts +17 -0
- package/src/contexts/chatContext.tsx +451 -0
- package/src/contexts/enviveConfigContext.tsx +70 -0
- package/src/contexts/index.ts +4 -0
- package/src/contexts/systemSettingsContext.tsx +61 -0
- package/src/contexts/types.ts +1059 -0
- package/src/enabled-features.ts +83 -0
- package/src/events/event-types.ts +11 -0
- package/src/events/index.ts +52 -0
- package/src/events/registerAnalyticsListeners.ts +49 -0
- package/src/extension.ts +63 -0
- package/src/hooks/index.ts +22 -0
- package/src/hooks/useBlockBackButton.ts +29 -0
- package/src/hooks/useChatToggle.ts +66 -0
- package/src/hooks/useCustomerSupportHandoff.ts +39 -0
- package/src/hooks/useDebounce.ts +17 -0
- package/src/hooks/useDynamicVariants.ts +210 -0
- package/src/hooks/useElementObserver.ts +245 -0
- package/src/hooks/useFileUpload.ts +61 -0
- package/src/hooks/useGrabAndScroll.ts +133 -0
- package/src/hooks/useHideElements.ts +82 -0
- package/src/hooks/useHorizontalScrollAnimation.ts +115 -0
- package/src/hooks/useImageResolver.ts +51 -0
- package/src/hooks/useIntersection.ts +28 -0
- package/src/hooks/useIsSmallScreen.ts +23 -0
- package/src/hooks/useMessageFilter.ts +49 -0
- package/src/hooks/useMessageScrollObserver.ts +47 -0
- package/src/hooks/useReducedMotionWithOverride.ts +15 -0
- package/src/hooks/useSearch.tsx +433 -0
- package/src/hooks/useSnapCalculator.ts +38 -0
- package/src/hooks/useSnapControl.ts +155 -0
- package/src/hooks/useSystemSettingsContext.ts +12 -0
- package/src/hooks/useTrackComponentVisibleEvent.ts +52 -0
- package/src/hooks/useUpdateAnalyticsProps.ts +56 -0
- package/src/hooks/utils.ts +153 -0
- package/src/index.ts +31 -0
- package/src/initialize.ts +163 -0
- package/src/interceptors/types.ts +6 -0
- package/src/interceptors/useFormEscalation.ts +40 -0
- package/src/interceptors/useMessageInterceptor.ts +32 -0
- package/src/main.ts +85 -0
- package/src/main.tsx +123 -0
- package/src/merchants/bandolier/bandolier.ts +1389 -0
- package/src/merchants/carpe/carpe.ts +656 -0
- package/src/merchants/coterie/coterie.ts +280 -0
- package/src/merchants/default.ts +193 -0
- package/src/merchants/dreamlandBaby/dreamlandBaby.ts +375 -0
- package/src/merchants/fiveCbd/fiveCbd.ts +697 -0
- package/src/merchants/forLoveAndLemons/forLoveAndLemons.ts +721 -0
- package/src/merchants/greenpan/greenpan.ts +440 -0
- package/src/merchants/grooveLife/grooveLife.ts +386 -0
- package/src/merchants/homegrownCannabis/homegrownCannabis.ts +468 -0
- package/src/merchants/init-merchant.sh +53 -0
- package/src/merchants/jackArcher/jackArcher.ts +974 -0
- package/src/merchants/jordanCraig/jordanCraig.ts +1927 -0
- package/src/merchants/kindredBravely/kindredBravely.ts +529 -0
- package/src/merchants/kutFromTheKloth/kutFromTheKloth.ts +418 -0
- package/src/merchants/larryAndSerges/larryAndSerges.ts +314 -0
- package/src/merchants/leapsAndRebounds/leapsAndRebounds.ts +424 -0
- package/src/merchants/longevityrx/longevityrx.ts +368 -0
- package/src/merchants/lookOptic/lookOptic.ts +323 -0
- package/src/merchants/mantraBrand/mantraBrand.ts +838 -0
- package/src/merchants/medterra/medterra.ts +670 -0
- package/src/merchants/modells/modells.ts +546 -0
- package/src/merchants/pressedFloral/pressedFloral.ts +734 -0
- package/src/merchants/skinPerfection/skinPerfection.ts +379 -0
- package/src/merchants/snapSupplements/snapSupplements.ts +325 -0
- package/src/merchants/spanx/spanx.ts +810 -0
- package/src/merchants/spanx/spanxStaging.ts +942 -0
- package/src/merchants/supergoop/supergoop.ts +376 -0
- package/src/merchants/uniqueVintage/uniqueVintage.ts +1314 -0
- package/src/merchants/uniqueVintage/views/useUniqueVintageChatSearch.ts +147 -0
- package/src/merchants/venaCbd/venaCbd.ts +410 -0
- package/src/merchants/westonJonBoucher/westonJonBoucher.ts +473 -0
- package/src/merchants/wineEnthusiast/wineEnthusiast.ts +990 -0
- package/src/merchants/wolfMattress/wolfMattress.ts +411 -0
- package/src/merchants/wolfTactical/wolfTactical.ts +389 -0
- package/src/types/custservice-types.ts +28 -0
- package/src/types/search-filter-types.ts +111 -0
- package/src/types/suggestionBarV2-types.ts +4 -0
- package/src/types/test-types.ts +3 -0
- 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,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
|
+
}
|