@faststore/core 4.6.0-dev.1 → 4.6.0-dev.3
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/.turbo/turbo-generate.log +4 -4
- package/.turbo/turbo-test.log +104 -98
- package/@generated/cached-operations.json +1 -1
- package/@generated/gql.ts +9 -9
- package/@generated/graphql.ts +18 -12
- package/@generated/persisted-documents.json +2 -2
- package/@generated/schema.graphql +6 -0
- package/CHANGELOG.md +13 -0
- package/package.json +6 -6
- package/src/components/sections/RecommendationShelf/README.md +4 -1
- package/src/components/sections/RecommendationShelf/RecommendationShelf.tsx +11 -92
- package/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts +3 -18
- package/src/sdk/cart/index.ts +15 -1
- package/src/sdk/cart/waitForSessionValidated.ts +43 -0
- package/src/sdk/recommendations/index.ts +17 -0
- package/src/sdk/recommendations/types.ts +17 -0
- package/src/sdk/recommendations/useRecommendationShelf.ts +141 -0
- package/src/{components/sections/RecommendationShelf → sdk/recommendations}/vrn.ts +1 -1
- package/src/sdk/session/index.ts +14 -0
- package/src/sdk/session/syncSalesChannelFromOrderForm.ts +68 -0
- package/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx +6 -12
- package/test/sdk/cart/waitForSessionValidated.test.ts +96 -0
- package/test/sdk/recommendations/useRecommendationShelf.browser.test.tsx +199 -0
- package/test/{components/RecommendationShelf → sdk/recommendations}/useRecommendationUserId.test.ts +1 -1
- package/test/{components/RecommendationShelf → sdk/recommendations}/useRecommendations.test.ts +1 -1
- package/test/{components/RecommendationShelf → sdk/recommendations}/vrn.test.ts +1 -4
- package/test/sdk/session/syncSalesChannelFromOrderForm.test.ts +125 -0
- /package/src/{components/sections/RecommendationShelf → sdk/recommendations}/useRecommendationUserId.ts +0 -0
- /package/src/{components/sections/RecommendationShelf → sdk/recommendations}/useRecommendations.ts +0 -0
package/@generated/graphql.ts
CHANGED
|
@@ -1538,6 +1538,12 @@ export type StoreOrder = {
|
|
|
1538
1538
|
acceptedOffer: Array<StoreOffer>;
|
|
1539
1539
|
/** ID of the order in [VTEX order management](https://help.vtex.com/en/tutorial/license-manager-resources-oms--60QcBsvWeum02cFi3GjBzg#). */
|
|
1540
1540
|
orderNumber: Scalars['String']['output'];
|
|
1541
|
+
/**
|
|
1542
|
+
* Sales channel of the underlying orderForm when FastStore adopted it because
|
|
1543
|
+
* the browser session was stale (e.g. after Quick Order). Clients should align
|
|
1544
|
+
* `session.channel` to this value. Null when no SC adoption happened.
|
|
1545
|
+
*/
|
|
1546
|
+
salesChannel: Maybe<Scalars['String']['output']>;
|
|
1541
1547
|
/** Indicates whether or not items with attachments should be split. */
|
|
1542
1548
|
shouldSplitItem: Maybe<Scalars['Boolean']['output']>;
|
|
1543
1549
|
};
|
|
@@ -2721,15 +2727,6 @@ export type Filter_FacetsFragment =
|
|
|
2721
2727
|
|
|
2722
2728
|
export type ProductDetailsFragment_ProductFragment = { sku: string, name: string, gtin: string, description: string, unitMultiplier: number | null, id: string, isVariantOf: { name: string, productGroupID: string, skuVariants: { activeVariations: any | null, slugsMap: any | null, availableVariations: any | null, allVariantProducts: Array<{ name: string, productID: string }> | null } | null }, image: Array<{ url: string, alternateName: string }>, brand: { name: string }, offers: { lowPrice: number, lowPriceWithTaxes: number, offers: Array<{ availability: string, price: number, priceWithTaxes: number, listPrice: number, listPriceWithTaxes: number, quantity: number, priceToken: string | null, seller: { identifier: string } }> }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }> };
|
|
2723
2729
|
|
|
2724
|
-
export type ClientRecommendationsQueryQueryVariables = Exact<{
|
|
2725
|
-
campaignVrn: Scalars['String']['input'];
|
|
2726
|
-
userId: InputMaybe<Scalars['String']['input']>;
|
|
2727
|
-
products: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>;
|
|
2728
|
-
}>;
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
export type ClientRecommendationsQueryQuery = { recommendations: { correlationId: string, products: Array<{ slug: string, sku: string, name: string, gtin: string, unitMultiplier: number | null, hasSpecifications: boolean | null, id: string, brand: { name: string, brandName: string }, isVariantOf: { productGroupID: string, name: string, skuVariants: { allVariantsByName: any | null, activeVariations: any | null, slugsMap: any | null, availableVariations: any | null, allVariantProducts: Array<{ name: string, productID: string }> | null } | null }, image: Array<{ url: string, alternateName: string }>, offers: { lowPrice: number, lowPriceWithTaxes: number, offers: Array<{ availability: string, price: number, listPrice: number, listPriceWithTaxes: number, priceWithTaxes: number, quantity: number, priceToken: string | null, seller: { identifier: string } }> }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }>, advertisement: { adId: string, adResponseId: string } | null, deliveryPromiseBadges: Array<{ typeName: string | null } | null> | null }>, campaign: { id: string, title: string | null, type: string } } };
|
|
2732
|
-
|
|
2733
2730
|
export type ProductComparisonFragment_ProductFragment = { sku: string, slug: string, name: string, gtin: string, description: string, unitMultiplier: number | null, hasSpecifications: boolean | null, id: string, isVariantOf: { name: string, productGroupID: string, skuVariants: { activeVariations: any | null, slugsMap: any | null, availableVariations: any | null, allVariantProducts: Array<{ name: string, productID: string }> | null } | null }, image: Array<{ url: string, alternateName: string }>, brand: { name: string }, offers: { lowPrice: number, lowPriceWithTaxes: number, offers: Array<{ availability: string, price: number, priceWithTaxes: number, listPrice: number, quantity: number, listPriceWithTaxes: number, priceToken: string | null, seller: { identifier: string } }> }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }>, advertisement: { adId: string, adResponseId: string } | null, skuSpecifications: Array<{ field: { id: string | null, name: string, originalName: string | null }, values: Array<{ name: string, id: string | null, fieldId: string | null, originalName: string | null }> }>, specificationGroups: Array<{ name: string, originalName: string, specifications: Array<{ name: string, originalName: string, values: Array<string> }> }> };
|
|
2734
2731
|
|
|
2735
2732
|
export type ProductSkuMatrixSidebarFragment_ProductFragment = { id: string, isVariantOf: { name: string, productGroupID: string, skuVariants: { activeVariations: any | null, slugsMap: any | null, availableVariations: any | null, allVariantProducts: Array<{ sku: string, name: string, image: Array<{ url: string, alternateName: string }>, offers: { highPrice: number, lowPrice: number, lowPriceWithTaxes: number, offerCount: number, priceCurrency: string, offers: Array<{ listPrice: number, listPriceWithTaxes: number, sellingPrice: number, priceCurrency: string, price: number, priceWithTaxes: number, priceValidUntil: string, itemCondition: string, availability: string, quantity: number, priceToken: string | null }> }, additionalProperty: Array<{ propertyID: string, value: any, name: string, valueReference: any }> }> | null } | null } };
|
|
@@ -2863,7 +2860,7 @@ export type ValidateCartMutationMutationVariables = Exact<{
|
|
|
2863
2860
|
}>;
|
|
2864
2861
|
|
|
2865
2862
|
|
|
2866
|
-
export type ValidateCartMutationMutation = { validateCart: { order: { orderNumber: string, shouldSplitItem: boolean | null, acceptedOffer: Array<{ quantity: number, price: number, priceWithTaxes: number, listPrice: number, listPriceWithTaxes: number, isGift: boolean | null, priceToken: string | null, seller: { identifier: string }, itemOffered: { sku: string, name: string, unitMultiplier: number | null, gtin: string, image: Array<{ url: string, alternateName: string }>, brand: { name: string }, isVariantOf: { productGroupID: string, name: string, skuVariants: { activeVariations: any | null, slugsMap: any | null, availableVariations: any | null } | null }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }> } }> }, messages: Array<{ text: string, status: StoreStatus }> } | null };
|
|
2863
|
+
export type ValidateCartMutationMutation = { validateCart: { order: { orderNumber: string, salesChannel: string | null, shouldSplitItem: boolean | null, acceptedOffer: Array<{ quantity: number, price: number, priceWithTaxes: number, listPrice: number, listPriceWithTaxes: number, isGift: boolean | null, priceToken: string | null, seller: { identifier: string }, itemOffered: { sku: string, name: string, unitMultiplier: number | null, gtin: string, image: Array<{ url: string, alternateName: string }>, brand: { name: string }, isVariantOf: { productGroupID: string, name: string, skuVariants: { activeVariations: any | null, slugsMap: any | null, availableVariations: any | null } | null }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }> } }> }, messages: Array<{ text: string, status: StoreStatus }> } | null };
|
|
2867
2864
|
|
|
2868
2865
|
export type CartMessageFragment = { text: string, status: StoreStatus };
|
|
2869
2866
|
|
|
@@ -2988,6 +2985,15 @@ export type ClientProfileQueryQueryVariables = Exact<{
|
|
|
2988
2985
|
|
|
2989
2986
|
export type ClientProfileQueryQuery = { profile: { addresses: Array<{ country: string | null, postalCode: string | null, geoCoordinate: Array<number | null> | null, city: string | null } | null> | null } | null };
|
|
2990
2987
|
|
|
2988
|
+
export type ClientRecommendationsQueryQueryVariables = Exact<{
|
|
2989
|
+
campaignVrn: Scalars['String']['input'];
|
|
2990
|
+
userId: InputMaybe<Scalars['String']['input']>;
|
|
2991
|
+
products: InputMaybe<Array<Scalars['String']['input']> | Scalars['String']['input']>;
|
|
2992
|
+
}>;
|
|
2993
|
+
|
|
2994
|
+
|
|
2995
|
+
export type ClientRecommendationsQueryQuery = { recommendations: { correlationId: string, products: Array<{ slug: string, sku: string, name: string, gtin: string, unitMultiplier: number | null, hasSpecifications: boolean | null, id: string, brand: { name: string, brandName: string }, isVariantOf: { productGroupID: string, name: string, skuVariants: { allVariantsByName: any | null, activeVariations: any | null, slugsMap: any | null, availableVariations: any | null, allVariantProducts: Array<{ name: string, productID: string }> | null } | null }, image: Array<{ url: string, alternateName: string }>, offers: { lowPrice: number, lowPriceWithTaxes: number, offers: Array<{ availability: string, price: number, listPrice: number, listPriceWithTaxes: number, priceWithTaxes: number, quantity: number, priceToken: string | null, seller: { identifier: string } }> }, additionalProperty: Array<{ propertyID: string, name: string, value: any, valueReference: any }>, advertisement: { adId: string, adResponseId: string } | null, deliveryPromiseBadges: Array<{ typeName: string | null } | null> | null }>, campaign: { id: string, title: string | null, type: string } } };
|
|
2996
|
+
|
|
2991
2997
|
export type ClientSearchSuggestionsQueryQueryVariables = Exact<{
|
|
2992
2998
|
term: Scalars['String']['input'];
|
|
2993
2999
|
selectedFacets: InputMaybe<Array<IStoreSelectedFacet> | IStoreSelectedFacet>;
|
|
@@ -3549,7 +3555,6 @@ export const SearchEvent_MetadataFragmentDoc = new TypedDocumentString(`
|
|
|
3549
3555
|
fuzzy
|
|
3550
3556
|
}
|
|
3551
3557
|
`, {"fragmentName":"SearchEvent_metadata"}) as unknown as TypedDocumentString<SearchEvent_MetadataFragment, unknown>;
|
|
3552
|
-
export const ClientRecommendationsQueryDocument = {"__meta__":{"operationName":"ClientRecommendationsQuery","operationHash":"b227071b0388469d7b90d04cff4cbc11f8f134d4"}} as unknown as TypedDocumentString<ClientRecommendationsQueryQuery, ClientRecommendationsQueryQueryVariables>;
|
|
3553
3558
|
export const ServerAccountPageQueryDocument = {"__meta__":{"operationName":"ServerAccountPageQuery","operationHash":"9baae331b75848a310fecb457e8c971ae27897ff"}} as unknown as TypedDocumentString<ServerAccountPageQueryQuery, ServerAccountPageQueryQueryVariables>;
|
|
3554
3559
|
export const ServerCollectionPageQueryDocument = {"__meta__":{"operationName":"ServerCollectionPageQuery","operationHash":"d46841b30ae1f6350021b5cf02f253d56c848664"}} as unknown as TypedDocumentString<ServerCollectionPageQueryQuery, ServerCollectionPageQueryQueryVariables>;
|
|
3555
3560
|
export const ServerProductQueryDocument = {"__meta__":{"operationName":"ServerProductQuery","operationHash":"b89e93519be01aebc01c402489a0ae640d38675a"}} as unknown as TypedDocumentString<ServerProductQueryQuery, ServerProductQueryQueryVariables>;
|
|
@@ -3565,7 +3570,7 @@ export const CancelOrderMutationDocument = {"__meta__":{"operationName":"CancelO
|
|
|
3565
3570
|
export const ProcessOrderAuthorizationMutationDocument = {"__meta__":{"operationName":"ProcessOrderAuthorizationMutation","operationHash":"8c25d37c8d6e7c20ab21bb8a4f4e6a2fe320ea8d"}} as unknown as TypedDocumentString<ProcessOrderAuthorizationMutationMutation, ProcessOrderAuthorizationMutationMutationVariables>;
|
|
3566
3571
|
export const ValidateUserDocument = {"__meta__":{"operationName":"ValidateUser","operationHash":"32f99c73c3de958b64d6bece1afe800469f54548"}} as unknown as TypedDocumentString<ValidateUserQuery, ValidateUserQueryVariables>;
|
|
3567
3572
|
export const StartRecommendationSessionDocument = {"__meta__":{"operationName":"StartRecommendationSession","operationHash":"1def6438c0cd87b85002411ac7326c221f192583"}} as unknown as TypedDocumentString<StartRecommendationSessionMutation, StartRecommendationSessionMutationVariables>;
|
|
3568
|
-
export const ValidateCartMutationDocument = {"__meta__":{"operationName":"ValidateCartMutation","operationHash":"
|
|
3573
|
+
export const ValidateCartMutationDocument = {"__meta__":{"operationName":"ValidateCartMutation","operationHash":"3c71b0d3fc2bade14df68abfa271858beb361b12"}} as unknown as TypedDocumentString<ValidateCartMutationMutation, ValidateCartMutationMutationVariables>;
|
|
3569
3574
|
export const ClientPickupPointsQueryDocument = {"__meta__":{"operationName":"ClientPickupPointsQuery","operationHash":"3fa04e88c811fcb5ece7206fd5aa745bdbc143a8"}} as unknown as TypedDocumentString<ClientPickupPointsQueryQuery, ClientPickupPointsQueryQueryVariables>;
|
|
3570
3575
|
export const SubscribeToNewsletterDocument = {"__meta__":{"operationName":"SubscribeToNewsletter","operationHash":"feb7005103a859e2bc8cf2360d568806fd88deba"}} as unknown as TypedDocumentString<SubscribeToNewsletterMutation, SubscribeToNewsletterMutationVariables>;
|
|
3571
3576
|
export const StartOrderEntryOperationMutationDocument = {"__meta__":{"operationName":"StartOrderEntryOperationMutation","operationHash":"78c50fbf9b85d03dbeac9b05b06405217f2ec440"}} as unknown as TypedDocumentString<StartOrderEntryOperationMutationMutation, StartOrderEntryOperationMutationMutationVariables>;
|
|
@@ -3580,6 +3585,7 @@ export const ClientProductQueryDocument = {"__meta__":{"operationName":"ClientPr
|
|
|
3580
3585
|
export const ClientManyProductsQueryDocument = {"__meta__":{"operationName":"ClientManyProductsQuery","operationHash":"ee14fd92b4a04fc3751efcbfcb60b3886d912253"}} as unknown as TypedDocumentString<ClientManyProductsQueryQuery, ClientManyProductsQueryQueryVariables>;
|
|
3581
3586
|
export const ClientManyProductsSelectedQueryDocument = {"__meta__":{"operationName":"ClientManyProductsSelectedQuery","operationHash":"7e1b06c167e411905ceac9db11c1a6a7892cfdd6"}} as unknown as TypedDocumentString<ClientManyProductsSelectedQueryQuery, ClientManyProductsSelectedQueryQueryVariables>;
|
|
3582
3587
|
export const ClientProfileQueryDocument = {"__meta__":{"operationName":"ClientProfileQuery","operationHash":"34ea14c0d4a57ddf9bc11e4be0cd2b5a6506d3d4"}} as unknown as TypedDocumentString<ClientProfileQueryQuery, ClientProfileQueryQueryVariables>;
|
|
3588
|
+
export const ClientRecommendationsQueryDocument = {"__meta__":{"operationName":"ClientRecommendationsQuery","operationHash":"b227071b0388469d7b90d04cff4cbc11f8f134d4"}} as unknown as TypedDocumentString<ClientRecommendationsQueryQuery, ClientRecommendationsQueryQueryVariables>;
|
|
3583
3589
|
export const ClientSearchSuggestionsQueryDocument = {"__meta__":{"operationName":"ClientSearchSuggestionsQuery","operationHash":"ee57cd392fdfde90620787c9954e2ecae47881f9"}} as unknown as TypedDocumentString<ClientSearchSuggestionsQueryQuery, ClientSearchSuggestionsQueryQueryVariables>;
|
|
3584
3590
|
export const ClientTopSearchSuggestionsQueryDocument = {"__meta__":{"operationName":"ClientTopSearchSuggestionsQuery","operationHash":"e2385b0f11726d0068f96548f57a8dd441c064e3"}} as unknown as TypedDocumentString<ClientTopSearchSuggestionsQueryQuery, ClientTopSearchSuggestionsQueryQueryVariables>;
|
|
3585
3591
|
export const ValidateSessionDocument = {"__meta__":{"operationName":"ValidateSession","operationHash":"8c3a5999496e227f167e9dc79697e6c478d48a9e"}} as unknown as TypedDocumentString<ValidateSessionMutation, ValidateSessionMutationVariables>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"b227071b0388469d7b90d04cff4cbc11f8f134d4": "fragment ProductSummary_product on StoreProduct { additionalProperty { name propertyID value valueReference } advertisement { adId adResponseId } brand { brandName: name } brand { name } deliveryPromiseBadges { typeName } gtin hasSpecifications image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantsByName availableVariations slugsMap } } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku slug unitMultiplier unitMultiplier } query ClientRecommendationsQuery($campaignVrn: String!, $products: [String!], $userId: String) { recommendations(userId: $userId, campaignVrn: $campaignVrn, products: $products) { campaign { id title type } correlationId products { ...ProductSummary_product } } }",
|
|
3
2
|
"9baae331b75848a310fecb457e8c971ae27897ff": "query ServerAccountPageQuery { accountProfile { name } }",
|
|
4
3
|
"d46841b30ae1f6350021b5cf02f253d56c848664": "fragment ServerCollectionPage on Query { collection(slug: $slug) { id } } query ServerCollectionPageQuery($slug: String!) { collection(slug: $slug) { breadcrumbList { itemListElement { item name position } } meta { selectedFacets { key value } } otherLocales { locale slug } seo { description title } } ...ServerCollectionPage }",
|
|
5
4
|
"b89e93519be01aebc01c402489a0ae640d38675a": "fragment CartProductItem on StoreProduct { additionalProperty { name propertyID value valueReference } brand { name } gtin image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations availableVariations slugsMap } } name sku unitMultiplier } fragment ProductDetailsFragment_product on StoreProduct { additionalProperty { name propertyID value valueReference } brand { name } description gtin image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku unitMultiplier ...CartProductItem } fragment ServerProduct on Query { product(locator: $locator) { id: productID } } query ServerProductQuery($locator: [IStoreSelectedFacet!]!) { product(locator: $locator) { brand { name } breadcrumbList { itemListElement { item name position } } description gtin image { alternateName url } isVariantOf { productGroupID } mpn name offers { highPrice lowPrice lowPriceWithTaxes offers { availability itemCondition price priceCurrency priceToken priceValidUntil seller { identifier } } priceCurrency } otherLocales { locale slug } id: productID releaseDate seo { canonical description title } sku ...ProductDetailsFragment_product } ...ServerProduct }",
|
|
@@ -15,7 +14,7 @@
|
|
|
15
14
|
"8c25d37c8d6e7c20ab21bb8a4f4e6a2fe320ea8d": "mutation ProcessOrderAuthorizationMutation($data: IProcessOrderAuthorization!) { processOrderAuthorization(data: $data) { isPendingForOtherAuthorizer ruleForAuthorization { dimensionId orderAuthorizationId rule { authorizationData { authorizers { authorizationDate email id type } requireAllApprovals } authorizedEmails doId id isUserAuthorized isUserNextAuthorizer name notification priority scoreInterval { accept deny } status timeout trigger { condition { conditionType description expression greatherThan lessThan } effect { description effectType funcPath } } } } } }",
|
|
16
15
|
"32f99c73c3de958b64d6bece1afe800469f54548": "query ValidateUser { validateUser { isValid } }",
|
|
17
16
|
"1def6438c0cd87b85002411ac7326c221f192583": "mutation StartRecommendationSession { startRecommendationSession }",
|
|
18
|
-
"
|
|
17
|
+
"3c71b0d3fc2bade14df68abfa271858beb361b12": "fragment CartItem on StoreOffer { isGift itemOffered { ...CartProductItem } listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } fragment CartMessage on StoreCartMessage { status text } fragment CartProductItem on StoreProduct { additionalProperty { name propertyID value valueReference } brand { name } gtin image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations availableVariations slugsMap } } name sku unitMultiplier } mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) { validateCart(cart: $cart, session: $session) { messages { ...CartMessage } order { acceptedOffer { ...CartItem } orderNumber salesChannel shouldSplitItem } } }",
|
|
19
18
|
"3fa04e88c811fcb5ece7206fd5aa745bdbc143a8": "query ClientPickupPointsQuery($geoCoordinates: IStoreGeoCoordinates) { pickupPoints(geoCoordinates: $geoCoordinates) { pickupPointDistances { address { city number postalCode state street } distance isActive pickupId pickupName } } }",
|
|
20
19
|
"feb7005103a859e2bc8cf2360d568806fd88deba": "mutation SubscribeToNewsletter($data: IPersonNewsletter!) { subscribeToNewsletter(data: $data) { id } }",
|
|
21
20
|
"78c50fbf9b85d03dbeac9b05b06405217f2ec440": "mutation StartOrderEntryOperationMutation($data: IOrderEntryOperation!) { startOrderEntryOperation(data: $data) { operationId } }",
|
|
@@ -30,6 +29,7 @@
|
|
|
30
29
|
"ee14fd92b4a04fc3751efcbfcb60b3886d912253": "fragment ClientManyProducts on Query { search( first: $first after: $after sort: $sort term: $term selectedFacets: $selectedFacets sponsoredCount: $sponsoredCount ) { products { pageInfo { totalCount } } } } fragment ProductSummary_product on StoreProduct { additionalProperty { name propertyID value valueReference } advertisement { adId adResponseId } brand { brandName: name } brand { name } deliveryPromiseBadges { typeName } gtin hasSpecifications image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantsByName availableVariations slugsMap } } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku slug unitMultiplier unitMultiplier } query ClientManyProductsQuery($after: String, $first: Int!, $selectedFacets: [IStoreSelectedFacet!]!, $sort: StoreSort!, $sponsoredCount: Int, $term: String!) { search( first: $first after: $after sort: $sort term: $term selectedFacets: $selectedFacets sponsoredCount: $sponsoredCount ) { products { edges { node { ...ProductSummary_product } } pageInfo { totalCount } } } ...ClientManyProducts }",
|
|
31
30
|
"7e1b06c167e411905ceac9db11c1a6a7892cfdd6": "fragment ProductComparisonFragment_product on StoreProduct { additionalProperty { name propertyID value valueReference } advertisement { adId adResponseId } brand { name } description gtin hasSpecifications image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku skuSpecifications { field { id name originalName } values { fieldId id name originalName } } slug specificationGroups { name originalName specifications { name originalName values } } unitMultiplier } query ClientManyProductsSelectedQuery($productIds: [String!]!) { products(productIds: $productIds) { ...ProductComparisonFragment_product } }",
|
|
32
31
|
"34ea14c0d4a57ddf9bc11e4be0cd2b5a6506d3d4": "query ClientProfileQuery($id: String!) { profile(id: $id) { addresses { city country geoCoordinate postalCode } } }",
|
|
32
|
+
"b227071b0388469d7b90d04cff4cbc11f8f134d4": "fragment ProductSummary_product on StoreProduct { additionalProperty { name propertyID value valueReference } advertisement { adId adResponseId } brand { brandName: name } brand { name } deliveryPromiseBadges { typeName } gtin hasSpecifications image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantsByName availableVariations slugsMap } } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku slug unitMultiplier unitMultiplier } query ClientRecommendationsQuery($campaignVrn: String!, $products: [String!], $userId: String) { recommendations(userId: $userId, campaignVrn: $campaignVrn, products: $products) { campaign { id title type } correlationId products { ...ProductSummary_product } } }",
|
|
33
33
|
"ee57cd392fdfde90620787c9954e2ecae47881f9": "fragment ClientSearchSuggestions on Query { search(first: 5, term: $term, selectedFacets: $selectedFacets) { suggestions { terms { value } } } } fragment ProductSummary_product on StoreProduct { additionalProperty { name propertyID value valueReference } advertisement { adId adResponseId } brand { brandName: name } brand { name } deliveryPromiseBadges { typeName } gtin hasSpecifications image { alternateName url } isVariantOf { name productGroupID skuVariants { activeVariations allVariantsByName availableVariations slugsMap } } isVariantOf { name productGroupID skuVariants { activeVariations allVariantProducts { name productID } availableVariations slugsMap } } name offers { lowPrice lowPriceWithTaxes offers { availability listPrice listPriceWithTaxes price priceToken priceWithTaxes quantity seller { identifier } } } id: productID sku slug unitMultiplier unitMultiplier } fragment SearchEvent_metadata on SearchMetadata { fuzzy isTermMisspelled logicalOperator } query ClientSearchSuggestionsQuery($selectedFacets: [IStoreSelectedFacet!], $term: String!) { search(first: 5, term: $term, selectedFacets: $selectedFacets) { metadata { ...SearchEvent_metadata } products { pageInfo { totalCount } } searchId suggestions { products { ...ProductSummary_product } terms { value } } } ...ClientSearchSuggestions }",
|
|
34
34
|
"e2385b0f11726d0068f96548f57a8dd441c064e3": "fragment ClientTopSearchSuggestions on Query { search(first: 5, term: $term, selectedFacets: $selectedFacets) { suggestions { terms { value } } } } query ClientTopSearchSuggestionsQuery($selectedFacets: [IStoreSelectedFacet!], $term: String!) { search(first: 5, term: $term, selectedFacets: $selectedFacets) { suggestions { terms { value } } } ...ClientTopSearchSuggestions }",
|
|
35
35
|
"8c3a5999496e227f167e9dc79697e6c478d48a9e": "mutation ValidateSession($search: String!, $session: IStoreSession!) { validateSession(session: $session, search: $search) { addressType b2b { contractName customerId firstName isRepresentative lastName organizationManager savedPostalCode unitId unitName userEmail userName } channel city country currency { code symbol } deliveryMode { deliveryChannel deliveryMethod deliveryWindow { endDate startDate } } geoCoordinates { latitude longitude } locale marketingData { utmCampaign utmMedium utmSource utmiCampaign utmiPage utmiPart } person { email familyName givenName id } postalCode refreshAfter } }",
|
|
@@ -1538,6 +1538,12 @@ type StoreOrder {
|
|
|
1538
1538
|
acceptedOffer: [StoreOffer!]!
|
|
1539
1539
|
"""Indicates whether or not items with attachments should be split."""
|
|
1540
1540
|
shouldSplitItem: Boolean
|
|
1541
|
+
"""
|
|
1542
|
+
Sales channel of the underlying orderForm when FastStore adopted it because
|
|
1543
|
+
the browser session was stale (e.g. after Quick Order). Clients should align
|
|
1544
|
+
`session.channel` to this value. Null when no SC adoption happened.
|
|
1545
|
+
"""
|
|
1546
|
+
salesChannel: String
|
|
1541
1547
|
}
|
|
1542
1548
|
|
|
1543
1549
|
type StoreOrderEntryMissingItem {
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [4.6.0-dev.3](https://github.com/vtex/faststore/compare/v4.6.0-dev.2...v4.6.0-dev.3) (2026-08-12)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @faststore/core
|
|
9
|
+
|
|
10
|
+
# [4.6.0-dev.2](https://github.com/vtex/faststore/compare/v4.6.0-dev.1...v4.6.0-dev.2) (2026-08-12)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **api:** address Sonar and harden session sales-channel sync ([5f6e8e9](https://github.com/vtex/faststore/commit/5f6e8e909def75f60c24cdaffae9303ed2c0a052))
|
|
15
|
+
- **api:** keep orderForm sales channel after stale validateCart sync ([09b051b](https://github.com/vtex/faststore/commit/09b051b80aaff7183adf7f5e86aece438bcd38f0))
|
|
16
|
+
- **api:** preserve orderForm sales channel in validateCart ([2336cda](https://github.com/vtex/faststore/commit/2336cda2c95970850dd0d29c1c18043ca8c9078a))
|
|
17
|
+
- **api:** sync adopted orderForm sales channel into session ([8943da7](https://github.com/vtex/faststore/commit/8943da7821f8abec08a39b7907a258bf156b05ee))
|
|
18
|
+
|
|
6
19
|
# [4.6.0-dev.1](https://github.com/vtex/faststore/compare/v4.6.0-dev.0...v4.6.0-dev.1) (2026-08-11)
|
|
7
20
|
|
|
8
21
|
## [4.5.1](https://github.com/vtex/faststore/compare/v4.5.1-dev.4...v4.5.1) (2026-08-11)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststore/core",
|
|
3
|
-
"version": "4.6.0-dev.
|
|
3
|
+
"version": "4.6.0-dev.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"style-loader": "^3.3.1",
|
|
72
72
|
"swr": "^2.2.5",
|
|
73
73
|
"use-sync-external-store": "^1.6.0",
|
|
74
|
-
"@faststore/
|
|
75
|
-
"@faststore/
|
|
76
|
-
"@faststore/
|
|
77
|
-
"@faststore/
|
|
78
|
-
"@faststore/
|
|
74
|
+
"@faststore/diagnostics": "4.6.0-dev.3",
|
|
75
|
+
"@faststore/lighthouse": "4.6.0-dev.3",
|
|
76
|
+
"@faststore/sdk": "4.6.0-dev.3",
|
|
77
|
+
"@faststore/ui": "4.6.0-dev.3",
|
|
78
|
+
"@faststore/api": "4.6.0-dev.3"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@cypress/code-coverage": "^3.12.1",
|
|
@@ -99,8 +99,11 @@ All recommendation work runs client-side after hydration:
|
|
|
99
99
|
5. **Render** — shows `ProductShelfSkeleton` while loading; returns `null` on error or empty results; otherwise renders the carousel and optional heading.
|
|
100
100
|
6. **Tracking** — when correlation and campaign identifiers are present, the shelf emits `data-af-*` attributes for Activity Flow. PDP product views use `product:*` meta tags from `pages/[slug]/p.tsx`.
|
|
101
101
|
|
|
102
|
+
Steps 1 to 4 carry no presentation: they live in `src/sdk/recommendations` and are orchestrated by `useRecommendationShelf()`, which the shelf consumes.
|
|
103
|
+
|
|
102
104
|
Related files:
|
|
103
105
|
|
|
106
|
+
- Shared data layer: `src/sdk/recommendations/`
|
|
104
107
|
- Shelf: `src/components/sections/RecommendationShelf/`
|
|
105
108
|
- Hook: `src/sdk/analytics/hooks/useStartRecommendationSession.ts`
|
|
106
109
|
- Layout: `src/Layout.tsx`
|
|
@@ -129,7 +132,7 @@ import { RecommendationShelf } from 'src/components/sections/RecommendationShelf
|
|
|
129
132
|
- **`ProductCard`**: custom card component (defaults to the core `ProductCard`).
|
|
130
133
|
- **`mapProductToProductCard`**: maps each recommended product (normalized `StoreProduct`) into card props. When provided, it fully owns the card props and the default `productCardConfiguration` merge no longer applies.
|
|
131
134
|
|
|
132
|
-
Campaign types and VRN validation are defined in `vrn.ts` (`VRN_TYPE_TO_RECOMMENDATION`). Updates should be made there and mirrored in the CMS schema `pattern`.
|
|
135
|
+
Campaign types and VRN validation are defined in `src/sdk/recommendations/vrn.ts` (`VRN_TYPE_TO_RECOMMENDATION`). Updates should be made there and mirrored in the CMS schema `pattern`.
|
|
133
136
|
|
|
134
137
|
## Placement guidelines
|
|
135
138
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { type ComponentType, useId, useMemo } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { ProductShelf, Carousel } from '@faststore/ui'
|
|
3
|
+
import { Carousel, ProductShelf } from '@faststore/ui'
|
|
5
4
|
import type { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
6
5
|
|
|
7
6
|
import DefaultProductCard, {
|
|
8
7
|
type ProductCardProps,
|
|
9
8
|
} from 'src/components/product/ProductCard'
|
|
10
9
|
import ProductShelfSkeleton from 'src/components/skeletons/ProductShelfSkeleton'
|
|
11
|
-
import {
|
|
10
|
+
import { useRecommendationShelf } from 'src/sdk/recommendations'
|
|
12
11
|
import useScreenResize from 'src/sdk/ui/useScreenResize'
|
|
13
12
|
|
|
14
13
|
import type {
|
|
@@ -16,48 +15,6 @@ import type {
|
|
|
16
15
|
RecommendationShelfProps,
|
|
17
16
|
} from './RecommendationShelf.types'
|
|
18
17
|
import styles from './section.module.scss'
|
|
19
|
-
import {
|
|
20
|
-
useRecommendations,
|
|
21
|
-
type RecommendationInput,
|
|
22
|
-
} from './useRecommendations'
|
|
23
|
-
import { useRecommendationUserId } from './useRecommendationUserId'
|
|
24
|
-
import { getTypeFromVrn } from './vrn'
|
|
25
|
-
|
|
26
|
-
function getRecommendationArguments(
|
|
27
|
-
campaignVrn: string,
|
|
28
|
-
context: { userId?: string | null; contextProducts: string[] }
|
|
29
|
-
): RecommendationInput | null {
|
|
30
|
-
const { userId, contextProducts } = context
|
|
31
|
-
const type = getTypeFromVrn(campaignVrn)
|
|
32
|
-
|
|
33
|
-
// `type` is null for malformed/unknown VRNs; bail out so we never fetch (and
|
|
34
|
-
// never throw) on an invalid campaign coming from the CMS.
|
|
35
|
-
if (!type || !userId) return null
|
|
36
|
-
|
|
37
|
-
switch (type) {
|
|
38
|
-
case 'NEXT_INTERACTION':
|
|
39
|
-
case 'VISUAL_SIMILARITY':
|
|
40
|
-
case 'CROSS_SELL':
|
|
41
|
-
case 'SIMILAR_ITEMS':
|
|
42
|
-
// These campaigns need product context. Without any (e.g. `CART` context
|
|
43
|
-
// on an empty cart, or `PDP` context outside a product page), skip the
|
|
44
|
-
// fetch instead of requesting recommendations we can't anchor.
|
|
45
|
-
if (contextProducts.length === 0) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
userId,
|
|
50
|
-
campaignVrn,
|
|
51
|
-
products: contextProducts,
|
|
52
|
-
}
|
|
53
|
-
default:
|
|
54
|
-
return {
|
|
55
|
-
userId,
|
|
56
|
-
campaignVrn,
|
|
57
|
-
products: [],
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
18
|
|
|
62
19
|
export function RecommendationShelf<
|
|
63
20
|
TCardProps extends object = ProductCardProps,
|
|
@@ -96,43 +53,14 @@ export function RecommendationShelf<
|
|
|
96
53
|
const itemsPerPage =
|
|
97
54
|
isMobile || isTablet ? itemsPerPageMobile : itemsPerPageDesktop
|
|
98
55
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// source: the current PDP product, or the (deduplicated) cart items.
|
|
106
|
-
const contextProducts = useMemo(() => {
|
|
107
|
-
if (itemsContext === 'CART') {
|
|
108
|
-
return Array.from(
|
|
109
|
-
new Set(
|
|
110
|
-
cartItems
|
|
111
|
-
.map((item) => item.itemOffered.isVariantOf.productGroupID)
|
|
112
|
-
.filter(Boolean)
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const pdpProduct = productDetailPage?.product?.isVariantOf?.productGroupID
|
|
118
|
-
|
|
119
|
-
return pdpProduct ? [pdpProduct] : []
|
|
120
|
-
}, [itemsContext, cartItems, productDetailPage])
|
|
121
|
-
|
|
122
|
-
// Gate the whole feature behind the CMS opt-in: when Recommendations is
|
|
123
|
-
// disabled the shelf never requests recommendations (nor renders).
|
|
124
|
-
const recommendationArgs = enableRecommendations
|
|
125
|
-
? getRecommendationArguments(campaignVrn, {
|
|
126
|
-
userId,
|
|
127
|
-
contextProducts,
|
|
128
|
-
})
|
|
129
|
-
: null
|
|
130
|
-
|
|
131
|
-
const { data, isLoading, error } = useRecommendations(recommendationArgs)
|
|
56
|
+
const { items, isLoading, error, campaign, correlationId } =
|
|
57
|
+
useRecommendationShelf({
|
|
58
|
+
campaignVrn,
|
|
59
|
+
enableRecommendations,
|
|
60
|
+
itemsContext,
|
|
61
|
+
})
|
|
132
62
|
|
|
133
|
-
const
|
|
134
|
-
const correlationId = data?.correlationId
|
|
135
|
-
const campaignId = data?.campaign.id
|
|
63
|
+
const campaignId = campaign?.id
|
|
136
64
|
|
|
137
65
|
const productIds = useMemo(
|
|
138
66
|
() => items.map((p) => p.isVariantOf.productGroupID).join(', '),
|
|
@@ -147,15 +75,6 @@ export function RecommendationShelf<
|
|
|
147
75
|
)
|
|
148
76
|
|
|
149
77
|
if (error) {
|
|
150
|
-
// Don't log `recommendationArgs`: it carries the userId. Log only
|
|
151
|
-
// non-identifying context.
|
|
152
|
-
console.error(
|
|
153
|
-
'Error fetching recommendations',
|
|
154
|
-
error.cause,
|
|
155
|
-
error.message,
|
|
156
|
-
'for campaign',
|
|
157
|
-
campaignVrn
|
|
158
|
-
)
|
|
159
78
|
return null
|
|
160
79
|
}
|
|
161
80
|
|
|
@@ -178,9 +97,9 @@ export function RecommendationShelf<
|
|
|
178
97
|
: {})}
|
|
179
98
|
>
|
|
180
99
|
<ProductShelfSkeleton loading={isLoading} itemsPerPage={itemsPerPage}>
|
|
181
|
-
{(title ||
|
|
100
|
+
{(title || campaign?.title) && (
|
|
182
101
|
<h2 className="text__title-section layout__content">
|
|
183
|
-
{title ||
|
|
102
|
+
{title || campaign?.title}
|
|
184
103
|
</h2>
|
|
185
104
|
)}
|
|
186
105
|
|
|
@@ -4,6 +4,9 @@ import type { CarouselProps } from '@faststore/ui'
|
|
|
4
4
|
import type { ProductSummary_ProductFragment } from '@generated/graphql'
|
|
5
5
|
|
|
6
6
|
import type { ProductCardProps } from 'src/components/product/ProductCard'
|
|
7
|
+
import type { ItemContext } from 'src/sdk/recommendations'
|
|
8
|
+
|
|
9
|
+
export type { ItemContext, RecommendationType } from 'src/sdk/recommendations'
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Maps a recommendation product (a normalized `StoreProduct`, identical to the
|
|
@@ -17,14 +20,6 @@ export type RecommendationProductCardMapper<TCardProps = ProductCardProps> = (
|
|
|
17
20
|
index: number
|
|
18
21
|
) => TCardProps
|
|
19
22
|
|
|
20
|
-
/**
|
|
21
|
-
* Source of the products used as context for the recommendation request:
|
|
22
|
-
* - `'PDP'`: the current product detail page product.
|
|
23
|
-
* - `'CART'`: the products currently in the cart (useful for cross-sell on the
|
|
24
|
-
* cart page).
|
|
25
|
-
*/
|
|
26
|
-
export type ItemContext = 'PDP' | 'CART'
|
|
27
|
-
|
|
28
23
|
export type RecommendationShelfProps<
|
|
29
24
|
TCardProps extends object = ProductCardProps,
|
|
30
25
|
> = {
|
|
@@ -106,13 +101,3 @@ export type RecommendationShelfProps<
|
|
|
106
101
|
readonly bordered?: boolean
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
|
-
|
|
110
|
-
export type RecommendationType =
|
|
111
|
-
| 'CROSS_SELL'
|
|
112
|
-
| 'SIMILAR_ITEMS'
|
|
113
|
-
| 'PERSONALIZED'
|
|
114
|
-
| 'TOP_ITEMS'
|
|
115
|
-
| 'LAST_SEEN'
|
|
116
|
-
| 'SEARCH_BASED'
|
|
117
|
-
| 'VISUAL_SIMILARITY'
|
|
118
|
-
| 'NEXT_INTERACTION'
|
package/src/sdk/cart/index.ts
CHANGED
|
@@ -13,8 +13,10 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
import storeConfig from '../../../discovery.config'
|
|
15
15
|
import { request } from '../graphql/request'
|
|
16
|
-
import { sessionStore } from '../session'
|
|
16
|
+
import { hasValidatedSessionStore, sessionStore } from '../session'
|
|
17
|
+
import { syncSalesChannelFromOrderForm } from '../session/syncSalesChannelFromOrderForm'
|
|
17
18
|
import { createValidationStore, useStore } from '../useStore'
|
|
19
|
+
import { waitForSessionValidated } from './waitForSessionValidated'
|
|
18
20
|
|
|
19
21
|
export interface CartItem
|
|
20
22
|
extends SDKCartItem,
|
|
@@ -33,6 +35,7 @@ export const ValidateCartMutation = gql(`
|
|
|
33
35
|
validateCart(cart: $cart, session: $session) {
|
|
34
36
|
order {
|
|
35
37
|
orderNumber
|
|
38
|
+
salesChannel
|
|
36
39
|
acceptedOffer {
|
|
37
40
|
...CartItem
|
|
38
41
|
}
|
|
@@ -114,6 +117,8 @@ const getItemId = (item: Pick<CartItem, 'itemOffered' | 'seller' | 'price'>) =>
|
|
|
114
117
|
.join('::')
|
|
115
118
|
|
|
116
119
|
const validateCart = async (cart: Cart): Promise<Cart | null> => {
|
|
120
|
+
await waitForSessionValidated(hasValidatedSessionStore)
|
|
121
|
+
|
|
117
122
|
const { validateCart: validated = null } = await request<
|
|
118
123
|
ValidateCartMutationMutation,
|
|
119
124
|
ValidateCartMutationMutationVariables
|
|
@@ -151,6 +156,15 @@ const validateCart = async (cart: Cart): Promise<Cart | null> => {
|
|
|
151
156
|
},
|
|
152
157
|
})
|
|
153
158
|
|
|
159
|
+
const adoptedSalesChannel = validated?.order?.salesChannel
|
|
160
|
+
if (adoptedSalesChannel) {
|
|
161
|
+
syncSalesChannelFromOrderForm(
|
|
162
|
+
adoptedSalesChannel,
|
|
163
|
+
() => sessionStore.read(),
|
|
164
|
+
(session) => sessionStore.setSilent(session)
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
return (
|
|
155
169
|
validated && {
|
|
156
170
|
id: validated.order.orderNumber,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Store } from '@faststore/sdk'
|
|
2
|
+
|
|
3
|
+
/** Max time to wait for the first validateSession before validating the cart. */
|
|
4
|
+
export const SESSION_VALIDATION_WAIT_MS = 3_000
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defers cart validation until session has been validated at least once.
|
|
8
|
+
* This reduces first-paint races between Session and Cart; sales-channel
|
|
9
|
+
* correctness after external flows (e.g. Quick Order) is enforced in the API
|
|
10
|
+
* by preserving/adopting the orderForm SC.
|
|
11
|
+
*/
|
|
12
|
+
export const waitForSessionValidated = async (
|
|
13
|
+
hasValidatedSessionStore: Store<boolean>,
|
|
14
|
+
waitMs = SESSION_VALIDATION_WAIT_MS
|
|
15
|
+
): Promise<void> => {
|
|
16
|
+
if (hasValidatedSessionStore.read()) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await new Promise<void>((resolve) => {
|
|
21
|
+
const timeoutId = setTimeout(() => {
|
|
22
|
+
unsubscribe()
|
|
23
|
+
resolve()
|
|
24
|
+
}, waitMs)
|
|
25
|
+
|
|
26
|
+
const unsubscribe = hasValidatedSessionStore.subscribe((validated) => {
|
|
27
|
+
if (!validated) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clearTimeout(timeoutId)
|
|
32
|
+
unsubscribe()
|
|
33
|
+
resolve()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Race: validation may have finished between read() and subscribe()
|
|
37
|
+
if (hasValidatedSessionStore.read()) {
|
|
38
|
+
clearTimeout(timeoutId)
|
|
39
|
+
unsubscribe()
|
|
40
|
+
resolve()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { ItemContext, RecommendationType } from './types'
|
|
2
|
+
export type {
|
|
3
|
+
RecommendationInput,
|
|
4
|
+
RecommendationProduct,
|
|
5
|
+
RecommendationResponse,
|
|
6
|
+
} from './useRecommendations'
|
|
7
|
+
export { useRecommendations } from './useRecommendations'
|
|
8
|
+
export { useRecommendationUserId } from './useRecommendationUserId'
|
|
9
|
+
export type {
|
|
10
|
+
UseRecommendationShelfArgs,
|
|
11
|
+
UseRecommendationShelfResult,
|
|
12
|
+
} from './useRecommendationShelf'
|
|
13
|
+
export {
|
|
14
|
+
getRecommendationArguments,
|
|
15
|
+
useRecommendationShelf,
|
|
16
|
+
} from './useRecommendationShelf'
|
|
17
|
+
export { getTypeFromVrn, isValidVrn } from './vrn'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source of the products used as context for the recommendation request:
|
|
3
|
+
* - `'PDP'`: the current product detail page product.
|
|
4
|
+
* - `'CART'`: the products currently in the cart (useful for cross-sell on the
|
|
5
|
+
* cart page and inside the cart drawer).
|
|
6
|
+
*/
|
|
7
|
+
export type ItemContext = 'PDP' | 'CART'
|
|
8
|
+
|
|
9
|
+
export type RecommendationType =
|
|
10
|
+
| 'CROSS_SELL'
|
|
11
|
+
| 'SIMILAR_ITEMS'
|
|
12
|
+
| 'PERSONALIZED'
|
|
13
|
+
| 'TOP_ITEMS'
|
|
14
|
+
| 'LAST_SEEN'
|
|
15
|
+
| 'SEARCH_BASED'
|
|
16
|
+
| 'VISUAL_SIMILARITY'
|
|
17
|
+
| 'NEXT_INTERACTION'
|