@faststore/core 4.10.0-dev.0 → 4.10.0-dev.1

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.
@@ -803,7 +803,10 @@ export type PickupPointDistance = {
803
803
  export type PickupPoints = {
804
804
  /** List of pickup point distances for the given location. */
805
805
  pickupPointDistances: Maybe<Array<Maybe<PickupPointDistance>>>;
806
- /** Hash of the pickup points data. */
806
+ /**
807
+ * Not returned by Intelligent Search pickup-point-availability. Always null.
808
+ * @deprecated Not returned by Intelligent Search pickup-point-availability. Always null.
809
+ */
807
810
  pickupPointsHash: Maybe<Scalars['String']['output']>;
808
811
  };
809
812
 
@@ -913,7 +916,7 @@ export type Query = {
913
916
  orderEntryOperation: Maybe<StoreOrderEntryOperationStatus>;
914
917
  /** Returns the items in an orderForm by its ID. */
915
918
  orderFormItems: Array<StoreOrderFormCartItem>;
916
- /** Returns a list of pickup points near to the given geo coordinates. */
919
+ /** Returns pickup points that have products available for the shopper location and sales channel. */
917
920
  pickupPoints: Maybe<PickupPoints>;
918
921
  /** Returns the details of a product based on the specified locator. */
919
922
  product: StoreProduct;
@@ -999,7 +1002,10 @@ export type QueryOrderFormItemsArgs = {
999
1002
 
1000
1003
 
1001
1004
  export type QueryPickupPointsArgs = {
1005
+ channel: InputMaybe<Scalars['String']['input']>;
1006
+ country: InputMaybe<Scalars['String']['input']>;
1002
1007
  geoCoordinates: InputMaybe<IStoreGeoCoordinates>;
1008
+ postalCode: InputMaybe<Scalars['String']['input']>;
1003
1009
  };
1004
1010
 
1005
1011
 
@@ -2961,6 +2967,9 @@ export type CartProductItemFragment = { sku: string, name: string, unitMultiplie
2961
2967
 
2962
2968
  export type ClientPickupPointsQueryQueryVariables = Exact<{
2963
2969
  geoCoordinates: InputMaybe<IStoreGeoCoordinates>;
2970
+ postalCode: InputMaybe<Scalars['String']['input']>;
2971
+ country: InputMaybe<Scalars['String']['input']>;
2972
+ channel: InputMaybe<Scalars['String']['input']>;
2964
2973
  }>;
2965
2974
 
2966
2975
 
@@ -3663,7 +3672,7 @@ export const ProcessOrderAuthorizationMutationDocument = {"__meta__":{"operation
3663
3672
  export const ValidateUserDocument = {"__meta__":{"operationName":"ValidateUser","operationHash":"32f99c73c3de958b64d6bece1afe800469f54548"}} as unknown as TypedDocumentString<ValidateUserQuery, ValidateUserQueryVariables>;
3664
3673
  export const StartRecommendationSessionDocument = {"__meta__":{"operationName":"StartRecommendationSession","operationHash":"1def6438c0cd87b85002411ac7326c221f192583"}} as unknown as TypedDocumentString<StartRecommendationSessionMutation, StartRecommendationSessionMutationVariables>;
3665
3674
  export const ValidateCartMutationDocument = {"__meta__":{"operationName":"ValidateCartMutation","operationHash":"3c71b0d3fc2bade14df68abfa271858beb361b12"}} as unknown as TypedDocumentString<ValidateCartMutationMutation, ValidateCartMutationMutationVariables>;
3666
- export const ClientPickupPointsQueryDocument = {"__meta__":{"operationName":"ClientPickupPointsQuery","operationHash":"3fa04e88c811fcb5ece7206fd5aa745bdbc143a8"}} as unknown as TypedDocumentString<ClientPickupPointsQueryQuery, ClientPickupPointsQueryQueryVariables>;
3675
+ export const ClientPickupPointsQueryDocument = {"__meta__":{"operationName":"ClientPickupPointsQuery","operationHash":"0f70fea0ad598b50c0ca9c086860907881a68e4f"}} as unknown as TypedDocumentString<ClientPickupPointsQueryQuery, ClientPickupPointsQueryQueryVariables>;
3667
3676
  export const SubscribeToNewsletterDocument = {"__meta__":{"operationName":"SubscribeToNewsletter","operationHash":"feb7005103a859e2bc8cf2360d568806fd88deba"}} as unknown as TypedDocumentString<SubscribeToNewsletterMutation, SubscribeToNewsletterMutationVariables>;
3668
3677
  export const StartOrderEntryOperationMutationDocument = {"__meta__":{"operationName":"StartOrderEntryOperationMutation","operationHash":"78c50fbf9b85d03dbeac9b05b06405217f2ec440"}} as unknown as TypedDocumentString<StartOrderEntryOperationMutationMutation, StartOrderEntryOperationMutationMutationVariables>;
3669
3678
  export const OrderEntryOperationQueryDocument = {"__meta__":{"operationName":"OrderEntryOperationQuery","operationHash":"93fc6c5c593dd4c82686fdf063fe419760297e54"}} as unknown as TypedDocumentString<OrderEntryOperationQueryQuery, OrderEntryOperationQueryQueryVariables>;
@@ -16,7 +16,7 @@
16
16
  "32f99c73c3de958b64d6bece1afe800469f54548": "query ValidateUser { validateUser { isValid } }",
17
17
  "1def6438c0cd87b85002411ac7326c221f192583": "mutation StartRecommendationSession { startRecommendationSession }",
18
18
  "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
- "3fa04e88c811fcb5ece7206fd5aa745bdbc143a8": "query ClientPickupPointsQuery($geoCoordinates: IStoreGeoCoordinates) { pickupPoints(geoCoordinates: $geoCoordinates) { pickupPointDistances { address { city number postalCode state street } distance isActive pickupId pickupName } } }",
19
+ "0f70fea0ad598b50c0ca9c086860907881a68e4f": "query ClientPickupPointsQuery($channel: String, $country: String, $geoCoordinates: IStoreGeoCoordinates, $postalCode: String) { pickupPoints( geoCoordinates: $geoCoordinates postalCode: $postalCode country: $country channel: $channel ) { pickupPointDistances { address { city number postalCode state street } distance isActive pickupId pickupName } } }",
20
20
  "feb7005103a859e2bc8cf2360d568806fd88deba": "mutation SubscribeToNewsletter($data: IPersonNewsletter!) { subscribeToNewsletter(data: $data) { id } }",
21
21
  "78c50fbf9b85d03dbeac9b05b06405217f2ec440": "mutation StartOrderEntryOperationMutation($data: IOrderEntryOperation!) { startOrderEntryOperation(data: $data) { operationId } }",
22
22
  "93fc6c5c593dd4c82686fdf063fe419760297e54": "query OrderEntryOperationQuery($operationId: String!) { orderEntryOperation(operationId: $operationId) { entityId message missingItems { itemId itemName reason } status } }",
@@ -764,8 +764,10 @@ type PickupPointDistance {
764
764
  type PickupPoints {
765
765
  """List of pickup point distances for the given location."""
766
766
  pickupPointDistances: [PickupPointDistance]
767
- """Hash of the pickup points data."""
768
- pickupPointsHash: String
767
+ """
768
+ Not returned by Intelligent Search pickup-point-availability. Always null.
769
+ """
770
+ pickupPointsHash: String @deprecated(reason: "Not returned by Intelligent Search pickup-point-availability. Always null.")
769
771
  }
770
772
 
771
773
  type PickupStoreInfo {
@@ -1008,11 +1010,25 @@ type Query {
1008
1010
  Returns whether the current authenticated user belongs to a B2B organization unit.
1009
1011
  """
1010
1012
  isOrganizationMember: Boolean! @auth @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
1011
- """Returns a list of pickup points near to the given geo coordinates."""
1013
+ """
1014
+ Returns pickup points that have products available for the shopper location and sales channel.
1015
+ """
1012
1016
  pickupPoints(
1013
- """Geo coordinates input."""
1017
+ """
1018
+ Geo coordinates used to sort pickup points by distance (`longitude,latitude`). Without `postalCode`, the segment postal code stays in place.
1019
+ """
1014
1020
  geoCoordinates: IStoreGeoCoordinates
1015
- ): PickupPoints @cacheControl(scope: "public", sMaxAge: 300, staleWhileRevalidate: 3600)
1021
+ """
1022
+ Postal code. When it differs from the segment cookie, delivery hashes are omitted so a simulated address is not resolved against the previous CEP.
1023
+ """
1024
+ postalCode: String
1025
+ """Three-letter country code for `postalCode`."""
1026
+ country: String
1027
+ """
1028
+ Shopper channel, the same JSON string used by the `channel` search facet. When set, its sales channel is used instead of the store default.
1029
+ """
1030
+ channel: String
1031
+ ): PickupPoints @cacheControl(scope: "private", sMaxAge: 300, staleWhileRevalidate: 3600)
1016
1032
  """Returns the status of an Order Entry Service operation by its ID."""
1017
1033
  orderEntryOperation(operationId: String!): StoreOrderEntryOperationStatus @auth
1018
1034
  """Returns the items in an orderForm by its ID."""
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
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.10.0-dev.1](https://github.com/vtex/faststore/compare/v4.10.0-dev.0...v4.10.0-dev.1) (2026-10-15)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **core:** drop a late pickup response and clear a failed simulation ([fa86e1c](https://github.com/vtex/faststore/commit/fa86e1c0a5026d73e5d869131b5647b39b2913f1))
11
+ - honor a postal code override without a country ([4a3f83c](https://github.com/vtex/faststore/commit/4a3f83cb56a9aef1a1802a493ea167be3bf3e618))
12
+ - ignore stale pickup point responses ([0936343](https://github.com/vtex/faststore/commit/0936343f334133e067aef9eded4821114191f08e))
13
+ - refresh pickup points when the shopper channel changes ([0a0e370](https://github.com/vtex/faststore/commit/0a0e370a270f8c6ab5de89a1889c2f808711077a))
14
+ - refresh stored pickup points for the current channel ([fac72e3](https://github.com/vtex/faststore/commit/fac72e3b02ee3997f413f98fdd92240f0db365a7))
15
+ - retry pickup points after a failed request ([6ead323](https://github.com/vtex/faststore/commit/6ead323d5d7170c1649d3d707dfe32d64807c04f))
16
+ - use the shopper sales channel for pickup points ([5034eb6](https://github.com/vtex/faststore/commit/5034eb6f564c154cf7a20bcf287d9365b10da022))
17
+
18
+ ### Features
19
+
20
+ - list pickup points from Intelligent Search ([6dfb717](https://github.com/vtex/faststore/commit/6dfb71779c107276620a671e8739d2cd286103fe))
21
+
6
22
  # [4.10.0-dev.0](https://github.com/vtex/faststore/compare/v4.9.3-dev.2...v4.10.0-dev.0) (2026-10-08)
7
23
 
8
24
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.0-dev.0",
3
+ "version": "4.10.0-dev.1",
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/api": "4.10.0-dev.0",
75
- "@faststore/diagnostics": "4.10.0-dev.0",
76
- "@faststore/lighthouse": "4.10.0-dev.0",
77
- "@faststore/ui": "4.10.0-dev.0",
78
- "@faststore/sdk": "4.10.0-dev.0"
74
+ "@faststore/api": "4.10.0-dev.1",
75
+ "@faststore/diagnostics": "4.10.0-dev.1",
76
+ "@faststore/lighthouse": "4.10.0-dev.1",
77
+ "@faststore/ui": "4.10.0-dev.1",
78
+ "@faststore/sdk": "4.10.0-dev.1"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -74,6 +74,7 @@ function RegionSlider() {
74
74
  )
75
75
  const pickupPoints = useMemo(
76
76
  () =>
77
+ pickupPointsSimulation?.postalCode ||
77
78
  pickupPointsSimulation?.geoCoordinates
78
79
  ? pickupPointsSimulation.pickupPoints
79
80
  : statePickupPoints,
@@ -6,6 +6,7 @@ import {
6
6
  useEffect,
7
7
  useMemo,
8
8
  useReducer,
9
+ useRef,
9
10
  } from 'react'
10
11
 
11
12
  import { useSession } from 'src/sdk/session'
@@ -24,32 +25,150 @@ type Context = DeliveryPromiseReducerState & {
24
25
  dispatchDeliveryPromiseAction: Dispatch<DeliveryPromiseReducerAction>
25
26
  }
26
27
 
28
+ export function pickupLocationKey(
29
+ channel?: string | null,
30
+ country?: string | null
31
+ ) {
32
+ return `${channel ?? ''}\0${country ?? ''}`
33
+ }
34
+
35
+ /** A cleared update flag still refetches when the shopper channel or country changes. */
36
+ export function shouldRefreshPickupPoints(
37
+ postalCode: string | null | undefined,
38
+ shouldUpdatePickupPoints: boolean,
39
+ previousLocationKey: string | null,
40
+ locationKey: string,
41
+ failedLocationKey: string | null = null,
42
+ hasPersistedList = false
43
+ ) {
44
+ if (!postalCode) {
45
+ return false
46
+ }
47
+
48
+ if (shouldUpdatePickupPoints) {
49
+ return true
50
+ }
51
+
52
+ if (failedLocationKey === locationKey) {
53
+ return false
54
+ }
55
+
56
+ // A list restored from storage has no channel recorded for this page load.
57
+ if (previousLocationKey === null && hasPersistedList) {
58
+ return true
59
+ }
60
+
61
+ const locationChanged =
62
+ previousLocationKey !== null && previousLocationKey !== locationKey
63
+
64
+ return locationChanged
65
+ }
66
+
67
+ export function isCurrentPickupRequest(
68
+ requestId: number,
69
+ latestRequestId: number
70
+ ) {
71
+ return requestId === latestRequestId
72
+ }
73
+
27
74
  const DeliveryPromiseContext = createContext<Context | undefined>(undefined)
28
75
 
29
76
  export function DeliveryPromiseProvider({
30
77
  children,
31
78
  }: PropsWithChildren<unknown>) {
32
- const { postalCode, geoCoordinates } = useSession()
79
+ const { postalCode, geoCoordinates, country, channel } = useSession()
33
80
  const [state, dispatch] = useReducer(
34
81
  deliveryPromiseReducer,
35
82
  undefined,
36
83
  initializeDeliveryPromiseState
37
84
  )
85
+ const fetchedLocationKey = useRef<string | null>(null)
86
+ const failedLocationKey = useRef<string | null>(null)
87
+ const pickupRequestId = useRef(0)
38
88
 
39
89
  useEffect(() => {
40
- if (!postalCode || !state.shouldUpdatePickupPoints) {
90
+ const locationKey = pickupLocationKey(channel, country)
91
+ const hasPersistedList = (state.pickupPoints?.length ?? 0) > 0
92
+
93
+ if (
94
+ !shouldRefreshPickupPoints(
95
+ postalCode,
96
+ state.shouldUpdatePickupPoints,
97
+ fetchedLocationKey.current,
98
+ locationKey,
99
+ failedLocationKey.current,
100
+ hasPersistedList
101
+ )
102
+ ) {
41
103
  return
42
104
  }
43
105
 
106
+ const currentRequest = ++pickupRequestId.current
107
+ failedLocationKey.current = null
108
+
44
109
  async function fetchPickupPoints() {
45
- const newPickupPoints = await getPickupPoints({
46
- geoCoordinates:
47
- state.simulatePickupPoints &&
48
- state.pickupPointsSimulation?.geoCoordinates
49
- ? state.pickupPointsSimulation.geoCoordinates
110
+ const simulation = state.simulatePickupPoints
111
+ ? state.pickupPointsSimulation
112
+ : undefined
113
+
114
+ try {
115
+ const newPickupPoints = await getPickupPoints({
116
+ geoCoordinates: simulation
117
+ ? (simulation.geoCoordinates ?? null)
50
118
  : geoCoordinates,
51
- })
119
+ postalCode: simulation
120
+ ? (simulation.postalCode ?? postalCode)
121
+ : postalCode,
122
+ country: simulation ? (simulation.country ?? country) : country,
123
+ channel,
124
+ })
52
125
 
126
+ if (!isCurrentPickupRequest(currentRequest, pickupRequestId.current)) {
127
+ return
128
+ }
129
+
130
+ fetchedLocationKey.current = locationKey
131
+ applyPickupPoints(newPickupPoints ?? [])
132
+ } catch (error) {
133
+ if (!isCurrentPickupRequest(currentRequest, pickupRequestId.current)) {
134
+ return
135
+ }
136
+
137
+ console.warn('Failed to fetch pickup points:', error)
138
+ failedLocationKey.current = locationKey
139
+
140
+ if (state.simulatePickupPoints) {
141
+ const failedSimulation = {
142
+ pickupPointsSimulation: {
143
+ ...state.pickupPointsSimulation,
144
+ pickupPoints: [],
145
+ },
146
+ shouldUpdatePickupPoints: false,
147
+ simulatePickupPoints: false,
148
+ }
149
+
150
+ deliveryPromiseStore.set(failedSimulation)
151
+ dispatch({
152
+ type: 'updateDeliveryPromiseState',
153
+ payload: failedSimulation,
154
+ })
155
+
156
+ return
157
+ }
158
+
159
+ dispatch({
160
+ type: 'updateDeliveryPromiseState',
161
+ payload: {
162
+ shouldUpdatePickupPoints: false,
163
+ simulatePickupPoints: false,
164
+ },
165
+ })
166
+ }
167
+ }
168
+
169
+ function applyPickupPoints(
170
+ newPickupPoints: NonNullable<Awaited<ReturnType<typeof getPickupPoints>>>
171
+ ) {
53
172
  // Pickup points simulation
54
173
  if (state.simulatePickupPoints) {
55
174
  deliveryPromiseStore.set({
@@ -91,7 +210,17 @@ export function DeliveryPromiseProvider({
91
210
  }
92
211
 
93
212
  fetchPickupPoints()
94
- }, [state.shouldUpdatePickupPoints, postalCode])
213
+
214
+ return () => {
215
+ pickupRequestId.current += 1
216
+ }
217
+ }, [
218
+ state.shouldUpdatePickupPoints,
219
+ state.pickupPoints?.length,
220
+ postalCode,
221
+ channel,
222
+ country,
223
+ ])
95
224
 
96
225
  const value = useMemo(
97
226
  () => ({
@@ -10,8 +10,16 @@ import { request } from 'src/sdk/graphql/request'
10
10
  const pickupPointsQuery = gql(`
11
11
  query ClientPickupPointsQuery(
12
12
  $geoCoordinates: IStoreGeoCoordinates
13
+ $postalCode: String
14
+ $country: String
15
+ $channel: String
13
16
  ) {
14
- pickupPoints(geoCoordinates: $geoCoordinates) {
17
+ pickupPoints(
18
+ geoCoordinates: $geoCoordinates
19
+ postalCode: $postalCode
20
+ country: $country
21
+ channel: $channel
22
+ ) {
15
23
  pickupPointDistances {
16
24
  pickupId
17
25
  distance
@@ -34,21 +42,30 @@ type GetPickupPointsProps = {
34
42
  latitude: number
35
43
  longitude: number
36
44
  } | null
45
+ postalCode?: string | null
46
+ country?: string | null
47
+ channel?: string | null
37
48
  }
38
49
 
39
50
  export const getPickupPoints = async ({
40
51
  geoCoordinates,
52
+ postalCode,
53
+ country,
54
+ channel,
41
55
  }: GetPickupPointsProps) => {
42
56
  if (!deliveryPromise.enabled) {
43
57
  return []
44
58
  }
45
59
 
46
- if (!geoCoordinates) {
60
+ if (!postalCode && !geoCoordinates) {
47
61
  return []
48
62
  }
49
63
 
50
64
  const variables = {
51
65
  geoCoordinates: geoCoordinates ?? undefined,
66
+ postalCode: postalCode ?? undefined,
67
+ country: country ?? undefined,
68
+ channel: channel ?? undefined,
52
69
  }
53
70
 
54
71
  const data = await request<
@@ -5,11 +5,15 @@ import type { PickupPoint } from '.'
5
5
  export type PickupPointsSimulation = {
6
6
  pickupPoints?: PickupPoint[]
7
7
  geoCoordinates: Session['geoCoordinates'] | null
8
+ postalCode?: string | null
9
+ country?: string | null
8
10
  }
9
11
 
10
12
  export const initialPickupPointsSimulation: PickupPointsSimulation = {
11
13
  pickupPoints: [],
12
14
  geoCoordinates: null,
15
+ postalCode: null,
16
+ country: null,
13
17
  }
14
18
 
15
19
  export type DeliveryPromiseReducerState = {
@@ -89,7 +93,9 @@ export const deliveryPromiseReducer = (
89
93
  simulatePickupPoints,
90
94
  pickupPointsSimulation: {
91
95
  ...state.pickupPointsSimulation,
92
- geoCoordinates: validatedSession.geoCoordinates,
96
+ geoCoordinates: validatedSession.geoCoordinates ?? null,
97
+ postalCode: validatedSession.postalCode ?? null,
98
+ country: validatedSession.country ?? null,
93
99
  },
94
100
  shouldUpdatePickupPoints: true,
95
101
  }