@fayz-ai/storefront 0.7.0 → 0.8.2
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/README.md +3 -1
- 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/components/StorefrontHeader.d.ts.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/createStorefrontApp.d.ts.map +1 -1
- package/dist/index.cjs +886 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +790 -275
- 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/presets.d.ts +5 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/stores/cart.store.d.ts +21 -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 +12 -0
- package/dist/testids.d.ts.map +1 -1
- package/dist/theme.d.ts +18 -2
- package/dist/theme.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 +13 -7
- 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/components/StorefrontHeader.tsx +35 -8
- package/src/config.ts +15 -3
- package/src/createStorefrontApp.tsx +18 -1
- package/src/index.ts +1 -0
- package/src/pages/CheckoutPage.tsx +240 -134
- package/src/pages/ProductDetailPage.tsx +17 -13
- package/src/presets.ts +66 -0
- package/src/stores/cart.store.ts +37 -1
- package/src/stores/delivery.store.ts +152 -0
- package/src/testids.ts +13 -0
- package/src/theme.ts +33 -3
- package/src/workflows/checkout.ts +34 -6
package/dist/index.cjs
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
var runtime = require('@fayz-ai/shop/runtime');
|
|
4
4
|
var mock = require('@fayz-ai/shop/mock');
|
|
5
5
|
var pluginAuth = require('@fayz-ai/plugin-auth');
|
|
6
|
+
var shop = require('@fayz-ai/sdk/shop');
|
|
6
7
|
var zustand = require('zustand');
|
|
7
8
|
var middleware = require('zustand/middleware');
|
|
8
|
-
var
|
|
9
|
+
var React7 = require('react');
|
|
9
10
|
var core = require('@fayz-ai/core');
|
|
10
11
|
var LucideIcons = require('lucide-react');
|
|
11
12
|
var jsxRuntime = require('react/jsx-runtime');
|
|
12
|
-
var shop = require('@fayz-ai/shop');
|
|
13
|
+
var shop$1 = require('@fayz-ai/shop');
|
|
13
14
|
|
|
14
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
|
|
@@ -31,7 +32,7 @@ function _interopNamespace(e) {
|
|
|
31
32
|
return Object.freeze(n);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
var
|
|
35
|
+
var React7__default = /*#__PURE__*/_interopDefault(React7);
|
|
35
36
|
var LucideIcons__namespace = /*#__PURE__*/_interopNamespace(LucideIcons);
|
|
36
37
|
|
|
37
38
|
// src/createStorefrontApp.tsx
|
|
@@ -76,10 +77,23 @@ function resolveAuthAdapter(configured, fallback) {
|
|
|
76
77
|
}
|
|
77
78
|
}).adapter;
|
|
78
79
|
}
|
|
80
|
+
var _accessToken = null;
|
|
81
|
+
var _hadAuthUser = false;
|
|
82
|
+
async function syncAccessToken(adapter2) {
|
|
83
|
+
try {
|
|
84
|
+
const existing = await adapter2.getSession();
|
|
85
|
+
_accessToken = existing?.session.accessToken ?? null;
|
|
86
|
+
} catch {
|
|
87
|
+
_accessToken = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
79
90
|
function initCustomerAuth(adapter2) {
|
|
80
91
|
_adapter = adapter2;
|
|
92
|
+
shop.setShopAccessTokenResolver(() => _accessToken);
|
|
81
93
|
adapter2.getSession().then((existing) => {
|
|
94
|
+
_accessToken = existing?.session.accessToken ?? null;
|
|
82
95
|
if (!existing) return;
|
|
96
|
+
_hadAuthUser = true;
|
|
83
97
|
const store = useSessionStore.getState();
|
|
84
98
|
if (store.email !== existing.user.email) {
|
|
85
99
|
void linkCustomer(existing.user);
|
|
@@ -87,7 +101,16 @@ function initCustomerAuth(adapter2) {
|
|
|
87
101
|
}).catch(() => {
|
|
88
102
|
});
|
|
89
103
|
adapter2.onAuthStateChange((user) => {
|
|
90
|
-
if (!user)
|
|
104
|
+
if (!user) {
|
|
105
|
+
_accessToken = null;
|
|
106
|
+
if (_hadAuthUser) {
|
|
107
|
+
_hadAuthUser = false;
|
|
108
|
+
useSessionStore.getState().signOut();
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
_hadAuthUser = true;
|
|
113
|
+
void syncAccessToken(adapter2);
|
|
91
114
|
});
|
|
92
115
|
}
|
|
93
116
|
function getCustomerAuthAdapter() {
|
|
@@ -123,6 +146,8 @@ async function establishCustomerSession(email, opts) {
|
|
|
123
146
|
if (opts?.password) {
|
|
124
147
|
const adapter2 = getCustomerAuthAdapter();
|
|
125
148
|
const session = await adapter2.signIn(normalized, opts.password);
|
|
149
|
+
_accessToken = session.accessToken ?? null;
|
|
150
|
+
_hadAuthUser = true;
|
|
126
151
|
return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, opts.name);
|
|
127
152
|
}
|
|
128
153
|
return linkCustomer({ email: normalized }, opts?.name);
|
|
@@ -134,12 +159,16 @@ async function signUpCustomer(email, password, name) {
|
|
|
134
159
|
const adapter2 = getCustomerAuthAdapter();
|
|
135
160
|
const normalized = email.trim().toLowerCase();
|
|
136
161
|
const session = await adapter2.signUp(normalized, password, name);
|
|
162
|
+
_accessToken = session.accessToken ?? null;
|
|
163
|
+
_hadAuthUser = true;
|
|
137
164
|
return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, name);
|
|
138
165
|
}
|
|
139
166
|
async function signOutCustomer() {
|
|
140
167
|
try {
|
|
141
168
|
await getCustomerAuthAdapter().signOut();
|
|
142
169
|
} finally {
|
|
170
|
+
_accessToken = null;
|
|
171
|
+
_hadAuthUser = false;
|
|
143
172
|
useSessionStore.getState().signOut();
|
|
144
173
|
}
|
|
145
174
|
}
|
|
@@ -152,7 +181,10 @@ function resolveConfig(config) {
|
|
|
152
181
|
currency: config.currency ?? "BRL",
|
|
153
182
|
locale: config.locale ?? "pt-BR",
|
|
154
183
|
shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
|
|
155
|
-
payments: {
|
|
184
|
+
payments: {
|
|
185
|
+
mode: config.payments?.mode ?? "mock",
|
|
186
|
+
methods: config.payments?.methods?.length ? config.payments.methods : ["pix", "credit_card", "cash"]
|
|
187
|
+
},
|
|
156
188
|
commerceMode,
|
|
157
189
|
enquiry: {
|
|
158
190
|
label: config.enquiry?.label ?? "Contact me",
|
|
@@ -184,15 +216,15 @@ function resolveConfig(config) {
|
|
|
184
216
|
] : [{ label: "Loja", to: "/" }])
|
|
185
217
|
};
|
|
186
218
|
}
|
|
187
|
-
var StorefrontConfigContext =
|
|
219
|
+
var StorefrontConfigContext = React7.createContext(null);
|
|
188
220
|
var StorefrontConfigProvider = StorefrontConfigContext.Provider;
|
|
189
221
|
function useStorefrontConfig() {
|
|
190
|
-
const ctx =
|
|
222
|
+
const ctx = React7.useContext(StorefrontConfigContext);
|
|
191
223
|
if (!ctx) throw new Error("useStorefrontConfig must be used inside createStorefrontApp");
|
|
192
224
|
return ctx;
|
|
193
225
|
}
|
|
194
226
|
function useStorefrontConfigOptional() {
|
|
195
|
-
return
|
|
227
|
+
return React7.useContext(StorefrontConfigContext);
|
|
196
228
|
}
|
|
197
229
|
function getStorefrontComponents(config) {
|
|
198
230
|
return config.components ?? {};
|
|
@@ -202,8 +234,8 @@ function navigateTo(to) {
|
|
|
202
234
|
adapter.navigate(to);
|
|
203
235
|
}
|
|
204
236
|
function useHashPath() {
|
|
205
|
-
const [path, setPath] =
|
|
206
|
-
|
|
237
|
+
const [path, setPath] = React7.useState(() => adapter.getCurrentPath() || "/");
|
|
238
|
+
React7.useEffect(() => {
|
|
207
239
|
const unsub = adapter.onPathChange((p) => {
|
|
208
240
|
setPath(p || "/");
|
|
209
241
|
window.scrollTo(0, 0);
|
|
@@ -227,93 +259,7 @@ function matchPath(pattern, path) {
|
|
|
227
259
|
return params;
|
|
228
260
|
}
|
|
229
261
|
function Link({ to, children, ...rest }) {
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
var RADIUS_MAP = {
|
|
233
|
-
none: { button: "0px", card: "0px", input: "0px" },
|
|
234
|
-
soft: { button: "6px", card: "10px", input: "6px" },
|
|
235
|
-
round: { button: "10px", card: "16px", input: "10px" },
|
|
236
|
-
pill: { button: "999px", card: "20px", input: "999px" }
|
|
237
|
-
};
|
|
238
|
-
var COLOR_VAR_MAP = {
|
|
239
|
-
background: "--background",
|
|
240
|
-
foreground: "--foreground",
|
|
241
|
-
primary: "--primary",
|
|
242
|
-
primaryForeground: "--primary-foreground",
|
|
243
|
-
card: "--card",
|
|
244
|
-
cardForeground: "--card-foreground",
|
|
245
|
-
muted: "--muted",
|
|
246
|
-
mutedForeground: "--muted-foreground",
|
|
247
|
-
border: "--border",
|
|
248
|
-
headerBackground: "--sf-header-bg",
|
|
249
|
-
headerForeground: "--sf-header-fg",
|
|
250
|
-
announcementBackground: "--sf-announcement-bg",
|
|
251
|
-
announcementForeground: "--sf-announcement-fg"
|
|
252
|
-
};
|
|
253
|
-
function themeToCss(theme) {
|
|
254
|
-
const lines = [];
|
|
255
|
-
for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
|
|
256
|
-
const value = theme.colors?.[key];
|
|
257
|
-
if (value) lines.push(`${cssVar}: ${value};`);
|
|
258
|
-
}
|
|
259
|
-
if (!theme.colors?.headerBackground && theme.colors?.background) {
|
|
260
|
-
lines.push(`--sf-header-bg: ${theme.colors.background};`);
|
|
261
|
-
}
|
|
262
|
-
if (!theme.colors?.headerForeground && theme.colors?.foreground) {
|
|
263
|
-
lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
|
|
264
|
-
}
|
|
265
|
-
if (theme.font) {
|
|
266
|
-
const fallback = theme.font.fallback ?? "sans-serif";
|
|
267
|
-
lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
|
|
268
|
-
lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
|
|
269
|
-
}
|
|
270
|
-
const radius = RADIUS_MAP[theme.radius ?? "soft"];
|
|
271
|
-
lines.push(`--button-radius: ${radius.button};`);
|
|
272
|
-
lines.push(`--card-radius: ${radius.card};`);
|
|
273
|
-
lines.push(`--input-radius: ${radius.input};`);
|
|
274
|
-
lines.push(`--sf-radius-button: ${radius.button};`);
|
|
275
|
-
lines.push(`--sf-radius-card: ${radius.card};`);
|
|
276
|
-
lines.push(`--sf-radius-input: ${radius.input};`);
|
|
277
|
-
let css = `:root {
|
|
278
|
-
${lines.join("\n ")}
|
|
279
|
-
}
|
|
280
|
-
`;
|
|
281
|
-
css += `body { font-family: var(--font-family); }
|
|
282
|
-
`;
|
|
283
|
-
css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
|
|
284
|
-
`;
|
|
285
|
-
if (theme.uppercaseButtons) {
|
|
286
|
-
css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
|
|
287
|
-
`;
|
|
288
|
-
}
|
|
289
|
-
if (theme.header?.uppercaseNav) {
|
|
290
|
-
css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
|
|
291
|
-
`;
|
|
292
|
-
}
|
|
293
|
-
return css;
|
|
294
|
-
}
|
|
295
|
-
function googleFontsHref(theme) {
|
|
296
|
-
if (!theme.font) return null;
|
|
297
|
-
const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body];
|
|
298
|
-
const unique = [...new Set(families)];
|
|
299
|
-
const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
|
|
300
|
-
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
301
|
-
}
|
|
302
|
-
function StorefrontThemeStyle({ theme }) {
|
|
303
|
-
const fontsHref = googleFontsHref(theme);
|
|
304
|
-
React6.useEffect(() => {
|
|
305
|
-
if (!fontsHref) return;
|
|
306
|
-
const id = "sf-theme-fonts";
|
|
307
|
-
let link = document.getElementById(id);
|
|
308
|
-
if (!link) {
|
|
309
|
-
link = document.createElement("link");
|
|
310
|
-
link.id = id;
|
|
311
|
-
link.rel = "stylesheet";
|
|
312
|
-
document.head.appendChild(link);
|
|
313
|
-
}
|
|
314
|
-
link.href = fontsHref;
|
|
315
|
-
}, [fontsHref]);
|
|
316
|
-
return React6__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
262
|
+
return React7__default.default.createElement("a", { href: `#${to}`, ...rest }, children);
|
|
317
263
|
}
|
|
318
264
|
|
|
319
265
|
// src/format.ts
|
|
@@ -323,6 +269,87 @@ function formatMoney(value, currency = "BRL", locale = "pt-BR") {
|
|
|
323
269
|
function roundCents(value) {
|
|
324
270
|
return Math.round(value * 100) / 100;
|
|
325
271
|
}
|
|
272
|
+
var EMPTY = {
|
|
273
|
+
postalCode: "",
|
|
274
|
+
address: null,
|
|
275
|
+
options: [],
|
|
276
|
+
selectedZoneId: null,
|
|
277
|
+
status: "idle",
|
|
278
|
+
error: null
|
|
279
|
+
};
|
|
280
|
+
var useDeliveryStore = zustand.create()(
|
|
281
|
+
middleware.persist(
|
|
282
|
+
(set, get) => ({
|
|
283
|
+
...EMPTY,
|
|
284
|
+
async resolve(postalCode, subtotal) {
|
|
285
|
+
const code = core.normalizePostalCode(postalCode);
|
|
286
|
+
if (code.length !== 8) {
|
|
287
|
+
set({ ...EMPTY, postalCode, status: "error", error: "Digite os 8 d\xEDgitos do CEP." });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
set({ postalCode: code, status: "loading", error: null });
|
|
291
|
+
let options = [];
|
|
292
|
+
try {
|
|
293
|
+
options = await runtime.getShopProvider().quoteShipping?.(code, subtotal) ?? [];
|
|
294
|
+
} catch {
|
|
295
|
+
set({
|
|
296
|
+
...EMPTY,
|
|
297
|
+
postalCode: code,
|
|
298
|
+
status: "error",
|
|
299
|
+
error: "N\xE3o foi poss\xEDvel calcular o frete agora. Tente de novo."
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
let address = null;
|
|
304
|
+
let notFound = false;
|
|
305
|
+
try {
|
|
306
|
+
address = await core.lookupPostalCode(code);
|
|
307
|
+
notFound = address === null;
|
|
308
|
+
} catch {
|
|
309
|
+
address = null;
|
|
310
|
+
}
|
|
311
|
+
set({
|
|
312
|
+
postalCode: code,
|
|
313
|
+
address,
|
|
314
|
+
options,
|
|
315
|
+
selectedZoneId: options[0]?.zoneId ?? null,
|
|
316
|
+
// A store with no zones configured quotes nothing, and that must NOT
|
|
317
|
+
// read as "we don't deliver here" — it falls back to the store-wide
|
|
318
|
+
// flat rate, which the cart already knows how to show.
|
|
319
|
+
status: options.length > 0 ? "served" : "unserved",
|
|
320
|
+
// Reported alongside the price, not instead of it: the CEP may be
|
|
321
|
+
// mistyped even though its range is covered.
|
|
322
|
+
error: notFound ? "CEP n\xE3o encontrado. Confira os n\xFAmeros." : null
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
selectZone(zoneId) {
|
|
326
|
+
if (get().options.some((option) => option.zoneId === zoneId)) set({ selectedZoneId: zoneId });
|
|
327
|
+
},
|
|
328
|
+
clear() {
|
|
329
|
+
set({ ...EMPTY });
|
|
330
|
+
}
|
|
331
|
+
}),
|
|
332
|
+
{
|
|
333
|
+
name: "fayz.storefront.delivery.v1",
|
|
334
|
+
// Deliberately NOT persisting `options`: a rehydrated
|
|
335
|
+
// price would be shown as current without anything having quoted it. The
|
|
336
|
+
// address survives, the money is asked for again.
|
|
337
|
+
partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
|
|
338
|
+
onRehydrateStorage: () => (state) => {
|
|
339
|
+
if (state?.address) state.status = "idle";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
);
|
|
344
|
+
function resolveOptionRate(option, subtotal) {
|
|
345
|
+
if (option.freeAbove != null && subtotal >= option.freeAbove) return 0;
|
|
346
|
+
return option.baseRate;
|
|
347
|
+
}
|
|
348
|
+
var selectQuotedShipping = (state, subtotal) => {
|
|
349
|
+
if (state.status !== "served") return null;
|
|
350
|
+
const option = state.options.find((o) => o.zoneId === state.selectedZoneId) ?? state.options[0];
|
|
351
|
+
return option ? resolveOptionRate(option, subtotal) : null;
|
|
352
|
+
};
|
|
326
353
|
|
|
327
354
|
// src/product-options.ts
|
|
328
355
|
function cleanValues(value) {
|
|
@@ -367,7 +394,7 @@ function productOptionSelectionKey(selection) {
|
|
|
367
394
|
// src/stores/cart.store.ts
|
|
368
395
|
var useCartStore = zustand.create()(
|
|
369
396
|
middleware.persist(
|
|
370
|
-
(set) => ({
|
|
397
|
+
(set, get) => ({
|
|
371
398
|
lines: [],
|
|
372
399
|
discountCode: null,
|
|
373
400
|
discountPercent: 0,
|
|
@@ -419,7 +446,16 @@ var useCartStore = zustand.create()(
|
|
|
419
446
|
clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
|
|
420
447
|
clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
|
|
421
448
|
openDrawer: () => set({ isOpen: true }),
|
|
422
|
-
closeDrawer: () => set({ isOpen: false, justAddedLineId: null })
|
|
449
|
+
closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
|
|
450
|
+
reconcile: async (resolve) => {
|
|
451
|
+
const lines = get().lines;
|
|
452
|
+
if (lines.length === 0) return;
|
|
453
|
+
const keep = await Promise.all(
|
|
454
|
+
lines.map((line) => resolve(line).catch(() => true))
|
|
455
|
+
);
|
|
456
|
+
const kept = lines.filter((_, i) => keep[i]);
|
|
457
|
+
if (kept.length !== lines.length) set({ lines: kept });
|
|
458
|
+
}
|
|
423
459
|
}),
|
|
424
460
|
{
|
|
425
461
|
name: "fayz.storefront.cart.v1",
|
|
@@ -437,10 +473,108 @@ var selectDiscountTotal = (s) => roundCents(selectSubtotal(s) * (s.discountPerce
|
|
|
437
473
|
var selectShipping = (s, cfg) => {
|
|
438
474
|
if (s.lines.length === 0) return 0;
|
|
439
475
|
const subtotal = selectSubtotal(s);
|
|
476
|
+
const quoted = selectQuotedShipping(useDeliveryStore.getState(), subtotal);
|
|
477
|
+
if (quoted != null) return quoted;
|
|
440
478
|
if (cfg.shipping.freeAbove != null && subtotal >= cfg.shipping.freeAbove) return 0;
|
|
441
479
|
return cfg.shipping.flatRate;
|
|
442
480
|
};
|
|
443
481
|
var selectTotal = (s, cfg) => roundCents(selectSubtotal(s) - selectDiscountTotal(s) + selectShipping(s, cfg));
|
|
482
|
+
var RADIUS_MAP = {
|
|
483
|
+
none: { button: "0px", card: "0px", input: "0px" },
|
|
484
|
+
soft: { button: "6px", card: "10px", input: "6px" },
|
|
485
|
+
round: { button: "10px", card: "16px", input: "10px" },
|
|
486
|
+
pill: { button: "999px", card: "20px", input: "999px" }
|
|
487
|
+
};
|
|
488
|
+
var COLOR_VAR_MAP = {
|
|
489
|
+
background: "--background",
|
|
490
|
+
foreground: "--foreground",
|
|
491
|
+
primary: "--primary",
|
|
492
|
+
primaryForeground: "--primary-foreground",
|
|
493
|
+
card: "--card",
|
|
494
|
+
cardForeground: "--card-foreground",
|
|
495
|
+
muted: "--muted",
|
|
496
|
+
mutedForeground: "--muted-foreground",
|
|
497
|
+
border: "--border",
|
|
498
|
+
headerBackground: "--sf-header-bg",
|
|
499
|
+
headerForeground: "--sf-header-fg",
|
|
500
|
+
announcementBackground: "--sf-announcement-bg",
|
|
501
|
+
announcementForeground: "--sf-announcement-fg",
|
|
502
|
+
accent: "--sf-accent",
|
|
503
|
+
accentForeground: "--sf-accent-foreground"
|
|
504
|
+
};
|
|
505
|
+
function themeToCss(theme) {
|
|
506
|
+
const lines = [];
|
|
507
|
+
for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
|
|
508
|
+
const value = theme.colors?.[key];
|
|
509
|
+
if (value) lines.push(`${cssVar}: ${value};`);
|
|
510
|
+
}
|
|
511
|
+
if (!theme.colors?.headerBackground && theme.colors?.background) {
|
|
512
|
+
lines.push(`--sf-header-bg: ${theme.colors.background};`);
|
|
513
|
+
}
|
|
514
|
+
if (!theme.colors?.headerForeground && theme.colors?.foreground) {
|
|
515
|
+
lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
|
|
516
|
+
}
|
|
517
|
+
if (theme.font) {
|
|
518
|
+
const fallback = theme.font.fallback ?? "sans-serif";
|
|
519
|
+
lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
|
|
520
|
+
lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
|
|
521
|
+
if (theme.font.display) lines.push(`--sf-font-display: '${theme.font.display}', ${fallback};`);
|
|
522
|
+
if (theme.font.serif) lines.push(`--sf-font-serif: '${theme.font.serif}', serif;`);
|
|
523
|
+
}
|
|
524
|
+
for (const [key, value] of Object.entries(theme.tokens ?? {})) {
|
|
525
|
+
const name = key.startsWith("--") ? key : `--${key}`;
|
|
526
|
+
lines.push(`${name}: ${value};`);
|
|
527
|
+
}
|
|
528
|
+
const radius = RADIUS_MAP[theme.radius ?? "soft"];
|
|
529
|
+
lines.push(`--button-radius: ${radius.button};`);
|
|
530
|
+
lines.push(`--card-radius: ${radius.card};`);
|
|
531
|
+
lines.push(`--input-radius: ${radius.input};`);
|
|
532
|
+
lines.push(`--sf-radius-button: ${radius.button};`);
|
|
533
|
+
lines.push(`--sf-radius-card: ${radius.card};`);
|
|
534
|
+
lines.push(`--sf-radius-input: ${radius.input};`);
|
|
535
|
+
let css = `:root {
|
|
536
|
+
${lines.join("\n ")}
|
|
537
|
+
}
|
|
538
|
+
`;
|
|
539
|
+
css += `body { font-family: var(--font-family); }
|
|
540
|
+
`;
|
|
541
|
+
css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
|
|
542
|
+
`;
|
|
543
|
+
if (theme.uppercaseButtons) {
|
|
544
|
+
css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
|
|
545
|
+
`;
|
|
546
|
+
}
|
|
547
|
+
if (theme.header?.uppercaseNav) {
|
|
548
|
+
css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
return css;
|
|
552
|
+
}
|
|
553
|
+
function googleFontsHref(theme) {
|
|
554
|
+
if (!theme.font) return null;
|
|
555
|
+
const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body, theme.font.display, theme.font.serif].filter(
|
|
556
|
+
(f) => Boolean(f)
|
|
557
|
+
);
|
|
558
|
+
const unique = [...new Set(families)];
|
|
559
|
+
const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
|
|
560
|
+
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
561
|
+
}
|
|
562
|
+
function StorefrontThemeStyle({ theme }) {
|
|
563
|
+
const fontsHref = googleFontsHref(theme);
|
|
564
|
+
React7.useEffect(() => {
|
|
565
|
+
if (!fontsHref) return;
|
|
566
|
+
const id = "sf-theme-fonts";
|
|
567
|
+
let link = document.getElementById(id);
|
|
568
|
+
if (!link) {
|
|
569
|
+
link = document.createElement("link");
|
|
570
|
+
link.id = id;
|
|
571
|
+
link.rel = "stylesheet";
|
|
572
|
+
document.head.appendChild(link);
|
|
573
|
+
}
|
|
574
|
+
link.href = fontsHref;
|
|
575
|
+
}, [fontsHref]);
|
|
576
|
+
return React7__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
577
|
+
}
|
|
444
578
|
var initial = {
|
|
445
579
|
search: "",
|
|
446
580
|
categoryId: null,
|
|
@@ -460,9 +594,9 @@ var useCatalogStore = zustand.create()((set) => ({
|
|
|
460
594
|
reset: () => set(initial)
|
|
461
595
|
}));
|
|
462
596
|
function useCategories() {
|
|
463
|
-
const [categories, setCategories] =
|
|
464
|
-
const [loading, setLoading] =
|
|
465
|
-
|
|
597
|
+
const [categories, setCategories] = React7.useState([]);
|
|
598
|
+
const [loading, setLoading] = React7.useState(true);
|
|
599
|
+
React7.useEffect(() => {
|
|
466
600
|
let cancelled = false;
|
|
467
601
|
runtime.getShopProvider().listCategories().then((data) => {
|
|
468
602
|
if (!cancelled) setCategories(data);
|
|
@@ -481,9 +615,9 @@ function prefersReducedMotion() {
|
|
|
481
615
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
482
616
|
}
|
|
483
617
|
function useInView(margin = "0px 0px -8% 0px") {
|
|
484
|
-
const ref =
|
|
485
|
-
const [inView, setInView] =
|
|
486
|
-
|
|
618
|
+
const ref = React7.useRef(null);
|
|
619
|
+
const [inView, setInView] = React7.useState(() => prefersReducedMotion());
|
|
620
|
+
React7.useEffect(() => {
|
|
487
621
|
if (inView) return;
|
|
488
622
|
const el = ref.current;
|
|
489
623
|
if (!el || typeof IntersectionObserver === "undefined") {
|
|
@@ -518,8 +652,8 @@ function Reveal({ delay = 0, children, className, style, ...rest }) {
|
|
|
518
652
|
);
|
|
519
653
|
}
|
|
520
654
|
function useScrolled(threshold = 8) {
|
|
521
|
-
const [scrolled, setScrolled] =
|
|
522
|
-
|
|
655
|
+
const [scrolled, setScrolled] = React7.useState(false);
|
|
656
|
+
React7.useEffect(() => {
|
|
523
657
|
const onScroll = () => setScrolled(window.scrollY > threshold);
|
|
524
658
|
onScroll();
|
|
525
659
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
@@ -528,9 +662,9 @@ function useScrolled(threshold = 8) {
|
|
|
528
662
|
return scrolled;
|
|
529
663
|
}
|
|
530
664
|
function usePopOnChange(value) {
|
|
531
|
-
const [pop, setPop] =
|
|
532
|
-
const prev =
|
|
533
|
-
|
|
665
|
+
const [pop, setPop] = React7.useState(false);
|
|
666
|
+
const prev = React7.useRef(value);
|
|
667
|
+
React7.useEffect(() => {
|
|
534
668
|
if (prev.current !== value && value > 0) {
|
|
535
669
|
setPop(true);
|
|
536
670
|
const t = setTimeout(() => setPop(false), 400);
|
|
@@ -539,7 +673,7 @@ function usePopOnChange(value) {
|
|
|
539
673
|
prev.current = value;
|
|
540
674
|
return void 0;
|
|
541
675
|
}, [value]);
|
|
542
|
-
|
|
676
|
+
React7.useEffect(() => {
|
|
543
677
|
prev.current = value;
|
|
544
678
|
});
|
|
545
679
|
return pop;
|
|
@@ -549,6 +683,7 @@ function usePopOnChange(value) {
|
|
|
549
683
|
var TID = {
|
|
550
684
|
// header
|
|
551
685
|
headerSearch: "header-search",
|
|
686
|
+
headerSearchToggle: "header-search-toggle",
|
|
552
687
|
cartButton: "cart-button",
|
|
553
688
|
cartCount: "cart-count",
|
|
554
689
|
accountLink: "account-link",
|
|
@@ -604,6 +739,9 @@ var TID = {
|
|
|
604
739
|
pdpAddToCart: "pdp-add-to-cart",
|
|
605
740
|
pdpRoot: "pdp-root",
|
|
606
741
|
pdpGallery: "pdp-gallery",
|
|
742
|
+
pdpGalleryImage: "pdp-gallery-image",
|
|
743
|
+
pdpGalleryThumbs: "pdp-gallery-thumbs",
|
|
744
|
+
pdpGalleryThumb: "pdp-gallery-thumb",
|
|
607
745
|
pdpActions: "pdp-actions",
|
|
608
746
|
pdpEnquiryButton: "pdp-enquiry-button",
|
|
609
747
|
enquiryForm: "enquiry-form",
|
|
@@ -632,6 +770,15 @@ var TID = {
|
|
|
632
770
|
cartTotal: "cart-total",
|
|
633
771
|
goCheckout: "go-checkout",
|
|
634
772
|
cartEmpty: "cart-empty",
|
|
773
|
+
// delivery estimate (product page + cart)
|
|
774
|
+
deliveryEstimator: "delivery-estimator",
|
|
775
|
+
deliveryCepInput: "delivery-cep-input",
|
|
776
|
+
deliveryCepSubmit: "delivery-cep-submit",
|
|
777
|
+
deliveryOption: "delivery-option",
|
|
778
|
+
deliveryUnserved: "delivery-unserved",
|
|
779
|
+
deliveryError: "delivery-error",
|
|
780
|
+
deliveryAddress: "delivery-address",
|
|
781
|
+
deliveryClear: "delivery-clear",
|
|
635
782
|
// checkout
|
|
636
783
|
checkoutEmail: "checkout-email",
|
|
637
784
|
checkoutName: "checkout-name",
|
|
@@ -678,7 +825,7 @@ var headerDivider = {
|
|
|
678
825
|
var ANNOUNCEMENT_KEY = "fayz.storefront.announcement.dismissed";
|
|
679
826
|
function AnnouncementBar() {
|
|
680
827
|
const config = useStorefrontConfig();
|
|
681
|
-
const [dismissed, setDismissed] =
|
|
828
|
+
const [dismissed, setDismissed] = React7.useState(() => {
|
|
682
829
|
try {
|
|
683
830
|
return localStorage.getItem(ANNOUNCEMENT_KEY) === config.announcement;
|
|
684
831
|
} catch {
|
|
@@ -737,12 +884,26 @@ function UtilityBar() {
|
|
|
737
884
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto flex items-center gap-4", children: social?.map((s) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: s.href, target: "_blank", rel: "noreferrer", className: "hover:underline", children: s.label }, s.href)) })
|
|
738
885
|
] }) });
|
|
739
886
|
}
|
|
740
|
-
function SearchInput({ className }) {
|
|
887
|
+
function SearchInput({ className, iconOnly = false }) {
|
|
741
888
|
const config = useStorefrontConfig();
|
|
742
889
|
const search = useCatalogStore((s) => s.search);
|
|
743
890
|
const setSearch = useCatalogStore((s) => s.setSearch);
|
|
744
891
|
const path = useHashPath();
|
|
745
|
-
|
|
892
|
+
const [open, setOpen] = React7.useState(false);
|
|
893
|
+
if (iconOnly && !open) {
|
|
894
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
895
|
+
"button",
|
|
896
|
+
{
|
|
897
|
+
type: "button",
|
|
898
|
+
"aria-label": "Buscar produtos",
|
|
899
|
+
"data-testid": TID.headerSearchToggle,
|
|
900
|
+
onClick: () => setOpen(true),
|
|
901
|
+
className: "rounded-full p-2.5 transition-opacity hover:opacity-70",
|
|
902
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "h-5 w-5" })
|
|
903
|
+
}
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${iconOnly ? "w-full max-w-xs" : className ?? ""}`, children: [
|
|
746
907
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 opacity-60" }),
|
|
747
908
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
748
909
|
"input",
|
|
@@ -751,10 +912,17 @@ function SearchInput({ className }) {
|
|
|
751
912
|
type: "search",
|
|
752
913
|
placeholder: "Buscar produtos...",
|
|
753
914
|
value: search,
|
|
915
|
+
autoFocus: iconOnly,
|
|
754
916
|
onChange: (e) => {
|
|
755
917
|
setSearch(e.target.value);
|
|
756
918
|
if (path !== config.catalogPath) navigateTo(config.catalogPath);
|
|
757
919
|
},
|
|
920
|
+
onKeyDown: (e) => {
|
|
921
|
+
if (e.key === "Escape" && iconOnly) setOpen(false);
|
|
922
|
+
},
|
|
923
|
+
onBlur: () => {
|
|
924
|
+
if (iconOnly && !search) setOpen(false);
|
|
925
|
+
},
|
|
758
926
|
className: "w-full border bg-white/10 py-2 pl-9 pr-4 text-sm outline-none transition-colors placeholder:opacity-60 focus:border-primary focus:bg-white focus:text-gray-900",
|
|
759
927
|
style: {
|
|
760
928
|
borderRadius: "var(--sf-radius-input)",
|
|
@@ -769,7 +937,7 @@ function HeaderActions() {
|
|
|
769
937
|
const count = useCartStore(selectCount);
|
|
770
938
|
const openDrawer = useCartStore((s) => s.openDrawer);
|
|
771
939
|
const session = useSessionStore();
|
|
772
|
-
const [accountOpen, setAccountOpen] =
|
|
940
|
+
const [accountOpen, setAccountOpen] = React7.useState(false);
|
|
773
941
|
const pop = usePopOnChange(count);
|
|
774
942
|
const firstName = session.name?.split(/\s+/)[0];
|
|
775
943
|
const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
|
|
@@ -907,6 +1075,7 @@ function StorefrontHeader() {
|
|
|
907
1075
|
const variant = config.theme?.header?.variant ?? "classic";
|
|
908
1076
|
const scrolled = useScrolled();
|
|
909
1077
|
const showSearch = config.theme?.header?.showSearch !== false;
|
|
1078
|
+
const iconSearch = config.theme?.header?.searchStyle === "icon";
|
|
910
1079
|
const logo = /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
|
|
911
1080
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
912
1081
|
"header",
|
|
@@ -920,7 +1089,7 @@ function StorefrontHeader() {
|
|
|
920
1089
|
// Rio/Flex pattern: centered logo row, nav row below
|
|
921
1090
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
922
1091
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6", children: [
|
|
923
|
-
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
1092
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
924
1093
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
925
1094
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
926
1095
|
] }),
|
|
@@ -930,7 +1099,7 @@ function StorefrontHeader() {
|
|
|
930
1099
|
// Brasília pattern: prominent search left, logo center, actions right; nav row below
|
|
931
1100
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
932
1101
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center gap-6 px-4 sm:px-6", children: [
|
|
933
|
-
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
1102
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
934
1103
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
935
1104
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
936
1105
|
] }),
|
|
@@ -942,17 +1111,19 @@ function StorefrontHeader() {
|
|
|
942
1111
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden sm:flex" }) }),
|
|
943
1112
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
944
1113
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3", children: [
|
|
945
|
-
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-44 lg:block" }),
|
|
1114
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-44 lg:block" }),
|
|
946
1115
|
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
947
1116
|
] })
|
|
948
1117
|
] })
|
|
949
1118
|
) : (
|
|
950
|
-
// classic: logo left,
|
|
1119
|
+
// classic: logo left, nav, then a right-aligned search + actions cluster
|
|
951
1120
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
|
|
952
1121
|
logo,
|
|
953
1122
|
/* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden md:flex" }),
|
|
954
|
-
|
|
955
|
-
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-2 sm:gap-3", children: [
|
|
1124
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: iconSearch ? "" : "hidden w-full max-w-sm sm:block" }),
|
|
1125
|
+
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
1126
|
+
] })
|
|
956
1127
|
] })
|
|
957
1128
|
)
|
|
958
1129
|
]
|
|
@@ -1045,12 +1216,12 @@ var REASON_MESSAGE = {
|
|
|
1045
1216
|
};
|
|
1046
1217
|
function useDiscountValidator() {
|
|
1047
1218
|
const config = useStorefrontConfig();
|
|
1048
|
-
return
|
|
1219
|
+
return React7.useCallback(async (code) => {
|
|
1049
1220
|
const normalized = code.trim().toUpperCase();
|
|
1050
1221
|
if (!normalized) return { valid: false, percent: 0, message: "Informe um cupom." };
|
|
1051
1222
|
const configDiscount = config.discounts?.find((d) => d.code.trim().toUpperCase() === normalized);
|
|
1052
1223
|
if (configDiscount) return { valid: true, percent: configDiscount.percent };
|
|
1053
|
-
const validation = await shop.validateDiscount({ code: normalized });
|
|
1224
|
+
const validation = await shop$1.validateDiscount({ code: normalized });
|
|
1054
1225
|
if (!validation.valid) {
|
|
1055
1226
|
return { valid: false, percent: 0, message: REASON_MESSAGE[validation.reason ?? "not_found"] ?? "Cupom inv\xE1lido ou expirado." };
|
|
1056
1227
|
}
|
|
@@ -1126,6 +1297,113 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
|
|
|
1126
1297
|
)
|
|
1127
1298
|
] });
|
|
1128
1299
|
}
|
|
1300
|
+
function DeliveryEstimator({ compact = false }) {
|
|
1301
|
+
const config = useStorefrontConfig();
|
|
1302
|
+
const cart = useCartStore();
|
|
1303
|
+
const delivery = useDeliveryStore();
|
|
1304
|
+
const [draft, setDraft] = React7.useState(() => core.formatPostalCode(delivery.postalCode));
|
|
1305
|
+
const subtotal = selectSubtotal(cart);
|
|
1306
|
+
const money = (value) => formatMoney(value, config.currency, config.locale);
|
|
1307
|
+
const loading = delivery.status === "loading";
|
|
1308
|
+
async function submit(event) {
|
|
1309
|
+
event.preventDefault();
|
|
1310
|
+
await delivery.resolve(draft, subtotal);
|
|
1311
|
+
}
|
|
1312
|
+
function eta(option) {
|
|
1313
|
+
const { etaMinDays: min, etaMaxDays: max } = option;
|
|
1314
|
+
if (min == null && max == null) return null;
|
|
1315
|
+
if (min === 0 && (max === 0 || max === 1)) return "hoje ou amanh\xE3";
|
|
1316
|
+
if (min != null && max != null && min !== max) return `${min} a ${max} dias \xFAteis`;
|
|
1317
|
+
return `${max ?? min} dia(s) \xFAtil(eis)`;
|
|
1318
|
+
}
|
|
1319
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1320
|
+
"section",
|
|
1321
|
+
{
|
|
1322
|
+
"data-testid": TID.deliveryEstimator,
|
|
1323
|
+
className: compact ? "rounded-lg border p-3" : "mt-6 rounded-lg border p-4",
|
|
1324
|
+
children: [
|
|
1325
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm font-semibold", children: [
|
|
1326
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Truck, { className: "h-4 w-4 text-primary" }),
|
|
1327
|
+
"Calcular entrega"
|
|
1328
|
+
] }),
|
|
1329
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "mt-3 flex gap-2", children: [
|
|
1330
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1331
|
+
"input",
|
|
1332
|
+
{
|
|
1333
|
+
"data-testid": TID.deliveryCepInput,
|
|
1334
|
+
"aria-label": "CEP",
|
|
1335
|
+
inputMode: "numeric",
|
|
1336
|
+
autoComplete: "postal-code",
|
|
1337
|
+
placeholder: "00000-000",
|
|
1338
|
+
value: draft,
|
|
1339
|
+
onChange: (event) => setDraft(core.formatPostalCode(event.target.value)),
|
|
1340
|
+
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"
|
|
1341
|
+
}
|
|
1342
|
+
),
|
|
1343
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1344
|
+
"button",
|
|
1345
|
+
{
|
|
1346
|
+
type: "submit",
|
|
1347
|
+
"data-testid": TID.deliveryCepSubmit,
|
|
1348
|
+
disabled: loading,
|
|
1349
|
+
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",
|
|
1350
|
+
children: loading ? "..." : "Calcular"
|
|
1351
|
+
}
|
|
1352
|
+
)
|
|
1353
|
+
] }),
|
|
1354
|
+
delivery.error && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.deliveryError, className: "mt-3 text-sm text-destructive", children: delivery.error }),
|
|
1355
|
+
delivery.address && /* @__PURE__ */ jsxRuntime.jsxs("p", { "data-testid": TID.deliveryAddress, className: "mt-3 flex items-start gap-1.5 text-xs text-muted-foreground", children: [
|
|
1356
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MapPin, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
1357
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1358
|
+
[delivery.address.street, delivery.address.district].filter(Boolean).join(", "),
|
|
1359
|
+
delivery.address.street || delivery.address.district ? " \u2014 " : "",
|
|
1360
|
+
delivery.address.city,
|
|
1361
|
+
"/",
|
|
1362
|
+
delivery.address.state
|
|
1363
|
+
] })
|
|
1364
|
+
] }),
|
|
1365
|
+
delivery.status === "served" && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-3 space-y-2", children: delivery.options.map((option) => {
|
|
1366
|
+
const selected = delivery.selectedZoneId === option.zoneId;
|
|
1367
|
+
const label = eta(option);
|
|
1368
|
+
const rate = resolveOptionRate(option, subtotal);
|
|
1369
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1370
|
+
"button",
|
|
1371
|
+
{
|
|
1372
|
+
type: "button",
|
|
1373
|
+
"data-testid": TID.deliveryOption,
|
|
1374
|
+
"data-zone": option.zoneId,
|
|
1375
|
+
"data-rate": rate.toFixed(2),
|
|
1376
|
+
"aria-pressed": selected,
|
|
1377
|
+
onClick: () => delivery.selectZone(option.zoneId),
|
|
1378
|
+
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"}`,
|
|
1379
|
+
children: [
|
|
1380
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
1381
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate font-medium", children: option.carrier ? `${option.carrier} \xB7 ${option.name}` : option.name }),
|
|
1382
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-muted-foreground", children: label })
|
|
1383
|
+
] }),
|
|
1384
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-semibold", children: rate === 0 ? "Gr\xE1tis" : money(rate) })
|
|
1385
|
+
]
|
|
1386
|
+
}
|
|
1387
|
+
) }, option.zoneId);
|
|
1388
|
+
}) }),
|
|
1389
|
+
delivery.status === "unserved" && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.deliveryUnserved, className: "mt-3 text-sm text-destructive", children: "Ainda n\xE3o entregamos nesse CEP." }),
|
|
1390
|
+
delivery.postalCode && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1391
|
+
"button",
|
|
1392
|
+
{
|
|
1393
|
+
type: "button",
|
|
1394
|
+
"data-testid": TID.deliveryClear,
|
|
1395
|
+
onClick: () => {
|
|
1396
|
+
delivery.clear();
|
|
1397
|
+
setDraft("");
|
|
1398
|
+
},
|
|
1399
|
+
className: "mt-3 text-xs text-muted-foreground underline-offset-2 hover:underline",
|
|
1400
|
+
children: "Trocar CEP"
|
|
1401
|
+
}
|
|
1402
|
+
)
|
|
1403
|
+
]
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1129
1407
|
function SmoothImage({
|
|
1130
1408
|
imageLoading,
|
|
1131
1409
|
loading = "lazy",
|
|
@@ -1143,9 +1421,9 @@ function SmoothImage({
|
|
|
1143
1421
|
const durationMs = resolved.durationMs ?? 420;
|
|
1144
1422
|
const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1145
1423
|
const blur = resolved.blur ?? true;
|
|
1146
|
-
const ref =
|
|
1147
|
-
const [loaded, setLoaded] =
|
|
1148
|
-
|
|
1424
|
+
const ref = React7__default.default.useRef(null);
|
|
1425
|
+
const [loaded, setLoaded] = React7__default.default.useState(mode === "none");
|
|
1426
|
+
React7__default.default.useEffect(() => {
|
|
1149
1427
|
if (mode === "none") {
|
|
1150
1428
|
setLoaded(true);
|
|
1151
1429
|
return;
|
|
@@ -1177,10 +1455,10 @@ function CartDrawer() {
|
|
|
1177
1455
|
const config = useStorefrontConfig();
|
|
1178
1456
|
const cart = useCartStore();
|
|
1179
1457
|
const validate = useDiscountValidator();
|
|
1180
|
-
const [code, setCode] =
|
|
1181
|
-
const [discountError, setDiscountError] =
|
|
1182
|
-
const [applying, setApplying] =
|
|
1183
|
-
|
|
1458
|
+
const [code, setCode] = React7.useState("");
|
|
1459
|
+
const [discountError, setDiscountError] = React7.useState(null);
|
|
1460
|
+
const [applying, setApplying] = React7.useState(false);
|
|
1461
|
+
React7.useEffect(() => {
|
|
1184
1462
|
if (!cart.isOpen) return;
|
|
1185
1463
|
const onKey = (e) => {
|
|
1186
1464
|
if (e.key === "Escape") cart.closeDrawer();
|
|
@@ -1188,6 +1466,7 @@ function CartDrawer() {
|
|
|
1188
1466
|
document.addEventListener("keydown", onKey);
|
|
1189
1467
|
return () => document.removeEventListener("keydown", onKey);
|
|
1190
1468
|
}, [cart.isOpen, cart.closeDrawer]);
|
|
1469
|
+
const delivery = useDeliveryStore();
|
|
1191
1470
|
const subtotal = selectSubtotal(cart);
|
|
1192
1471
|
const discountTotal = selectDiscountTotal(cart);
|
|
1193
1472
|
const shipping = selectShipping(cart, config);
|
|
@@ -1344,6 +1623,7 @@ function CartDrawer() {
|
|
|
1344
1623
|
] }),
|
|
1345
1624
|
discountError && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.discountError, className: "text-xs text-destructive", children: discountError })
|
|
1346
1625
|
] }),
|
|
1626
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsxRuntime.jsx(DeliveryEstimator, { compact: true }) }),
|
|
1347
1627
|
/* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "space-y-1.5 text-sm", children: [
|
|
1348
1628
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
|
|
1349
1629
|
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: "Subtotal" }),
|
|
@@ -1373,7 +1653,13 @@ function CartDrawer() {
|
|
|
1373
1653
|
] })
|
|
1374
1654
|
] }),
|
|
1375
1655
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
|
|
1376
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1656
|
+
/* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "text-muted-foreground", children: [
|
|
1657
|
+
"Frete",
|
|
1658
|
+
delivery.status === "served" && delivery.postalCode && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1 text-xs", children: [
|
|
1659
|
+
"\xB7 ",
|
|
1660
|
+
core.formatPostalCode(delivery.postalCode)
|
|
1661
|
+
] })
|
|
1662
|
+
] }),
|
|
1377
1663
|
/* @__PURE__ */ jsxRuntime.jsx("dd", { "data-testid": TID.cartShipping, "data-price": shipping.toFixed(2), children: shipping === 0 ? "Gr\xE1tis" : money(shipping) })
|
|
1378
1664
|
] }),
|
|
1379
1665
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between border-t pt-2 text-base font-semibold", children: [
|
|
@@ -1403,12 +1689,12 @@ function CartDrawer() {
|
|
|
1403
1689
|
] });
|
|
1404
1690
|
}
|
|
1405
1691
|
function useProducts(opts) {
|
|
1406
|
-
const [products, setProducts] =
|
|
1407
|
-
const [loading, setLoading] =
|
|
1408
|
-
const [error, setError] =
|
|
1409
|
-
const [nonce, setNonce] =
|
|
1692
|
+
const [products, setProducts] = React7.useState([]);
|
|
1693
|
+
const [loading, setLoading] = React7.useState(true);
|
|
1694
|
+
const [error, setError] = React7.useState(null);
|
|
1695
|
+
const [nonce, setNonce] = React7.useState(0);
|
|
1410
1696
|
const key = JSON.stringify(opts);
|
|
1411
|
-
|
|
1697
|
+
React7.useEffect(() => {
|
|
1412
1698
|
let cancelled = false;
|
|
1413
1699
|
setLoading(true);
|
|
1414
1700
|
runtime.getShopProvider().listProducts(JSON.parse(key)).then((data) => {
|
|
@@ -1726,10 +2012,10 @@ function FiltersPanel() {
|
|
|
1726
2012
|
}
|
|
1727
2013
|
function useStorefrontHead(input) {
|
|
1728
2014
|
const { title, description } = input;
|
|
1729
|
-
|
|
2015
|
+
React7.useEffect(() => {
|
|
1730
2016
|
if (title) document.title = title;
|
|
1731
2017
|
}, [title]);
|
|
1732
|
-
|
|
2018
|
+
React7.useEffect(() => {
|
|
1733
2019
|
if (!description) return;
|
|
1734
2020
|
let tag = document.head.querySelector('meta[name="description"]');
|
|
1735
2021
|
if (!tag) {
|
|
@@ -1748,9 +2034,9 @@ var SORT_MAP = {
|
|
|
1748
2034
|
};
|
|
1749
2035
|
var SORTS = ["newest", "price-asc", "price-desc", "name"];
|
|
1750
2036
|
function useCatalogUrlSync() {
|
|
1751
|
-
const hydrated =
|
|
2037
|
+
const hydrated = React7.useRef(false);
|
|
1752
2038
|
const catalog = useCatalogStore();
|
|
1753
|
-
|
|
2039
|
+
React7.useEffect(() => {
|
|
1754
2040
|
if (hydrated.current) return;
|
|
1755
2041
|
hydrated.current = true;
|
|
1756
2042
|
const q = window.location.hash.split("?")[1];
|
|
@@ -1765,7 +2051,7 @@ function useCatalogUrlSync() {
|
|
|
1765
2051
|
if (p.has("min")) s.setPriceMin(Number(p.get("min")) || null);
|
|
1766
2052
|
if (p.has("max")) s.setPriceMax(Number(p.get("max")) || null);
|
|
1767
2053
|
}, []);
|
|
1768
|
-
|
|
2054
|
+
React7.useEffect(() => {
|
|
1769
2055
|
const p = new URLSearchParams();
|
|
1770
2056
|
if (catalog.search) p.set("search", catalog.search);
|
|
1771
2057
|
if (catalog.categoryId) p.set("category", catalog.categoryId);
|
|
@@ -1793,7 +2079,7 @@ function CatalogPage() {
|
|
|
1793
2079
|
...SORT_MAP[catalog.sort]
|
|
1794
2080
|
};
|
|
1795
2081
|
const { products, loading, error, reload } = useProducts(providerOpts);
|
|
1796
|
-
const filtered =
|
|
2082
|
+
const filtered = React7.useMemo(
|
|
1797
2083
|
() => products.filter((p) => {
|
|
1798
2084
|
if (catalog.priceMin != null && p.price < catalog.priceMin) return false;
|
|
1799
2085
|
if (catalog.priceMax != null && p.price > catalog.priceMax) return false;
|
|
@@ -1858,17 +2144,17 @@ function HeroSection({
|
|
|
1858
2144
|
slides,
|
|
1859
2145
|
height = "tall"
|
|
1860
2146
|
}) {
|
|
1861
|
-
const [index, setIndex] =
|
|
1862
|
-
const [loadedSlides, setLoadedSlides] =
|
|
2147
|
+
const [index, setIndex] = React7.useState(0);
|
|
2148
|
+
const [loadedSlides, setLoadedSlides] = React7.useState({});
|
|
1863
2149
|
const config = useStorefrontConfig();
|
|
1864
|
-
const interacted =
|
|
2150
|
+
const interacted = React7.useRef(false);
|
|
1865
2151
|
const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
|
|
1866
2152
|
const revealImages = config.imageLoading.mode !== "none";
|
|
1867
2153
|
const revealStyle = (loaded) => revealImages ? {
|
|
1868
2154
|
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
1869
2155
|
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
1870
2156
|
} : void 0;
|
|
1871
|
-
|
|
2157
|
+
React7.useEffect(() => {
|
|
1872
2158
|
if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
|
|
1873
2159
|
const timer = setInterval(() => {
|
|
1874
2160
|
if (!interacted.current) setIndex((i) => (i + 1) % slides.length);
|
|
@@ -2029,7 +2315,7 @@ function ProductRail({
|
|
|
2029
2315
|
const { products, loading } = useProducts(
|
|
2030
2316
|
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
2031
2317
|
);
|
|
2032
|
-
const visible =
|
|
2318
|
+
const visible = React7.useMemo(() => {
|
|
2033
2319
|
let list = products;
|
|
2034
2320
|
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
2035
2321
|
return list.slice(0, limit);
|
|
@@ -2078,16 +2364,16 @@ function MediaCarousel({
|
|
|
2078
2364
|
overlay = "dark",
|
|
2079
2365
|
className = ""
|
|
2080
2366
|
}) {
|
|
2081
|
-
const [index, setIndex] =
|
|
2082
|
-
const [loadedItems, setLoadedItems] =
|
|
2367
|
+
const [index, setIndex] = React7.useState(0);
|
|
2368
|
+
const [loadedItems, setLoadedItems] = React7.useState({});
|
|
2083
2369
|
const config = useStorefrontConfig();
|
|
2084
|
-
const interacted =
|
|
2370
|
+
const interacted = React7.useRef(false);
|
|
2085
2371
|
const revealImages = config.imageLoading.mode !== "none";
|
|
2086
2372
|
const revealStyle = (loaded) => revealImages ? {
|
|
2087
2373
|
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
2088
2374
|
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
2089
2375
|
} : void 0;
|
|
2090
|
-
|
|
2376
|
+
React7.useEffect(() => {
|
|
2091
2377
|
if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
|
|
2092
2378
|
const timer = setInterval(() => {
|
|
2093
2379
|
if (!interacted.current) setIndex((i) => (i + 1) % items.length);
|
|
@@ -2180,13 +2466,13 @@ function ProductSlider({
|
|
|
2180
2466
|
}) {
|
|
2181
2467
|
const config = useStorefrontConfig();
|
|
2182
2468
|
const actions = useStorefrontActions();
|
|
2183
|
-
const viewportRef =
|
|
2469
|
+
const viewportRef = React7.useRef(null);
|
|
2184
2470
|
const components = getStorefrontComponents(config);
|
|
2185
2471
|
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
2186
2472
|
const { products, loading } = useProducts(
|
|
2187
2473
|
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
2188
2474
|
);
|
|
2189
|
-
const visible =
|
|
2475
|
+
const visible = React7.useMemo(() => {
|
|
2190
2476
|
let list = products;
|
|
2191
2477
|
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
2192
2478
|
if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
|
|
@@ -2334,8 +2620,8 @@ function Testimonials({ title, items }) {
|
|
|
2334
2620
|
] }) });
|
|
2335
2621
|
}
|
|
2336
2622
|
function NewsletterBand({ title, subtitle }) {
|
|
2337
|
-
const [email, setEmail] =
|
|
2338
|
-
const [done, setDone] =
|
|
2623
|
+
const [email, setEmail] = React7.useState("");
|
|
2624
|
+
const [done, setDone] = React7.useState(false);
|
|
2339
2625
|
return /* @__PURE__ */ jsxRuntime.jsx("section", { "data-testid": TID.newsletter, className: "bg-primary py-16 text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs(Reveal, { className: "mx-auto max-w-xl px-4 text-center sm:px-6", children: [
|
|
2340
2626
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold", children: title ?? "Receba nossas novidades" }),
|
|
2341
2627
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 opacity-90", children: subtitle }),
|
|
@@ -2407,10 +2693,10 @@ function HomePage({ sections }) {
|
|
|
2407
2693
|
return /* @__PURE__ */ jsxRuntime.jsx(StorefrontSections, { sections });
|
|
2408
2694
|
}
|
|
2409
2695
|
function useProduct(slug2) {
|
|
2410
|
-
const [product, setProduct] =
|
|
2411
|
-
const [loading, setLoading] =
|
|
2412
|
-
const [error, setError] =
|
|
2413
|
-
|
|
2696
|
+
const [product, setProduct] = React7.useState(null);
|
|
2697
|
+
const [loading, setLoading] = React7.useState(true);
|
|
2698
|
+
const [error, setError] = React7.useState(null);
|
|
2699
|
+
React7.useEffect(() => {
|
|
2414
2700
|
let cancelled = false;
|
|
2415
2701
|
setLoading(true);
|
|
2416
2702
|
runtime.getShopProvider().listProducts({ slug: slug2, status: "active", limit: 1 }).then((data) => {
|
|
@@ -2548,13 +2834,13 @@ function buildWhatsAppHref(url, product) {
|
|
|
2548
2834
|
}
|
|
2549
2835
|
function ProductEnquiryForm({ product, onSuccess }) {
|
|
2550
2836
|
const config = useStorefrontConfig();
|
|
2551
|
-
const [customerName, setCustomerName] =
|
|
2552
|
-
const [customerEmail, setCustomerEmail] =
|
|
2553
|
-
const [customerPhone, setCustomerPhone] =
|
|
2554
|
-
const [subject, setSubject] =
|
|
2555
|
-
const [message, setMessage] =
|
|
2556
|
-
const [status, setStatus] =
|
|
2557
|
-
const [error, setError] =
|
|
2837
|
+
const [customerName, setCustomerName] = React7.useState("");
|
|
2838
|
+
const [customerEmail, setCustomerEmail] = React7.useState("");
|
|
2839
|
+
const [customerPhone, setCustomerPhone] = React7.useState("");
|
|
2840
|
+
const [subject, setSubject] = React7.useState(defaultSubject(config.enquiry.subjectPrefix, product));
|
|
2841
|
+
const [message, setMessage] = React7.useState(`I would like more information about ${product.name}.`);
|
|
2842
|
+
const [status, setStatus] = React7.useState("idle");
|
|
2843
|
+
const [error, setError] = React7.useState(null);
|
|
2558
2844
|
const submit = async (event) => {
|
|
2559
2845
|
event.preventDefault();
|
|
2560
2846
|
setStatus("submitting");
|
|
@@ -2716,19 +3002,141 @@ function ProductEnquiryForm({ product, onSuccess }) {
|
|
|
2716
3002
|
}
|
|
2717
3003
|
);
|
|
2718
3004
|
}
|
|
3005
|
+
function ProductGallery({ product, images, primaryImage }) {
|
|
3006
|
+
const ordered = orderImages(images, primaryImage);
|
|
3007
|
+
const [index, setIndex] = React7.useState(0);
|
|
3008
|
+
const [zoom, setZoom] = React7.useState(null);
|
|
3009
|
+
const frameRef = React7.useRef(null);
|
|
3010
|
+
const canHover = useCanHover();
|
|
3011
|
+
React7.useEffect(() => {
|
|
3012
|
+
setIndex(0);
|
|
3013
|
+
setZoom(null);
|
|
3014
|
+
}, [product.id]);
|
|
3015
|
+
const current = ordered[Math.min(index, ordered.length - 1)];
|
|
3016
|
+
function trackPointer(event) {
|
|
3017
|
+
if (!canHover) return;
|
|
3018
|
+
const frame = frameRef.current;
|
|
3019
|
+
if (!frame) return;
|
|
3020
|
+
const rect = frame.getBoundingClientRect();
|
|
3021
|
+
setZoom({
|
|
3022
|
+
x: (event.clientX - rect.left) / rect.width * 100,
|
|
3023
|
+
y: (event.clientY - rect.top) / rect.height * 100
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
function step(delta) {
|
|
3027
|
+
if (ordered.length < 2) return;
|
|
3028
|
+
setIndex((current2) => (current2 + delta + ordered.length) % ordered.length);
|
|
3029
|
+
setZoom(null);
|
|
3030
|
+
}
|
|
3031
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 self-start", children: [
|
|
3032
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3033
|
+
"div",
|
|
3034
|
+
{
|
|
3035
|
+
...storefrontComponentContracts.productDetail.gallery,
|
|
3036
|
+
ref: frameRef,
|
|
3037
|
+
className: "group relative aspect-square w-full overflow-hidden border bg-muted",
|
|
3038
|
+
style: { borderRadius: "var(--sf-radius-card)" },
|
|
3039
|
+
onMouseMove: trackPointer,
|
|
3040
|
+
onMouseLeave: () => setZoom(null),
|
|
3041
|
+
onKeyDown: (event) => {
|
|
3042
|
+
if (event.key === "ArrowRight") step(1);
|
|
3043
|
+
if (event.key === "ArrowLeft") step(-1);
|
|
3044
|
+
},
|
|
3045
|
+
tabIndex: ordered.length > 1 ? 0 : -1,
|
|
3046
|
+
role: ordered.length > 1 ? "group" : void 0,
|
|
3047
|
+
"aria-label": ordered.length > 1 ? `Imagens de ${product.name}` : void 0,
|
|
3048
|
+
children: [
|
|
3049
|
+
current && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3050
|
+
"div",
|
|
3051
|
+
{
|
|
3052
|
+
className: "h-full w-full transition-transform duration-150 ease-out",
|
|
3053
|
+
style: zoom ? { transform: "scale(2)", transformOrigin: `${zoom.x}% ${zoom.y}%` } : { transform: "scale(1)" },
|
|
3054
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3055
|
+
SmoothImage,
|
|
3056
|
+
{
|
|
3057
|
+
src: current.url,
|
|
3058
|
+
alt: current.altText ?? product.name,
|
|
3059
|
+
"data-testid": TID.pdpGalleryImage,
|
|
3060
|
+
className: "h-full w-full object-cover"
|
|
3061
|
+
},
|
|
3062
|
+
current.id
|
|
3063
|
+
)
|
|
3064
|
+
}
|
|
3065
|
+
),
|
|
3066
|
+
ordered.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3067
|
+
/* @__PURE__ */ jsxRuntime.jsx(GalleryArrow, { side: "left", onClick: () => step(-1) }),
|
|
3068
|
+
/* @__PURE__ */ jsxRuntime.jsx(GalleryArrow, { side: "right", onClick: () => step(1) }),
|
|
3069
|
+
/* @__PURE__ */ jsxRuntime.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: [
|
|
3070
|
+
index + 1,
|
|
3071
|
+
"/",
|
|
3072
|
+
ordered.length
|
|
3073
|
+
] })
|
|
3074
|
+
] })
|
|
3075
|
+
]
|
|
3076
|
+
}
|
|
3077
|
+
),
|
|
3078
|
+
ordered.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("ul", { "data-testid": TID.pdpGalleryThumbs, className: "flex gap-2 overflow-x-auto pb-1", children: ordered.map((image, position) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3079
|
+
"button",
|
|
3080
|
+
{
|
|
3081
|
+
type: "button",
|
|
3082
|
+
"data-testid": TID.pdpGalleryThumb,
|
|
3083
|
+
"data-index": position,
|
|
3084
|
+
"aria-label": `Imagem ${position + 1} de ${ordered.length}`,
|
|
3085
|
+
"aria-current": position === index,
|
|
3086
|
+
onClick: () => {
|
|
3087
|
+
setIndex(position);
|
|
3088
|
+
setZoom(null);
|
|
3089
|
+
},
|
|
3090
|
+
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"}`,
|
|
3091
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: image.url, alt: "", className: "h-full w-full object-cover", loading: "lazy" })
|
|
3092
|
+
}
|
|
3093
|
+
) }, image.id)) })
|
|
3094
|
+
] });
|
|
3095
|
+
}
|
|
3096
|
+
function orderImages(images, primary) {
|
|
3097
|
+
const all = images.length > 0 ? images : primary ? [primary] : [];
|
|
3098
|
+
return [...all].sort((a, b) => {
|
|
3099
|
+
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1;
|
|
3100
|
+
return (a.sortOrder ?? 0) - (b.sortOrder ?? 0);
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
function useCanHover() {
|
|
3104
|
+
const [canHover, setCanHover] = React7.useState(false);
|
|
3105
|
+
React7.useEffect(() => {
|
|
3106
|
+
const query = window.matchMedia?.("(hover: hover) and (pointer: fine)");
|
|
3107
|
+
if (!query) return;
|
|
3108
|
+
setCanHover(query.matches);
|
|
3109
|
+
const onChange = (event) => setCanHover(event.matches);
|
|
3110
|
+
query.addEventListener?.("change", onChange);
|
|
3111
|
+
return () => query.removeEventListener?.("change", onChange);
|
|
3112
|
+
}, []);
|
|
3113
|
+
return canHover;
|
|
3114
|
+
}
|
|
3115
|
+
function GalleryArrow({ side, onClick }) {
|
|
3116
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3117
|
+
"button",
|
|
3118
|
+
{
|
|
3119
|
+
type: "button",
|
|
3120
|
+
onClick,
|
|
3121
|
+
"aria-label": side === "left" ? "Imagem anterior" : "Pr\xF3xima imagem",
|
|
3122
|
+
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"}`,
|
|
3123
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "text-lg leading-none", children: side === "left" ? "\u2039" : "\u203A" })
|
|
3124
|
+
}
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
2719
3127
|
function ProductDetailPage({ slug: slug2 }) {
|
|
2720
3128
|
const config = useStorefrontConfig();
|
|
2721
3129
|
const actions = useStorefrontActions();
|
|
2722
3130
|
const { product, loading } = useProduct(slug2);
|
|
2723
3131
|
const addItem = useCartStore((s) => s.addItem);
|
|
2724
|
-
const [qty, setQty] =
|
|
2725
|
-
const [selectedOptions, setSelectedOptions] =
|
|
3132
|
+
const [qty, setQty] = React7.useState(1);
|
|
3133
|
+
const [selectedOptions, setSelectedOptions] = React7.useState({});
|
|
2726
3134
|
const optionGroups = product ? getProductOptionGroups(product) : [];
|
|
2727
3135
|
useStorefrontHead({
|
|
2728
3136
|
title: product ? `${product.name} \u2014 ${config.name}` : config.name,
|
|
2729
3137
|
description: product?.description
|
|
2730
3138
|
});
|
|
2731
|
-
|
|
3139
|
+
React7.useEffect(() => {
|
|
2732
3140
|
if (!product) return;
|
|
2733
3141
|
const defaults = Object.fromEntries(
|
|
2734
3142
|
getProductOptionGroups(product).map((group) => [group.label, group.values[0] ?? ""])
|
|
@@ -2756,6 +3164,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2756
3164
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
2757
3165
|
const components = getStorefrontComponents(config);
|
|
2758
3166
|
const ProductDetailComponent = components.ProductDetail;
|
|
3167
|
+
const GalleryComponent = components.ProductGallery ?? ProductGallery;
|
|
2759
3168
|
const addToCart = () => addItem(product, qty, selectedOptions);
|
|
2760
3169
|
const openEnquiry = () => {
|
|
2761
3170
|
document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
@@ -2789,19 +3198,13 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2789
3198
|
] }),
|
|
2790
3199
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
|
|
2791
3200
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2792
|
-
|
|
3201
|
+
GalleryComponent,
|
|
2793
3202
|
{
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
{
|
|
2800
|
-
src: image.url,
|
|
2801
|
-
alt: image.altText ?? product.name,
|
|
2802
|
-
className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
2803
|
-
}
|
|
2804
|
-
)
|
|
3203
|
+
product,
|
|
3204
|
+
images: product.images,
|
|
3205
|
+
primaryImage: image,
|
|
3206
|
+
config,
|
|
3207
|
+
commerceMode: config.commerceMode
|
|
2805
3208
|
}
|
|
2806
3209
|
),
|
|
2807
3210
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
|
|
@@ -2899,6 +3302,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2899
3302
|
children: "Voltar ao cat\xE1logo"
|
|
2900
3303
|
}
|
|
2901
3304
|
) }),
|
|
3305
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsx(DeliveryEstimator, {}),
|
|
2902
3306
|
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
|
|
2903
3307
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
|
|
2904
3308
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Truck, { className: "h-5 w-5 text-primary" }),
|
|
@@ -2924,9 +3328,10 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2924
3328
|
/* @__PURE__ */ jsxRuntime.jsx(RelatedProducts, { product })
|
|
2925
3329
|
] });
|
|
2926
3330
|
}
|
|
2927
|
-
function formatDeliveryNotes(address) {
|
|
3331
|
+
function formatDeliveryNotes(address, shippingAddress) {
|
|
3332
|
+
if (address?.notes?.trim()) return address.notes.trim();
|
|
3333
|
+
if (shippingAddress) return void 0;
|
|
2928
3334
|
if (!address) return void 0;
|
|
2929
|
-
if (address.notes?.trim()) return address.notes.trim();
|
|
2930
3335
|
const parts = [address.street, address.city, address.zip].map((part) => part?.trim()).filter(Boolean);
|
|
2931
3336
|
return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
|
|
2932
3337
|
}
|
|
@@ -2936,6 +3341,8 @@ async function placeStorefrontOrder({
|
|
|
2936
3341
|
session,
|
|
2937
3342
|
customer,
|
|
2938
3343
|
address,
|
|
3344
|
+
shippingAddress,
|
|
3345
|
+
paymentMethod,
|
|
2939
3346
|
markPaid = false
|
|
2940
3347
|
}) {
|
|
2941
3348
|
const email = customer.email.trim().toLowerCase();
|
|
@@ -2950,9 +3357,14 @@ async function placeStorefrontOrder({
|
|
|
2950
3357
|
customerId: customerId ?? void 0,
|
|
2951
3358
|
customer: { name, email },
|
|
2952
3359
|
currency: config.currency,
|
|
2953
|
-
notes: formatDeliveryNotes(address),
|
|
3360
|
+
notes: formatDeliveryNotes(address, shippingAddress),
|
|
2954
3361
|
discountCode: cart.discountCode ?? void 0,
|
|
2955
3362
|
shippingTotal: selectShipping(cart, config),
|
|
3363
|
+
// Structured address + method are what populate public.addresses and the
|
|
3364
|
+
// transactions ledger. `notes` stays for backwards compatibility with
|
|
3365
|
+
// checkouts that have not been updated yet.
|
|
3366
|
+
shippingAddress,
|
|
3367
|
+
paymentMethod,
|
|
2956
3368
|
items: cart.lines.map((line) => ({
|
|
2957
3369
|
productId: line.productId,
|
|
2958
3370
|
quantity: line.quantity,
|
|
@@ -2960,16 +3372,17 @@ async function placeStorefrontOrder({
|
|
|
2960
3372
|
}))
|
|
2961
3373
|
});
|
|
2962
3374
|
if (markPaid) {
|
|
2963
|
-
await provider.
|
|
3375
|
+
if (provider.confirmPayment) await provider.confirmPayment(order.id);
|
|
3376
|
+
else await provider.updateOrder(order.id, { financialStatus: "paid" });
|
|
2964
3377
|
}
|
|
2965
3378
|
return { order, customerId: customerId ?? order.customerId ?? "" };
|
|
2966
3379
|
}
|
|
2967
3380
|
function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
|
|
2968
|
-
const [email, setEmail] =
|
|
2969
|
-
const [password, setPassword] =
|
|
2970
|
-
const [busy, setBusy] =
|
|
2971
|
-
const [error, setError] =
|
|
2972
|
-
|
|
3381
|
+
const [email, setEmail] = React7.useState(defaultEmail);
|
|
3382
|
+
const [password, setPassword] = React7.useState("");
|
|
3383
|
+
const [busy, setBusy] = React7.useState(false);
|
|
3384
|
+
const [error, setError] = React7.useState(null);
|
|
3385
|
+
React7.useEffect(() => {
|
|
2973
3386
|
const onKey = (event) => {
|
|
2974
3387
|
if (event.key === "Escape") onClose();
|
|
2975
3388
|
};
|
|
@@ -3074,19 +3487,18 @@ function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
|
|
|
3074
3487
|
}
|
|
3075
3488
|
);
|
|
3076
3489
|
}
|
|
3077
|
-
var
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3490
|
+
var PAYMENT_LABELS = {
|
|
3491
|
+
pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
|
|
3492
|
+
credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
|
|
3493
|
+
debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
|
|
3494
|
+
boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
|
|
3495
|
+
cash: { label: "Dinheiro", hint: "Pagamento na entrega" },
|
|
3496
|
+
other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
|
|
3497
|
+
};
|
|
3083
3498
|
var PROCESSING_STEPS = [
|
|
3084
3499
|
{ icon: LucideIcons.Lock, label: "Validando seus dados..." },
|
|
3085
|
-
{ icon: LucideIcons.
|
|
3086
|
-
{ icon: LucideIcons.PackageCheck, label: "Confirmando seu pedido..." }
|
|
3500
|
+
{ icon: LucideIcons.PackageCheck, label: "Registrando seu pedido..." }
|
|
3087
3501
|
];
|
|
3088
|
-
var DEFAULT_ADDRESS = SAVED_ADDRESSES[0];
|
|
3089
|
-
var DEFAULT_PAYMENT = SAVED_PAYMENT_METHODS[0];
|
|
3090
3502
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3091
3503
|
function ProcessingOverlay({ step }) {
|
|
3092
3504
|
const current = PROCESSING_STEPS[Math.min(step, PROCESSING_STEPS.length - 1)];
|
|
@@ -3129,41 +3541,69 @@ function CheckoutPage() {
|
|
|
3129
3541
|
const config = useStorefrontConfig();
|
|
3130
3542
|
const cart = useCartStore();
|
|
3131
3543
|
const session = useSessionStore();
|
|
3544
|
+
const delivery = useDeliveryStore();
|
|
3132
3545
|
const validateDiscount2 = useDiscountValidator();
|
|
3133
3546
|
useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
|
|
3134
|
-
const
|
|
3135
|
-
const [
|
|
3136
|
-
const [savedAddresses, setSavedAddresses] =
|
|
3137
|
-
const [
|
|
3138
|
-
const [
|
|
3139
|
-
const [
|
|
3140
|
-
const [form, setForm] = React6.useState({
|
|
3547
|
+
const paymentMethods = config.payments.methods;
|
|
3548
|
+
const [selectedAddressId, setSelectedAddressId] = React7.useState("new");
|
|
3549
|
+
const [savedAddresses, setSavedAddresses] = React7.useState([]);
|
|
3550
|
+
const [addressMode, setAddressMode] = React7.useState("new");
|
|
3551
|
+
const [paymentMethod, setPaymentMethod] = React7.useState(paymentMethods[0] ?? "pix");
|
|
3552
|
+
const [form, setForm] = React7.useState({
|
|
3141
3553
|
email: session.email ?? "",
|
|
3142
3554
|
name: session.name ?? "",
|
|
3143
|
-
street:
|
|
3144
|
-
city:
|
|
3145
|
-
zip:
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3555
|
+
street: "",
|
|
3556
|
+
city: "",
|
|
3557
|
+
zip: "",
|
|
3558
|
+
number: "",
|
|
3559
|
+
complement: "",
|
|
3560
|
+
district: "",
|
|
3561
|
+
state: ""
|
|
3149
3562
|
});
|
|
3150
|
-
const [error, setError] =
|
|
3151
|
-
const [discountCode, setDiscountCode] =
|
|
3152
|
-
const [discountError, setDiscountError] =
|
|
3153
|
-
const [discountSuccess, setDiscountSuccess] =
|
|
3154
|
-
const [applyingDiscount, setApplyingDiscount] =
|
|
3155
|
-
const [placing, setPlacing] =
|
|
3156
|
-
const [processingStep, setProcessingStep] =
|
|
3157
|
-
const [showSignin, setShowSignin] =
|
|
3563
|
+
const [error, setError] = React7.useState(null);
|
|
3564
|
+
const [discountCode, setDiscountCode] = React7.useState("");
|
|
3565
|
+
const [discountError, setDiscountError] = React7.useState(null);
|
|
3566
|
+
const [discountSuccess, setDiscountSuccess] = React7.useState(null);
|
|
3567
|
+
const [applyingDiscount, setApplyingDiscount] = React7.useState(false);
|
|
3568
|
+
const [placing, setPlacing] = React7.useState(false);
|
|
3569
|
+
const [processingStep, setProcessingStep] = React7.useState(0);
|
|
3570
|
+
const [showSignin, setShowSignin] = React7.useState(false);
|
|
3571
|
+
const [zipLookup, setZipLookup] = React7.useState("idle");
|
|
3158
3572
|
const subtotal = selectSubtotal(cart);
|
|
3159
3573
|
const discountTotal = selectDiscountTotal(cart);
|
|
3160
3574
|
const shipping = selectShipping(cart, config);
|
|
3161
3575
|
const total = selectTotal(cart, config);
|
|
3162
3576
|
const money = (value) => formatMoney(value, config.currency, config.locale);
|
|
3163
|
-
|
|
3577
|
+
React7.useEffect(() => {
|
|
3164
3578
|
if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
|
|
3165
3579
|
}, [cart.lines.length, config.catalogPath, placing]);
|
|
3166
3580
|
const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
|
|
3581
|
+
async function setZip(value) {
|
|
3582
|
+
const masked = core.formatPostalCode(value);
|
|
3583
|
+
setForm((current) => ({ ...current, zip: masked }));
|
|
3584
|
+
if (core.normalizePostalCode(masked).length !== 8) {
|
|
3585
|
+
setZipLookup("idle");
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
setZipLookup("loading");
|
|
3589
|
+
try {
|
|
3590
|
+
const found = await core.lookupPostalCode(masked);
|
|
3591
|
+
if (!found) {
|
|
3592
|
+
setZipLookup("not-found");
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
setZipLookup("found");
|
|
3596
|
+
setForm((current) => ({
|
|
3597
|
+
...current,
|
|
3598
|
+
street: found.street || current.street,
|
|
3599
|
+
district: found.district || current.district,
|
|
3600
|
+
city: found.city,
|
|
3601
|
+
state: found.state
|
|
3602
|
+
}));
|
|
3603
|
+
} catch {
|
|
3604
|
+
setZipLookup("idle");
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3167
3607
|
async function applyDiscountCode() {
|
|
3168
3608
|
setApplyingDiscount(true);
|
|
3169
3609
|
setDiscountError(null);
|
|
@@ -3190,10 +3630,54 @@ function CheckoutPage() {
|
|
|
3190
3630
|
setDiscountSuccess(null);
|
|
3191
3631
|
setDiscountError(null);
|
|
3192
3632
|
}
|
|
3633
|
+
React7.useEffect(() => {
|
|
3634
|
+
const customerId = session.customerId;
|
|
3635
|
+
if (!customerId) {
|
|
3636
|
+
setSavedAddresses([]);
|
|
3637
|
+
setAddressMode("new");
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
let cancelled = false;
|
|
3641
|
+
const provider = runtime.getShopProvider();
|
|
3642
|
+
void provider.listCustomerAddresses?.(customerId).then((addresses) => {
|
|
3643
|
+
if (cancelled || addresses.length === 0) return;
|
|
3644
|
+
setSavedAddresses(addresses);
|
|
3645
|
+
applySavedAddress(addresses[0]);
|
|
3646
|
+
}).catch(() => {
|
|
3647
|
+
});
|
|
3648
|
+
return () => {
|
|
3649
|
+
cancelled = true;
|
|
3650
|
+
};
|
|
3651
|
+
}, [session.customerId]);
|
|
3652
|
+
React7.useEffect(() => {
|
|
3653
|
+
if (addressMode !== "new") return;
|
|
3654
|
+
const looked = delivery.address;
|
|
3655
|
+
if (!looked) return;
|
|
3656
|
+
setForm((current) => {
|
|
3657
|
+
if (current.zip.trim() || current.street.trim()) return current;
|
|
3658
|
+
return {
|
|
3659
|
+
...current,
|
|
3660
|
+
zip: core.formatPostalCode(looked.postalCode),
|
|
3661
|
+
street: looked.street,
|
|
3662
|
+
district: looked.district,
|
|
3663
|
+
city: looked.city,
|
|
3664
|
+
state: looked.state
|
|
3665
|
+
};
|
|
3666
|
+
});
|
|
3667
|
+
}, [delivery.address, addressMode]);
|
|
3193
3668
|
function applySavedAddress(address) {
|
|
3194
3669
|
setAddressMode("saved");
|
|
3195
3670
|
setSelectedAddressId(address.id);
|
|
3196
|
-
setForm((current) => ({
|
|
3671
|
+
setForm((current) => ({
|
|
3672
|
+
...current,
|
|
3673
|
+
street: address.street,
|
|
3674
|
+
city: address.city,
|
|
3675
|
+
zip: address.postalCode,
|
|
3676
|
+
number: address.number ?? "",
|
|
3677
|
+
complement: address.complement ?? "",
|
|
3678
|
+
district: address.district ?? "",
|
|
3679
|
+
state: address.state
|
|
3680
|
+
}));
|
|
3197
3681
|
}
|
|
3198
3682
|
function selectAddress(addressId) {
|
|
3199
3683
|
const address = savedAddresses.find((item) => item.id === addressId);
|
|
@@ -3202,37 +3686,7 @@ function CheckoutPage() {
|
|
|
3202
3686
|
function addNewAddress() {
|
|
3203
3687
|
setAddressMode("new");
|
|
3204
3688
|
setSelectedAddressId("new");
|
|
3205
|
-
setForm((current) => ({ ...current, street: "", city: "", zip: "" }));
|
|
3206
|
-
}
|
|
3207
|
-
function removeAddress(addressId) {
|
|
3208
|
-
const nextAddresses = savedAddresses.filter((item) => item.id !== addressId);
|
|
3209
|
-
setSavedAddresses(nextAddresses);
|
|
3210
|
-
if (selectedAddressId !== addressId) return;
|
|
3211
|
-
const fallback = nextAddresses[0];
|
|
3212
|
-
if (fallback) applySavedAddress(fallback);
|
|
3213
|
-
else addNewAddress();
|
|
3214
|
-
}
|
|
3215
|
-
function applySavedPayment(method) {
|
|
3216
|
-
setPaymentMode("saved");
|
|
3217
|
-
setSelectedPaymentId(method.id);
|
|
3218
|
-
setForm((current) => ({ ...current, card: method.card, expiry: method.expiry, cvc: method.cvc }));
|
|
3219
|
-
}
|
|
3220
|
-
function selectPayment(paymentId) {
|
|
3221
|
-
const method = savedPaymentMethods.find((item) => item.id === paymentId);
|
|
3222
|
-
if (method) applySavedPayment(method);
|
|
3223
|
-
}
|
|
3224
|
-
function addNewPayment() {
|
|
3225
|
-
setPaymentMode("new");
|
|
3226
|
-
setSelectedPaymentId("new");
|
|
3227
|
-
setForm((current) => ({ ...current, card: "", expiry: "", cvc: "" }));
|
|
3228
|
-
}
|
|
3229
|
-
function removePayment(paymentId) {
|
|
3230
|
-
const nextMethods = savedPaymentMethods.filter((item) => item.id !== paymentId);
|
|
3231
|
-
setSavedPaymentMethods(nextMethods);
|
|
3232
|
-
if (selectedPaymentId !== paymentId) return;
|
|
3233
|
-
const fallback = nextMethods[0];
|
|
3234
|
-
if (fallback) applySavedPayment(fallback);
|
|
3235
|
-
else addNewPayment();
|
|
3689
|
+
setForm((current) => ({ ...current, street: "", city: "", zip: "", number: "", complement: "", district: "", state: "" }));
|
|
3236
3690
|
}
|
|
3237
3691
|
function validate() {
|
|
3238
3692
|
setError(null);
|
|
@@ -3244,12 +3698,20 @@ function CheckoutPage() {
|
|
|
3244
3698
|
setError("Informe seu nome completo.");
|
|
3245
3699
|
return false;
|
|
3246
3700
|
}
|
|
3247
|
-
if (!form.street.trim() || !form.city.trim() || !form.zip.trim()) {
|
|
3701
|
+
if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
|
|
3248
3702
|
setError("Preencha ou selecione o endere\xE7o de entrega.");
|
|
3249
3703
|
return false;
|
|
3250
3704
|
}
|
|
3251
|
-
if (
|
|
3252
|
-
setError("
|
|
3705
|
+
if (!form.number.trim()) {
|
|
3706
|
+
setError("Falta o n\xFAmero do endere\xE7o.");
|
|
3707
|
+
return false;
|
|
3708
|
+
}
|
|
3709
|
+
if (form.state.trim().length !== 2) {
|
|
3710
|
+
setError("Informe a UF com duas letras (ex.: RJ).");
|
|
3711
|
+
return false;
|
|
3712
|
+
}
|
|
3713
|
+
if (delivery.status === "unserved" && delivery.postalCode === core.normalizePostalCode(form.zip)) {
|
|
3714
|
+
setError("Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.");
|
|
3253
3715
|
return false;
|
|
3254
3716
|
}
|
|
3255
3717
|
return true;
|
|
@@ -3271,9 +3733,22 @@ function CheckoutPage() {
|
|
|
3271
3733
|
session,
|
|
3272
3734
|
customer: { email: form.email, name: form.name },
|
|
3273
3735
|
address: { street: form.street, city: form.city, zip: form.zip },
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3736
|
+
shippingAddress: {
|
|
3737
|
+
postalCode: form.zip.trim(),
|
|
3738
|
+
street: form.street.trim(),
|
|
3739
|
+
number: form.number.trim() || void 0,
|
|
3740
|
+
complement: form.complement.trim() || void 0,
|
|
3741
|
+
district: form.district.trim() || void 0,
|
|
3742
|
+
city: form.city.trim(),
|
|
3743
|
+
state: form.state.trim().toUpperCase()
|
|
3744
|
+
},
|
|
3745
|
+
// The method the buyer actually chose, which opens the ledger row in
|
|
3746
|
+
// public.transactions with the right kind.
|
|
3747
|
+
paymentMethod
|
|
3748
|
+
// Never marked paid from the browser. It used to be, through an RPC that
|
|
3749
|
+
// was granted to anon — so any buyer holding their own order id could
|
|
3750
|
+
// declare it settled. The order is now born `pending` and only the
|
|
3751
|
+
// merchant (or a PSP webhook) can confirm the money arrived.
|
|
3277
3752
|
});
|
|
3278
3753
|
await minDuration;
|
|
3279
3754
|
cart.clear();
|
|
@@ -3346,35 +3821,25 @@ function CheckoutPage() {
|
|
|
3346
3821
|
/* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
3347
3822
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Endere\xE7o de entrega" }),
|
|
3348
3823
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 grid gap-3", children: [
|
|
3349
|
-
savedAddresses.map((address) => /* @__PURE__ */ jsxRuntime.
|
|
3824
|
+
savedAddresses.map((address) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3350
3825
|
"div",
|
|
3351
3826
|
{
|
|
3352
3827
|
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"}`,
|
|
3353
|
-
children: [
|
|
3354
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3358
|
-
] }),
|
|
3359
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-muted-foreground", children: address.street }),
|
|
3360
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-muted-foreground", children: [
|
|
3361
|
-
address.city,
|
|
3362
|
-
" - ",
|
|
3363
|
-
address.zip
|
|
3364
|
-
] })
|
|
3828
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => selectAddress(address.id), className: "block w-full text-left", children: [
|
|
3829
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3830
|
+
address.label,
|
|
3831
|
+
addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3365
3832
|
] }),
|
|
3366
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3367
|
-
"
|
|
3368
|
-
{
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
)
|
|
3377
|
-
]
|
|
3833
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
|
|
3834
|
+
[address.street, address.number].filter(Boolean).join(", "),
|
|
3835
|
+
address.complement ? ` \u2014 ${address.complement}` : ""
|
|
3836
|
+
] }),
|
|
3837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-muted-foreground", children: [
|
|
3838
|
+
[address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
|
|
3839
|
+
" \u2014 ",
|
|
3840
|
+
address.postalCode
|
|
3841
|
+
] })
|
|
3842
|
+
] })
|
|
3378
3843
|
},
|
|
3379
3844
|
address.id
|
|
3380
3845
|
)),
|
|
@@ -3385,83 +3850,62 @@ function CheckoutPage() {
|
|
|
3385
3850
|
onClick: addNewAddress,
|
|
3386
3851
|
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"}`,
|
|
3387
3852
|
children: [
|
|
3388
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block font-semibold text-primary", children: "+ Adicionar novo endere\xE7o" }),
|
|
3853
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block font-semibold text-primary", children: savedAddresses.length > 0 ? "+ Adicionar novo endere\xE7o" : "Informe o endere\xE7o de entrega" }),
|
|
3389
3854
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-muted-foreground", children: "Preencher outro endere\xE7o para esta compra" })
|
|
3390
3855
|
]
|
|
3391
3856
|
}
|
|
3392
3857
|
)
|
|
3393
3858
|
] }),
|
|
3394
3859
|
addressMode === "new" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3", children: [
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3860
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center", children: [
|
|
3861
|
+
field("CEP", TID.checkoutZip, form.zip, setZip, {
|
|
3862
|
+
placeholder: "CEP",
|
|
3863
|
+
inputMode: "numeric",
|
|
3864
|
+
autoComplete: "postal-code"
|
|
3865
|
+
}),
|
|
3866
|
+
/* @__PURE__ */ jsxRuntime.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." })
|
|
3867
|
+
] }),
|
|
3868
|
+
field("Endere\xE7o", TID.checkoutStreet, form.street, set("street"), { placeholder: "Rua, avenida\u2026" }),
|
|
3869
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
3870
|
+
field("N\xFAmero", "checkout-number", form.number, set("number"), { placeholder: "N\xFAmero" }),
|
|
3871
|
+
field("Complemento", "checkout-complement", form.complement, set("complement"), { placeholder: "Apto, bloco (opcional)" })
|
|
3872
|
+
] }),
|
|
3873
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
3874
|
+
field("Bairro", "checkout-district", form.district, set("district"), { placeholder: "Bairro" }),
|
|
3875
|
+
field("UF", "checkout-state", form.state, set("state"), { placeholder: "UF" })
|
|
3876
|
+
] }),
|
|
3877
|
+
field("Cidade", TID.checkoutCity, form.city, set("city"), { placeholder: "Cidade" })
|
|
3400
3878
|
] })
|
|
3401
3879
|
] }),
|
|
3402
3880
|
/* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
|
|
3403
3881
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
|
|
3404
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
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"}`,
|
|
3409
|
-
children: [
|
|
3410
|
-
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => selectPayment(method.id), className: "block w-full text-left", children: [
|
|
3411
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3412
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3413
|
-
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.CreditCard, { className: "h-4 w-4" }),
|
|
3414
|
-
method.label
|
|
3415
|
-
] }),
|
|
3416
|
-
paymentMode === "saved" && selectedPaymentId === method.id && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3417
|
-
] }),
|
|
3418
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
|
|
3419
|
-
"Expira em ",
|
|
3420
|
-
method.expiry
|
|
3421
|
-
] })
|
|
3422
|
-
] }),
|
|
3423
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3424
|
-
"button",
|
|
3425
|
-
{
|
|
3426
|
-
type: "button",
|
|
3427
|
-
onClick: () => removePayment(method.id),
|
|
3428
|
-
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",
|
|
3429
|
-
"aria-label": `Remover cart\xE3o ${method.label}`,
|
|
3430
|
-
title: "Remover cart\xE3o",
|
|
3431
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Trash2, { className: "h-4 w-4" })
|
|
3432
|
-
}
|
|
3433
|
-
)
|
|
3434
|
-
]
|
|
3435
|
-
},
|
|
3436
|
-
method.id
|
|
3437
|
-
)),
|
|
3438
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3882
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
|
|
3883
|
+
const copy = PAYMENT_LABELS[method];
|
|
3884
|
+
const selected = paymentMethod === method;
|
|
3885
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3439
3886
|
"button",
|
|
3440
3887
|
{
|
|
3441
3888
|
type: "button",
|
|
3442
|
-
|
|
3443
|
-
|
|
3889
|
+
role: "radio",
|
|
3890
|
+
"aria-checked": selected,
|
|
3891
|
+
"data-testid": `checkout-payment-${method}`,
|
|
3892
|
+
onClick: () => setPaymentMethod(method),
|
|
3893
|
+
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"}`,
|
|
3444
3894
|
children: [
|
|
3445
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-
|
|
3446
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3447
|
-
|
|
3895
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
|
|
3896
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
3897
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.CreditCard, { className: "h-4 w-4" }),
|
|
3898
|
+
copy.label
|
|
3899
|
+
] }),
|
|
3900
|
+
selected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
|
|
3448
3901
|
] }),
|
|
3449
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-muted-foreground", children:
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-muted-foreground", children: copy.hint })
|
|
3450
3903
|
]
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
placeholder: "N\xFAmero do cart\xE3o",
|
|
3457
|
-
inputMode: "numeric"
|
|
3458
|
-
}),
|
|
3459
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
3460
|
-
field("Validade", TID.checkoutExpiry, form.expiry, set("expiry"), { placeholder: "MM/AA" }),
|
|
3461
|
-
field("CVC", TID.checkoutCvc, form.cvc, set("cvc"), { placeholder: "CVC", inputMode: "numeric" })
|
|
3462
|
-
] })
|
|
3463
|
-
] }),
|
|
3464
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Pagamento de demonstra\xE7\xE3o - nenhuma cobran\xE7a \xE9 feita." })
|
|
3904
|
+
},
|
|
3905
|
+
method
|
|
3906
|
+
);
|
|
3907
|
+
}) }),
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.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." })
|
|
3465
3909
|
] }),
|
|
3466
3910
|
error && /* @__PURE__ */ jsxRuntime.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 })
|
|
3467
3911
|
] }),
|
|
@@ -3815,11 +4259,11 @@ function SuccessMark() {
|
|
|
3815
4259
|
}
|
|
3816
4260
|
function OrderConfirmationPage({ orderId }) {
|
|
3817
4261
|
const config = useStorefrontConfig();
|
|
3818
|
-
const [order, setOrder] =
|
|
3819
|
-
const [loading, setLoading] =
|
|
4262
|
+
const [order, setOrder] = React7.useState(null);
|
|
4263
|
+
const [loading, setLoading] = React7.useState(true);
|
|
3820
4264
|
const money = (v) => formatMoney(v, config.currency, config.locale);
|
|
3821
4265
|
useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
|
|
3822
|
-
|
|
4266
|
+
React7.useEffect(() => {
|
|
3823
4267
|
let cancelled = false;
|
|
3824
4268
|
runtime.getShopProvider().getOrder(orderId).then((o) => {
|
|
3825
4269
|
if (!cancelled) setOrder(o);
|
|
@@ -3951,11 +4395,11 @@ function OrderConfirmationPage({ orderId }) {
|
|
|
3951
4395
|
function useMyOrders() {
|
|
3952
4396
|
const customerId = useSessionStore((s) => s.customerId);
|
|
3953
4397
|
const email = useSessionStore((s) => s.email);
|
|
3954
|
-
const [orders, setOrders] =
|
|
3955
|
-
const [loading, setLoading] =
|
|
3956
|
-
const [tick, setTick] =
|
|
3957
|
-
const refresh =
|
|
3958
|
-
|
|
4398
|
+
const [orders, setOrders] = React7.useState([]);
|
|
4399
|
+
const [loading, setLoading] = React7.useState(true);
|
|
4400
|
+
const [tick, setTick] = React7.useState(0);
|
|
4401
|
+
const refresh = React7.useCallback(() => setTick((t) => t + 1), []);
|
|
4402
|
+
React7.useEffect(() => {
|
|
3959
4403
|
let cancelled = false;
|
|
3960
4404
|
if (!customerId && !email) {
|
|
3961
4405
|
setOrders([]);
|
|
@@ -3977,12 +4421,12 @@ function useMyOrders() {
|
|
|
3977
4421
|
return { orders, loading, refresh };
|
|
3978
4422
|
}
|
|
3979
4423
|
function AuthForm() {
|
|
3980
|
-
const [mode, setMode] =
|
|
3981
|
-
const [email, setEmail] =
|
|
3982
|
-
const [password, setPassword] =
|
|
3983
|
-
const [name, setName] =
|
|
3984
|
-
const [busy, setBusy] =
|
|
3985
|
-
const [error, setError] =
|
|
4424
|
+
const [mode, setMode] = React7.useState("signin");
|
|
4425
|
+
const [email, setEmail] = React7.useState("");
|
|
4426
|
+
const [password, setPassword] = React7.useState("");
|
|
4427
|
+
const [name, setName] = React7.useState("");
|
|
4428
|
+
const [busy, setBusy] = React7.useState(false);
|
|
4429
|
+
const [error, setError] = React7.useState(null);
|
|
3986
4430
|
async function submit(e) {
|
|
3987
4431
|
e.preventDefault();
|
|
3988
4432
|
setError(null);
|
|
@@ -4124,9 +4568,9 @@ function OrdersPanel() {
|
|
|
4124
4568
|
}
|
|
4125
4569
|
function ProfilePanel() {
|
|
4126
4570
|
const session = useSessionStore();
|
|
4127
|
-
const [name, setName] =
|
|
4128
|
-
const [saving, setSaving] =
|
|
4129
|
-
const [saved, setSaved] =
|
|
4571
|
+
const [name, setName] = React7.useState(session.name ?? "");
|
|
4572
|
+
const [saving, setSaving] = React7.useState(false);
|
|
4573
|
+
const [saved, setSaved] = React7.useState(false);
|
|
4130
4574
|
async function save(e) {
|
|
4131
4575
|
e.preventDefault();
|
|
4132
4576
|
const trimmed = name.trim();
|
|
@@ -4222,7 +4666,7 @@ var SECTION_SUBTITLE = {
|
|
|
4222
4666
|
function MyPurchasesPage() {
|
|
4223
4667
|
const config = useStorefrontConfig();
|
|
4224
4668
|
const session = useSessionStore();
|
|
4225
|
-
const [section, setSection] =
|
|
4669
|
+
const [section, setSection] = React7.useState("orders");
|
|
4226
4670
|
useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
|
|
4227
4671
|
if (!session.email) {
|
|
4228
4672
|
return /* @__PURE__ */ jsxRuntime.jsx("main", { className: "mx-auto max-w-3xl px-4 py-12 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(AuthForm, {}) });
|
|
@@ -4285,7 +4729,7 @@ function PolicyPage({ kind }) {
|
|
|
4285
4729
|
] }) })
|
|
4286
4730
|
] });
|
|
4287
4731
|
}
|
|
4288
|
-
var StorefrontErrorBoundary = class extends
|
|
4732
|
+
var StorefrontErrorBoundary = class extends React7__default.default.Component {
|
|
4289
4733
|
constructor() {
|
|
4290
4734
|
super(...arguments);
|
|
4291
4735
|
this.state = { error: null };
|
|
@@ -4329,7 +4773,7 @@ var ACCENT = {
|
|
|
4329
4773
|
function ToastCard({ toast: toast2 }) {
|
|
4330
4774
|
const dismiss = useToastStore((s) => s.dismiss);
|
|
4331
4775
|
const Icon2 = ICON[toast2.variant];
|
|
4332
|
-
|
|
4776
|
+
React7.useEffect(() => {
|
|
4333
4777
|
const timer = window.setTimeout(() => dismiss(toast2.id), toast2.durationMs);
|
|
4334
4778
|
return () => window.clearTimeout(timer);
|
|
4335
4779
|
}, [toast2.id, toast2.durationMs, dismiss]);
|
|
@@ -4440,6 +4884,15 @@ function initStorefrontRuntime(config) {
|
|
|
4440
4884
|
} else {
|
|
4441
4885
|
runtime.setShopProvider(mock.createMockShopProvider(buildMockSeed(config)));
|
|
4442
4886
|
}
|
|
4887
|
+
const reconcileCart = () => {
|
|
4888
|
+
const provider = runtime.getShopProvider();
|
|
4889
|
+
void useCartStore.getState().reconcile(async (line) => {
|
|
4890
|
+
const product = await provider.getProduct(line.productId);
|
|
4891
|
+
return product != null;
|
|
4892
|
+
});
|
|
4893
|
+
};
|
|
4894
|
+
if (useCartStore.persist.hasHydrated()) reconcileCart();
|
|
4895
|
+
else useCartStore.persist.onFinishHydration(reconcileCart);
|
|
4443
4896
|
if (config.supabaseUrl || config.supabaseAnonKey) {
|
|
4444
4897
|
console.warn(
|
|
4445
4898
|
"@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path."
|
|
@@ -4571,9 +5024,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
4571
5024
|
};
|
|
4572
5025
|
}
|
|
4573
5026
|
function StorefrontScaffold({ manifest, surface }) {
|
|
4574
|
-
const config =
|
|
4575
|
-
const resolved =
|
|
4576
|
-
const inited =
|
|
5027
|
+
const config = React7__default.default.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
|
|
5028
|
+
const resolved = React7__default.default.useMemo(() => resolveConfig(config), [config]);
|
|
5029
|
+
const inited = React7__default.default.useRef(false);
|
|
4577
5030
|
if (!inited.current) {
|
|
4578
5031
|
initStorefrontRuntime(config);
|
|
4579
5032
|
inited.current = true;
|
|
@@ -4799,11 +5252,73 @@ var atelierTemplate = {
|
|
|
4799
5252
|
]
|
|
4800
5253
|
})
|
|
4801
5254
|
};
|
|
5255
|
+
var foodTemplate = {
|
|
5256
|
+
id: "food",
|
|
5257
|
+
inspiration: "Delivery / restaurant storefront",
|
|
5258
|
+
announcement: "ENTREGA GR\xC1TIS ACIMA DE R$ 150 \xB7 PE\xC7A J\xC1",
|
|
5259
|
+
theme: {
|
|
5260
|
+
name: "food",
|
|
5261
|
+
colors: {
|
|
5262
|
+
background: "30 33% 97%",
|
|
5263
|
+
foreground: "20 14% 15%",
|
|
5264
|
+
primary: "14 84% 50%",
|
|
5265
|
+
primaryForeground: "0 0% 100%",
|
|
5266
|
+
card: "30 33% 99%",
|
|
5267
|
+
cardForeground: "20 14% 15%",
|
|
5268
|
+
muted: "28 20% 92%",
|
|
5269
|
+
mutedForeground: "22 10% 42%",
|
|
5270
|
+
border: "28 18% 85%",
|
|
5271
|
+
headerBackground: "20 14% 11%",
|
|
5272
|
+
headerForeground: "30 24% 96%",
|
|
5273
|
+
announcementBackground: "14 84% 50%",
|
|
5274
|
+
announcementForeground: "0 0% 100%"
|
|
5275
|
+
},
|
|
5276
|
+
font: { heading: "Poppins", body: "Inter" },
|
|
5277
|
+
radius: "round",
|
|
5278
|
+
header: { variant: "classic" },
|
|
5279
|
+
productCard: { style: "card", imageAspect: "square" }
|
|
5280
|
+
},
|
|
5281
|
+
home: (storeName, images) => ({
|
|
5282
|
+
sections: [
|
|
5283
|
+
{
|
|
5284
|
+
type: "hero",
|
|
5285
|
+
variant: "banner",
|
|
5286
|
+
height: "tall",
|
|
5287
|
+
slides: [
|
|
5288
|
+
{ title: "Sabor de verdade, na sua porta", subtitle: `${storeName} \u2014 feito na hora, do jeito que voc\xEA ama.`, cta: "Ver card\xE1pio", href: "/catalog", hue: 20, image: images?.hero?.[0] }
|
|
5289
|
+
]
|
|
5290
|
+
},
|
|
5291
|
+
{
|
|
5292
|
+
type: "benefits",
|
|
5293
|
+
items: [
|
|
5294
|
+
{ icon: "Flame", title: "Feito na hora", text: "Preparado com capricho" },
|
|
5295
|
+
{ icon: "Truck", title: "Entrega r\xE1pida", text: "Frete gr\xE1tis acima de R$ 150" },
|
|
5296
|
+
{ icon: "CreditCard", title: "Pagamento f\xE1cil", text: "Pix, cart\xE3o e mais" }
|
|
5297
|
+
]
|
|
5298
|
+
},
|
|
5299
|
+
{ type: "categories", style: "tiles", title: "Nosso card\xE1pio" },
|
|
5300
|
+
{ type: "products", title: "Mais pedidos", eyebrow: "favoritos da casa", filter: "all", limit: 8 },
|
|
5301
|
+
{ type: "banner", title: "Combo do dia", eyebrow: "oferta", subtitle: "Sele\xE7\xE3o especial com desconto", cta: "Aproveitar", hue: 14, image: images?.banners?.[0] },
|
|
5302
|
+
{ type: "products", title: "Novidades no card\xE1pio", filter: "new", limit: 4 },
|
|
5303
|
+
{
|
|
5304
|
+
type: "testimonials",
|
|
5305
|
+
title: "O que dizem por a\xED",
|
|
5306
|
+
items: [
|
|
5307
|
+
{ quote: "Chegou quentinho e absurdo de bom. Virei cliente!", author: "Rafael M." },
|
|
5308
|
+
{ quote: "Sabor de restaurante em casa, sem complica\xE7\xE3o.", author: "Beatriz L." },
|
|
5309
|
+
{ quote: "Entrega r\xE1pida e por\xE7\xE3o generosa. Recomendo demais.", author: "Diego S." }
|
|
5310
|
+
]
|
|
5311
|
+
},
|
|
5312
|
+
{ type: "newsletter", title: "Receba nossas ofertas", subtitle: "Promo\xE7\xF5es e novidades do card\xE1pio, direto no seu e-mail." }
|
|
5313
|
+
]
|
|
5314
|
+
})
|
|
5315
|
+
};
|
|
4802
5316
|
var storefrontTemplates = {
|
|
4803
5317
|
mare: mareTemplate,
|
|
4804
5318
|
sertao: sertaoTemplate,
|
|
4805
5319
|
volt: voltTemplate,
|
|
4806
|
-
atelier: atelierTemplate
|
|
5320
|
+
atelier: atelierTemplate,
|
|
5321
|
+
food: foodTemplate
|
|
4807
5322
|
};
|
|
4808
5323
|
|
|
4809
5324
|
Object.defineProperty(exports, "getSupabaseClientOptional", {
|
|
@@ -4860,6 +5375,7 @@ exports.defineStorefrontConfig = defineStorefrontConfig;
|
|
|
4860
5375
|
exports.defineStorefrontRoutes = defineStorefrontRoutes;
|
|
4861
5376
|
exports.defineStorefrontSections = defineStorefrontSections;
|
|
4862
5377
|
exports.establishCustomerSession = establishCustomerSession;
|
|
5378
|
+
exports.foodTemplate = foodTemplate;
|
|
4863
5379
|
exports.formatMoney = formatMoney;
|
|
4864
5380
|
exports.formatProductOptionSelection = formatProductOptionSelection;
|
|
4865
5381
|
exports.getCustomerAuthAdapter = getCustomerAuthAdapter;
|