create-brainerce-store 1.61.0 → 1.62.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/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "create-brainerce-store",
|
|
34
|
-
version: "1.
|
|
34
|
+
version: "1.62.0",
|
|
35
35
|
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
36
|
bin: {
|
|
37
37
|
"create-brainerce-store": "dist/index.js"
|
|
@@ -187,7 +187,12 @@ var BRAINERCE_RUNTIME_DEPS = Object.freeze({
|
|
|
187
187
|
// label live carrier rates, so an older SDK fails type-check at scaffold time
|
|
188
188
|
// — plus getBusinessHoursForDate(), which the scaffolded DATETIME checkout
|
|
189
189
|
// field uses to tell "day closed" from "field isn't slot-based".
|
|
190
|
-
|
|
190
|
+
// 1.53 strips lat/lng/formattedAddress from setShippingAddress/
|
|
191
|
+
// setBillingAddress bodies. The address endpoints reject ANY unknown
|
|
192
|
+
// property with a 400 that blocks checkout outright, and those three come
|
|
193
|
+
// straight off getAddressDetails() — so a scaffolded store on an older SDK
|
|
194
|
+
// is one `...address` spread away from a checkout nobody can complete.
|
|
195
|
+
brainerce: "^1.53.0",
|
|
191
196
|
"isomorphic-dompurify": "^3.8.0"
|
|
192
197
|
});
|
|
193
198
|
|
package/package.json
CHANGED
|
@@ -13,6 +13,18 @@ import { getClient } from '@/core/lib/brainerce';
|
|
|
13
13
|
const ADDRESS_SEARCH_DEBOUNCE_MS = 300;
|
|
14
14
|
const ADDRESS_SEARCH_MIN_CHARS = 3;
|
|
15
15
|
|
|
16
|
+
// Editing any of these invalidates a previously picked suggestion: its
|
|
17
|
+
// coordinates describe the address the shopper selected, not what is in the
|
|
18
|
+
// inputs now. See `pickedPlace` below.
|
|
19
|
+
const PLACE_INVALIDATING_FIELDS = new Set<string>([
|
|
20
|
+
'line1',
|
|
21
|
+
'line2',
|
|
22
|
+
'city',
|
|
23
|
+
'region',
|
|
24
|
+
'postalCode',
|
|
25
|
+
'country',
|
|
26
|
+
]);
|
|
27
|
+
|
|
16
28
|
interface CheckoutConsent {
|
|
17
29
|
acceptsMarketing: boolean;
|
|
18
30
|
saveDetails: boolean;
|
|
@@ -65,6 +77,16 @@ export function CheckoutForm({
|
|
|
65
77
|
const [showAddressSuggestions, setShowAddressSuggestions] = useState(false);
|
|
66
78
|
const [isSearchingAddress, setIsSearchingAddress] = useState(false);
|
|
67
79
|
const [outsideDeliveryZone, setOutsideDeliveryZone] = useState(false);
|
|
80
|
+
// The suggestion the shopper actually picked, kept so it can ride along to
|
|
81
|
+
// `setShippingAddress`. That is what lets the server match map-drawn
|
|
82
|
+
// ("polygon") delivery zones against this address's exact coordinates
|
|
83
|
+
// instead of geocoding the typed text — skipping it is the most common
|
|
84
|
+
// cause of "the store says it doesn't deliver here, but it does". Null
|
|
85
|
+
// whenever the address was typed by hand or edited after picking.
|
|
86
|
+
const [pickedPlace, setPickedPlace] = useState<{
|
|
87
|
+
placeId: string;
|
|
88
|
+
sessionToken: string;
|
|
89
|
+
} | null>(null);
|
|
68
90
|
const addressSessionToken = useRef(
|
|
69
91
|
typeof crypto !== 'undefined' ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`
|
|
70
92
|
);
|
|
@@ -108,10 +130,14 @@ export function CheckoutForm({
|
|
|
108
130
|
|
|
109
131
|
async function handleSelectAddressSuggestion(suggestion: AddressSuggestion) {
|
|
110
132
|
setShowAddressSuggestions(false);
|
|
133
|
+
// The token this resolution is billed under — hand the SAME one to
|
|
134
|
+
// `setShippingAddress` so a server-side cache miss re-resolves within this
|
|
135
|
+
// autocomplete session instead of opening a new (billed) one.
|
|
136
|
+
const resolvingSessionToken = addressSessionToken.current;
|
|
111
137
|
try {
|
|
112
138
|
const { address, inZone } = await getClient().getAddressDetails(
|
|
113
139
|
suggestion.placeId,
|
|
114
|
-
|
|
140
|
+
resolvingSessionToken
|
|
115
141
|
);
|
|
116
142
|
setFormData((prev) => {
|
|
117
143
|
const country = address.country || prev.country;
|
|
@@ -138,9 +164,15 @@ export function CheckoutForm({
|
|
|
138
164
|
};
|
|
139
165
|
});
|
|
140
166
|
setOutsideDeliveryZone(!inZone);
|
|
167
|
+
// Note we deliberately do NOT copy address.lat/lng into the form: no
|
|
168
|
+
// address endpoint accepts coordinates (zone matching decides which rate
|
|
169
|
+
// is charged, so the server resolves them from the placeId itself).
|
|
170
|
+
// They're yours to use for a map pin and nothing else.
|
|
171
|
+
setPickedPlace({ placeId: suggestion.placeId, sessionToken: resolvingSessionToken });
|
|
141
172
|
} catch {
|
|
142
173
|
// Resolution failed — keep whatever the shopper had typed/selected as
|
|
143
174
|
// the visible text; they can still fill in the rest manually.
|
|
175
|
+
setPickedPlace(null);
|
|
144
176
|
} finally {
|
|
145
177
|
// New session for the next address-entry attempt.
|
|
146
178
|
addressSessionToken.current =
|
|
@@ -212,7 +244,17 @@ export function CheckoutForm({
|
|
|
212
244
|
function handleSubmit(e: React.FormEvent) {
|
|
213
245
|
e.preventDefault();
|
|
214
246
|
if (validate()) {
|
|
215
|
-
onSubmit(
|
|
247
|
+
onSubmit(
|
|
248
|
+
{
|
|
249
|
+
...formData,
|
|
250
|
+
// Both undefined when the shopper typed the address by hand or
|
|
251
|
+
// edited it after picking a suggestion — the server then geocodes
|
|
252
|
+
// the text, which is the right behaviour once the two disagree.
|
|
253
|
+
placeId: pickedPlace?.placeId,
|
|
254
|
+
placeSessionToken: pickedPlace?.sessionToken,
|
|
255
|
+
},
|
|
256
|
+
{ acceptsMarketing, saveDetails: showSaveDetails && saveDetails }
|
|
257
|
+
);
|
|
216
258
|
}
|
|
217
259
|
}
|
|
218
260
|
|
|
@@ -225,6 +267,12 @@ export function CheckoutForm({
|
|
|
225
267
|
}
|
|
226
268
|
return next;
|
|
227
269
|
});
|
|
270
|
+
// Once any address field is edited, the picked suggestion no longer
|
|
271
|
+
// describes what is in the form — keeping its placeId would match delivery
|
|
272
|
+
// zones against the address the shopper originally selected.
|
|
273
|
+
if (PLACE_INVALIDATING_FIELDS.has(field)) {
|
|
274
|
+
setPickedPlace(null);
|
|
275
|
+
}
|
|
228
276
|
if (errors[field]) {
|
|
229
277
|
setErrors((prev) => {
|
|
230
278
|
const next = { ...prev };
|