@fayz-ai/storefront 0.8.0 → 0.8.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/dist/auth.d.ts.map +1 -1
- package/dist/components/CartDrawer.d.ts.map +1 -1
- package/dist/components/DeliveryEstimator.d.ts +17 -0
- package/dist/components/DeliveryEstimator.d.ts.map +1 -0
- package/dist/components/ProductGallery.d.ts +22 -0
- package/dist/components/ProductGallery.d.ts.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +674 -275
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +576 -177
- package/dist/index.js.map +1 -1
- package/dist/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/stores/cart.store.d.ts +16 -0
- package/dist/stores/cart.store.d.ts.map +1 -1
- package/dist/stores/delivery.store.d.ts +64 -0
- package/dist/stores/delivery.store.d.ts.map +1 -0
- package/dist/testids.d.ts +11 -0
- package/dist/testids.d.ts.map +1 -1
- package/dist/workflows/checkout.d.ts +5 -2
- package/dist/workflows/checkout.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/auth.ts +57 -1
- package/src/components/CartDrawer.tsx +23 -1
- package/src/components/DeliveryEstimator.tsx +157 -0
- package/src/components/ProductGallery.tsx +174 -0
- package/src/config.ts +15 -3
- package/src/pages/CheckoutPage.tsx +240 -134
- package/src/pages/ProductDetailPage.tsx +17 -13
- package/src/stores/cart.store.ts +21 -0
- package/src/stores/delivery.store.ts +152 -0
- package/src/testids.ts +12 -0
- package/src/workflows/checkout.ts +30 -5
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getShopProvider, setShopProvider } from '@fayz-ai/shop/runtime';
|
|
2
2
|
import { createMockShopProvider } from '@fayz-ai/shop/mock';
|
|
3
3
|
import { createAuthRuntime, createMockAuthAdapter } from '@fayz-ai/plugin-auth';
|
|
4
|
+
import { setShopAccessTokenResolver } from '@fayz-ai/sdk/shop';
|
|
4
5
|
import { create } from 'zustand';
|
|
5
6
|
import { persist } from 'zustand/middleware';
|
|
6
|
-
import
|
|
7
|
-
import { hashRouterAdapter, registerScaffold, registerBlock, renderBlocks, defineApp } from '@fayz-ai/core';
|
|
7
|
+
import React7, { createContext, useState, useRef, useEffect, useMemo, useContext, useCallback } from 'react';
|
|
8
|
+
import { hashRouterAdapter, normalizePostalCode, lookupPostalCode, registerScaffold, registerBlock, formatPostalCode, renderBlocks, defineApp } from '@fayz-ai/core';
|
|
8
9
|
export { getSupabaseClientOptional, renderApp } from '@fayz-ai/core';
|
|
9
10
|
import * as LucideIcons from 'lucide-react';
|
|
10
|
-
import { ChevronLeft, ChevronRight, ShoppingBag, X, Trash2, Phone, Mail, Search, User, UserCircle, Package, CreditCard, LogOut, Truck, RefreshCcw, ShieldCheck, Lock, Minus, Plus, Info, AlertCircle, Check, Send, MessageCircle,
|
|
11
|
+
import { ChevronLeft, ChevronRight, ShoppingBag, X, Trash2, Phone, Mail, Search, User, UserCircle, Package, CreditCard, LogOut, Truck, RefreshCcw, ShieldCheck, Lock, Minus, Plus, MapPin, Info, AlertCircle, Check, Send, MessageCircle, PackageCheck, QrCode, XCircle, RotateCcw, Clock } from 'lucide-react';
|
|
11
12
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
13
|
import { validateDiscount } from '@fayz-ai/shop';
|
|
13
14
|
|
|
@@ -53,10 +54,23 @@ function resolveAuthAdapter(configured, fallback) {
|
|
|
53
54
|
}
|
|
54
55
|
}).adapter;
|
|
55
56
|
}
|
|
57
|
+
var _accessToken = null;
|
|
58
|
+
var _hadAuthUser = false;
|
|
59
|
+
async function syncAccessToken(adapter2) {
|
|
60
|
+
try {
|
|
61
|
+
const existing = await adapter2.getSession();
|
|
62
|
+
_accessToken = existing?.session.accessToken ?? null;
|
|
63
|
+
} catch {
|
|
64
|
+
_accessToken = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
56
67
|
function initCustomerAuth(adapter2) {
|
|
57
68
|
_adapter = adapter2;
|
|
69
|
+
setShopAccessTokenResolver(() => _accessToken);
|
|
58
70
|
adapter2.getSession().then((existing) => {
|
|
71
|
+
_accessToken = existing?.session.accessToken ?? null;
|
|
59
72
|
if (!existing) return;
|
|
73
|
+
_hadAuthUser = true;
|
|
60
74
|
const store = useSessionStore.getState();
|
|
61
75
|
if (store.email !== existing.user.email) {
|
|
62
76
|
void linkCustomer(existing.user);
|
|
@@ -64,7 +78,16 @@ function initCustomerAuth(adapter2) {
|
|
|
64
78
|
}).catch(() => {
|
|
65
79
|
});
|
|
66
80
|
adapter2.onAuthStateChange((user) => {
|
|
67
|
-
if (!user)
|
|
81
|
+
if (!user) {
|
|
82
|
+
_accessToken = null;
|
|
83
|
+
if (_hadAuthUser) {
|
|
84
|
+
_hadAuthUser = false;
|
|
85
|
+
useSessionStore.getState().signOut();
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
_hadAuthUser = true;
|
|
90
|
+
void syncAccessToken(adapter2);
|
|
68
91
|
});
|
|
69
92
|
}
|
|
70
93
|
function getCustomerAuthAdapter() {
|
|
@@ -100,6 +123,8 @@ async function establishCustomerSession(email, opts) {
|
|
|
100
123
|
if (opts?.password) {
|
|
101
124
|
const adapter2 = getCustomerAuthAdapter();
|
|
102
125
|
const session = await adapter2.signIn(normalized, opts.password);
|
|
126
|
+
_accessToken = session.accessToken ?? null;
|
|
127
|
+
_hadAuthUser = true;
|
|
103
128
|
return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, opts.name);
|
|
104
129
|
}
|
|
105
130
|
return linkCustomer({ email: normalized }, opts?.name);
|
|
@@ -111,12 +136,16 @@ async function signUpCustomer(email, password, name) {
|
|
|
111
136
|
const adapter2 = getCustomerAuthAdapter();
|
|
112
137
|
const normalized = email.trim().toLowerCase();
|
|
113
138
|
const session = await adapter2.signUp(normalized, password, name);
|
|
139
|
+
_accessToken = session.accessToken ?? null;
|
|
140
|
+
_hadAuthUser = true;
|
|
114
141
|
return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, name);
|
|
115
142
|
}
|
|
116
143
|
async function signOutCustomer() {
|
|
117
144
|
try {
|
|
118
145
|
await getCustomerAuthAdapter().signOut();
|
|
119
146
|
} finally {
|
|
147
|
+
_accessToken = null;
|
|
148
|
+
_hadAuthUser = false;
|
|
120
149
|
useSessionStore.getState().signOut();
|
|
121
150
|
}
|
|
122
151
|
}
|
|
@@ -129,7 +158,10 @@ function resolveConfig(config) {
|
|
|
129
158
|
currency: config.currency ?? "BRL",
|
|
130
159
|
locale: config.locale ?? "pt-BR",
|
|
131
160
|
shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
|
|
132
|
-
payments: {
|
|
161
|
+
payments: {
|
|
162
|
+
mode: config.payments?.mode ?? "mock",
|
|
163
|
+
methods: config.payments?.methods?.length ? config.payments.methods : ["pix", "credit_card", "cash"]
|
|
164
|
+
},
|
|
133
165
|
commerceMode,
|
|
134
166
|
enquiry: {
|
|
135
167
|
label: config.enquiry?.label ?? "Contact me",
|
|
@@ -204,7 +236,7 @@ function matchPath(pattern, path) {
|
|
|
204
236
|
return params;
|
|
205
237
|
}
|
|
206
238
|
function Link({ to, children, ...rest }) {
|
|
207
|
-
return
|
|
239
|
+
return React7.createElement("a", { href: `#${to}`, ...rest }, children);
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
// src/format.ts
|
|
@@ -214,6 +246,87 @@ function formatMoney(value, currency = "BRL", locale = "pt-BR") {
|
|
|
214
246
|
function roundCents(value) {
|
|
215
247
|
return Math.round(value * 100) / 100;
|
|
216
248
|
}
|
|
249
|
+
var EMPTY = {
|
|
250
|
+
postalCode: "",
|
|
251
|
+
address: null,
|
|
252
|
+
options: [],
|
|
253
|
+
selectedZoneId: null,
|
|
254
|
+
status: "idle",
|
|
255
|
+
error: null
|
|
256
|
+
};
|
|
257
|
+
var useDeliveryStore = create()(
|
|
258
|
+
persist(
|
|
259
|
+
(set, get) => ({
|
|
260
|
+
...EMPTY,
|
|
261
|
+
async resolve(postalCode, subtotal) {
|
|
262
|
+
const code = normalizePostalCode(postalCode);
|
|
263
|
+
if (code.length !== 8) {
|
|
264
|
+
set({ ...EMPTY, postalCode, status: "error", error: "Digite os 8 d\xEDgitos do CEP." });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
set({ postalCode: code, status: "loading", error: null });
|
|
268
|
+
let options = [];
|
|
269
|
+
try {
|
|
270
|
+
options = await getShopProvider().quoteShipping?.(code, subtotal) ?? [];
|
|
271
|
+
} catch {
|
|
272
|
+
set({
|
|
273
|
+
...EMPTY,
|
|
274
|
+
postalCode: code,
|
|
275
|
+
status: "error",
|
|
276
|
+
error: "N\xE3o foi poss\xEDvel calcular o frete agora. Tente de novo."
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
let address = null;
|
|
281
|
+
let notFound = false;
|
|
282
|
+
try {
|
|
283
|
+
address = await lookupPostalCode(code);
|
|
284
|
+
notFound = address === null;
|
|
285
|
+
} catch {
|
|
286
|
+
address = null;
|
|
287
|
+
}
|
|
288
|
+
set({
|
|
289
|
+
postalCode: code,
|
|
290
|
+
address,
|
|
291
|
+
options,
|
|
292
|
+
selectedZoneId: options[0]?.zoneId ?? null,
|
|
293
|
+
// A store with no zones configured quotes nothing, and that must NOT
|
|
294
|
+
// read as "we don't deliver here" — it falls back to the store-wide
|
|
295
|
+
// flat rate, which the cart already knows how to show.
|
|
296
|
+
status: options.length > 0 ? "served" : "unserved",
|
|
297
|
+
// Reported alongside the price, not instead of it: the CEP may be
|
|
298
|
+
// mistyped even though its range is covered.
|
|
299
|
+
error: notFound ? "CEP n\xE3o encontrado. Confira os n\xFAmeros." : null
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
selectZone(zoneId) {
|
|
303
|
+
if (get().options.some((option) => option.zoneId === zoneId)) set({ selectedZoneId: zoneId });
|
|
304
|
+
},
|
|
305
|
+
clear() {
|
|
306
|
+
set({ ...EMPTY });
|
|
307
|
+
}
|
|
308
|
+
}),
|
|
309
|
+
{
|
|
310
|
+
name: "fayz.storefront.delivery.v1",
|
|
311
|
+
// Deliberately NOT persisting `options`: a rehydrated
|
|
312
|
+
// price would be shown as current without anything having quoted it. The
|
|
313
|
+
// address survives, the money is asked for again.
|
|
314
|
+
partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
|
|
315
|
+
onRehydrateStorage: () => (state) => {
|
|
316
|
+
if (state?.address) state.status = "idle";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
function resolveOptionRate(option, subtotal) {
|
|
322
|
+
if (option.freeAbove != null && subtotal >= option.freeAbove) return 0;
|
|
323
|
+
return option.baseRate;
|
|
324
|
+
}
|
|
325
|
+
var selectQuotedShipping = (state, subtotal) => {
|
|
326
|
+
if (state.status !== "served") return null;
|
|
327
|
+
const option = state.options.find((o) => o.zoneId === state.selectedZoneId) ?? state.options[0];
|
|
328
|
+
return option ? resolveOptionRate(option, subtotal) : null;
|
|
329
|
+
};
|
|
217
330
|
|
|
218
331
|
// src/product-options.ts
|
|
219
332
|
function cleanValues(value) {
|
|
@@ -337,6 +450,8 @@ var selectDiscountTotal = (s) => roundCents(selectSubtotal(s) * (s.discountPerce
|
|
|
337
450
|
var selectShipping = (s, cfg) => {
|
|
338
451
|
if (s.lines.length === 0) return 0;
|
|
339
452
|
const subtotal = selectSubtotal(s);
|
|
453
|
+
const quoted = selectQuotedShipping(useDeliveryStore.getState(), subtotal);
|
|
454
|
+
if (quoted != null) return quoted;
|
|
340
455
|
if (cfg.shipping.freeAbove != null && subtotal >= cfg.shipping.freeAbove) return 0;
|
|
341
456
|
return cfg.shipping.flatRate;
|
|
342
457
|
};
|
|
@@ -435,7 +550,7 @@ function StorefrontThemeStyle({ theme }) {
|
|
|
435
550
|
}
|
|
436
551
|
link.href = fontsHref;
|
|
437
552
|
}, [fontsHref]);
|
|
438
|
-
return
|
|
553
|
+
return React7.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
439
554
|
}
|
|
440
555
|
var initial = {
|
|
441
556
|
search: "",
|
|
@@ -601,6 +716,9 @@ var TID = {
|
|
|
601
716
|
pdpAddToCart: "pdp-add-to-cart",
|
|
602
717
|
pdpRoot: "pdp-root",
|
|
603
718
|
pdpGallery: "pdp-gallery",
|
|
719
|
+
pdpGalleryImage: "pdp-gallery-image",
|
|
720
|
+
pdpGalleryThumbs: "pdp-gallery-thumbs",
|
|
721
|
+
pdpGalleryThumb: "pdp-gallery-thumb",
|
|
604
722
|
pdpActions: "pdp-actions",
|
|
605
723
|
pdpEnquiryButton: "pdp-enquiry-button",
|
|
606
724
|
enquiryForm: "enquiry-form",
|
|
@@ -629,6 +747,15 @@ var TID = {
|
|
|
629
747
|
cartTotal: "cart-total",
|
|
630
748
|
goCheckout: "go-checkout",
|
|
631
749
|
cartEmpty: "cart-empty",
|
|
750
|
+
// delivery estimate (product page + cart)
|
|
751
|
+
deliveryEstimator: "delivery-estimator",
|
|
752
|
+
deliveryCepInput: "delivery-cep-input",
|
|
753
|
+
deliveryCepSubmit: "delivery-cep-submit",
|
|
754
|
+
deliveryOption: "delivery-option",
|
|
755
|
+
deliveryUnserved: "delivery-unserved",
|
|
756
|
+
deliveryError: "delivery-error",
|
|
757
|
+
deliveryAddress: "delivery-address",
|
|
758
|
+
deliveryClear: "delivery-clear",
|
|
632
759
|
// checkout
|
|
633
760
|
checkoutEmail: "checkout-email",
|
|
634
761
|
checkoutName: "checkout-name",
|
|
@@ -1147,6 +1274,113 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
|
|
|
1147
1274
|
)
|
|
1148
1275
|
] });
|
|
1149
1276
|
}
|
|
1277
|
+
function DeliveryEstimator({ compact = false }) {
|
|
1278
|
+
const config = useStorefrontConfig();
|
|
1279
|
+
const cart = useCartStore();
|
|
1280
|
+
const delivery = useDeliveryStore();
|
|
1281
|
+
const [draft, setDraft] = useState(() => formatPostalCode(delivery.postalCode));
|
|
1282
|
+
const subtotal = selectSubtotal(cart);
|
|
1283
|
+
const money = (value) => formatMoney(value, config.currency, config.locale);
|
|
1284
|
+
const loading = delivery.status === "loading";
|
|
1285
|
+
async function submit(event) {
|
|
1286
|
+
event.preventDefault();
|
|
1287
|
+
await delivery.resolve(draft, subtotal);
|
|
1288
|
+
}
|
|
1289
|
+
function eta(option) {
|
|
1290
|
+
const { etaMinDays: min, etaMaxDays: max } = option;
|
|
1291
|
+
if (min == null && max == null) return null;
|
|
1292
|
+
if (min === 0 && (max === 0 || max === 1)) return "hoje ou amanh\xE3";
|
|
1293
|
+
if (min != null && max != null && min !== max) return `${min} a ${max} dias \xFAteis`;
|
|
1294
|
+
return `${max ?? min} dia(s) \xFAtil(eis)`;
|
|
1295
|
+
}
|
|
1296
|
+
return /* @__PURE__ */ jsxs(
|
|
1297
|
+
"section",
|
|
1298
|
+
{
|
|
1299
|
+
"data-testid": TID.deliveryEstimator,
|
|
1300
|
+
className: compact ? "rounded-lg border p-3" : "mt-6 rounded-lg border p-4",
|
|
1301
|
+
children: [
|
|
1302
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-semibold", children: [
|
|
1303
|
+
/* @__PURE__ */ jsx(Truck, { className: "h-4 w-4 text-primary" }),
|
|
1304
|
+
"Calcular entrega"
|
|
1305
|
+
] }),
|
|
1306
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "mt-3 flex gap-2", children: [
|
|
1307
|
+
/* @__PURE__ */ jsx(
|
|
1308
|
+
"input",
|
|
1309
|
+
{
|
|
1310
|
+
"data-testid": TID.deliveryCepInput,
|
|
1311
|
+
"aria-label": "CEP",
|
|
1312
|
+
inputMode: "numeric",
|
|
1313
|
+
autoComplete: "postal-code",
|
|
1314
|
+
placeholder: "00000-000",
|
|
1315
|
+
value: draft,
|
|
1316
|
+
onChange: (event) => setDraft(formatPostalCode(event.target.value)),
|
|
1317
|
+
className: "min-w-0 flex-1 rounded-lg border bg-background px-3 py-2 text-sm outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/10"
|
|
1318
|
+
}
|
|
1319
|
+
),
|
|
1320
|
+
/* @__PURE__ */ jsx(
|
|
1321
|
+
"button",
|
|
1322
|
+
{
|
|
1323
|
+
type: "submit",
|
|
1324
|
+
"data-testid": TID.deliveryCepSubmit,
|
|
1325
|
+
disabled: loading,
|
|
1326
|
+
className: "shrink-0 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
|
|
1327
|
+
children: loading ? "..." : "Calcular"
|
|
1328
|
+
}
|
|
1329
|
+
)
|
|
1330
|
+
] }),
|
|
1331
|
+
delivery.error && /* @__PURE__ */ jsx("p", { "data-testid": TID.deliveryError, className: "mt-3 text-sm text-destructive", children: delivery.error }),
|
|
1332
|
+
delivery.address && /* @__PURE__ */ jsxs("p", { "data-testid": TID.deliveryAddress, className: "mt-3 flex items-start gap-1.5 text-xs text-muted-foreground", children: [
|
|
1333
|
+
/* @__PURE__ */ jsx(MapPin, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
1334
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1335
|
+
[delivery.address.street, delivery.address.district].filter(Boolean).join(", "),
|
|
1336
|
+
delivery.address.street || delivery.address.district ? " \u2014 " : "",
|
|
1337
|
+
delivery.address.city,
|
|
1338
|
+
"/",
|
|
1339
|
+
delivery.address.state
|
|
1340
|
+
] })
|
|
1341
|
+
] }),
|
|
1342
|
+
delivery.status === "served" && /* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-2", children: delivery.options.map((option) => {
|
|
1343
|
+
const selected = delivery.selectedZoneId === option.zoneId;
|
|
1344
|
+
const label = eta(option);
|
|
1345
|
+
const rate = resolveOptionRate(option, subtotal);
|
|
1346
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
1347
|
+
"button",
|
|
1348
|
+
{
|
|
1349
|
+
type: "button",
|
|
1350
|
+
"data-testid": TID.deliveryOption,
|
|
1351
|
+
"data-zone": option.zoneId,
|
|
1352
|
+
"data-rate": rate.toFixed(2),
|
|
1353
|
+
"aria-pressed": selected,
|
|
1354
|
+
onClick: () => delivery.selectZone(option.zoneId),
|
|
1355
|
+
className: `flex w-full items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left text-sm transition ${selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/40"}`,
|
|
1356
|
+
children: [
|
|
1357
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
1358
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate font-medium", children: option.carrier ? `${option.carrier} \xB7 ${option.name}` : option.name }),
|
|
1359
|
+
label && /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: label })
|
|
1360
|
+
] }),
|
|
1361
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 font-semibold", children: rate === 0 ? "Gr\xE1tis" : money(rate) })
|
|
1362
|
+
]
|
|
1363
|
+
}
|
|
1364
|
+
) }, option.zoneId);
|
|
1365
|
+
}) }),
|
|
1366
|
+
delivery.status === "unserved" && /* @__PURE__ */ jsx("p", { "data-testid": TID.deliveryUnserved, className: "mt-3 text-sm text-destructive", children: "Ainda n\xE3o entregamos nesse CEP." }),
|
|
1367
|
+
delivery.postalCode && /* @__PURE__ */ jsx(
|
|
1368
|
+
"button",
|
|
1369
|
+
{
|
|
1370
|
+
type: "button",
|
|
1371
|
+
"data-testid": TID.deliveryClear,
|
|
1372
|
+
onClick: () => {
|
|
1373
|
+
delivery.clear();
|
|
1374
|
+
setDraft("");
|
|
1375
|
+
},
|
|
1376
|
+
className: "mt-3 text-xs text-muted-foreground underline-offset-2 hover:underline",
|
|
1377
|
+
children: "Trocar CEP"
|
|
1378
|
+
}
|
|
1379
|
+
)
|
|
1380
|
+
]
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1150
1384
|
function SmoothImage({
|
|
1151
1385
|
imageLoading,
|
|
1152
1386
|
loading = "lazy",
|
|
@@ -1164,9 +1398,9 @@ function SmoothImage({
|
|
|
1164
1398
|
const durationMs = resolved.durationMs ?? 420;
|
|
1165
1399
|
const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1166
1400
|
const blur = resolved.blur ?? true;
|
|
1167
|
-
const ref =
|
|
1168
|
-
const [loaded, setLoaded] =
|
|
1169
|
-
|
|
1401
|
+
const ref = React7.useRef(null);
|
|
1402
|
+
const [loaded, setLoaded] = React7.useState(mode === "none");
|
|
1403
|
+
React7.useEffect(() => {
|
|
1170
1404
|
if (mode === "none") {
|
|
1171
1405
|
setLoaded(true);
|
|
1172
1406
|
return;
|
|
@@ -1209,6 +1443,7 @@ function CartDrawer() {
|
|
|
1209
1443
|
document.addEventListener("keydown", onKey);
|
|
1210
1444
|
return () => document.removeEventListener("keydown", onKey);
|
|
1211
1445
|
}, [cart.isOpen, cart.closeDrawer]);
|
|
1446
|
+
const delivery = useDeliveryStore();
|
|
1212
1447
|
const subtotal = selectSubtotal(cart);
|
|
1213
1448
|
const discountTotal = selectDiscountTotal(cart);
|
|
1214
1449
|
const shipping = selectShipping(cart, config);
|
|
@@ -1365,6 +1600,7 @@ function CartDrawer() {
|
|
|
1365
1600
|
] }),
|
|
1366
1601
|
discountError && /* @__PURE__ */ jsx("p", { "data-testid": TID.discountError, className: "text-xs text-destructive", children: discountError })
|
|
1367
1602
|
] }),
|
|
1603
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(DeliveryEstimator, { compact: true }) }),
|
|
1368
1604
|
/* @__PURE__ */ jsxs("dl", { className: "space-y-1.5 text-sm", children: [
|
|
1369
1605
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1370
1606
|
/* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Subtotal" }),
|
|
@@ -1394,7 +1630,13 @@ function CartDrawer() {
|
|
|
1394
1630
|
] })
|
|
1395
1631
|
] }),
|
|
1396
1632
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1397
|
-
/* @__PURE__ */
|
|
1633
|
+
/* @__PURE__ */ jsxs("dt", { className: "text-muted-foreground", children: [
|
|
1634
|
+
"Frete",
|
|
1635
|
+
delivery.status === "served" && delivery.postalCode && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-xs", children: [
|
|
1636
|
+
"\xB7 ",
|
|
1637
|
+
formatPostalCode(delivery.postalCode)
|
|
1638
|
+
] })
|
|
1639
|
+
] }),
|
|
1398
1640
|
/* @__PURE__ */ jsx("dd", { "data-testid": TID.cartShipping, "data-price": shipping.toFixed(2), children: shipping === 0 ? "Gr\xE1tis" : money(shipping) })
|
|
1399
1641
|
] }),
|
|
1400
1642
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between border-t pt-2 text-base font-semibold", children: [
|
|
@@ -2737,6 +2979,128 @@ function ProductEnquiryForm({ product, onSuccess }) {
|
|
|
2737
2979
|
}
|
|
2738
2980
|
);
|
|
2739
2981
|
}
|
|
2982
|
+
function ProductGallery({ product, images, primaryImage }) {
|
|
2983
|
+
const ordered = orderImages(images, primaryImage);
|
|
2984
|
+
const [index, setIndex] = useState(0);
|
|
2985
|
+
const [zoom, setZoom] = useState(null);
|
|
2986
|
+
const frameRef = useRef(null);
|
|
2987
|
+
const canHover = useCanHover();
|
|
2988
|
+
useEffect(() => {
|
|
2989
|
+
setIndex(0);
|
|
2990
|
+
setZoom(null);
|
|
2991
|
+
}, [product.id]);
|
|
2992
|
+
const current = ordered[Math.min(index, ordered.length - 1)];
|
|
2993
|
+
function trackPointer(event) {
|
|
2994
|
+
if (!canHover) return;
|
|
2995
|
+
const frame = frameRef.current;
|
|
2996
|
+
if (!frame) return;
|
|
2997
|
+
const rect = frame.getBoundingClientRect();
|
|
2998
|
+
setZoom({
|
|
2999
|
+
x: (event.clientX - rect.left) / rect.width * 100,
|
|
3000
|
+
y: (event.clientY - rect.top) / rect.height * 100
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
function step(delta) {
|
|
3004
|
+
if (ordered.length < 2) return;
|
|
3005
|
+
setIndex((current2) => (current2 + delta + ordered.length) % ordered.length);
|
|
3006
|
+
setZoom(null);
|
|
3007
|
+
}
|
|
3008
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 self-start", children: [
|
|
3009
|
+
/* @__PURE__ */ jsxs(
|
|
3010
|
+
"div",
|
|
3011
|
+
{
|
|
3012
|
+
...storefrontComponentContracts.productDetail.gallery,
|
|
3013
|
+
ref: frameRef,
|
|
3014
|
+
className: "group relative aspect-square w-full overflow-hidden border bg-muted",
|
|
3015
|
+
style: { borderRadius: "var(--sf-radius-card)" },
|
|
3016
|
+
onMouseMove: trackPointer,
|
|
3017
|
+
onMouseLeave: () => setZoom(null),
|
|
3018
|
+
onKeyDown: (event) => {
|
|
3019
|
+
if (event.key === "ArrowRight") step(1);
|
|
3020
|
+
if (event.key === "ArrowLeft") step(-1);
|
|
3021
|
+
},
|
|
3022
|
+
tabIndex: ordered.length > 1 ? 0 : -1,
|
|
3023
|
+
role: ordered.length > 1 ? "group" : void 0,
|
|
3024
|
+
"aria-label": ordered.length > 1 ? `Imagens de ${product.name}` : void 0,
|
|
3025
|
+
children: [
|
|
3026
|
+
current && /* @__PURE__ */ jsx(
|
|
3027
|
+
"div",
|
|
3028
|
+
{
|
|
3029
|
+
className: "h-full w-full transition-transform duration-150 ease-out",
|
|
3030
|
+
style: zoom ? { transform: "scale(2)", transformOrigin: `${zoom.x}% ${zoom.y}%` } : { transform: "scale(1)" },
|
|
3031
|
+
children: /* @__PURE__ */ jsx(
|
|
3032
|
+
SmoothImage,
|
|
3033
|
+
{
|
|
3034
|
+
src: current.url,
|
|
3035
|
+
alt: current.altText ?? product.name,
|
|
3036
|
+
"data-testid": TID.pdpGalleryImage,
|
|
3037
|
+
className: "h-full w-full object-cover"
|
|
3038
|
+
},
|
|
3039
|
+
current.id
|
|
3040
|
+
)
|
|
3041
|
+
}
|
|
3042
|
+
),
|
|
3043
|
+
ordered.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3044
|
+
/* @__PURE__ */ jsx(GalleryArrow, { side: "left", onClick: () => step(-1) }),
|
|
3045
|
+
/* @__PURE__ */ jsx(GalleryArrow, { side: "right", onClick: () => step(1) }),
|
|
3046
|
+
/* @__PURE__ */ jsxs("span", { className: "pointer-events-none absolute bottom-3 right-3 rounded-full bg-black/60 px-2 py-0.5 text-[11px] font-medium text-white", children: [
|
|
3047
|
+
index + 1,
|
|
3048
|
+
"/",
|
|
3049
|
+
ordered.length
|
|
3050
|
+
] })
|
|
3051
|
+
] })
|
|
3052
|
+
]
|
|
3053
|
+
}
|
|
3054
|
+
),
|
|
3055
|
+
ordered.length > 1 && /* @__PURE__ */ jsx("ul", { "data-testid": TID.pdpGalleryThumbs, className: "flex gap-2 overflow-x-auto pb-1", children: ordered.map((image, position) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
3056
|
+
"button",
|
|
3057
|
+
{
|
|
3058
|
+
type: "button",
|
|
3059
|
+
"data-testid": TID.pdpGalleryThumb,
|
|
3060
|
+
"data-index": position,
|
|
3061
|
+
"aria-label": `Imagem ${position + 1} de ${ordered.length}`,
|
|
3062
|
+
"aria-current": position === index,
|
|
3063
|
+
onClick: () => {
|
|
3064
|
+
setIndex(position);
|
|
3065
|
+
setZoom(null);
|
|
3066
|
+
},
|
|
3067
|
+
className: `h-16 w-16 shrink-0 overflow-hidden rounded-lg border transition ${position === index ? "border-primary ring-2 ring-primary/20" : "border-border opacity-70 hover:opacity-100"}`,
|
|
3068
|
+
children: /* @__PURE__ */ jsx("img", { src: image.url, alt: "", className: "h-full w-full object-cover", loading: "lazy" })
|
|
3069
|
+
}
|
|
3070
|
+
) }, image.id)) })
|
|
3071
|
+
] });
|
|
3072
|
+
}
|
|
3073
|
+
function orderImages(images, primary) {
|
|
3074
|
+
const all = images.length > 0 ? images : primary ? [primary] : [];
|
|
3075
|
+
return [...all].sort((a, b) => {
|
|
3076
|
+
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
|
|
3077
|
+
return (a.sortOrder ?? 0) - (b.sortOrder ?? 0);
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
function useCanHover() {
|
|
3081
|
+
const [canHover, setCanHover] = useState(false);
|
|
3082
|
+
useEffect(() => {
|
|
3083
|
+
const query = window.matchMedia?.("(hover: hover) and (pointer: fine)");
|
|
3084
|
+
if (!query) return;
|
|
3085
|
+
setCanHover(query.matches);
|
|
3086
|
+
const onChange = (event) => setCanHover(event.matches);
|
|
3087
|
+
query.addEventListener?.("change", onChange);
|
|
3088
|
+
return () => query.removeEventListener?.("change", onChange);
|
|
3089
|
+
}, []);
|
|
3090
|
+
return canHover;
|
|
3091
|
+
}
|
|
3092
|
+
function GalleryArrow({ side, onClick }) {
|
|
3093
|
+
return /* @__PURE__ */ jsx(
|
|
3094
|
+
"button",
|
|
3095
|
+
{
|
|
3096
|
+
type: "button",
|
|
3097
|
+
onClick,
|
|
3098
|
+
"aria-label": side === "left" ? "Imagem anterior" : "Pr\xF3xima imagem",
|
|
3099
|
+
className: `absolute top-1/2 z-10 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-background/80 opacity-0 shadow-md backdrop-blur transition hover:bg-background focus:opacity-100 group-hover:opacity-100 ${side === "left" ? "left-3" : "right-3"}`,
|
|
3100
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-lg leading-none", children: side === "left" ? "\u2039" : "\u203A" })
|
|
3101
|
+
}
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
2740
3104
|
function ProductDetailPage({ slug: slug2 }) {
|
|
2741
3105
|
const config = useStorefrontConfig();
|
|
2742
3106
|
const actions = useStorefrontActions();
|
|
@@ -2777,6 +3141,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2777
3141
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
2778
3142
|
const components = getStorefrontComponents(config);
|
|
2779
3143
|
const ProductDetailComponent = components.ProductDetail;
|
|
3144
|
+
const GalleryComponent = components.ProductGallery ?? ProductGallery;
|
|
2780
3145
|
const addToCart = () => addItem(product, qty, selectedOptions);
|
|
2781
3146
|
const openEnquiry = () => {
|
|
2782
3147
|
document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
@@ -2810,19 +3175,13 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2810
3175
|
] }),
|
|
2811
3176
|
/* @__PURE__ */ jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
|
|
2812
3177
|
/* @__PURE__ */ jsx(
|
|
2813
|
-
|
|
3178
|
+
GalleryComponent,
|
|
2814
3179
|
{
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
{
|
|
2821
|
-
src: image.url,
|
|
2822
|
-
alt: image.altText ?? product.name,
|
|
2823
|
-
className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
2824
|
-
}
|
|
2825
|
-
)
|
|
3180
|
+
product,
|
|
3181
|
+
images: product.images,
|
|
3182
|
+
primaryImage: image,
|
|
3183
|
+
config,
|
|
3184
|
+
commerceMode: config.commerceMode
|
|
2826
3185
|
}
|
|
2827
3186
|
),
|
|
2828
3187
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
|
|
@@ -2920,6 +3279,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2920
3279
|
children: "Voltar ao cat\xE1logo"
|
|
2921
3280
|
}
|
|
2922
3281
|
) }),
|
|
3282
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsx(DeliveryEstimator, {}),
|
|
2923
3283
|
config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
|
|
2924
3284
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
|
|
2925
3285
|
/* @__PURE__ */ jsx(Truck, { className: "h-5 w-5 text-primary" }),
|
|
@@ -2945,9 +3305,10 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2945
3305
|
/* @__PURE__ */ jsx(RelatedProducts, { product })
|
|
2946
3306
|
] });
|
|
2947
3307
|
}
|
|
2948
|
-
function formatDeliveryNotes(address) {
|
|
3308
|
+
function formatDeliveryNotes(address, shippingAddress) {
|
|
3309
|
+
if (address?.notes?.trim()) return address.notes.trim();
|
|
3310
|
+
if (shippingAddress) return void 0;
|
|
2949
3311
|
if (!address) return void 0;
|
|
2950
|
-
if (address.notes?.trim()) return address.notes.trim();
|
|
2951
3312
|
const parts = [address.street, address.city, address.zip].map((part) => part?.trim()).filter(Boolean);
|
|
2952
3313
|
return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
|
|
2953
3314
|
}
|
|
@@ -2957,6 +3318,8 @@ async function placeStorefrontOrder({
|
|
|
2957
3318
|
session,
|
|
2958
3319
|
customer,
|
|
2959
3320
|
address,
|
|
3321
|
+
shippingAddress,
|
|
3322
|
+
paymentMethod,
|
|
2960
3323
|
markPaid = false
|
|
2961
3324
|
}) {
|
|
2962
3325
|
const email = customer.email.trim().toLowerCase();
|
|
@@ -2971,9 +3334,14 @@ async function placeStorefrontOrder({
|
|
|
2971
3334
|
customerId: customerId ?? void 0,
|
|
2972
3335
|
customer: { name, email },
|
|
2973
3336
|
currency: config.currency,
|
|
2974
|
-
notes: formatDeliveryNotes(address),
|
|
3337
|
+
notes: formatDeliveryNotes(address, shippingAddress),
|
|
2975
3338
|
discountCode: cart.discountCode ?? void 0,
|
|
2976
3339
|
shippingTotal: selectShipping(cart, config),
|
|
3340
|
+
// Structured address + method are what populate public.addresses and the
|
|
3341
|
+
// transactions ledger. `notes` stays for backwards compatibility with
|
|
3342
|
+
// checkouts that have not been updated yet.
|
|
3343
|
+
shippingAddress,
|
|
3344
|
+
paymentMethod,
|
|
2977
3345
|
items: cart.lines.map((line) => ({
|
|
2978
3346
|
productId: line.productId,
|
|
2979
3347
|
quantity: line.quantity,
|
|
@@ -3096,19 +3464,18 @@ function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
|
|
|
3096
3464
|
}
|
|
3097
3465
|
);
|
|
3098
3466
|
}
|
|
3099
|
-
var
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3467
|
+
var PAYMENT_LABELS = {
|
|
3468
|
+
pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
|
|
3469
|
+
credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
|
|
3470
|
+
debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
|
|
3471
|
+
boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
|
|
3472
|
+
cash: { label: "Dinheiro", hint: "Pagamento na entrega" },
|
|
3473
|
+
other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
|
|
3474
|
+
};
|
|
3105
3475
|
var PROCESSING_STEPS = [
|
|
3106
3476
|
{ icon: Lock, label: "Validando seus dados..." },
|
|
3107
|
-
{ icon:
|
|
3108
|
-
{ icon: PackageCheck, label: "Confirmando seu pedido..." }
|
|
3477
|
+
{ icon: PackageCheck, label: "Registrando seu pedido..." }
|
|
3109
3478
|
];
|
|
3110
|
-
var DEFAULT_ADDRESS = SAVED_ADDRESSES[0];
|
|
3111
|
-
var DEFAULT_PAYMENT = SAVED_PAYMENT_METHODS[0];
|
|
3112
3479
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3113
3480
|
function ProcessingOverlay({ step }) {
|
|
3114
3481
|
const current = PROCESSING_STEPS[Math.min(step, PROCESSING_STEPS.length - 1)];
|
|
@@ -3151,23 +3518,24 @@ function CheckoutPage() {
|
|
|
3151
3518
|
const config = useStorefrontConfig();
|
|
3152
3519
|
const cart = useCartStore();
|
|
3153
3520
|
const session = useSessionStore();
|
|
3521
|
+
const delivery = useDeliveryStore();
|
|
3154
3522
|
const validateDiscount2 = useDiscountValidator();
|
|
3155
3523
|
useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
|
|
3156
|
-
const
|
|
3157
|
-
const [
|
|
3158
|
-
const [savedAddresses, setSavedAddresses] = useState(
|
|
3159
|
-
const [
|
|
3160
|
-
const [
|
|
3161
|
-
const [paymentMode, setPaymentMode] = useState("saved");
|
|
3524
|
+
const paymentMethods = config.payments.methods;
|
|
3525
|
+
const [selectedAddressId, setSelectedAddressId] = useState("new");
|
|
3526
|
+
const [savedAddresses, setSavedAddresses] = useState([]);
|
|
3527
|
+
const [addressMode, setAddressMode] = useState("new");
|
|
3528
|
+
const [paymentMethod, setPaymentMethod] = useState(paymentMethods[0] ?? "pix");
|
|
3162
3529
|
const [form, setForm] = useState({
|
|
3163
3530
|
email: session.email ?? "",
|
|
3164
3531
|
name: session.name ?? "",
|
|
3165
|
-
street:
|
|
3166
|
-
city:
|
|
3167
|
-
zip:
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3532
|
+
street: "",
|
|
3533
|
+
city: "",
|
|
3534
|
+
zip: "",
|
|
3535
|
+
number: "",
|
|
3536
|
+
complement: "",
|
|
3537
|
+
district: "",
|
|
3538
|
+
state: ""
|
|
3171
3539
|
});
|
|
3172
3540
|
const [error, setError] = useState(null);
|
|
3173
3541
|
const [discountCode, setDiscountCode] = useState("");
|
|
@@ -3177,6 +3545,7 @@ function CheckoutPage() {
|
|
|
3177
3545
|
const [placing, setPlacing] = useState(false);
|
|
3178
3546
|
const [processingStep, setProcessingStep] = useState(0);
|
|
3179
3547
|
const [showSignin, setShowSignin] = useState(false);
|
|
3548
|
+
const [zipLookup, setZipLookup] = useState("idle");
|
|
3180
3549
|
const subtotal = selectSubtotal(cart);
|
|
3181
3550
|
const discountTotal = selectDiscountTotal(cart);
|
|
3182
3551
|
const shipping = selectShipping(cart, config);
|
|
@@ -3186,6 +3555,32 @@ function CheckoutPage() {
|
|
|
3186
3555
|
if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
|
|
3187
3556
|
}, [cart.lines.length, config.catalogPath, placing]);
|
|
3188
3557
|
const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
|
|
3558
|
+
async function setZip(value) {
|
|
3559
|
+
const masked = formatPostalCode(value);
|
|
3560
|
+
setForm((current) => ({ ...current, zip: masked }));
|
|
3561
|
+
if (normalizePostalCode(masked).length !== 8) {
|
|
3562
|
+
setZipLookup("idle");
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
setZipLookup("loading");
|
|
3566
|
+
try {
|
|
3567
|
+
const found = await lookupPostalCode(masked);
|
|
3568
|
+
if (!found) {
|
|
3569
|
+
setZipLookup("not-found");
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
setZipLookup("found");
|
|
3573
|
+
setForm((current) => ({
|
|
3574
|
+
...current,
|
|
3575
|
+
street: found.street || current.street,
|
|
3576
|
+
district: found.district || current.district,
|
|
3577
|
+
city: found.city,
|
|
3578
|
+
state: found.state
|
|
3579
|
+
}));
|
|
3580
|
+
} catch {
|
|
3581
|
+
setZipLookup("idle");
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3189
3584
|
async function applyDiscountCode() {
|
|
3190
3585
|
setApplyingDiscount(true);
|
|
3191
3586
|
setDiscountError(null);
|
|
@@ -3212,10 +3607,54 @@ function CheckoutPage() {
|
|
|
3212
3607
|
setDiscountSuccess(null);
|
|
3213
3608
|
setDiscountError(null);
|
|
3214
3609
|
}
|
|
3610
|
+
useEffect(() => {
|
|
3611
|
+
const customerId = session.customerId;
|
|
3612
|
+
if (!customerId) {
|
|
3613
|
+
setSavedAddresses([]);
|
|
3614
|
+
setAddressMode("new");
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
let cancelled = false;
|
|
3618
|
+
const provider = getShopProvider();
|
|
3619
|
+
void provider.listCustomerAddresses?.(customerId).then((addresses) => {
|
|
3620
|
+
if (cancelled || addresses.length === 0) return;
|
|
3621
|
+
setSavedAddresses(addresses);
|
|
3622
|
+
applySavedAddress(addresses[0]);
|
|
3623
|
+
}).catch(() => {
|
|
3624
|
+
});
|
|
3625
|
+
return () => {
|
|
3626
|
+
cancelled = true;
|
|
3627
|
+
};
|
|
3628
|
+
}, [session.customerId]);
|
|
3629
|
+
useEffect(() => {
|
|
3630
|
+
if (addressMode !== "new") return;
|
|
3631
|
+
const looked = delivery.address;
|
|
3632
|
+
if (!looked) return;
|
|
3633
|
+
setForm((current) => {
|
|
3634
|
+
if (current.zip.trim() || current.street.trim()) return current;
|
|
3635
|
+
return {
|
|
3636
|
+
...current,
|
|
3637
|
+
zip: formatPostalCode(looked.postalCode),
|
|
3638
|
+
street: looked.street,
|
|
3639
|
+
district: looked.district,
|
|
3640
|
+
city: looked.city,
|
|
3641
|
+
state: looked.state
|
|
3642
|
+
};
|
|
3643
|
+
});
|
|
3644
|
+
}, [delivery.address, addressMode]);
|
|
3215
3645
|
function applySavedAddress(address) {
|
|
3216
3646
|
setAddressMode("saved");
|
|
3217
3647
|
setSelectedAddressId(address.id);
|
|
3218
|
-
setForm((current) => ({
|
|
3648
|
+
setForm((current) => ({
|
|
3649
|
+
...current,
|
|
3650
|
+
street: address.street,
|
|
3651
|
+
city: address.city,
|
|
3652
|
+
zip: address.postalCode,
|
|
3653
|
+
number: address.number ?? "",
|
|
3654
|
+
complement: address.complement ?? "",
|
|
3655
|
+
district: address.district ?? "",
|
|
3656
|
+
state: address.state
|
|
3657
|
+
}));
|
|
3219
3658
|
}
|
|
3220
3659
|
function selectAddress(addressId) {
|
|
3221
3660
|
const address = savedAddresses.find((item) => item.id === addressId);
|
|
@@ -3224,37 +3663,7 @@ function CheckoutPage() {
|
|
|
3224
3663
|
function addNewAddress() {
|
|
3225
3664
|
setAddressMode("new");
|
|
3226
3665
|
setSelectedAddressId("new");
|
|
3227
|
-
setForm((current) => ({ ...current, street: "", city: "", zip: "" }));
|
|
3228
|
-
}
|
|
3229
|
-
function removeAddress(addressId) {
|
|
3230
|
-
const nextAddresses = savedAddresses.filter((item) => item.id !== addressId);
|
|
3231
|
-
setSavedAddresses(nextAddresses);
|
|
3232
|
-
if (selectedAddressId !== addressId) return;
|
|
3233
|
-
const fallback = nextAddresses[0];
|
|
3234
|
-
if (fallback) applySavedAddress(fallback);
|
|
3235
|
-
else addNewAddress();
|
|
3236
|
-
}
|
|
3237
|
-
function applySavedPayment(method) {
|
|
3238
|
-
setPaymentMode("saved");
|
|
3239
|
-
setSelectedPaymentId(method.id);
|
|
3240
|
-
setForm((current) => ({ ...current, card: method.card, expiry: method.expiry, cvc: method.cvc }));
|
|
3241
|
-
}
|
|
3242
|
-
function selectPayment(paymentId) {
|
|
3243
|
-
const method = savedPaymentMethods.find((item) => item.id === paymentId);
|
|
3244
|
-
if (method) applySavedPayment(method);
|
|
3245
|
-
}
|
|
3246
|
-
function addNewPayment() {
|
|
3247
|
-
setPaymentMode("new");
|
|
3248
|
-
setSelectedPaymentId("new");
|
|
3249
|
-
setForm((current) => ({ ...current, card: "", expiry: "", cvc: "" }));
|
|
3250
|
-
}
|
|
3251
|
-
function removePayment(paymentId) {
|
|
3252
|
-
const nextMethods = savedPaymentMethods.filter((item) => item.id !== paymentId);
|
|
3253
|
-
setSavedPaymentMethods(nextMethods);
|
|
3254
|
-
if (selectedPaymentId !== paymentId) return;
|
|
3255
|
-
const fallback = nextMethods[0];
|
|
3256
|
-
if (fallback) applySavedPayment(fallback);
|
|
3257
|
-
else addNewPayment();
|
|
3666
|
+
setForm((current) => ({ ...current, street: "", city: "", zip: "", number: "", complement: "", district: "", state: "" }));
|
|
3258
3667
|
}
|
|
3259
3668
|
function validate() {
|
|
3260
3669
|
setError(null);
|
|
@@ -3266,12 +3675,20 @@ function CheckoutPage() {
|
|
|
3266
3675
|
setError("Informe seu nome completo.");
|
|
3267
3676
|
return false;
|
|
3268
3677
|
}
|
|
3269
|
-
if (!form.street.trim() || !form.city.trim() || !form.zip.trim()) {
|
|
3678
|
+
if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
|
|
3270
3679
|
setError("Preencha ou selecione o endere\xE7o de entrega.");
|
|
3271
3680
|
return false;
|
|
3272
3681
|
}
|
|
3273
|
-
if (
|
|
3274
|
-
setError("
|
|
3682
|
+
if (!form.number.trim()) {
|
|
3683
|
+
setError("Falta o n\xFAmero do endere\xE7o.");
|
|
3684
|
+
return false;
|
|
3685
|
+
}
|
|
3686
|
+
if (form.state.trim().length !== 2) {
|
|
3687
|
+
setError("Informe a UF com duas letras (ex.: RJ).");
|
|
3688
|
+
return false;
|
|
3689
|
+
}
|
|
3690
|
+
if (delivery.status === "unserved" && delivery.postalCode === normalizePostalCode(form.zip)) {
|
|
3691
|
+
setError("Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.");
|
|
3275
3692
|
return false;
|
|
3276
3693
|
}
|
|
3277
3694
|
return true;
|
|
@@ -3293,9 +3710,22 @@ function CheckoutPage() {
|
|
|
3293
3710
|
session,
|
|
3294
3711
|
customer: { email: form.email, name: form.name },
|
|
3295
3712
|
address: { street: form.street, city: form.city, zip: form.zip },
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3713
|
+
shippingAddress: {
|
|
3714
|
+
postalCode: form.zip.trim(),
|
|
3715
|
+
street: form.street.trim(),
|
|
3716
|
+
number: form.number.trim() || void 0,
|
|
3717
|
+
complement: form.complement.trim() || void 0,
|
|
3718
|
+
district: form.district.trim() || void 0,
|
|
3719
|
+
city: form.city.trim(),
|
|
3720
|
+
state: form.state.trim().toUpperCase()
|
|
3721
|
+
},
|
|
3722
|
+
// The method the buyer actually chose, which opens the ledger row in
|
|
3723
|
+
// public.transactions with the right kind.
|
|
3724
|
+
paymentMethod
|
|
3725
|
+
// Never marked paid from the browser. It used to be, through an RPC that
|
|
3726
|
+
// was granted to anon — so any buyer holding their own order id could
|
|
3727
|
+
// declare it settled. The order is now born `pending` and only the
|
|
3728
|
+
// merchant (or a PSP webhook) can confirm the money arrived.
|
|
3299
3729
|
});
|
|
3300
3730
|
await minDuration;
|
|
3301
3731
|
cart.clear();
|
|
@@ -3368,35 +3798,25 @@ function CheckoutPage() {
|
|
|
3368
3798
|
/* @__PURE__ */ jsxs("section", { children: [
|
|
3369
3799
|
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Endere\xE7o de entrega" }),
|
|
3370
3800
|
/* @__PURE__ */ jsxs("div", { className: "mb-3 grid gap-3", children: [
|
|
3371
|
-
savedAddresses.map((address) => /* @__PURE__ */
|
|
3801
|
+
savedAddresses.map((address) => /* @__PURE__ */ jsx(
|
|
3372
3802
|
"div",
|
|
3373
3803
|
{
|
|
3374
3804
|
className: `group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${selectedAddressId === address.id ? "border-primary bg-primary/5" : "border-border bg-background"}`,
|
|
3375
|
-
children: [
|
|
3376
|
-
/* @__PURE__ */ jsxs("
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3380
|
-
] }),
|
|
3381
|
-
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: address.street }),
|
|
3382
|
-
/* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
|
|
3383
|
-
address.city,
|
|
3384
|
-
" - ",
|
|
3385
|
-
address.zip
|
|
3386
|
-
] })
|
|
3805
|
+
children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => selectAddress(address.id), className: "block w-full text-left", children: [
|
|
3806
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3807
|
+
address.label,
|
|
3808
|
+
addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3387
3809
|
] }),
|
|
3388
|
-
/* @__PURE__ */
|
|
3389
|
-
"
|
|
3390
|
-
{
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
)
|
|
3399
|
-
]
|
|
3810
|
+
/* @__PURE__ */ jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
|
|
3811
|
+
[address.street, address.number].filter(Boolean).join(", "),
|
|
3812
|
+
address.complement ? ` \u2014 ${address.complement}` : ""
|
|
3813
|
+
] }),
|
|
3814
|
+
/* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
|
|
3815
|
+
[address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
|
|
3816
|
+
" \u2014 ",
|
|
3817
|
+
address.postalCode
|
|
3818
|
+
] })
|
|
3819
|
+
] })
|
|
3400
3820
|
},
|
|
3401
3821
|
address.id
|
|
3402
3822
|
)),
|
|
@@ -3407,83 +3827,62 @@ function CheckoutPage() {
|
|
|
3407
3827
|
onClick: addNewAddress,
|
|
3408
3828
|
className: `rounded-lg border border-dashed p-3 text-left text-sm transition hover:bg-muted/40 ${addressMode === "new" ? "border-primary bg-primary/5" : "border-border bg-muted/20"}`,
|
|
3409
3829
|
children: [
|
|
3410
|
-
/* @__PURE__ */ jsx("span", { className: "block font-semibold text-primary", children: "+ Adicionar novo endere\xE7o" }),
|
|
3830
|
+
/* @__PURE__ */ jsx("span", { className: "block font-semibold text-primary", children: savedAddresses.length > 0 ? "+ Adicionar novo endere\xE7o" : "Informe o endere\xE7o de entrega" }),
|
|
3411
3831
|
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: "Preencher outro endere\xE7o para esta compra" })
|
|
3412
3832
|
]
|
|
3413
3833
|
}
|
|
3414
3834
|
)
|
|
3415
3835
|
] }),
|
|
3416
3836
|
addressMode === "new" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3837
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center", children: [
|
|
3838
|
+
field("CEP", TID.checkoutZip, form.zip, setZip, {
|
|
3839
|
+
placeholder: "CEP",
|
|
3840
|
+
inputMode: "numeric",
|
|
3841
|
+
autoComplete: "postal-code"
|
|
3842
|
+
}),
|
|
3843
|
+
/* @__PURE__ */ jsx("p", { className: "px-1 text-xs text-muted-foreground", children: zipLookup === "loading" ? "Buscando endere\xE7o\u2026" : zipLookup === "not-found" ? "CEP n\xE3o encontrado \u2014 preencha o endere\xE7o \xE0 m\xE3o." : "Preenchemos o endere\xE7o para voc\xEA." })
|
|
3844
|
+
] }),
|
|
3845
|
+
field("Endere\xE7o", TID.checkoutStreet, form.street, set("street"), { placeholder: "Rua, avenida\u2026" }),
|
|
3846
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
3847
|
+
field("N\xFAmero", "checkout-number", form.number, set("number"), { placeholder: "N\xFAmero" }),
|
|
3848
|
+
field("Complemento", "checkout-complement", form.complement, set("complement"), { placeholder: "Apto, bloco (opcional)" })
|
|
3849
|
+
] }),
|
|
3850
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
3851
|
+
field("Bairro", "checkout-district", form.district, set("district"), { placeholder: "Bairro" }),
|
|
3852
|
+
field("UF", "checkout-state", form.state, set("state"), { placeholder: "UF" })
|
|
3853
|
+
] }),
|
|
3854
|
+
field("Cidade", TID.checkoutCity, form.city, set("city"), { placeholder: "Cidade" })
|
|
3422
3855
|
] })
|
|
3423
3856
|
] }),
|
|
3424
3857
|
/* @__PURE__ */ jsxs("section", { children: [
|
|
3425
3858
|
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
|
|
3426
|
-
/* @__PURE__ */
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
className: `group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${selectedPaymentId === method.id ? "border-primary bg-primary/5" : "border-border bg-background"}`,
|
|
3431
|
-
children: [
|
|
3432
|
-
/* @__PURE__ */ jsxs("button", { type: "button", onClick: () => selectPayment(method.id), className: "block w-full text-left", children: [
|
|
3433
|
-
/* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3434
|
-
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3435
|
-
/* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
|
|
3436
|
-
method.label
|
|
3437
|
-
] }),
|
|
3438
|
-
paymentMode === "saved" && selectedPaymentId === method.id && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3439
|
-
] }),
|
|
3440
|
-
/* @__PURE__ */ jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
|
|
3441
|
-
"Expira em ",
|
|
3442
|
-
method.expiry
|
|
3443
|
-
] })
|
|
3444
|
-
] }),
|
|
3445
|
-
/* @__PURE__ */ jsx(
|
|
3446
|
-
"button",
|
|
3447
|
-
{
|
|
3448
|
-
type: "button",
|
|
3449
|
-
onClick: () => removePayment(method.id),
|
|
3450
|
-
className: "absolute right-3 top-3 inline-flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground opacity-0 transition hover:bg-destructive/10 hover:text-destructive focus:opacity-100 group-hover:opacity-100",
|
|
3451
|
-
"aria-label": `Remover cart\xE3o ${method.label}`,
|
|
3452
|
-
title: "Remover cart\xE3o",
|
|
3453
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
3454
|
-
}
|
|
3455
|
-
)
|
|
3456
|
-
]
|
|
3457
|
-
},
|
|
3458
|
-
method.id
|
|
3459
|
-
)),
|
|
3460
|
-
/* @__PURE__ */ jsxs(
|
|
3859
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
|
|
3860
|
+
const copy = PAYMENT_LABELS[method];
|
|
3861
|
+
const selected = paymentMethod === method;
|
|
3862
|
+
return /* @__PURE__ */ jsxs(
|
|
3461
3863
|
"button",
|
|
3462
3864
|
{
|
|
3463
3865
|
type: "button",
|
|
3464
|
-
|
|
3465
|
-
|
|
3866
|
+
role: "radio",
|
|
3867
|
+
"aria-checked": selected,
|
|
3868
|
+
"data-testid": `checkout-payment-${method}`,
|
|
3869
|
+
onClick: () => setPaymentMethod(method),
|
|
3870
|
+
className: `rounded-lg border p-3 text-left text-sm transition hover:bg-muted/40 ${selected ? "border-primary bg-primary/5" : "border-border bg-background"}`,
|
|
3466
3871
|
children: [
|
|
3467
|
-
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-
|
|
3468
|
-
/* @__PURE__ */
|
|
3469
|
-
|
|
3872
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3873
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3874
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
|
|
3875
|
+
copy.label
|
|
3876
|
+
] }),
|
|
3877
|
+
selected && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3470
3878
|
] }),
|
|
3471
|
-
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children:
|
|
3879
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: copy.hint })
|
|
3472
3880
|
]
|
|
3473
|
-
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
placeholder: "N\xFAmero do cart\xE3o",
|
|
3479
|
-
inputMode: "numeric"
|
|
3480
|
-
}),
|
|
3481
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
3482
|
-
field("Validade", TID.checkoutExpiry, form.expiry, set("expiry"), { placeholder: "MM/AA" }),
|
|
3483
|
-
field("CVC", TID.checkoutCvc, form.cvc, set("cvc"), { placeholder: "CVC", inputMode: "numeric" })
|
|
3484
|
-
] })
|
|
3485
|
-
] }),
|
|
3486
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Pagamento de demonstra\xE7\xE3o - nenhuma cobran\xE7a \xE9 feita." })
|
|
3881
|
+
},
|
|
3882
|
+
method
|
|
3883
|
+
);
|
|
3884
|
+
}) }),
|
|
3885
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Nenhuma cobran\xE7a \xE9 feita agora. Seu pedido \xE9 registrado e a loja confirma o pagamento." })
|
|
3487
3886
|
] }),
|
|
3488
3887
|
error && /* @__PURE__ */ jsx("p", { "data-testid": TID.checkoutError, className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error })
|
|
3489
3888
|
] }),
|
|
@@ -4307,7 +4706,7 @@ function PolicyPage({ kind }) {
|
|
|
4307
4706
|
] }) })
|
|
4308
4707
|
] });
|
|
4309
4708
|
}
|
|
4310
|
-
var StorefrontErrorBoundary = class extends
|
|
4709
|
+
var StorefrontErrorBoundary = class extends React7.Component {
|
|
4311
4710
|
constructor() {
|
|
4312
4711
|
super(...arguments);
|
|
4313
4712
|
this.state = { error: null };
|
|
@@ -4602,9 +5001,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
4602
5001
|
};
|
|
4603
5002
|
}
|
|
4604
5003
|
function StorefrontScaffold({ manifest, surface }) {
|
|
4605
|
-
const config =
|
|
4606
|
-
const resolved =
|
|
4607
|
-
const inited =
|
|
5004
|
+
const config = React7.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
|
|
5005
|
+
const resolved = React7.useMemo(() => resolveConfig(config), [config]);
|
|
5006
|
+
const inited = React7.useRef(false);
|
|
4608
5007
|
if (!inited.current) {
|
|
4609
5008
|
initStorefrontRuntime(config);
|
|
4610
5009
|
inited.current = true;
|