@salesforce/retail-react-app 8.3.0-nightly-20251209080224 → 8.3.0-preview.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.
@@ -0,0 +1,237 @@
1
+ /*
2
+ * Copyright (c) 2025, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+
8
+ const COUNTRY_CODE_MAP = {
9
+ USA: 'US',
10
+ Canada: 'CA'
11
+ }
12
+
13
+ /**
14
+ * Resolve country code from country name
15
+ * @param {string} countryName - Full country name from address
16
+ * @returns {string} Standardized country code
17
+ */
18
+ const resolveCountryCode = (countryName) => {
19
+ return COUNTRY_CODE_MAP[countryName] || countryName
20
+ }
21
+
22
+ /**
23
+ * Convert Google Maps API suggestions to our expected format
24
+ * @param {Array} suggestions - Array of suggestions from Google Maps API
25
+ * @returns {Array} Converted suggestions in our expected format
26
+ */
27
+ export const convertGoogleMapsSuggestions = (suggestions) => {
28
+ return suggestions.map((suggestion) => ({
29
+ description: suggestion.placePrediction.text.text,
30
+ place_id: suggestion.placePrediction.placeId,
31
+ structured_formatting: {
32
+ main_text:
33
+ suggestion.placePrediction.text.text.split(',')[0] ||
34
+ suggestion.placePrediction.text.text,
35
+ secondary_text: suggestion.placePrediction.text.text
36
+ .split(',')
37
+ .slice(1)
38
+ .join(',')
39
+ .trim()
40
+ },
41
+ terms: suggestion.placePrediction.text.text
42
+ .split(',')
43
+ .map((term) => ({value: term.trim()})),
44
+ placePrediction: suggestion.placePrediction
45
+ }))
46
+ }
47
+
48
+ /**
49
+ * Parse address suggestion data to extract individual address fields
50
+ * @param {Object} suggestion - Address suggestion object from the API
51
+ * @returns {Object} Parsed address fields
52
+ */
53
+ export const parseAddressSuggestion = async (suggestion) => {
54
+ const {structured_formatting, terms} = suggestion
55
+ const {main_text, secondary_text} = structured_formatting
56
+
57
+ const parsedFields = {
58
+ address1: main_text
59
+ }
60
+
61
+ const countryTerm = terms[terms.length - 1]?.value || ''
62
+ parsedFields.countryCode = resolveCountryCode(countryTerm)
63
+
64
+ if (!secondary_text) {
65
+ return parsedFields
66
+ }
67
+
68
+ /*
69
+ * Parse secondary text to extract city, state, and postal code
70
+ * Format examples:
71
+ * "New York, NY 10001, USA"
72
+ * "Toronto, ON M5C 1W4, Canada"
73
+ * "London, UK NW1 6XE"
74
+ * "New York" (single part)
75
+ */
76
+
77
+ const parts = secondary_text.split(',')
78
+
79
+ if (parts.length >= 2) {
80
+ // Extract city (first part)
81
+ parsedFields.city = parts[0].trim()
82
+
83
+ // Extract state and postal code (second part)
84
+ const statePostalPart = parts[1].trim()
85
+
86
+ const statePostalMatch = statePostalPart.match(/^([A-Z]{2})\s+([A-Z0-9\s]+)$/)
87
+
88
+ if (statePostalMatch) {
89
+ parsedFields.stateCode = statePostalMatch[1]
90
+ parsedFields.postalCode = statePostalMatch[2].trim()
91
+ } else {
92
+ // If no state/postal pattern, just use the part as state
93
+ parsedFields.stateCode = statePostalPart
94
+ }
95
+ } else if (parts.length === 1) {
96
+ // Single part - could be just city or just state
97
+ const singlePart = parts[0].trim()
98
+ const stateMatch = singlePart.match(/^[A-Z]{2}$/)
99
+
100
+ if (stateMatch) {
101
+ parsedFields.stateCode = singlePart
102
+ } else {
103
+ parsedFields.city = singlePart
104
+ }
105
+ }
106
+
107
+ return parsedFields
108
+ }
109
+
110
+ /**
111
+ * Extract address fields from Google Maps place and return structured object
112
+ * @param {Object} place - Google Maps place object
113
+ * @returns {Promise<Object>} Structured address fields
114
+ */
115
+ export const extractAddressFieldsFromPlace = async (place) => {
116
+ await place.fetchFields({
117
+ fields: ['formattedAddress']
118
+ })
119
+
120
+ const formattedAddress = place.formattedAddress || ''
121
+
122
+ // Parse the formatted address to extract individual fields
123
+ return parseFormattedAddress(formattedAddress)
124
+ }
125
+
126
+ /**
127
+ * Parse formatted address string to extract individual address fields
128
+ * @param {string} formattedAddress - Full formatted address string
129
+ * @returns {Object} Structured address fields following adr microformat
130
+ */
131
+ export const parseFormattedAddress = (formattedAddress) => {
132
+ if (!formattedAddress) {
133
+ return {address1: ''}
134
+ }
135
+
136
+ // Split by comma
137
+ const parts = formattedAddress.split(',').map((part) => part.trim())
138
+
139
+ // Initialize with microformat structure following adr specification
140
+ const addressFields = {
141
+ 'street-address': parts[0] || '', // street-address (adr microformat)
142
+ locality: '', // locality (adr microformat)
143
+ region: '', // region (adr microformat)
144
+ 'postal-code': '', // postal-code (adr microformat)
145
+ 'country-name': '' // country-name (adr microformat)
146
+ }
147
+
148
+ // Map parts to microformat fields based on adr specification
149
+ if (parts.length >= 4) {
150
+ // Format: "123 Main St, New York, NY 10001, USA" OR "123 Main St, New York, CA, USA"
151
+ addressFields['locality'] = parts[1] // City
152
+ const statePostalPart = parts[2]
153
+ const statePostalSplit = statePostalPart.split(' ')
154
+
155
+ if (statePostalSplit.length >= 2) {
156
+ // Has both state and postal code
157
+ addressFields['region'] = statePostalSplit[0] // State (first part)
158
+ addressFields['postal-code'] = statePostalSplit.slice(1).join(' ') // Postal code
159
+ } else {
160
+ // Just state code
161
+ addressFields['region'] = statePostalPart
162
+ }
163
+ addressFields['country-name'] = parts[3]
164
+ } else if (parts.length === 3) {
165
+ // Format: "123 Main St, New York, NY" or "123 Main St, New York, USA"
166
+ addressFields['locality'] = parts[1]
167
+ const lastPart = parts[2]
168
+
169
+ if (COUNTRY_CODE_MAP[lastPart]) {
170
+ addressFields['country-name'] = lastPart
171
+ } else {
172
+ // Parse state and postal code
173
+ const statePostalSplit = lastPart.split(' ')
174
+ if (statePostalSplit.length >= 2) {
175
+ addressFields['region'] = statePostalSplit[0]
176
+ addressFields['postal-code'] = statePostalSplit.slice(1).join(' ')
177
+ } else {
178
+ addressFields['region'] = lastPart
179
+ }
180
+ }
181
+ } else if (parts.length === 2) {
182
+ // Format: "123 Main St, New York"
183
+ addressFields['locality'] = parts[1]
184
+ }
185
+
186
+ // Convert microformat fields to expected format
187
+ return {
188
+ address1: addressFields['street-address'],
189
+ city: addressFields['locality'],
190
+ stateCode: addressFields['region'],
191
+ postalCode: addressFields['postal-code'],
192
+ countryCode: resolveCountryCode(addressFields['country-name'])
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Set address field values in form
198
+ * @param {Function} setValue - Form setValue function
199
+ * @param {string} prefix - Field prefix
200
+ * @param {Object} addressFields - Address fields object
201
+ */
202
+ export const setAddressFieldValues = (setValue, prefix, addressFields) => {
203
+ setValue(`${prefix}address1`, addressFields.address1)
204
+ if (addressFields.city) {
205
+ setValue(`${prefix}city`, addressFields.city)
206
+ }
207
+ if (addressFields.stateCode) {
208
+ setValue(`${prefix}stateCode`, addressFields.stateCode)
209
+ }
210
+ if (addressFields.postalCode) {
211
+ setValue(`${prefix}postalCode`, addressFields.postalCode)
212
+ }
213
+ if (addressFields.countryCode) {
214
+ setValue(`${prefix}countryCode`, addressFields.countryCode)
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Process address suggestion and extract structured address fields
220
+ * This unified method handles both placePrediction.toPlace() and fallback scenarios
221
+ * @param {Object} suggestion - Address suggestion object from the API
222
+ * @returns {Promise<Object>} Structured address fields
223
+ */
224
+ export const processAddressSuggestion = async (suggestion) => {
225
+ let addressFields
226
+
227
+ // If we have the placePrediction, get detailed place information using toPlace()
228
+ if (suggestion.placePrediction) {
229
+ const place = suggestion.placePrediction.toPlace()
230
+ addressFields = await extractAddressFieldsFromPlace(place)
231
+ } else {
232
+ // Fallback to parsing from structured_formatting when placePrediction is not available
233
+ addressFields = await parseAddressSuggestion(suggestion)
234
+ }
235
+
236
+ return addressFields
237
+ }
package/config/default.js CHANGED
@@ -79,7 +79,10 @@ module.exports = {
79
79
  }
80
80
  },
81
81
  storeLocatorEnabled: true,
82
- multishipEnabled: true
82
+ multishipEnabled: true,
83
+ googleCloudAPI: {
84
+ apiKey: process.env.GOOGLE_CLOUD_API_KEY
85
+ }
83
86
  },
84
87
  envBasePath: '/',
85
88
  externals: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/retail-react-app",
3
- "version": "8.3.0-nightly-20251209080224",
3
+ "version": "8.3.0-preview.0",
4
4
  "license": "See license in LICENSE",
5
5
  "author": "cc-pwa-kit@salesforce.com",
6
6
  "ccExtensibility": {
@@ -46,16 +46,17 @@
46
46
  "@loadable/component": "^5.15.3",
47
47
  "@peculiar/webcrypto": "^1.4.2",
48
48
  "@salesforce/cc-datacloud-typescript": "1.1.2",
49
- "@salesforce/commerce-sdk-react": "4.3.0-nightly-20251209080224",
50
- "@salesforce/pwa-kit-dev": "3.15.0-nightly-20251209080224",
51
- "@salesforce/pwa-kit-react-sdk": "3.15.0-nightly-20251209080224",
52
- "@salesforce/pwa-kit-runtime": "3.15.0-nightly-20251209080224",
49
+ "@salesforce/commerce-sdk-react": "4.3.0-preview.0",
50
+ "@salesforce/pwa-kit-dev": "3.15.0-preview.0",
51
+ "@salesforce/pwa-kit-react-sdk": "3.15.0-preview.0",
52
+ "@salesforce/pwa-kit-runtime": "3.15.0-preview.0",
53
53
  "@tanstack/react-query": "^4.28.0",
54
54
  "@tanstack/react-query-devtools": "^4.29.1",
55
55
  "@testing-library/dom": "^9.0.1",
56
56
  "@testing-library/jest-dom": "^5.16.5",
57
57
  "@testing-library/react": "^14.0.0",
58
58
  "@testing-library/user-event": "14.4.3",
59
+ "@vis.gl/react-google-maps": "^1.5.4",
59
60
  "babel-plugin-module-resolver": "5.0.2",
60
61
  "base64-arraybuffer": "^0.2.0",
61
62
  "bundlesize2": "^0.0.35",
@@ -100,12 +101,12 @@
100
101
  "bundlesize": [
101
102
  {
102
103
  "path": "build/main.js",
103
- "maxSize": "84 kB"
104
+ "maxSize": "86 kB"
104
105
  },
105
106
  {
106
107
  "path": "build/vendor.js",
107
- "maxSize": "335 kB"
108
+ "maxSize": "363 kB"
108
109
  }
109
110
  ],
110
- "gitHead": "5272de8755b9a7e807d91820b665830278309963"
111
+ "gitHead": "40542ae8754e3274bc7910e89e57c96089e2d22a"
111
112
  }
@@ -162,6 +162,9 @@
162
162
  "add_to_cart_modal.recommended_products.title.might_also_like": {
163
163
  "defaultMessage": "You Might Also Like"
164
164
  },
165
+ "addressSuggestionDropdown.suggested": {
166
+ "defaultMessage": "SUGGESTED"
167
+ },
165
168
  "auth_modal.button.close.assistive_msg": {
166
169
  "defaultMessage": "Close login form"
167
170
  },
@@ -162,6 +162,9 @@
162
162
  "add_to_cart_modal.recommended_products.title.might_also_like": {
163
163
  "defaultMessage": "You Might Also Like"
164
164
  },
165
+ "addressSuggestionDropdown.suggested": {
166
+ "defaultMessage": "SUGGESTED"
167
+ },
165
168
  "auth_modal.button.close.assistive_msg": {
166
169
  "defaultMessage": "Close login form"
167
170
  },