Skip to content

Commit d683080

Browse files
lariciamotalucasfp13hellofanny
authored
feat: Delivery Promise - navigation based on shopper location (#2716)
## What's the purpose of this pull request? Feature branch for the Delivery Promise feature - phase 1 (navigation based on shopper location). - #2708 - #2728 - #2739 - #2745 - #2749 - #2750 - #2768 - #2772 - #2775 - #2809 - #2817 - #2824 - #2835 ## How it works? ## How to test it? ### Starters Deploy Preview ## References - [Jira epic](https://vtex-dev.atlassian.net/browse/SFS-1696) ## Checklist - [ ] For documentation changes, ping `@Mariana-Caetano` to review and update (Or submit a doc request) - [ ] TODO: Request Doc for PopOver - [ ] TODO: Remind to run cms sync in the default account when the feature branch is merged since new section was added. (And add the new section in the admin!) --------- Co-authored-by: Lucas Feijó <[email protected]> Co-authored-by: Fanny Chien <[email protected]>
1 parent 201303e commit d683080

File tree

54 files changed

+1545
-139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1545
-139
lines changed

packages/api/src/__generated__/schema.ts

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/api/src/platforms/vtex/clients/commerce/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,17 +357,19 @@ export const VtexCommerce = (
357357

358358
params.set(
359359
'items',
360-
'profile.id,profile.email,profile.firstName,profile.lastName,store.channel,store.countryCode,store.cultureInfo,store.currencyCode,store.currencySymbol,authentication.customerId,'
360+
'profile.id,profile.email,profile.firstName,profile.lastName,store.channel,store.countryCode,store.cultureInfo,store.currencyCode,store.currencySymbol,authentication.customerId,checkout.regionId,'
361361
)
362362

363363
const headers: HeadersInit = withCookie({
364364
'content-type': 'application/json',
365365
})
366366

367+
const sessionCookie = parse(ctx?.headers?.cookie ?? '')?.vtex_session
368+
367369
return fetchAPI(
368370
`${base}/api/sessions?${params.toString()}`,
369371
{
370-
method: 'POST',
372+
method: sessionCookie ? 'PATCH' : 'POST',
371373
headers,
372374
body: '{}',
373375
},

packages/api/src/platforms/vtex/clients/commerce/types/Address.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export interface Address {
1313
neighborhood: string
1414
complement: string
1515
reference: string
16-
geoCoordinates: [number]
16+
geoCoordinates: [number, number] // [longitude, latitude]
1717
}

packages/api/src/platforms/vtex/clients/commerce/types/Session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface Profile {
3636

3737
export interface Checkout {
3838
orderFormId?: Value
39+
regionId?: Value
3940
}
4041

4142
export interface Public {

packages/api/src/platforms/vtex/clients/search/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
ProductSearchResult,
1717
Suggestion,
1818
} from './types/ProductSearchResult'
19+
import type { ProductCountResult } from './types/ProductCountResult'
1920

2021
export type Sort =
2122
| 'price:desc'
@@ -265,10 +266,26 @@ export const IntelligentSearch = (
265266
const facets = (args: Omit<SearchArgs, 'type'>) =>
266267
search<FacetSearchResult>({ ...args, type: 'facets' })
267268

269+
const productCount = (
270+
args: Pick<SearchArgs, 'query'>
271+
): Promise<ProductCountResult> => {
272+
const params = new URLSearchParams()
273+
274+
if (args?.query) {
275+
params.append('query', args.query.toString())
276+
}
277+
278+
return fetchAPI(
279+
`${base}/_v/api/intelligent-search/catalog_count?${params.toString()}`,
280+
{ headers }
281+
)
282+
}
283+
268284
return {
269285
facets,
270286
products,
271287
suggestedTerms,
272288
topSearches,
289+
productCount,
273290
}
274291
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface ProductCountResult {
2+
/**
3+
* @description Total product count.
4+
*/
5+
total: number
6+
}

packages/api/src/platforms/vtex/resolvers/query.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
QuerySearchArgs,
99
QuerySellersArgs,
1010
QueryShippingArgs,
11+
QueryProductCountArgs,
1112
} from '../../../__generated__/schema'
1213
import { BadRequestError, NotFoundError } from '../../errors'
1314
import type { CategoryTree } from '../clients/commerce/types/CategoryTree'
@@ -361,4 +362,19 @@ export const Query = {
361362

362363
return { addresses: parsedAddresses }
363364
},
365+
productCount: async (
366+
_: unknown,
367+
{ term }: QueryProductCountArgs,
368+
ctx: Context
369+
) => {
370+
const {
371+
clients: { search },
372+
} = ctx
373+
374+
const result = await search.productCount({
375+
query: term ?? undefined,
376+
})
377+
378+
return result
379+
},
364380
}

packages/api/src/platforms/vtex/resolvers/validateSession.ts

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
import deepEquals from 'fast-deep-equal'
22

3-
import ChannelMarshal from '../utils/channel'
43
import type { Context } from '..'
54
import type {
65
MutationValidateSessionArgs,
76
StoreMarketingData,
87
StoreSession,
98
} from '../../../__generated__/schema'
9+
import ChannelMarshal from '../utils/channel'
10+
11+
async function getPreciseLocationData(
12+
clients: Context['clients'],
13+
country: string,
14+
postalCode: string
15+
) {
16+
try {
17+
const address = await clients.commerce.checkout.address({
18+
postalCode,
19+
country,
20+
})
21+
22+
const [longitude, latitude] = address.geoCoordinates
23+
return { city: address.city, geoCoordinates: { latitude, longitude } }
24+
} catch (err) {
25+
console.error(
26+
`Error while getting geo coordinates for the current postal code (${postalCode}) and country (${country}).\n`
27+
)
28+
29+
throw err
30+
}
31+
}
1032

1133
export const validateSession = async (
1234
_: any,
@@ -15,15 +37,45 @@ export const validateSession = async (
1537
): Promise<StoreSession | null> => {
1638
const channel = ChannelMarshal.parse(oldSession.channel ?? '')
1739
const postalCode = String(oldSession.postalCode ?? '')
18-
const geoCoordinates = oldSession.geoCoordinates ?? null
19-
2040
const country = oldSession.country ?? ''
41+
let city = oldSession.city ?? null
42+
let geoCoordinates = oldSession.geoCoordinates ?? null
43+
44+
// Update location data if postal code and country are provided
45+
const shouldGetPreciseLocation = !city || !geoCoordinates
46+
if (shouldGetPreciseLocation && postalCode !== '' && country !== '') {
47+
const preciseLocation = await getPreciseLocationData(
48+
clients,
49+
country,
50+
postalCode
51+
)
52+
city = preciseLocation.city
53+
geoCoordinates = preciseLocation.geoCoordinates
54+
}
2155

56+
/**
57+
* The Session Manager API (https://developers.vtex.com/docs/api-reference/session-manager-api#patch-/api/sessions) adds the query params to the session public namespace.
58+
* This is used by Checkout (checkout-session) and Intelligent Search (search-session)
59+
*/
2260
const params = new URLSearchParams(search)
2361
const salesChannel = params.get('sc') ?? channel.salesChannel
24-
2562
params.set('sc', salesChannel)
2663

64+
if (!!postalCode) {
65+
params.set('postalCode', postalCode)
66+
}
67+
68+
if (!!country) {
69+
params.set('country', country)
70+
}
71+
72+
if (!!geoCoordinates) {
73+
params.set(
74+
'geoCoordinates',
75+
`${geoCoordinates.latitude},${geoCoordinates.longitude}`
76+
)
77+
}
78+
2779
const { marketingData: oldMarketingData } = oldSession
2880

2981
const marketingData: StoreMarketingData = {
@@ -36,24 +88,27 @@ export const validateSession = async (
3688
utmiPart: params.get('utmi_pc') ?? oldMarketingData?.utmiPart ?? '',
3789
}
3890

39-
const [regionData, sessionData] = await Promise.all([
40-
postalCode || geoCoordinates
41-
? clients.commerce.checkout.region({
42-
postalCode,
43-
geoCoordinates,
44-
country,
45-
salesChannel,
46-
})
47-
: Promise.resolve(null),
48-
clients.commerce.session(params.toString()).catch(() => null),
49-
])
91+
const sessionData = await clients.commerce
92+
.session(params.toString())
93+
.catch(() => null)
5094

5195
const profile = sessionData?.namespaces.profile ?? null
5296
const store = sessionData?.namespaces.store ?? null
5397
const authentication = sessionData?.namespaces.authentication ?? null
54-
const region = regionData?.[0]
98+
const checkout = sessionData?.namespaces.checkout ?? null
99+
55100
// Set seller only if it's inside a region
56-
const seller = region?.sellers.find((seller) => channel.seller === seller.id)
101+
let seller
102+
if (!!channel.seller && (postalCode || geoCoordinates)) {
103+
const regionData = await clients.commerce.checkout.region({
104+
postalCode,
105+
geoCoordinates,
106+
country,
107+
salesChannel,
108+
})
109+
const region = regionData?.[0]
110+
seller = region?.sellers.find((seller) => channel.seller === seller.id)
111+
}
57112

58113
const newSession = {
59114
...oldSession,
@@ -64,7 +119,7 @@ export const validateSession = async (
64119
country: store?.countryCode?.value ?? oldSession.country,
65120
channel: ChannelMarshal.stringify({
66121
salesChannel: store?.channel?.value ?? channel.salesChannel,
67-
regionId: region?.id ?? channel.regionId,
122+
regionId: checkout?.regionId?.value ?? channel.regionId,
68123
seller: seller?.id,
69124
hasOnlyDefaultSalesChannel: !store?.channel?.value,
70125
}),
@@ -80,6 +135,8 @@ export const validateSession = async (
80135
familyName: profile.lastName?.value ?? '',
81136
}
82137
: null,
138+
geoCoordinates,
139+
city,
83140
}
84141

85142
if (deepEquals(oldSession, newSession)) {

packages/api/src/typeDefs/query.graphql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,13 @@ input IGeoCoordinates {
199199
longitude: Float!
200200
}
201201

202+
type ProductCountResult {
203+
"""
204+
Total product count.
205+
"""
206+
total: Int!
207+
}
208+
202209
type Query {
203210
"""
204211
Returns the details of a product based on the specified locator.
@@ -349,6 +356,17 @@ type Query {
349356
id: String!
350357
): Profile
351358
@cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
359+
360+
"""
361+
Returns the total product count information based on a specific location accessible through the VTEX segment cookie.
362+
"""
363+
productCount(
364+
"""
365+
Search term.
366+
"""
367+
term: String
368+
): ProductCountResult
369+
@cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
352370
}
353371

354372
"""

packages/api/src/typeDefs/session.graphql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ type StoreSession {
159159
"""
160160
addressType: String
161161
"""
162+
Session city.
163+
"""
164+
city: String
165+
"""
162166
Session postal code.
163167
"""
164168
postalCode: String
@@ -217,6 +221,10 @@ input IStoreSession {
217221
"""
218222
addressType: String
219223
"""
224+
Session input city.
225+
"""
226+
city: String
227+
"""
220228
Session input postal code.
221229
"""
222230
postalCode: String

packages/api/test/integration/schema.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const QUERIES = [
6868
'redirect',
6969
'sellers',
7070
'profile',
71+
'productCount',
7172
]
7273

7374
const MUTATIONS = ['validateCart', 'validateSession', 'subscribeToNewsletter']

0 commit comments

Comments
 (0)