@fayz-ai/storefront 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/auth.d.ts +12 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/blocks.d.ts +3 -3
- package/dist/blocks.d.ts.map +1 -1
- package/dist/component-contracts.d.ts +124 -0
- package/dist/component-contracts.d.ts.map +1 -0
- package/dist/component-selectors.d.ts +61 -0
- package/dist/component-selectors.d.ts.map +1 -0
- package/dist/components/CartDrawer.d.ts.map +1 -1
- package/dist/components/ProductCard.d.ts +2 -5
- package/dist/components/ProductCard.d.ts.map +1 -1
- package/dist/components/ProductEnquiryForm.d.ts +8 -0
- package/dist/components/ProductEnquiryForm.d.ts.map +1 -0
- package/dist/components/ProductGrid.d.ts.map +1 -1
- package/dist/components/RelatedProducts.d.ts +1 -1
- package/dist/components/RelatedProducts.d.ts.map +1 -1
- package/dist/components/SmoothImage.d.ts +7 -0
- package/dist/components/SmoothImage.d.ts.map +1 -0
- package/dist/components/StorefrontFooter.d.ts.map +1 -1
- package/dist/components/StorefrontHeader.d.ts.map +1 -1
- package/dist/components/sections/CategoryShowcase.d.ts.map +1 -1
- package/dist/components/sections/HeroSection.d.ts.map +1 -1
- package/dist/components/sections/MediaCarousel.d.ts +11 -0
- package/dist/components/sections/MediaCarousel.d.ts.map +1 -0
- package/dist/components/sections/MiscSections.d.ts.map +1 -1
- package/dist/components/sections/ProductRail.d.ts.map +1 -1
- package/dist/components/sections/ProductSlider.d.ts +15 -0
- package/dist/components/sections/ProductSlider.d.ts.map +1 -0
- package/dist/config.d.ts +38 -21
- package/dist/config.d.ts.map +1 -1
- package/dist/createStorefrontApp.d.ts +12 -0
- package/dist/createStorefrontApp.d.ts.map +1 -1
- package/dist/define.d.ts +17 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useStorefront.d.ts +35 -0
- package/dist/hooks/useStorefront.d.ts.map +1 -0
- package/dist/index.cjs +1113 -277
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +21 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1006 -188
- package/dist/index.js.map +1 -1
- package/dist/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/pages/HomePage.d.ts +5 -2
- package/dist/pages/HomePage.d.ts.map +1 -1
- package/dist/pages/MyPurchasesPage.d.ts.map +1 -1
- package/dist/pages/OrderConfirmationPage.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/sections.d.ts +45 -2
- package/dist/sections.d.ts.map +1 -1
- package/dist/stores/cart.store.d.ts +5 -0
- package/dist/stores/cart.store.d.ts.map +1 -1
- package/dist/testids.d.ts +21 -0
- package/dist/testids.d.ts.map +1 -1
- package/dist/theme.d.ts +21 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/workflows/checkout.d.ts.map +1 -1
- package/package.json +10 -6
- package/src/auth.ts +47 -10
- package/src/blocks.tsx +7 -3
- package/src/component-contracts.ts +140 -0
- package/src/component-selectors.ts +31 -0
- package/src/components/CartDrawer.tsx +2 -1
- package/src/components/ProductCard.tsx +39 -23
- package/src/components/ProductEnquiryForm.tsx +175 -0
- package/src/components/ProductGrid.tsx +12 -3
- package/src/components/RelatedProducts.tsx +13 -4
- package/src/components/SmoothImage.tsx +59 -0
- package/src/components/StorefrontFooter.tsx +24 -14
- package/src/components/StorefrontHeader.tsx +74 -42
- package/src/components/sections/CategoryShowcase.tsx +3 -2
- package/src/components/sections/HeroSection.tsx +18 -2
- package/src/components/sections/MediaCarousel.tsx +141 -0
- package/src/components/sections/MiscSections.tsx +2 -1
- package/src/components/sections/ProductRail.tsx +11 -3
- package/src/components/sections/ProductSlider.tsx +119 -0
- package/src/config.ts +65 -24
- package/src/createStorefrontApp.tsx +80 -10
- package/src/define.ts +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useStorefront.ts +58 -0
- package/src/index.ts +62 -11
- package/src/pages/CheckoutPage.tsx +2 -1
- package/src/pages/HomePage.tsx +6 -2
- package/src/pages/MyPurchasesPage.tsx +2 -1
- package/src/pages/OrderConfirmationPage.tsx +2 -1
- package/src/pages/ProductDetailPage.tsx +138 -62
- package/src/presets.ts +66 -0
- package/src/scaffold.tsx +1 -1
- package/src/sections.ts +49 -2
- package/src/stores/cart.store.ts +16 -1
- package/src/testids.ts +21 -0
- package/src/theme.ts +36 -2
- package/src/workflows/checkout.ts +5 -2
- package/dist/slot-contracts.d.ts +0 -34
- package/dist/slot-contracts.d.ts.map +0 -1
- package/src/slot-contracts.ts +0 -20
package/dist/index.cjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
var runtime = require('@fayz-ai/shop/runtime');
|
|
4
4
|
var mock = require('@fayz-ai/shop/mock');
|
|
5
|
-
var
|
|
5
|
+
var pluginAuth = require('@fayz-ai/plugin-auth');
|
|
6
6
|
var zustand = require('zustand');
|
|
7
7
|
var middleware = require('zustand/middleware');
|
|
8
|
-
var
|
|
8
|
+
var React6 = require('react');
|
|
9
9
|
var core = require('@fayz-ai/core');
|
|
10
10
|
var LucideIcons = require('lucide-react');
|
|
11
11
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -31,7 +31,7 @@ function _interopNamespace(e) {
|
|
|
31
31
|
return Object.freeze(n);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
var
|
|
34
|
+
var React6__default = /*#__PURE__*/_interopDefault(React6);
|
|
35
35
|
var LucideIcons__namespace = /*#__PURE__*/_interopNamespace(LucideIcons);
|
|
36
36
|
|
|
37
37
|
// src/createStorefrontApp.tsx
|
|
@@ -50,14 +50,31 @@ var useSessionStore = zustand.create()(
|
|
|
50
50
|
|
|
51
51
|
// src/auth.ts
|
|
52
52
|
var _adapter = null;
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
53
|
+
function isAuthAdapter(value) {
|
|
54
|
+
return Boolean(
|
|
55
|
+
value && typeof value === "object" && "getSession" in value && "signIn" in value && "signOut" in value
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
function normalizeAuthConfig(configured) {
|
|
59
|
+
if (!configured) return {};
|
|
60
|
+
if (typeof configured === "string" || isAuthAdapter(configured)) return { adapter: configured };
|
|
61
|
+
return configured;
|
|
62
|
+
}
|
|
63
|
+
function resolveAuthAdapter(configured, fallback) {
|
|
64
|
+
const config = normalizeAuthConfig(configured);
|
|
65
|
+
return pluginAuth.createAuthRuntime({
|
|
66
|
+
provider: config.provider ?? config.adapter ?? (fallback?.supabaseUrl ? "supabase" : "mock"),
|
|
67
|
+
adapter: config.adapter,
|
|
68
|
+
routes: config.routes,
|
|
69
|
+
requireAuth: false,
|
|
70
|
+
supabase: {
|
|
71
|
+
url: config.supabase?.url ?? fallback?.supabaseUrl,
|
|
72
|
+
anonKey: config.supabase?.anonKey ?? fallback?.supabaseAnonKey,
|
|
73
|
+
client: config.supabase?.client,
|
|
74
|
+
callbackUrl: config.supabase?.callbackUrl,
|
|
75
|
+
resetPasswordUrl: config.supabase?.resetPasswordUrl
|
|
76
|
+
}
|
|
77
|
+
}).adapter;
|
|
61
78
|
}
|
|
62
79
|
function initCustomerAuth(adapter2) {
|
|
63
80
|
_adapter = adapter2;
|
|
@@ -74,7 +91,7 @@ function initCustomerAuth(adapter2) {
|
|
|
74
91
|
});
|
|
75
92
|
}
|
|
76
93
|
function getCustomerAuthAdapter() {
|
|
77
|
-
if (!_adapter) _adapter =
|
|
94
|
+
if (!_adapter) _adapter = pluginAuth.createMockAuthAdapter();
|
|
78
95
|
return _adapter;
|
|
79
96
|
}
|
|
80
97
|
async function resolveShopCustomer(email, name) {
|
|
@@ -128,18 +145,37 @@ async function signOutCustomer() {
|
|
|
128
145
|
}
|
|
129
146
|
function resolveConfig(config) {
|
|
130
147
|
const catalogPath = config.home ? "/catalog" : "/";
|
|
148
|
+
const commerceMode = config.commerceMode ?? "checkout";
|
|
149
|
+
const checkoutEnabled = commerceMode === "checkout";
|
|
131
150
|
return {
|
|
132
151
|
...config,
|
|
133
152
|
currency: config.currency ?? "BRL",
|
|
134
153
|
locale: config.locale ?? "pt-BR",
|
|
135
154
|
shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
|
|
136
155
|
payments: { mode: config.payments?.mode ?? "mock" },
|
|
156
|
+
commerceMode,
|
|
157
|
+
enquiry: {
|
|
158
|
+
label: config.enquiry?.label ?? "Contact me",
|
|
159
|
+
successMessage: config.enquiry?.successMessage ?? "Thanks. We received your enquiry.",
|
|
160
|
+
subjectPrefix: config.enquiry?.subjectPrefix ?? "Product enquiry",
|
|
161
|
+
email: config.enquiry?.email,
|
|
162
|
+
whatsappUrl: config.enquiry?.whatsappUrl,
|
|
163
|
+
requirePhone: config.enquiry?.requirePhone
|
|
164
|
+
},
|
|
137
165
|
features: {
|
|
138
166
|
// Auto-enable the coupon UI when the store actually ships discount codes
|
|
139
167
|
// (via config.discounts or a seeded catalog), unless explicitly overridden —
|
|
140
168
|
// otherwise a store can advertise a coupon with no field to enter it.
|
|
141
169
|
discounts: config.features?.discounts ?? Boolean(config.discounts?.length || config.catalog?.discounts?.length),
|
|
142
|
-
accounts: config.features?.accounts ??
|
|
170
|
+
accounts: config.features?.accounts ?? checkoutEnabled,
|
|
171
|
+
cart: config.features?.cart ?? checkoutEnabled,
|
|
172
|
+
checkout: config.features?.checkout ?? checkoutEnabled
|
|
173
|
+
},
|
|
174
|
+
imageLoading: {
|
|
175
|
+
mode: config.imageLoading?.mode ?? "fade",
|
|
176
|
+
durationMs: config.imageLoading?.durationMs ?? 420,
|
|
177
|
+
easing: config.imageLoading?.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
178
|
+
blur: config.imageLoading?.blur ?? true
|
|
143
179
|
},
|
|
144
180
|
catalogPath,
|
|
145
181
|
nav: config.nav ?? (config.home ? [
|
|
@@ -148,20 +184,26 @@ function resolveConfig(config) {
|
|
|
148
184
|
] : [{ label: "Loja", to: "/" }])
|
|
149
185
|
};
|
|
150
186
|
}
|
|
151
|
-
var StorefrontConfigContext =
|
|
187
|
+
var StorefrontConfigContext = React6.createContext(null);
|
|
152
188
|
var StorefrontConfigProvider = StorefrontConfigContext.Provider;
|
|
153
189
|
function useStorefrontConfig() {
|
|
154
|
-
const ctx =
|
|
190
|
+
const ctx = React6.useContext(StorefrontConfigContext);
|
|
155
191
|
if (!ctx) throw new Error("useStorefrontConfig must be used inside createStorefrontApp");
|
|
156
192
|
return ctx;
|
|
157
193
|
}
|
|
194
|
+
function useStorefrontConfigOptional() {
|
|
195
|
+
return React6.useContext(StorefrontConfigContext);
|
|
196
|
+
}
|
|
197
|
+
function getStorefrontComponents(config) {
|
|
198
|
+
return config.components ?? {};
|
|
199
|
+
}
|
|
158
200
|
var adapter = core.hashRouterAdapter();
|
|
159
201
|
function navigateTo(to) {
|
|
160
202
|
adapter.navigate(to);
|
|
161
203
|
}
|
|
162
204
|
function useHashPath() {
|
|
163
|
-
const [path, setPath] =
|
|
164
|
-
|
|
205
|
+
const [path, setPath] = React6.useState(() => adapter.getCurrentPath() || "/");
|
|
206
|
+
React6.useEffect(() => {
|
|
165
207
|
const unsub = adapter.onPathChange((p) => {
|
|
166
208
|
setPath(p || "/");
|
|
167
209
|
window.scrollTo(0, 0);
|
|
@@ -185,93 +227,7 @@ function matchPath(pattern, path) {
|
|
|
185
227
|
return params;
|
|
186
228
|
}
|
|
187
229
|
function Link({ to, children, ...rest }) {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
var RADIUS_MAP = {
|
|
191
|
-
none: { button: "0px", card: "0px", input: "0px" },
|
|
192
|
-
soft: { button: "6px", card: "10px", input: "6px" },
|
|
193
|
-
round: { button: "10px", card: "16px", input: "10px" },
|
|
194
|
-
pill: { button: "999px", card: "20px", input: "999px" }
|
|
195
|
-
};
|
|
196
|
-
var COLOR_VAR_MAP = {
|
|
197
|
-
background: "--background",
|
|
198
|
-
foreground: "--foreground",
|
|
199
|
-
primary: "--primary",
|
|
200
|
-
primaryForeground: "--primary-foreground",
|
|
201
|
-
card: "--card",
|
|
202
|
-
cardForeground: "--card-foreground",
|
|
203
|
-
muted: "--muted",
|
|
204
|
-
mutedForeground: "--muted-foreground",
|
|
205
|
-
border: "--border",
|
|
206
|
-
headerBackground: "--sf-header-bg",
|
|
207
|
-
headerForeground: "--sf-header-fg",
|
|
208
|
-
announcementBackground: "--sf-announcement-bg",
|
|
209
|
-
announcementForeground: "--sf-announcement-fg"
|
|
210
|
-
};
|
|
211
|
-
function themeToCss(theme) {
|
|
212
|
-
const lines = [];
|
|
213
|
-
for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
|
|
214
|
-
const value = theme.colors?.[key];
|
|
215
|
-
if (value) lines.push(`${cssVar}: ${value};`);
|
|
216
|
-
}
|
|
217
|
-
if (!theme.colors?.headerBackground && theme.colors?.background) {
|
|
218
|
-
lines.push(`--sf-header-bg: ${theme.colors.background};`);
|
|
219
|
-
}
|
|
220
|
-
if (!theme.colors?.headerForeground && theme.colors?.foreground) {
|
|
221
|
-
lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
|
|
222
|
-
}
|
|
223
|
-
if (theme.font) {
|
|
224
|
-
const fallback = theme.font.fallback ?? "sans-serif";
|
|
225
|
-
lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
|
|
226
|
-
lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
|
|
227
|
-
}
|
|
228
|
-
const radius = RADIUS_MAP[theme.radius ?? "soft"];
|
|
229
|
-
lines.push(`--button-radius: ${radius.button};`);
|
|
230
|
-
lines.push(`--card-radius: ${radius.card};`);
|
|
231
|
-
lines.push(`--input-radius: ${radius.input};`);
|
|
232
|
-
lines.push(`--sf-radius-button: ${radius.button};`);
|
|
233
|
-
lines.push(`--sf-radius-card: ${radius.card};`);
|
|
234
|
-
lines.push(`--sf-radius-input: ${radius.input};`);
|
|
235
|
-
let css = `:root {
|
|
236
|
-
${lines.join("\n ")}
|
|
237
|
-
}
|
|
238
|
-
`;
|
|
239
|
-
css += `body { font-family: var(--font-family); }
|
|
240
|
-
`;
|
|
241
|
-
css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
|
|
242
|
-
`;
|
|
243
|
-
if (theme.uppercaseButtons) {
|
|
244
|
-
css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
|
|
245
|
-
`;
|
|
246
|
-
}
|
|
247
|
-
if (theme.header?.uppercaseNav) {
|
|
248
|
-
css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
|
|
249
|
-
`;
|
|
250
|
-
}
|
|
251
|
-
return css;
|
|
252
|
-
}
|
|
253
|
-
function googleFontsHref(theme) {
|
|
254
|
-
if (!theme.font) return null;
|
|
255
|
-
const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body];
|
|
256
|
-
const unique = [...new Set(families)];
|
|
257
|
-
const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
|
|
258
|
-
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
259
|
-
}
|
|
260
|
-
function StorefrontThemeStyle({ theme }) {
|
|
261
|
-
const fontsHref = googleFontsHref(theme);
|
|
262
|
-
React18.useEffect(() => {
|
|
263
|
-
if (!fontsHref) return;
|
|
264
|
-
const id = "sf-theme-fonts";
|
|
265
|
-
let link = document.getElementById(id);
|
|
266
|
-
if (!link) {
|
|
267
|
-
link = document.createElement("link");
|
|
268
|
-
link.id = id;
|
|
269
|
-
link.rel = "stylesheet";
|
|
270
|
-
document.head.appendChild(link);
|
|
271
|
-
}
|
|
272
|
-
link.href = fontsHref;
|
|
273
|
-
}, [fontsHref]);
|
|
274
|
-
return React18__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
230
|
+
return React6__default.default.createElement("a", { href: `#${to}`, ...rest }, children);
|
|
275
231
|
}
|
|
276
232
|
|
|
277
233
|
// src/format.ts
|
|
@@ -325,7 +281,7 @@ function productOptionSelectionKey(selection) {
|
|
|
325
281
|
// src/stores/cart.store.ts
|
|
326
282
|
var useCartStore = zustand.create()(
|
|
327
283
|
middleware.persist(
|
|
328
|
-
(set) => ({
|
|
284
|
+
(set, get) => ({
|
|
329
285
|
lines: [],
|
|
330
286
|
discountCode: null,
|
|
331
287
|
discountPercent: 0,
|
|
@@ -377,7 +333,16 @@ var useCartStore = zustand.create()(
|
|
|
377
333
|
clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
|
|
378
334
|
clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
|
|
379
335
|
openDrawer: () => set({ isOpen: true }),
|
|
380
|
-
closeDrawer: () => set({ isOpen: false, justAddedLineId: null })
|
|
336
|
+
closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
|
|
337
|
+
reconcile: async (resolve) => {
|
|
338
|
+
const lines = get().lines;
|
|
339
|
+
if (lines.length === 0) return;
|
|
340
|
+
const keep = await Promise.all(
|
|
341
|
+
lines.map((line) => resolve(line).catch(() => true))
|
|
342
|
+
);
|
|
343
|
+
const kept = lines.filter((_, i) => keep[i]);
|
|
344
|
+
if (kept.length !== lines.length) set({ lines: kept });
|
|
345
|
+
}
|
|
381
346
|
}),
|
|
382
347
|
{
|
|
383
348
|
name: "fayz.storefront.cart.v1",
|
|
@@ -399,6 +364,102 @@ var selectShipping = (s, cfg) => {
|
|
|
399
364
|
return cfg.shipping.flatRate;
|
|
400
365
|
};
|
|
401
366
|
var selectTotal = (s, cfg) => roundCents(selectSubtotal(s) - selectDiscountTotal(s) + selectShipping(s, cfg));
|
|
367
|
+
var RADIUS_MAP = {
|
|
368
|
+
none: { button: "0px", card: "0px", input: "0px" },
|
|
369
|
+
soft: { button: "6px", card: "10px", input: "6px" },
|
|
370
|
+
round: { button: "10px", card: "16px", input: "10px" },
|
|
371
|
+
pill: { button: "999px", card: "20px", input: "999px" }
|
|
372
|
+
};
|
|
373
|
+
var COLOR_VAR_MAP = {
|
|
374
|
+
background: "--background",
|
|
375
|
+
foreground: "--foreground",
|
|
376
|
+
primary: "--primary",
|
|
377
|
+
primaryForeground: "--primary-foreground",
|
|
378
|
+
card: "--card",
|
|
379
|
+
cardForeground: "--card-foreground",
|
|
380
|
+
muted: "--muted",
|
|
381
|
+
mutedForeground: "--muted-foreground",
|
|
382
|
+
border: "--border",
|
|
383
|
+
headerBackground: "--sf-header-bg",
|
|
384
|
+
headerForeground: "--sf-header-fg",
|
|
385
|
+
announcementBackground: "--sf-announcement-bg",
|
|
386
|
+
announcementForeground: "--sf-announcement-fg",
|
|
387
|
+
accent: "--sf-accent",
|
|
388
|
+
accentForeground: "--sf-accent-foreground"
|
|
389
|
+
};
|
|
390
|
+
function themeToCss(theme) {
|
|
391
|
+
const lines = [];
|
|
392
|
+
for (const [key, cssVar] of Object.entries(COLOR_VAR_MAP)) {
|
|
393
|
+
const value = theme.colors?.[key];
|
|
394
|
+
if (value) lines.push(`${cssVar}: ${value};`);
|
|
395
|
+
}
|
|
396
|
+
if (!theme.colors?.headerBackground && theme.colors?.background) {
|
|
397
|
+
lines.push(`--sf-header-bg: ${theme.colors.background};`);
|
|
398
|
+
}
|
|
399
|
+
if (!theme.colors?.headerForeground && theme.colors?.foreground) {
|
|
400
|
+
lines.push(`--sf-header-fg: ${theme.colors.foreground};`);
|
|
401
|
+
}
|
|
402
|
+
if (theme.font) {
|
|
403
|
+
const fallback = theme.font.fallback ?? "sans-serif";
|
|
404
|
+
lines.push(`--font-family: '${theme.font.body}', ${fallback};`);
|
|
405
|
+
lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`);
|
|
406
|
+
if (theme.font.display) lines.push(`--sf-font-display: '${theme.font.display}', ${fallback};`);
|
|
407
|
+
if (theme.font.serif) lines.push(`--sf-font-serif: '${theme.font.serif}', serif;`);
|
|
408
|
+
}
|
|
409
|
+
for (const [key, value] of Object.entries(theme.tokens ?? {})) {
|
|
410
|
+
const name = key.startsWith("--") ? key : `--${key}`;
|
|
411
|
+
lines.push(`${name}: ${value};`);
|
|
412
|
+
}
|
|
413
|
+
const radius = RADIUS_MAP[theme.radius ?? "soft"];
|
|
414
|
+
lines.push(`--button-radius: ${radius.button};`);
|
|
415
|
+
lines.push(`--card-radius: ${radius.card};`);
|
|
416
|
+
lines.push(`--input-radius: ${radius.input};`);
|
|
417
|
+
lines.push(`--sf-radius-button: ${radius.button};`);
|
|
418
|
+
lines.push(`--sf-radius-card: ${radius.card};`);
|
|
419
|
+
lines.push(`--sf-radius-input: ${radius.input};`);
|
|
420
|
+
let css = `:root {
|
|
421
|
+
${lines.join("\n ")}
|
|
422
|
+
}
|
|
423
|
+
`;
|
|
424
|
+
css += `body { font-family: var(--font-family); }
|
|
425
|
+
`;
|
|
426
|
+
css += `h1, h2, h3, h4, .sf-heading { font-family: var(--sf-font-heading, var(--font-family)); }
|
|
427
|
+
`;
|
|
428
|
+
if (theme.uppercaseButtons) {
|
|
429
|
+
css += `.sf-cta { text-transform: uppercase; letter-spacing: 0.08em; }
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
if (theme.header?.uppercaseNav) {
|
|
433
|
+
css += `.sf-nav-link { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.8rem; }
|
|
434
|
+
`;
|
|
435
|
+
}
|
|
436
|
+
return css;
|
|
437
|
+
}
|
|
438
|
+
function googleFontsHref(theme) {
|
|
439
|
+
if (!theme.font) return null;
|
|
440
|
+
const families = theme.font.googleFonts ?? [theme.font.heading, theme.font.body, theme.font.display, theme.font.serif].filter(
|
|
441
|
+
(f) => Boolean(f)
|
|
442
|
+
);
|
|
443
|
+
const unique = [...new Set(families)];
|
|
444
|
+
const params = unique.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@300;400;500;600;700`).join("&");
|
|
445
|
+
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
446
|
+
}
|
|
447
|
+
function StorefrontThemeStyle({ theme }) {
|
|
448
|
+
const fontsHref = googleFontsHref(theme);
|
|
449
|
+
React6.useEffect(() => {
|
|
450
|
+
if (!fontsHref) return;
|
|
451
|
+
const id = "sf-theme-fonts";
|
|
452
|
+
let link = document.getElementById(id);
|
|
453
|
+
if (!link) {
|
|
454
|
+
link = document.createElement("link");
|
|
455
|
+
link.id = id;
|
|
456
|
+
link.rel = "stylesheet";
|
|
457
|
+
document.head.appendChild(link);
|
|
458
|
+
}
|
|
459
|
+
link.href = fontsHref;
|
|
460
|
+
}, [fontsHref]);
|
|
461
|
+
return React6__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
462
|
+
}
|
|
402
463
|
var initial = {
|
|
403
464
|
search: "",
|
|
404
465
|
categoryId: null,
|
|
@@ -418,9 +479,9 @@ var useCatalogStore = zustand.create()((set) => ({
|
|
|
418
479
|
reset: () => set(initial)
|
|
419
480
|
}));
|
|
420
481
|
function useCategories() {
|
|
421
|
-
const [categories, setCategories] =
|
|
422
|
-
const [loading, setLoading] =
|
|
423
|
-
|
|
482
|
+
const [categories, setCategories] = React6.useState([]);
|
|
483
|
+
const [loading, setLoading] = React6.useState(true);
|
|
484
|
+
React6.useEffect(() => {
|
|
424
485
|
let cancelled = false;
|
|
425
486
|
runtime.getShopProvider().listCategories().then((data) => {
|
|
426
487
|
if (!cancelled) setCategories(data);
|
|
@@ -439,9 +500,9 @@ function prefersReducedMotion() {
|
|
|
439
500
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
440
501
|
}
|
|
441
502
|
function useInView(margin = "0px 0px -8% 0px") {
|
|
442
|
-
const ref =
|
|
443
|
-
const [inView, setInView] =
|
|
444
|
-
|
|
503
|
+
const ref = React6.useRef(null);
|
|
504
|
+
const [inView, setInView] = React6.useState(() => prefersReducedMotion());
|
|
505
|
+
React6.useEffect(() => {
|
|
445
506
|
if (inView) return;
|
|
446
507
|
const el = ref.current;
|
|
447
508
|
if (!el || typeof IntersectionObserver === "undefined") {
|
|
@@ -476,8 +537,8 @@ function Reveal({ delay = 0, children, className, style, ...rest }) {
|
|
|
476
537
|
);
|
|
477
538
|
}
|
|
478
539
|
function useScrolled(threshold = 8) {
|
|
479
|
-
const [scrolled, setScrolled] =
|
|
480
|
-
|
|
540
|
+
const [scrolled, setScrolled] = React6.useState(false);
|
|
541
|
+
React6.useEffect(() => {
|
|
481
542
|
const onScroll = () => setScrolled(window.scrollY > threshold);
|
|
482
543
|
onScroll();
|
|
483
544
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
@@ -486,9 +547,9 @@ function useScrolled(threshold = 8) {
|
|
|
486
547
|
return scrolled;
|
|
487
548
|
}
|
|
488
549
|
function usePopOnChange(value) {
|
|
489
|
-
const [pop, setPop] =
|
|
490
|
-
const prev =
|
|
491
|
-
|
|
550
|
+
const [pop, setPop] = React6.useState(false);
|
|
551
|
+
const prev = React6.useRef(value);
|
|
552
|
+
React6.useEffect(() => {
|
|
492
553
|
if (prev.current !== value && value > 0) {
|
|
493
554
|
setPop(true);
|
|
494
555
|
const t = setTimeout(() => setPop(false), 400);
|
|
@@ -497,7 +558,7 @@ function usePopOnChange(value) {
|
|
|
497
558
|
prev.current = value;
|
|
498
559
|
return void 0;
|
|
499
560
|
}, [value]);
|
|
500
|
-
|
|
561
|
+
React6.useEffect(() => {
|
|
501
562
|
prev.current = value;
|
|
502
563
|
});
|
|
503
564
|
return pop;
|
|
@@ -507,6 +568,7 @@ function usePopOnChange(value) {
|
|
|
507
568
|
var TID = {
|
|
508
569
|
// header
|
|
509
570
|
headerSearch: "header-search",
|
|
571
|
+
headerSearchToggle: "header-search-toggle",
|
|
510
572
|
cartButton: "cart-button",
|
|
511
573
|
cartCount: "cart-count",
|
|
512
574
|
accountLink: "account-link",
|
|
@@ -519,6 +581,13 @@ var TID = {
|
|
|
519
581
|
hero: "home-hero",
|
|
520
582
|
heroPrev: "hero-prev",
|
|
521
583
|
heroNext: "hero-next",
|
|
584
|
+
mediaCarousel: "media-carousel",
|
|
585
|
+
mediaCarouselPrev: "media-carousel-prev",
|
|
586
|
+
mediaCarouselNext: "media-carousel-next",
|
|
587
|
+
productSlider: "product-slider",
|
|
588
|
+
productSliderViewport: "product-slider-viewport",
|
|
589
|
+
productSliderPrev: "product-slider-prev",
|
|
590
|
+
productSliderNext: "product-slider-next",
|
|
522
591
|
categoryShowcase: "category-showcase",
|
|
523
592
|
productRail: "product-rail",
|
|
524
593
|
benefits: "benefits-row",
|
|
@@ -553,6 +622,19 @@ var TID = {
|
|
|
553
622
|
productOptionValue: (id, value) => `product-option-${id.toLowerCase().replace(/\s+/g, "-")}-${value.toLowerCase().replace(/\s+/g, "-")}`,
|
|
554
623
|
pdpQtyInput: "pdp-qty-input",
|
|
555
624
|
pdpAddToCart: "pdp-add-to-cart",
|
|
625
|
+
pdpRoot: "pdp-root",
|
|
626
|
+
pdpGallery: "pdp-gallery",
|
|
627
|
+
pdpActions: "pdp-actions",
|
|
628
|
+
pdpEnquiryButton: "pdp-enquiry-button",
|
|
629
|
+
enquiryForm: "enquiry-form",
|
|
630
|
+
enquiryName: "enquiry-name",
|
|
631
|
+
enquiryEmail: "enquiry-email",
|
|
632
|
+
enquiryPhone: "enquiry-phone",
|
|
633
|
+
enquirySubject: "enquiry-subject",
|
|
634
|
+
enquiryMessage: "enquiry-message",
|
|
635
|
+
enquirySubmit: "enquiry-submit",
|
|
636
|
+
enquirySuccess: "enquiry-success",
|
|
637
|
+
enquiryError: "enquiry-error",
|
|
556
638
|
// cart
|
|
557
639
|
cartDrawer: "cart-drawer",
|
|
558
640
|
cartLine: "cart-line",
|
|
@@ -616,7 +698,7 @@ var headerDivider = {
|
|
|
616
698
|
var ANNOUNCEMENT_KEY = "fayz.storefront.announcement.dismissed";
|
|
617
699
|
function AnnouncementBar() {
|
|
618
700
|
const config = useStorefrontConfig();
|
|
619
|
-
const [dismissed, setDismissed] =
|
|
701
|
+
const [dismissed, setDismissed] = React6.useState(() => {
|
|
620
702
|
try {
|
|
621
703
|
return localStorage.getItem(ANNOUNCEMENT_KEY) === config.announcement;
|
|
622
704
|
} catch {
|
|
@@ -675,12 +757,26 @@ function UtilityBar() {
|
|
|
675
757
|
/* @__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)) })
|
|
676
758
|
] }) });
|
|
677
759
|
}
|
|
678
|
-
function SearchInput({ className }) {
|
|
760
|
+
function SearchInput({ className, iconOnly = false }) {
|
|
679
761
|
const config = useStorefrontConfig();
|
|
680
762
|
const search = useCatalogStore((s) => s.search);
|
|
681
763
|
const setSearch = useCatalogStore((s) => s.setSearch);
|
|
682
764
|
const path = useHashPath();
|
|
683
|
-
|
|
765
|
+
const [open, setOpen] = React6.useState(false);
|
|
766
|
+
if (iconOnly && !open) {
|
|
767
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
768
|
+
"button",
|
|
769
|
+
{
|
|
770
|
+
type: "button",
|
|
771
|
+
"aria-label": "Buscar produtos",
|
|
772
|
+
"data-testid": TID.headerSearchToggle,
|
|
773
|
+
onClick: () => setOpen(true),
|
|
774
|
+
className: "rounded-full p-2.5 transition-opacity hover:opacity-70",
|
|
775
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "h-5 w-5" })
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${iconOnly ? "w-full max-w-xs" : className ?? ""}`, children: [
|
|
684
780
|
/* @__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" }),
|
|
685
781
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
686
782
|
"input",
|
|
@@ -689,10 +785,17 @@ function SearchInput({ className }) {
|
|
|
689
785
|
type: "search",
|
|
690
786
|
placeholder: "Buscar produtos...",
|
|
691
787
|
value: search,
|
|
788
|
+
autoFocus: iconOnly,
|
|
692
789
|
onChange: (e) => {
|
|
693
790
|
setSearch(e.target.value);
|
|
694
791
|
if (path !== config.catalogPath) navigateTo(config.catalogPath);
|
|
695
792
|
},
|
|
793
|
+
onKeyDown: (e) => {
|
|
794
|
+
if (e.key === "Escape" && iconOnly) setOpen(false);
|
|
795
|
+
},
|
|
796
|
+
onBlur: () => {
|
|
797
|
+
if (iconOnly && !search) setOpen(false);
|
|
798
|
+
},
|
|
696
799
|
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",
|
|
697
800
|
style: {
|
|
698
801
|
borderRadius: "var(--sf-radius-input)",
|
|
@@ -707,7 +810,7 @@ function HeaderActions() {
|
|
|
707
810
|
const count = useCartStore(selectCount);
|
|
708
811
|
const openDrawer = useCartStore((s) => s.openDrawer);
|
|
709
812
|
const session = useSessionStore();
|
|
710
|
-
const [accountOpen, setAccountOpen] =
|
|
813
|
+
const [accountOpen, setAccountOpen] = React6.useState(false);
|
|
711
814
|
const pop = usePopOnChange(count);
|
|
712
815
|
const firstName = session.name?.split(/\s+/)[0];
|
|
713
816
|
const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
|
|
@@ -780,7 +883,7 @@ function HeaderActions() {
|
|
|
780
883
|
}
|
|
781
884
|
)
|
|
782
885
|
] }),
|
|
783
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
886
|
+
config.features.cart && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
784
887
|
"button",
|
|
785
888
|
{
|
|
786
889
|
type: "button",
|
|
@@ -807,12 +910,13 @@ function NavLinks({ className }) {
|
|
|
807
910
|
const config = useStorefrontConfig();
|
|
808
911
|
const { categories } = useCategories();
|
|
809
912
|
const setCategoryId = useCatalogStore((s) => s.setCategoryId);
|
|
913
|
+
const showCategories = config.theme?.header?.showCategories !== false;
|
|
810
914
|
const goCategory = (categoryId) => {
|
|
811
915
|
useCatalogStore.getState().reset();
|
|
812
916
|
setCategoryId(categoryId);
|
|
813
917
|
navigateTo(config.catalogPath);
|
|
814
918
|
};
|
|
815
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex items-center gap-6 ${className ?? ""}`, children: [
|
|
919
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex min-w-max items-center gap-6 ${className ?? ""}`, children: [
|
|
816
920
|
config.nav.map((link) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
817
921
|
Link,
|
|
818
922
|
{
|
|
@@ -823,7 +927,7 @@ function NavLinks({ className }) {
|
|
|
823
927
|
},
|
|
824
928
|
link.to
|
|
825
929
|
)),
|
|
826
|
-
categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
930
|
+
showCategories && categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
827
931
|
"button",
|
|
828
932
|
{
|
|
829
933
|
type: "button",
|
|
@@ -843,6 +947,8 @@ function StorefrontHeader() {
|
|
|
843
947
|
const config = useStorefrontConfig();
|
|
844
948
|
const variant = config.theme?.header?.variant ?? "classic";
|
|
845
949
|
const scrolled = useScrolled();
|
|
950
|
+
const showSearch = config.theme?.header?.showSearch !== false;
|
|
951
|
+
const iconSearch = config.theme?.header?.searchStyle === "icon";
|
|
846
952
|
const logo = /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
|
|
847
953
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
848
954
|
"header",
|
|
@@ -856,21 +962,21 @@ function StorefrontHeader() {
|
|
|
856
962
|
// Rio/Flex pattern: centered logo row, nav row below
|
|
857
963
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
858
964
|
/* @__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: [
|
|
859
|
-
/* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }),
|
|
965
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
860
966
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
861
967
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
862
968
|
] }),
|
|
863
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex overflow-x-auto border-t py-2.5 sm:justify-center", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) })
|
|
864
970
|
] })
|
|
865
971
|
) : variant === "search" ? (
|
|
866
972
|
// Brasília pattern: prominent search left, logo center, actions right; nav row below
|
|
867
973
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
868
974
|
/* @__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: [
|
|
869
|
-
/* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }),
|
|
975
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
870
976
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
871
977
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
872
978
|
] }),
|
|
873
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
979
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto border-t", style: headerDivider, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, {}) }) })
|
|
874
980
|
] })
|
|
875
981
|
) : variant === "minimal" ? (
|
|
876
982
|
// Uyuni pattern: nav left, centered logo, actions right — single compact row
|
|
@@ -878,17 +984,19 @@ function StorefrontHeader() {
|
|
|
878
984
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden sm:flex" }) }),
|
|
879
985
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
880
986
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3", children: [
|
|
881
|
-
/* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-44 lg:block" }),
|
|
987
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: "hidden w-44 lg:block" }),
|
|
882
988
|
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
883
989
|
] })
|
|
884
990
|
] })
|
|
885
991
|
) : (
|
|
886
|
-
// classic: logo left,
|
|
992
|
+
// classic: logo left, nav, then a right-aligned search + actions cluster
|
|
887
993
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
|
|
888
994
|
logo,
|
|
889
995
|
/* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden md:flex" }),
|
|
890
|
-
/* @__PURE__ */ jsxRuntime.
|
|
891
|
-
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-2 sm:gap-3", children: [
|
|
997
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { iconOnly: iconSearch, className: iconSearch ? "" : "hidden w-full max-w-sm sm:block" }),
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
999
|
+
] })
|
|
892
1000
|
] })
|
|
893
1001
|
)
|
|
894
1002
|
]
|
|
@@ -900,6 +1008,17 @@ function StorefrontFooter() {
|
|
|
900
1008
|
const { categories } = useCategories();
|
|
901
1009
|
const visibleCategories = categories.slice(0, 8);
|
|
902
1010
|
const hiddenCategoryCount = Math.max(categories.length - visibleCategories.length, 0);
|
|
1011
|
+
const labels = {
|
|
1012
|
+
categories: "Departamentos",
|
|
1013
|
+
navigation: "Navega\xE7\xE3o",
|
|
1014
|
+
contact: "Contato",
|
|
1015
|
+
viewAll: "Ver todos",
|
|
1016
|
+
purchases: "Minhas compras",
|
|
1017
|
+
privacy: "Privacidade",
|
|
1018
|
+
terms: "Termos",
|
|
1019
|
+
returns: "Trocas e devolu\xE7\xF5es",
|
|
1020
|
+
...config.footer?.labels
|
|
1021
|
+
};
|
|
903
1022
|
const goCategory = (categoryId) => {
|
|
904
1023
|
useCatalogStore.getState().reset();
|
|
905
1024
|
useCatalogStore.getState().setCategoryId(categoryId);
|
|
@@ -912,7 +1031,7 @@ function StorefrontFooter() {
|
|
|
912
1031
|
config.footer?.about && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: config.footer.about })
|
|
913
1032
|
] }),
|
|
914
1033
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
915
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.categories }),
|
|
916
1035
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
|
|
917
1036
|
visibleCategories.map((c) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
918
1037
|
"button",
|
|
@@ -924,21 +1043,22 @@ function StorefrontFooter() {
|
|
|
924
1043
|
}
|
|
925
1044
|
) }, c.id)),
|
|
926
1045
|
hiddenCategoryCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(Link, { to: config.catalogPath, className: "font-medium underline-offset-2 transition-opacity hover:underline", children: [
|
|
927
|
-
|
|
1046
|
+
labels.viewAll,
|
|
1047
|
+
" (",
|
|
928
1048
|
categories.length,
|
|
929
1049
|
")"
|
|
930
1050
|
] }) })
|
|
931
1051
|
] })
|
|
932
1052
|
] }),
|
|
933
1053
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
934
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
1054
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.navigation }),
|
|
935
1055
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
|
|
936
1056
|
config.nav.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: link.to, className: "transition-opacity hover:opacity-70", children: link.label }) }, link.to)),
|
|
937
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children:
|
|
1057
|
+
config.features.accounts && /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: labels.purchases }) })
|
|
938
1058
|
] })
|
|
939
1059
|
] }),
|
|
940
1060
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
941
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.contact }),
|
|
942
1062
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm text-muted-foreground", children: [
|
|
943
1063
|
config.footer?.contact?.phone && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.phone }),
|
|
944
1064
|
config.footer?.contact?.email && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.email }),
|
|
@@ -949,17 +1069,15 @@ function StorefrontFooter() {
|
|
|
949
1069
|
] }),
|
|
950
1070
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex max-w-7xl flex-col items-center gap-3 px-4 py-5 text-xs text-muted-foreground sm:flex-row sm:justify-between sm:px-6", children: [
|
|
951
1071
|
/* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-wrap items-center justify-center gap-x-5 gap-y-2", children: [
|
|
952
|
-
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children:
|
|
953
|
-
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children:
|
|
954
|
-
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children:
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: labels.privacy }),
|
|
1073
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: labels.terms }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: labels.returns })
|
|
955
1075
|
] }),
|
|
956
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1076
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center", children: config.footer?.credit ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
957
1077
|
"\xA9 ",
|
|
958
1078
|
config.name,
|
|
959
|
-
" \u2014 powered by Fayz
|
|
960
|
-
|
|
961
|
-
/* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://unsplash.com", target: "_blank", rel: "noreferrer", className: "underline-offset-2 hover:underline", children: "Unsplash" })
|
|
962
|
-
] })
|
|
1079
|
+
" \u2014 powered by Fayz"
|
|
1080
|
+
] }) })
|
|
963
1081
|
] }) })
|
|
964
1082
|
] });
|
|
965
1083
|
}
|
|
@@ -971,7 +1089,7 @@ var REASON_MESSAGE = {
|
|
|
971
1089
|
};
|
|
972
1090
|
function useDiscountValidator() {
|
|
973
1091
|
const config = useStorefrontConfig();
|
|
974
|
-
return
|
|
1092
|
+
return React6.useCallback(async (code) => {
|
|
975
1093
|
const normalized = code.trim().toUpperCase();
|
|
976
1094
|
if (!normalized) return { valid: false, percent: 0, message: "Informe um cupom." };
|
|
977
1095
|
const configDiscount = config.discounts?.find((d) => d.code.trim().toUpperCase() === normalized);
|
|
@@ -1052,14 +1170,61 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
|
|
|
1052
1170
|
)
|
|
1053
1171
|
] });
|
|
1054
1172
|
}
|
|
1173
|
+
function SmoothImage({
|
|
1174
|
+
imageLoading,
|
|
1175
|
+
loading = "lazy",
|
|
1176
|
+
decoding = "async",
|
|
1177
|
+
onLoad,
|
|
1178
|
+
style,
|
|
1179
|
+
...props
|
|
1180
|
+
}) {
|
|
1181
|
+
const config = useStorefrontConfigOptional();
|
|
1182
|
+
const resolved = {
|
|
1183
|
+
...config?.imageLoading,
|
|
1184
|
+
...imageLoading
|
|
1185
|
+
};
|
|
1186
|
+
const mode = resolved.mode ?? "fade";
|
|
1187
|
+
const durationMs = resolved.durationMs ?? 420;
|
|
1188
|
+
const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1189
|
+
const blur = resolved.blur ?? true;
|
|
1190
|
+
const ref = React6__default.default.useRef(null);
|
|
1191
|
+
const [loaded, setLoaded] = React6__default.default.useState(mode === "none");
|
|
1192
|
+
React6__default.default.useEffect(() => {
|
|
1193
|
+
if (mode === "none") {
|
|
1194
|
+
setLoaded(true);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true);
|
|
1198
|
+
}, [mode, props.src]);
|
|
1199
|
+
const revealStyle = mode === "none" ? {} : {
|
|
1200
|
+
opacity: loaded ? 1 : 0,
|
|
1201
|
+
filter: blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
1202
|
+
transition: `opacity ${durationMs}ms ${easing}, filter ${durationMs}ms ${easing}, transform ${durationMs}ms ${easing}`
|
|
1203
|
+
};
|
|
1204
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1205
|
+
"img",
|
|
1206
|
+
{
|
|
1207
|
+
...props,
|
|
1208
|
+
ref,
|
|
1209
|
+
loading,
|
|
1210
|
+
decoding,
|
|
1211
|
+
"data-sf-image-loaded": loaded ? "true" : "false",
|
|
1212
|
+
style: { ...style, ...revealStyle },
|
|
1213
|
+
onLoad: (event) => {
|
|
1214
|
+
setLoaded(true);
|
|
1215
|
+
onLoad?.(event);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1055
1220
|
function CartDrawer() {
|
|
1056
1221
|
const config = useStorefrontConfig();
|
|
1057
1222
|
const cart = useCartStore();
|
|
1058
1223
|
const validate = useDiscountValidator();
|
|
1059
|
-
const [code, setCode] =
|
|
1060
|
-
const [discountError, setDiscountError] =
|
|
1061
|
-
const [applying, setApplying] =
|
|
1062
|
-
|
|
1224
|
+
const [code, setCode] = React6.useState("");
|
|
1225
|
+
const [discountError, setDiscountError] = React6.useState(null);
|
|
1226
|
+
const [applying, setApplying] = React6.useState(false);
|
|
1227
|
+
React6.useEffect(() => {
|
|
1063
1228
|
if (!cart.isOpen) return;
|
|
1064
1229
|
const onKey = (e) => {
|
|
1065
1230
|
if (e.key === "Escape") cart.closeDrawer();
|
|
@@ -1151,7 +1316,7 @@ function CartDrawer() {
|
|
|
1151
1316
|
"data-line-id": line.lineId ?? line.productId,
|
|
1152
1317
|
className: `flex animate-fade-up gap-3 rounded-lg p-1.5 transition-all hover:bg-muted/40 ${(line.lineId ?? line.productId) === cart.justAddedLineId ? "bg-primary/5 ring-1 ring-primary/30" : ""}`,
|
|
1153
1318
|
children: [
|
|
1154
|
-
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1319
|
+
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-20 w-20 shrink-0 rounded-lg border object-cover" }),
|
|
1155
1320
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
|
|
1156
1321
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
1157
1322
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium leading-snug", children: line.name }),
|
|
@@ -1282,12 +1447,12 @@ function CartDrawer() {
|
|
|
1282
1447
|
] });
|
|
1283
1448
|
}
|
|
1284
1449
|
function useProducts(opts) {
|
|
1285
|
-
const [products, setProducts] =
|
|
1286
|
-
const [loading, setLoading] =
|
|
1287
|
-
const [error, setError] =
|
|
1288
|
-
const [nonce, setNonce] =
|
|
1450
|
+
const [products, setProducts] = React6.useState([]);
|
|
1451
|
+
const [loading, setLoading] = React6.useState(true);
|
|
1452
|
+
const [error, setError] = React6.useState(null);
|
|
1453
|
+
const [nonce, setNonce] = React6.useState(0);
|
|
1289
1454
|
const key = JSON.stringify(opts);
|
|
1290
|
-
|
|
1455
|
+
React6.useEffect(() => {
|
|
1291
1456
|
let cancelled = false;
|
|
1292
1457
|
setLoading(true);
|
|
1293
1458
|
runtime.getShopProvider().listProducts(JSON.parse(key)).then((data) => {
|
|
@@ -1321,36 +1486,91 @@ function Price({ value, compareAt, testId, compareTestId, className }) {
|
|
|
1321
1486
|
] });
|
|
1322
1487
|
}
|
|
1323
1488
|
|
|
1324
|
-
// src/
|
|
1325
|
-
var
|
|
1489
|
+
// src/component-selectors.ts
|
|
1490
|
+
var productCardComponentContract = {
|
|
1326
1491
|
root: { "data-testid": TID.productCard },
|
|
1327
1492
|
name: { "data-testid": TID.productCardName },
|
|
1328
1493
|
priceTestId: TID.productCardPrice,
|
|
1329
1494
|
addButton: { "data-testid": TID.productCardAdd }
|
|
1330
1495
|
};
|
|
1331
|
-
var
|
|
1332
|
-
productCard:
|
|
1496
|
+
var storefrontComponentContracts = {
|
|
1497
|
+
productCard: productCardComponentContract,
|
|
1498
|
+
productDetail: {
|
|
1499
|
+
root: { "data-testid": TID.pdpRoot },
|
|
1500
|
+
gallery: { "data-testid": TID.pdpGallery },
|
|
1501
|
+
name: { "data-testid": TID.pdpName },
|
|
1502
|
+
description: { "data-testid": TID.pdpDescription },
|
|
1503
|
+
actions: { "data-testid": TID.pdpActions },
|
|
1504
|
+
addButton: { "data-testid": TID.pdpAddToCart },
|
|
1505
|
+
enquiryButton: { "data-testid": TID.pdpEnquiryButton },
|
|
1506
|
+
enquiryForm: { "data-testid": TID.enquiryForm }
|
|
1507
|
+
}
|
|
1333
1508
|
};
|
|
1334
|
-
function
|
|
1509
|
+
function useStorefront() {
|
|
1510
|
+
return useStorefrontConfig();
|
|
1511
|
+
}
|
|
1512
|
+
function useCatalog() {
|
|
1335
1513
|
const config = useStorefrontConfig();
|
|
1336
|
-
const
|
|
1514
|
+
const catalog = useCatalogStore();
|
|
1515
|
+
return {
|
|
1516
|
+
...catalog,
|
|
1517
|
+
catalogPath: config.catalogPath,
|
|
1518
|
+
config
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
function useCart() {
|
|
1522
|
+
return useCartStore();
|
|
1523
|
+
}
|
|
1524
|
+
function useEnquiry() {
|
|
1525
|
+
const config = useStorefrontConfig();
|
|
1526
|
+
const createProductEnquiry = async (input) => {
|
|
1527
|
+
const provider = runtime.getShopProvider();
|
|
1528
|
+
if (!provider.createProductEnquiry) {
|
|
1529
|
+
throw new Error("The active shop provider does not support product enquiries.");
|
|
1530
|
+
}
|
|
1531
|
+
return provider.createProductEnquiry(input);
|
|
1532
|
+
};
|
|
1533
|
+
return {
|
|
1534
|
+
enquiry: config.enquiry,
|
|
1535
|
+
createProductEnquiry
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
function useStorefrontActions() {
|
|
1539
|
+
const addItem = useCartStore((state) => state.addItem);
|
|
1540
|
+
const openDrawer = useCartStore((state) => state.openDrawer);
|
|
1541
|
+
const closeDrawer = useCartStore((state) => state.closeDrawer);
|
|
1542
|
+
const { createProductEnquiry } = useEnquiry();
|
|
1543
|
+
return {
|
|
1544
|
+
navigateTo,
|
|
1545
|
+
addToCart: (product, quantity, options) => addItem(product, quantity, options),
|
|
1546
|
+
openCart: openDrawer,
|
|
1547
|
+
closeCart: closeDrawer,
|
|
1548
|
+
createProductEnquiry
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function ProductCard({ product, config: providedConfig, actions: providedActions }) {
|
|
1552
|
+
const runtimeConfig = useStorefrontConfig();
|
|
1553
|
+
const runtimeActions = useStorefrontActions();
|
|
1554
|
+
const config = providedConfig ?? runtimeConfig;
|
|
1555
|
+
const actions = providedActions ?? runtimeActions;
|
|
1337
1556
|
const hasOptions = getProductOptionGroups(product).length > 0;
|
|
1338
1557
|
const soldOut = product.inventoryCount <= 0;
|
|
1339
1558
|
const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
|
|
1340
1559
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
1341
1560
|
const cardStyle = config.theme?.productCard?.style ?? "card";
|
|
1342
1561
|
const aspect = config.theme?.productCard?.imageAspect === "portrait" ? "aspect-[3/4]" : "aspect-square";
|
|
1562
|
+
const purchaseMode = config.commerceMode === "checkout";
|
|
1343
1563
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1344
1564
|
"div",
|
|
1345
1565
|
{
|
|
1346
|
-
...
|
|
1566
|
+
...productCardComponentContract.root,
|
|
1347
1567
|
"data-slug": product.slug,
|
|
1348
1568
|
className: `group flex flex-col overflow-hidden transition-all duration-300 ${cardStyle === "editorial" ? "hover:-translate-y-1" : "border bg-card hover:-translate-y-1 hover:shadow-lg"}`,
|
|
1349
1569
|
style: { borderRadius: "var(--sf-radius-card)" },
|
|
1350
1570
|
children: [
|
|
1351
1571
|
/* @__PURE__ */ jsxRuntime.jsxs(Link, { to: `/product/${product.slug}`, className: `relative block ${aspect} overflow-hidden bg-muted`, style: { borderRadius: cardStyle === "editorial" ? "var(--sf-radius-card)" : void 0 }, children: [
|
|
1352
1572
|
image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1353
|
-
|
|
1573
|
+
SmoothImage,
|
|
1354
1574
|
{
|
|
1355
1575
|
src: image.url,
|
|
1356
1576
|
alt: image.altText ?? product.name,
|
|
@@ -1383,33 +1603,37 @@ function ProductCard({ product }) {
|
|
|
1383
1603
|
Link,
|
|
1384
1604
|
{
|
|
1385
1605
|
to: `/product/${product.slug}`,
|
|
1386
|
-
...
|
|
1606
|
+
...productCardComponentContract.name,
|
|
1387
1607
|
className: "font-medium leading-snug hover:underline",
|
|
1388
1608
|
children: product.name
|
|
1389
1609
|
}
|
|
1390
1610
|
),
|
|
1391
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
|
|
1392
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1611
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between gap-3 pt-2", children: [
|
|
1612
|
+
purchaseMode ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1393
1613
|
Price,
|
|
1394
1614
|
{
|
|
1395
1615
|
value: product.price,
|
|
1396
1616
|
compareAt: product.compareAtPrice,
|
|
1397
|
-
testId:
|
|
1617
|
+
testId: productCardComponentContract.priceTestId
|
|
1398
1618
|
}
|
|
1399
|
-
),
|
|
1619
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": productCardComponentContract.priceTestId, className: "text-sm text-muted-foreground", children: config.commerceMode === "enquiry" ? config.enquiry.label : "Ver detalhes" }),
|
|
1400
1620
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1401
1621
|
"button",
|
|
1402
1622
|
{
|
|
1403
1623
|
type: "button",
|
|
1404
|
-
...
|
|
1405
|
-
disabled: soldOut,
|
|
1406
|
-
"aria-label": hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho`,
|
|
1624
|
+
...productCardComponentContract.addButton,
|
|
1625
|
+
disabled: purchaseMode && soldOut,
|
|
1626
|
+
"aria-label": purchaseMode ? hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho` : `${config.enquiry.label} ${product.name}`,
|
|
1407
1627
|
onClick: () => {
|
|
1628
|
+
if (!purchaseMode) {
|
|
1629
|
+
navigateTo(`/product/${product.slug}`);
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1408
1632
|
if (hasOptions) {
|
|
1409
1633
|
navigateTo(`/product/${product.slug}`);
|
|
1410
1634
|
return;
|
|
1411
1635
|
}
|
|
1412
|
-
|
|
1636
|
+
actions.addToCart(product);
|
|
1413
1637
|
},
|
|
1414
1638
|
className: "rounded-full border bg-background/90 p-2.5 text-foreground shadow-sm transition-all duration-200 hover:scale-110 hover:bg-primary hover:text-primary-foreground hover:shadow disabled:cursor-not-allowed disabled:opacity-40 lg:translate-y-1 lg:opacity-0 lg:group-hover:translate-y-0 lg:group-hover:opacity-100 lg:disabled:opacity-0 lg:group-hover:disabled:opacity-40",
|
|
1415
1639
|
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ShoppingBag, { className: "h-4 w-4" })
|
|
@@ -1423,14 +1647,25 @@ function ProductCard({ product }) {
|
|
|
1423
1647
|
}
|
|
1424
1648
|
function ProductGrid({ products, loading }) {
|
|
1425
1649
|
const config = useStorefrontConfig();
|
|
1426
|
-
const
|
|
1650
|
+
const actions = useStorefrontActions();
|
|
1651
|
+
const components = getStorefrontComponents(config);
|
|
1652
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
1427
1653
|
if (loading) {
|
|
1428
1654
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse rounded-xl bg-muted" }, i)) });
|
|
1429
1655
|
}
|
|
1430
1656
|
if (products.length === 0) {
|
|
1431
1657
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "py-20 text-center text-muted-foreground", children: "Nenhum produto encontrado." });
|
|
1432
1658
|
}
|
|
1433
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: products.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1659
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4", children: products.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1660
|
+
ProductCardComponent,
|
|
1661
|
+
{
|
|
1662
|
+
product: p,
|
|
1663
|
+
config,
|
|
1664
|
+
commerceMode: config.commerceMode,
|
|
1665
|
+
actions
|
|
1666
|
+
},
|
|
1667
|
+
p.id
|
|
1668
|
+
)) });
|
|
1434
1669
|
}
|
|
1435
1670
|
function FiltersPanel() {
|
|
1436
1671
|
const { categories } = useCategories();
|
|
@@ -1535,10 +1770,10 @@ function FiltersPanel() {
|
|
|
1535
1770
|
}
|
|
1536
1771
|
function useStorefrontHead(input) {
|
|
1537
1772
|
const { title, description } = input;
|
|
1538
|
-
|
|
1773
|
+
React6.useEffect(() => {
|
|
1539
1774
|
if (title) document.title = title;
|
|
1540
1775
|
}, [title]);
|
|
1541
|
-
|
|
1776
|
+
React6.useEffect(() => {
|
|
1542
1777
|
if (!description) return;
|
|
1543
1778
|
let tag = document.head.querySelector('meta[name="description"]');
|
|
1544
1779
|
if (!tag) {
|
|
@@ -1557,9 +1792,9 @@ var SORT_MAP = {
|
|
|
1557
1792
|
};
|
|
1558
1793
|
var SORTS = ["newest", "price-asc", "price-desc", "name"];
|
|
1559
1794
|
function useCatalogUrlSync() {
|
|
1560
|
-
const hydrated =
|
|
1795
|
+
const hydrated = React6.useRef(false);
|
|
1561
1796
|
const catalog = useCatalogStore();
|
|
1562
|
-
|
|
1797
|
+
React6.useEffect(() => {
|
|
1563
1798
|
if (hydrated.current) return;
|
|
1564
1799
|
hydrated.current = true;
|
|
1565
1800
|
const q = window.location.hash.split("?")[1];
|
|
@@ -1574,7 +1809,7 @@ function useCatalogUrlSync() {
|
|
|
1574
1809
|
if (p.has("min")) s.setPriceMin(Number(p.get("min")) || null);
|
|
1575
1810
|
if (p.has("max")) s.setPriceMax(Number(p.get("max")) || null);
|
|
1576
1811
|
}, []);
|
|
1577
|
-
|
|
1812
|
+
React6.useEffect(() => {
|
|
1578
1813
|
const p = new URLSearchParams();
|
|
1579
1814
|
if (catalog.search) p.set("search", catalog.search);
|
|
1580
1815
|
if (catalog.categoryId) p.set("category", catalog.categoryId);
|
|
@@ -1602,7 +1837,7 @@ function CatalogPage() {
|
|
|
1602
1837
|
...SORT_MAP[catalog.sort]
|
|
1603
1838
|
};
|
|
1604
1839
|
const { products, loading, error, reload } = useProducts(providerOpts);
|
|
1605
|
-
const filtered =
|
|
1840
|
+
const filtered = React6.useMemo(
|
|
1606
1841
|
() => products.filter((p) => {
|
|
1607
1842
|
if (catalog.priceMin != null && p.price < catalog.priceMin) return false;
|
|
1608
1843
|
if (catalog.priceMax != null && p.price > catalog.priceMax) return false;
|
|
@@ -1667,10 +1902,17 @@ function HeroSection({
|
|
|
1667
1902
|
slides,
|
|
1668
1903
|
height = "tall"
|
|
1669
1904
|
}) {
|
|
1670
|
-
const [index, setIndex] =
|
|
1671
|
-
const
|
|
1905
|
+
const [index, setIndex] = React6.useState(0);
|
|
1906
|
+
const [loadedSlides, setLoadedSlides] = React6.useState({});
|
|
1907
|
+
const config = useStorefrontConfig();
|
|
1908
|
+
const interacted = React6.useRef(false);
|
|
1672
1909
|
const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
|
|
1673
|
-
|
|
1910
|
+
const revealImages = config.imageLoading.mode !== "none";
|
|
1911
|
+
const revealStyle = (loaded) => revealImages ? {
|
|
1912
|
+
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
1913
|
+
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
1914
|
+
} : void 0;
|
|
1915
|
+
React6.useEffect(() => {
|
|
1674
1916
|
if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
|
|
1675
1917
|
const timer = setInterval(() => {
|
|
1676
1918
|
if (!interacted.current) setIndex((i) => (i + 1) % slides.length);
|
|
@@ -1690,7 +1932,7 @@ function HeroSection({
|
|
|
1690
1932
|
className: "group relative block h-[42vh] min-h-[320px] overflow-hidden",
|
|
1691
1933
|
children: [
|
|
1692
1934
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1693
|
-
|
|
1935
|
+
SmoothImage,
|
|
1694
1936
|
{
|
|
1695
1937
|
src: slideImage(slide, 900, 700),
|
|
1696
1938
|
alt: slide.title,
|
|
@@ -1722,7 +1964,11 @@ function HeroSection({
|
|
|
1722
1964
|
src: slideImage(slide),
|
|
1723
1965
|
alt: slide.title,
|
|
1724
1966
|
"aria-hidden": i !== index,
|
|
1725
|
-
|
|
1967
|
+
loading: i === 0 ? "eager" : "lazy",
|
|
1968
|
+
decoding: "async",
|
|
1969
|
+
onLoad: () => setLoadedSlides((current) => ({ ...current, [i]: true })),
|
|
1970
|
+
style: revealStyle(Boolean(loadedSlides[i])),
|
|
1971
|
+
className: `absolute inset-0 h-full w-full object-cover transition-opacity duration-700 ${i === index && (!revealImages || loadedSlides[i]) ? "animate-kenburns opacity-100" : "opacity-0"}`
|
|
1726
1972
|
},
|
|
1727
1973
|
i
|
|
1728
1974
|
)),
|
|
@@ -1784,7 +2030,7 @@ function CategoryShowcase({ style, title }) {
|
|
|
1784
2030
|
title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
|
|
1785
2031
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-start justify-center gap-8", children: categories.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 80, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => go(c.id), className: "group flex w-28 flex-col items-center gap-3", children: [
|
|
1786
2032
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-24 w-24 overflow-hidden rounded-full border-2 border-transparent shadow-sm transition-all duration-300 group-hover:scale-105 group-hover:border-primary group-hover:shadow-md", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1787
|
-
|
|
2033
|
+
SmoothImage,
|
|
1788
2034
|
{
|
|
1789
2035
|
src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 200, 200),
|
|
1790
2036
|
alt: c.name,
|
|
@@ -1799,7 +2045,7 @@ function CategoryShowcase({ style, title }) {
|
|
|
1799
2045
|
title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
|
|
1800
2046
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-4 lg:grid-cols-4", children: categories.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => go(c.id), className: "group relative block aspect-[4/5] w-full overflow-hidden", style: { borderRadius: "var(--sf-radius-card)" }, children: [
|
|
1801
2047
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1802
|
-
|
|
2048
|
+
SmoothImage,
|
|
1803
2049
|
{
|
|
1804
2050
|
src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 480, 600),
|
|
1805
2051
|
alt: c.name,
|
|
@@ -1821,11 +2067,13 @@ function ProductRail({
|
|
|
1821
2067
|
limit = 4
|
|
1822
2068
|
}) {
|
|
1823
2069
|
const config = useStorefrontConfig();
|
|
1824
|
-
const
|
|
2070
|
+
const actions = useStorefrontActions();
|
|
2071
|
+
const components = getStorefrontComponents(config);
|
|
2072
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
1825
2073
|
const { products, loading } = useProducts(
|
|
1826
2074
|
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
1827
2075
|
);
|
|
1828
|
-
const visible =
|
|
2076
|
+
const visible = React6.useMemo(() => {
|
|
1829
2077
|
let list = products;
|
|
1830
2078
|
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
1831
2079
|
return list.slice(0, limit);
|
|
@@ -1837,7 +2085,15 @@ function ProductRail({
|
|
|
1837
2085
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: title }),
|
|
1838
2086
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: subtitle })
|
|
1839
2087
|
] }),
|
|
1840
|
-
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2088
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: Array.from({ length: limit }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-[3/4] animate-pulse bg-muted", style: { borderRadius: "var(--sf-radius-card)" } }, i)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-5 md:grid-cols-4", children: visible.map((p, i) => /* @__PURE__ */ jsxRuntime.jsx(Reveal, { delay: i * 90, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2089
|
+
ProductCardComponent,
|
|
2090
|
+
{
|
|
2091
|
+
product: p,
|
|
2092
|
+
config,
|
|
2093
|
+
commerceMode: config.commerceMode,
|
|
2094
|
+
actions
|
|
2095
|
+
}
|
|
2096
|
+
) }, p.id)) }),
|
|
1841
2097
|
/* @__PURE__ */ jsxRuntime.jsx(Reveal, { className: "mt-10 text-center", delay: 200, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1842
2098
|
Link,
|
|
1843
2099
|
{
|
|
@@ -1849,6 +2105,201 @@ function ProductRail({
|
|
|
1849
2105
|
) })
|
|
1850
2106
|
] });
|
|
1851
2107
|
}
|
|
2108
|
+
var heightClass = {
|
|
2109
|
+
medium: "h-[58vh] min-h-[420px]",
|
|
2110
|
+
tall: "h-[74vh] min-h-[520px]",
|
|
2111
|
+
screen: "min-h-[calc(100vh-64px)]"
|
|
2112
|
+
};
|
|
2113
|
+
var alignClass = {
|
|
2114
|
+
start: "items-start text-left",
|
|
2115
|
+
center: "items-center text-center",
|
|
2116
|
+
end: "items-end text-right"
|
|
2117
|
+
};
|
|
2118
|
+
function MediaCarousel({
|
|
2119
|
+
items,
|
|
2120
|
+
height = "tall",
|
|
2121
|
+
autoplayMs = 7e3,
|
|
2122
|
+
overlay = "dark",
|
|
2123
|
+
className = ""
|
|
2124
|
+
}) {
|
|
2125
|
+
const [index, setIndex] = React6.useState(0);
|
|
2126
|
+
const [loadedItems, setLoadedItems] = React6.useState({});
|
|
2127
|
+
const config = useStorefrontConfig();
|
|
2128
|
+
const interacted = React6.useRef(false);
|
|
2129
|
+
const revealImages = config.imageLoading.mode !== "none";
|
|
2130
|
+
const revealStyle = (loaded) => revealImages ? {
|
|
2131
|
+
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
2132
|
+
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
2133
|
+
} : void 0;
|
|
2134
|
+
React6.useEffect(() => {
|
|
2135
|
+
if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
|
|
2136
|
+
const timer = setInterval(() => {
|
|
2137
|
+
if (!interacted.current) setIndex((i) => (i + 1) % items.length);
|
|
2138
|
+
}, autoplayMs);
|
|
2139
|
+
return () => clearInterval(timer);
|
|
2140
|
+
}, [autoplayMs, items.length]);
|
|
2141
|
+
if (items.length === 0) return null;
|
|
2142
|
+
const active = items[Math.min(index, items.length - 1)];
|
|
2143
|
+
const goTo = (next) => {
|
|
2144
|
+
interacted.current = true;
|
|
2145
|
+
setIndex((next + items.length) % items.length);
|
|
2146
|
+
};
|
|
2147
|
+
const overlayClass = overlay === "dark" ? "bg-gradient-to-r from-black/70 via-black/25 to-black/25" : overlay === "soft" ? "bg-gradient-to-t from-black/45 via-black/10 to-transparent" : "";
|
|
2148
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.mediaCarousel, className: `relative isolate overflow-hidden ${heightClass[height]} ${className}`, children: [
|
|
2149
|
+
items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2150
|
+
"img",
|
|
2151
|
+
{
|
|
2152
|
+
src: item.image,
|
|
2153
|
+
alt: item.imageAlt ?? item.title,
|
|
2154
|
+
"aria-hidden": i !== index,
|
|
2155
|
+
loading: i === 0 ? "eager" : "lazy",
|
|
2156
|
+
decoding: "async",
|
|
2157
|
+
onLoad: () => setLoadedItems((current) => ({ ...current, [i]: true })),
|
|
2158
|
+
style: revealStyle(Boolean(loadedItems[i])),
|
|
2159
|
+
className: `absolute inset-0 -z-10 h-full w-full object-cover transition-opacity duration-700 ${i === index && (!revealImages || loadedItems[i]) ? "opacity-100" : "opacity-0"}`
|
|
2160
|
+
},
|
|
2161
|
+
`${item.title}-${i}`
|
|
2162
|
+
)),
|
|
2163
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 -z-10 ${overlayClass}` }),
|
|
2164
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex h-full px-5 py-16 sm:px-10 lg:px-16 ${alignClass[active.align ?? "start"]}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-auto max-w-2xl text-white drop-shadow-md", children: [
|
|
2165
|
+
active.eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-white/80", children: active.eyebrow }),
|
|
2166
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "sf-heading text-5xl font-normal leading-[0.95] sm:text-7xl lg:text-8xl", children: active.title }),
|
|
2167
|
+
active.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-5 max-w-xl text-base leading-8 text-white/88 sm:text-lg", children: active.subtitle }),
|
|
2168
|
+
active.cta && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2169
|
+
Link,
|
|
2170
|
+
{
|
|
2171
|
+
to: active.href ?? "/catalog",
|
|
2172
|
+
className: "mt-8 inline-flex border border-white/75 px-6 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-white transition hover:bg-white hover:text-black",
|
|
2173
|
+
children: active.cta
|
|
2174
|
+
}
|
|
2175
|
+
)
|
|
2176
|
+
] }) }),
|
|
2177
|
+
items.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2178
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2179
|
+
"button",
|
|
2180
|
+
{
|
|
2181
|
+
type: "button",
|
|
2182
|
+
"aria-label": "Previous slide",
|
|
2183
|
+
"data-testid": TID.mediaCarouselPrev,
|
|
2184
|
+
onClick: () => goTo(index - 1),
|
|
2185
|
+
className: "absolute left-4 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center border border-white/60 bg-black/20 text-white backdrop-blur transition hover:bg-white hover:text-black",
|
|
2186
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
|
|
2187
|
+
}
|
|
2188
|
+
),
|
|
2189
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2190
|
+
"button",
|
|
2191
|
+
{
|
|
2192
|
+
type: "button",
|
|
2193
|
+
"aria-label": "Next slide",
|
|
2194
|
+
"data-testid": TID.mediaCarouselNext,
|
|
2195
|
+
onClick: () => goTo(index + 1),
|
|
2196
|
+
className: "absolute right-4 top-1/2 inline-flex h-10 w-10 -translate-y-1/2 items-center justify-center border border-white/60 bg-black/20 text-white backdrop-blur transition hover:bg-white hover:text-black",
|
|
2197
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
|
|
2198
|
+
}
|
|
2199
|
+
),
|
|
2200
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-5 left-1/2 flex -translate-x-1/2 gap-2", children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2201
|
+
"button",
|
|
2202
|
+
{
|
|
2203
|
+
type: "button",
|
|
2204
|
+
"aria-label": `Go to slide ${i + 1}`,
|
|
2205
|
+
onClick: () => goTo(i),
|
|
2206
|
+
className: `h-2 rounded-full transition-all ${i === index ? "w-8 bg-white" : "w-2 bg-white/55 hover:bg-white"}`
|
|
2207
|
+
},
|
|
2208
|
+
`${item.title}-dot-${i}`
|
|
2209
|
+
)) })
|
|
2210
|
+
] })
|
|
2211
|
+
] });
|
|
2212
|
+
}
|
|
2213
|
+
function ProductSlider({
|
|
2214
|
+
title,
|
|
2215
|
+
subtitle,
|
|
2216
|
+
eyebrow,
|
|
2217
|
+
filter = "all",
|
|
2218
|
+
categoryName,
|
|
2219
|
+
collection,
|
|
2220
|
+
limit = 10,
|
|
2221
|
+
cta = "View all",
|
|
2222
|
+
href,
|
|
2223
|
+
className = ""
|
|
2224
|
+
}) {
|
|
2225
|
+
const config = useStorefrontConfig();
|
|
2226
|
+
const actions = useStorefrontActions();
|
|
2227
|
+
const viewportRef = React6.useRef(null);
|
|
2228
|
+
const components = getStorefrontComponents(config);
|
|
2229
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
2230
|
+
const { products, loading } = useProducts(
|
|
2231
|
+
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
2232
|
+
);
|
|
2233
|
+
const visible = React6.useMemo(() => {
|
|
2234
|
+
let list = products;
|
|
2235
|
+
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
2236
|
+
if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
|
|
2237
|
+
if (collection) list = list.filter((p) => p.metadata?.collection === collection);
|
|
2238
|
+
return list.slice(0, limit);
|
|
2239
|
+
}, [categoryName, collection, filter, limit, products]);
|
|
2240
|
+
const scroll = (direction) => {
|
|
2241
|
+
viewportRef.current?.scrollBy({ left: direction * Math.max(320, viewportRef.current.clientWidth * 0.75), behavior: "smooth" });
|
|
2242
|
+
};
|
|
2243
|
+
if (!loading && visible.length === 0) return null;
|
|
2244
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.productSlider, className: `mx-auto max-w-7xl px-4 py-14 sm:px-6 ${className}`, children: [
|
|
2245
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between", children: [
|
|
2246
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-2xl", children: [
|
|
2247
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
|
|
2248
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-normal leading-tight tracking-normal sm:text-5xl", children: title }),
|
|
2249
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 leading-7 text-muted-foreground", children: subtitle })
|
|
2250
|
+
] }),
|
|
2251
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2252
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2253
|
+
"button",
|
|
2254
|
+
{
|
|
2255
|
+
type: "button",
|
|
2256
|
+
"aria-label": "Previous products",
|
|
2257
|
+
"data-testid": TID.productSliderPrev,
|
|
2258
|
+
onClick: () => scroll(-1),
|
|
2259
|
+
className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
|
|
2260
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
|
|
2261
|
+
}
|
|
2262
|
+
),
|
|
2263
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2264
|
+
"button",
|
|
2265
|
+
{
|
|
2266
|
+
type: "button",
|
|
2267
|
+
"aria-label": "Next products",
|
|
2268
|
+
"data-testid": TID.productSliderNext,
|
|
2269
|
+
onClick: () => scroll(1),
|
|
2270
|
+
className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
|
|
2271
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
|
|
2272
|
+
}
|
|
2273
|
+
)
|
|
2274
|
+
] })
|
|
2275
|
+
] }),
|
|
2276
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2277
|
+
"div",
|
|
2278
|
+
{
|
|
2279
|
+
ref: viewportRef,
|
|
2280
|
+
"data-testid": TID.productSliderViewport,
|
|
2281
|
+
className: "flex snap-x gap-5 overflow-x-auto pb-5 [scrollbar-width:thin]",
|
|
2282
|
+
children: loading ? Array.from({ length: Math.min(limit, 6) }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[360px] min-w-[260px] snap-start animate-pulse bg-muted" }, i)) : visible.map((product) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-[260px] max-w-[300px] flex-[0_0_72vw] snap-start sm:flex-[0_0_300px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2283
|
+
ProductCardComponent,
|
|
2284
|
+
{
|
|
2285
|
+
product,
|
|
2286
|
+
config,
|
|
2287
|
+
commerceMode: config.commerceMode,
|
|
2288
|
+
actions
|
|
2289
|
+
}
|
|
2290
|
+
) }, product.id))
|
|
2291
|
+
}
|
|
2292
|
+
),
|
|
2293
|
+
cta && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2294
|
+
Link,
|
|
2295
|
+
{
|
|
2296
|
+
to: href ?? config.catalogPath,
|
|
2297
|
+
className: "mt-4 inline-flex border border-foreground/30 px-6 py-3 text-xs font-semibold uppercase tracking-[0.16em] transition hover:bg-foreground hover:text-background",
|
|
2298
|
+
children: cta
|
|
2299
|
+
}
|
|
2300
|
+
)
|
|
2301
|
+
] });
|
|
2302
|
+
}
|
|
1852
2303
|
function Icon({ name, className }) {
|
|
1853
2304
|
const C = LucideIcons__namespace[name];
|
|
1854
2305
|
return C ? /* @__PURE__ */ jsxRuntime.jsx(C, { className }) : null;
|
|
@@ -1873,7 +2324,7 @@ function PromoBanner({
|
|
|
1873
2324
|
}) {
|
|
1874
2325
|
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.promoBanner, className: "group relative my-12 overflow-hidden", children: [
|
|
1875
2326
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1876
|
-
|
|
2327
|
+
SmoothImage,
|
|
1877
2328
|
{
|
|
1878
2329
|
src: image ?? bannerPlaceholder(title, hue, hue + 50, 1600, 420),
|
|
1879
2330
|
alt: title,
|
|
@@ -1927,8 +2378,8 @@ function Testimonials({ title, items }) {
|
|
|
1927
2378
|
] }) });
|
|
1928
2379
|
}
|
|
1929
2380
|
function NewsletterBand({ title, subtitle }) {
|
|
1930
|
-
const [email, setEmail] =
|
|
1931
|
-
const [done, setDone] =
|
|
2381
|
+
const [email, setEmail] = React6.useState("");
|
|
2382
|
+
const [done, setDone] = React6.useState(false);
|
|
1932
2383
|
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: [
|
|
1933
2384
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold", children: title ?? "Receba nossas novidades" }),
|
|
1934
2385
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 opacity-90", children: subtitle }),
|
|
@@ -1976,8 +2427,10 @@ function registerStorefrontBlocks() {
|
|
|
1976
2427
|
if (registered) return;
|
|
1977
2428
|
registered = true;
|
|
1978
2429
|
core.registerBlock("hero", HeroSection, { source: "sdk", label: "Hero" });
|
|
2430
|
+
core.registerBlock("mediaCarousel", MediaCarousel, { source: "sdk", label: "Media carousel" });
|
|
1979
2431
|
core.registerBlock("categories", CategoryShowcase, { source: "sdk", label: "Categories" });
|
|
1980
2432
|
core.registerBlock("products", ProductRail, { source: "sdk", label: "Product rail" });
|
|
2433
|
+
core.registerBlock("productSlider", ProductSlider, { source: "sdk", label: "Product slider" });
|
|
1981
2434
|
core.registerBlock("benefits", BenefitsRow, { source: "sdk", label: "Benefits row" });
|
|
1982
2435
|
core.registerBlock("banner", PromoBanner, { source: "sdk", label: "Promo banner" });
|
|
1983
2436
|
core.registerBlock("manifesto", ManifestoBlock, { source: "sdk", label: "Manifesto" });
|
|
@@ -1991,14 +2444,17 @@ function sectionsToBlocks(sections) {
|
|
|
1991
2444
|
return { type, id: `${type}-${i}`, props };
|
|
1992
2445
|
});
|
|
1993
2446
|
}
|
|
1994
|
-
function
|
|
2447
|
+
function StorefrontSections({ sections }) {
|
|
1995
2448
|
return /* @__PURE__ */ jsxRuntime.jsx("main", { children: core.renderBlocks(sectionsToBlocks(sections)) });
|
|
1996
2449
|
}
|
|
2450
|
+
function HomePage({ sections }) {
|
|
2451
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StorefrontSections, { sections });
|
|
2452
|
+
}
|
|
1997
2453
|
function useProduct(slug2) {
|
|
1998
|
-
const [product, setProduct] =
|
|
1999
|
-
const [loading, setLoading] =
|
|
2000
|
-
const [error, setError] =
|
|
2001
|
-
|
|
2454
|
+
const [product, setProduct] = React6.useState(null);
|
|
2455
|
+
const [loading, setLoading] = React6.useState(true);
|
|
2456
|
+
const [error, setError] = React6.useState(null);
|
|
2457
|
+
React6.useEffect(() => {
|
|
2002
2458
|
let cancelled = false;
|
|
2003
2459
|
setLoading(true);
|
|
2004
2460
|
runtime.getShopProvider().listProducts({ slug: slug2, status: "active", limit: 1 }).then((data) => {
|
|
@@ -2105,28 +2561,218 @@ function ProductSpecs({ product }) {
|
|
|
2105
2561
|
}
|
|
2106
2562
|
function RelatedProducts({ product }) {
|
|
2107
2563
|
const config = useStorefrontConfig();
|
|
2108
|
-
const
|
|
2564
|
+
const actions = useStorefrontActions();
|
|
2565
|
+
const components = getStorefrontComponents(config);
|
|
2566
|
+
const Card = components.ProductCard ?? ProductCard;
|
|
2109
2567
|
const opts = product.categoryId ? { categoryId: product.categoryId, status: "active", limit: 5 } : { status: "active", limit: 5 };
|
|
2110
2568
|
const { products, loading } = useProducts(opts);
|
|
2111
2569
|
const related = products.filter((p) => p.id !== product.id).slice(0, 4);
|
|
2112
2570
|
if (loading || related.length === 0) return null;
|
|
2113
2571
|
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mt-16", children: [
|
|
2114
2572
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-xl font-semibold tracking-tight", children: "Voc\xEA tamb\xE9m pode gostar" }),
|
|
2115
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2573
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2574
|
+
Card,
|
|
2575
|
+
{
|
|
2576
|
+
product: p,
|
|
2577
|
+
config,
|
|
2578
|
+
commerceMode: config.commerceMode,
|
|
2579
|
+
actions
|
|
2580
|
+
},
|
|
2581
|
+
p.id
|
|
2582
|
+
)) })
|
|
2116
2583
|
] });
|
|
2117
2584
|
}
|
|
2585
|
+
function defaultSubject(prefix, product) {
|
|
2586
|
+
return `${prefix}: ${product.name}`;
|
|
2587
|
+
}
|
|
2588
|
+
function buildWhatsAppHref(url, product) {
|
|
2589
|
+
const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`);
|
|
2590
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
2591
|
+
return `${url}${sep}text=${text}`;
|
|
2592
|
+
}
|
|
2593
|
+
function ProductEnquiryForm({ product, onSuccess }) {
|
|
2594
|
+
const config = useStorefrontConfig();
|
|
2595
|
+
const [customerName, setCustomerName] = React6.useState("");
|
|
2596
|
+
const [customerEmail, setCustomerEmail] = React6.useState("");
|
|
2597
|
+
const [customerPhone, setCustomerPhone] = React6.useState("");
|
|
2598
|
+
const [subject, setSubject] = React6.useState(defaultSubject(config.enquiry.subjectPrefix, product));
|
|
2599
|
+
const [message, setMessage] = React6.useState(`I would like more information about ${product.name}.`);
|
|
2600
|
+
const [status, setStatus] = React6.useState("idle");
|
|
2601
|
+
const [error, setError] = React6.useState(null);
|
|
2602
|
+
const submit = async (event) => {
|
|
2603
|
+
event.preventDefault();
|
|
2604
|
+
setStatus("submitting");
|
|
2605
|
+
setError(null);
|
|
2606
|
+
try {
|
|
2607
|
+
const provider = runtime.getShopProvider();
|
|
2608
|
+
if (provider.createProductEnquiry) {
|
|
2609
|
+
await provider.createProductEnquiry({
|
|
2610
|
+
productId: product.id,
|
|
2611
|
+
productName: product.name,
|
|
2612
|
+
productSlug: product.slug,
|
|
2613
|
+
customerName,
|
|
2614
|
+
customerEmail,
|
|
2615
|
+
customerPhone: customerPhone || void 0,
|
|
2616
|
+
subject,
|
|
2617
|
+
message,
|
|
2618
|
+
sourceUrl: typeof window === "undefined" ? void 0 : window.location.href,
|
|
2619
|
+
metadata: { sku: product.sku, categoryName: product.categoryName }
|
|
2620
|
+
});
|
|
2621
|
+
} else if (config.enquiry.email) {
|
|
2622
|
+
window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
|
|
2623
|
+
}
|
|
2624
|
+
setStatus("success");
|
|
2625
|
+
onSuccess?.();
|
|
2626
|
+
} catch (e) {
|
|
2627
|
+
setStatus("error");
|
|
2628
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2632
|
+
"form",
|
|
2633
|
+
{
|
|
2634
|
+
...storefrontComponentContracts.productDetail.enquiryForm,
|
|
2635
|
+
onSubmit: submit,
|
|
2636
|
+
className: "space-y-4",
|
|
2637
|
+
children: [
|
|
2638
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
2639
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
|
|
2640
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your name" }),
|
|
2641
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2642
|
+
"input",
|
|
2643
|
+
{
|
|
2644
|
+
"data-testid": TID.enquiryName,
|
|
2645
|
+
required: true,
|
|
2646
|
+
value: customerName,
|
|
2647
|
+
onChange: (e) => setCustomerName(e.target.value),
|
|
2648
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2649
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2650
|
+
}
|
|
2651
|
+
)
|
|
2652
|
+
] }),
|
|
2653
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
|
|
2654
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your email" }),
|
|
2655
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2656
|
+
"input",
|
|
2657
|
+
{
|
|
2658
|
+
"data-testid": TID.enquiryEmail,
|
|
2659
|
+
required: true,
|
|
2660
|
+
type: "email",
|
|
2661
|
+
value: customerEmail,
|
|
2662
|
+
onChange: (e) => setCustomerEmail(e.target.value),
|
|
2663
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2664
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2665
|
+
}
|
|
2666
|
+
)
|
|
2667
|
+
] })
|
|
2668
|
+
] }),
|
|
2669
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2670
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2671
|
+
"Phone",
|
|
2672
|
+
config.enquiry.requirePhone ? "" : " (optional)"
|
|
2673
|
+
] }),
|
|
2674
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2675
|
+
"input",
|
|
2676
|
+
{
|
|
2677
|
+
"data-testid": TID.enquiryPhone,
|
|
2678
|
+
required: config.enquiry.requirePhone,
|
|
2679
|
+
value: customerPhone,
|
|
2680
|
+
onChange: (e) => setCustomerPhone(e.target.value),
|
|
2681
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2682
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2683
|
+
}
|
|
2684
|
+
)
|
|
2685
|
+
] }),
|
|
2686
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2687
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Subject" }),
|
|
2688
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2689
|
+
"input",
|
|
2690
|
+
{
|
|
2691
|
+
"data-testid": TID.enquirySubject,
|
|
2692
|
+
required: true,
|
|
2693
|
+
value: subject,
|
|
2694
|
+
onChange: (e) => setSubject(e.target.value),
|
|
2695
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2696
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2697
|
+
}
|
|
2698
|
+
)
|
|
2699
|
+
] }),
|
|
2700
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2701
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Message" }),
|
|
2702
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2703
|
+
"textarea",
|
|
2704
|
+
{
|
|
2705
|
+
"data-testid": TID.enquiryMessage,
|
|
2706
|
+
required: true,
|
|
2707
|
+
rows: 5,
|
|
2708
|
+
value: message,
|
|
2709
|
+
onChange: (e) => setMessage(e.target.value),
|
|
2710
|
+
className: "w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2711
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2712
|
+
}
|
|
2713
|
+
)
|
|
2714
|
+
] }),
|
|
2715
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
2716
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2717
|
+
"button",
|
|
2718
|
+
{
|
|
2719
|
+
"data-testid": TID.enquirySubmit,
|
|
2720
|
+
type: "submit",
|
|
2721
|
+
disabled: status === "submitting",
|
|
2722
|
+
className: "sf-cta inline-flex items-center gap-2 bg-primary px-5 py-3 font-semibold text-primary-foreground disabled:cursor-wait disabled:opacity-60",
|
|
2723
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2724
|
+
children: [
|
|
2725
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Send, { className: "h-4 w-4" }),
|
|
2726
|
+
status === "submitting" ? "Sending..." : "Send enquiry"
|
|
2727
|
+
]
|
|
2728
|
+
}
|
|
2729
|
+
),
|
|
2730
|
+
config.enquiry.whatsappUrl && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2731
|
+
"a",
|
|
2732
|
+
{
|
|
2733
|
+
href: buildWhatsAppHref(config.enquiry.whatsappUrl, product),
|
|
2734
|
+
target: "_blank",
|
|
2735
|
+
rel: "noreferrer",
|
|
2736
|
+
className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2737
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2738
|
+
children: [
|
|
2739
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MessageCircle, { className: "h-4 w-4" }),
|
|
2740
|
+
"WhatsApp"
|
|
2741
|
+
]
|
|
2742
|
+
}
|
|
2743
|
+
),
|
|
2744
|
+
config.enquiry.email && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2745
|
+
"a",
|
|
2746
|
+
{
|
|
2747
|
+
href: `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`,
|
|
2748
|
+
className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2749
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2750
|
+
children: [
|
|
2751
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Mail, { className: "h-4 w-4" }),
|
|
2752
|
+
"Email"
|
|
2753
|
+
]
|
|
2754
|
+
}
|
|
2755
|
+
)
|
|
2756
|
+
] }),
|
|
2757
|
+
status === "success" && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquirySuccess, className: "text-sm font-medium text-emerald-600", children: config.enquiry.successMessage }),
|
|
2758
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquiryError, className: "text-sm font-medium text-red-600", children: error })
|
|
2759
|
+
]
|
|
2760
|
+
}
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2118
2763
|
function ProductDetailPage({ slug: slug2 }) {
|
|
2119
2764
|
const config = useStorefrontConfig();
|
|
2765
|
+
const actions = useStorefrontActions();
|
|
2120
2766
|
const { product, loading } = useProduct(slug2);
|
|
2121
2767
|
const addItem = useCartStore((s) => s.addItem);
|
|
2122
|
-
const [qty, setQty] =
|
|
2123
|
-
const [selectedOptions, setSelectedOptions] =
|
|
2768
|
+
const [qty, setQty] = React6.useState(1);
|
|
2769
|
+
const [selectedOptions, setSelectedOptions] = React6.useState({});
|
|
2124
2770
|
const optionGroups = product ? getProductOptionGroups(product) : [];
|
|
2125
2771
|
useStorefrontHead({
|
|
2126
2772
|
title: product ? `${product.name} \u2014 ${config.name}` : config.name,
|
|
2127
2773
|
description: product?.description
|
|
2128
2774
|
});
|
|
2129
|
-
|
|
2775
|
+
React6.useEffect(() => {
|
|
2130
2776
|
if (!product) return;
|
|
2131
2777
|
const defaults = Object.fromEntries(
|
|
2132
2778
|
getProductOptionGroups(product).map((group) => [group.label, group.values[0] ?? ""])
|
|
@@ -2152,21 +2798,56 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2152
2798
|
const soldOut = product.inventoryCount <= 0;
|
|
2153
2799
|
const lowStock = !soldOut && product.inventoryCount <= 5;
|
|
2154
2800
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
2801
|
+
const components = getStorefrontComponents(config);
|
|
2802
|
+
const ProductDetailComponent = components.ProductDetail;
|
|
2803
|
+
const addToCart = () => addItem(product, qty, selectedOptions);
|
|
2804
|
+
const openEnquiry = () => {
|
|
2805
|
+
document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
2806
|
+
};
|
|
2155
2807
|
const discountPct = product.compareAtPrice && product.compareAtPrice > product.price ? Math.round((1 - product.price / product.compareAtPrice) * 100) : 0;
|
|
2156
|
-
|
|
2808
|
+
if (ProductDetailComponent) {
|
|
2809
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
|
+
ProductDetailComponent,
|
|
2811
|
+
{
|
|
2812
|
+
product,
|
|
2813
|
+
config,
|
|
2814
|
+
commerceMode: config.commerceMode,
|
|
2815
|
+
primaryImage: image,
|
|
2816
|
+
optionGroups,
|
|
2817
|
+
selectedOptions,
|
|
2818
|
+
setSelectedOptions,
|
|
2819
|
+
quantity: qty,
|
|
2820
|
+
setQuantity: setQty,
|
|
2821
|
+
soldOut,
|
|
2822
|
+
lowStock,
|
|
2823
|
+
actions,
|
|
2824
|
+
addToCart,
|
|
2825
|
+
openEnquiry
|
|
2826
|
+
}
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("main", { ...storefrontComponentContracts.productDetail.root, className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
|
|
2157
2830
|
/* @__PURE__ */ jsxRuntime.jsxs(Link, { to: "/", className: "mb-6 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [
|
|
2158
2831
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-4 w-4" }),
|
|
2159
2832
|
" Continuar comprando"
|
|
2160
2833
|
] }),
|
|
2161
2834
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
|
|
2162
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2163
|
-
"
|
|
2835
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2836
|
+
"div",
|
|
2164
2837
|
{
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2838
|
+
...storefrontComponentContracts.productDetail.gallery,
|
|
2839
|
+
className: "group overflow-hidden border bg-muted",
|
|
2840
|
+
style: { borderRadius: "var(--sf-radius-card)" },
|
|
2841
|
+
children: image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2842
|
+
SmoothImage,
|
|
2843
|
+
{
|
|
2844
|
+
src: image.url,
|
|
2845
|
+
alt: image.altText ?? product.name,
|
|
2846
|
+
className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
2847
|
+
}
|
|
2848
|
+
)
|
|
2168
2849
|
}
|
|
2169
|
-
)
|
|
2850
|
+
),
|
|
2170
2851
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
|
|
2171
2852
|
/* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
2172
2853
|
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "transition-colors hover:text-foreground", children: "In\xEDcio" }),
|
|
@@ -2178,7 +2859,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2178
2859
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: product.name })
|
|
2179
2860
|
] }),
|
|
2180
2861
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { "data-testid": TID.pdpName, className: "sf-heading text-3xl font-bold tracking-tight", children: product.name }),
|
|
2181
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
|
|
2862
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
|
|
2182
2863
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xl", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2183
2864
|
Price,
|
|
2184
2865
|
{
|
|
@@ -2195,7 +2876,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2195
2876
|
] })
|
|
2196
2877
|
] }),
|
|
2197
2878
|
/* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.pdpDescription, className: "mt-5 leading-relaxed text-muted-foreground", children: product.description }),
|
|
2198
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
|
|
2879
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: [
|
|
2199
2880
|
soldOut ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2 font-medium text-muted-foreground", children: [
|
|
2200
2881
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full bg-muted-foreground" }),
|
|
2201
2882
|
" Sem estoque"
|
|
@@ -2221,7 +2902,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2221
2902
|
onChange: setSelectedOptions
|
|
2222
2903
|
}
|
|
2223
2904
|
),
|
|
2224
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2905
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ...storefrontComponentContracts.productDetail.actions, className: "mt-8", children: config.commerceMode === "checkout" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
2225
2906
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2226
2907
|
QuantityInput,
|
|
2227
2908
|
{
|
|
@@ -2237,14 +2918,32 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2237
2918
|
type: "button",
|
|
2238
2919
|
"data-testid": TID.pdpAddToCart,
|
|
2239
2920
|
disabled: soldOut,
|
|
2240
|
-
onClick:
|
|
2921
|
+
onClick: addToCart,
|
|
2241
2922
|
className: "sf-cta flex-1 bg-primary py-3.5 font-semibold text-primary-foreground shadow-md transition-all hover:-translate-y-0.5 hover:shadow-lg disabled:cursor-not-allowed disabled:opacity-40 disabled:shadow-none disabled:hover:translate-y-0",
|
|
2242
2923
|
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2243
2924
|
children: soldOut ? "Esgotado" : "Adicionar ao carrinho"
|
|
2244
2925
|
}
|
|
2245
2926
|
)
|
|
2246
|
-
] })
|
|
2247
|
-
|
|
2927
|
+
] }) : config.commerceMode === "enquiry" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2928
|
+
"button",
|
|
2929
|
+
{
|
|
2930
|
+
type: "button",
|
|
2931
|
+
...storefrontComponentContracts.productDetail.enquiryButton,
|
|
2932
|
+
onClick: openEnquiry,
|
|
2933
|
+
className: "sf-cta w-full bg-primary py-3.5 font-semibold text-primary-foreground shadow-md transition-all hover:-translate-y-0.5 hover:shadow-lg",
|
|
2934
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2935
|
+
children: config.enquiry.label
|
|
2936
|
+
}
|
|
2937
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2938
|
+
Link,
|
|
2939
|
+
{
|
|
2940
|
+
to: config.catalogPath,
|
|
2941
|
+
className: "inline-flex border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2942
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2943
|
+
children: "Voltar ao cat\xE1logo"
|
|
2944
|
+
}
|
|
2945
|
+
) }),
|
|
2946
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
|
|
2248
2947
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
|
|
2249
2948
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Truck, { className: "h-5 w-5 text-primary" }),
|
|
2250
2949
|
" Envio r\xE1pido"
|
|
@@ -2260,6 +2959,11 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2260
2959
|
] })
|
|
2261
2960
|
] })
|
|
2262
2961
|
] }),
|
|
2962
|
+
config.commerceMode === "enquiry" && /* @__PURE__ */ jsxRuntime.jsx("section", { id: "storefront-product-enquiry", className: "mt-12 border-t pt-10", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-3xl", children: [
|
|
2963
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-2xl font-semibold", children: "Need more information?" }),
|
|
2964
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: "Send an enquiry about this product." }),
|
|
2965
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(ProductEnquiryForm, { product }) })
|
|
2966
|
+
] }) }),
|
|
2263
2967
|
/* @__PURE__ */ jsxRuntime.jsx(ProductSpecs, { product }),
|
|
2264
2968
|
/* @__PURE__ */ jsxRuntime.jsx(RelatedProducts, { product })
|
|
2265
2969
|
] });
|
|
@@ -2300,16 +3004,17 @@ async function placeStorefrontOrder({
|
|
|
2300
3004
|
}))
|
|
2301
3005
|
});
|
|
2302
3006
|
if (markPaid) {
|
|
2303
|
-
await provider.
|
|
3007
|
+
if (provider.confirmPayment) await provider.confirmPayment(order.id);
|
|
3008
|
+
else await provider.updateOrder(order.id, { financialStatus: "paid" });
|
|
2304
3009
|
}
|
|
2305
3010
|
return { order, customerId: customerId ?? order.customerId ?? "" };
|
|
2306
3011
|
}
|
|
2307
3012
|
function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
|
|
2308
|
-
const [email, setEmail] =
|
|
2309
|
-
const [password, setPassword] =
|
|
2310
|
-
const [busy, setBusy] =
|
|
2311
|
-
const [error, setError] =
|
|
2312
|
-
|
|
3013
|
+
const [email, setEmail] = React6.useState(defaultEmail);
|
|
3014
|
+
const [password, setPassword] = React6.useState("");
|
|
3015
|
+
const [busy, setBusy] = React6.useState(false);
|
|
3016
|
+
const [error, setError] = React6.useState(null);
|
|
3017
|
+
React6.useEffect(() => {
|
|
2313
3018
|
const onKey = (event) => {
|
|
2314
3019
|
if (event.key === "Escape") onClose();
|
|
2315
3020
|
};
|
|
@@ -2471,13 +3176,13 @@ function CheckoutPage() {
|
|
|
2471
3176
|
const session = useSessionStore();
|
|
2472
3177
|
const validateDiscount2 = useDiscountValidator();
|
|
2473
3178
|
useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
|
|
2474
|
-
const [selectedAddressId, setSelectedAddressId] =
|
|
2475
|
-
const [selectedPaymentId, setSelectedPaymentId] =
|
|
2476
|
-
const [savedAddresses, setSavedAddresses] =
|
|
2477
|
-
const [savedPaymentMethods, setSavedPaymentMethods] =
|
|
2478
|
-
const [addressMode, setAddressMode] =
|
|
2479
|
-
const [paymentMode, setPaymentMode] =
|
|
2480
|
-
const [form, setForm] =
|
|
3179
|
+
const [selectedAddressId, setSelectedAddressId] = React6.useState(DEFAULT_ADDRESS.id);
|
|
3180
|
+
const [selectedPaymentId, setSelectedPaymentId] = React6.useState(DEFAULT_PAYMENT.id);
|
|
3181
|
+
const [savedAddresses, setSavedAddresses] = React6.useState(SAVED_ADDRESSES);
|
|
3182
|
+
const [savedPaymentMethods, setSavedPaymentMethods] = React6.useState(SAVED_PAYMENT_METHODS);
|
|
3183
|
+
const [addressMode, setAddressMode] = React6.useState("saved");
|
|
3184
|
+
const [paymentMode, setPaymentMode] = React6.useState("saved");
|
|
3185
|
+
const [form, setForm] = React6.useState({
|
|
2481
3186
|
email: session.email ?? "",
|
|
2482
3187
|
name: session.name ?? "",
|
|
2483
3188
|
street: DEFAULT_ADDRESS.street,
|
|
@@ -2487,20 +3192,20 @@ function CheckoutPage() {
|
|
|
2487
3192
|
expiry: DEFAULT_PAYMENT.expiry,
|
|
2488
3193
|
cvc: DEFAULT_PAYMENT.cvc
|
|
2489
3194
|
});
|
|
2490
|
-
const [error, setError] =
|
|
2491
|
-
const [discountCode, setDiscountCode] =
|
|
2492
|
-
const [discountError, setDiscountError] =
|
|
2493
|
-
const [discountSuccess, setDiscountSuccess] =
|
|
2494
|
-
const [applyingDiscount, setApplyingDiscount] =
|
|
2495
|
-
const [placing, setPlacing] =
|
|
2496
|
-
const [processingStep, setProcessingStep] =
|
|
2497
|
-
const [showSignin, setShowSignin] =
|
|
3195
|
+
const [error, setError] = React6.useState(null);
|
|
3196
|
+
const [discountCode, setDiscountCode] = React6.useState("");
|
|
3197
|
+
const [discountError, setDiscountError] = React6.useState(null);
|
|
3198
|
+
const [discountSuccess, setDiscountSuccess] = React6.useState(null);
|
|
3199
|
+
const [applyingDiscount, setApplyingDiscount] = React6.useState(false);
|
|
3200
|
+
const [placing, setPlacing] = React6.useState(false);
|
|
3201
|
+
const [processingStep, setProcessingStep] = React6.useState(0);
|
|
3202
|
+
const [showSignin, setShowSignin] = React6.useState(false);
|
|
2498
3203
|
const subtotal = selectSubtotal(cart);
|
|
2499
3204
|
const discountTotal = selectDiscountTotal(cart);
|
|
2500
3205
|
const shipping = selectShipping(cart, config);
|
|
2501
3206
|
const total = selectTotal(cart, config);
|
|
2502
3207
|
const money = (value) => formatMoney(value, config.currency, config.locale);
|
|
2503
|
-
|
|
3208
|
+
React6.useEffect(() => {
|
|
2504
3209
|
if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
|
|
2505
3210
|
}, [cart.lines.length, config.catalogPath, placing]);
|
|
2506
3211
|
const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
|
|
@@ -2815,7 +3520,7 @@ function CheckoutPage() {
|
|
|
2815
3520
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
|
|
2816
3521
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-4", children: cart.lines.map((line) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex gap-3 text-sm", children: [
|
|
2817
3522
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background", children: [
|
|
2818
|
-
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3523
|
+
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
|
|
2819
3524
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted-foreground px-1.5 text-[10px] font-bold text-background", children: line.quantity })
|
|
2820
3525
|
] }),
|
|
2821
3526
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -3155,11 +3860,11 @@ function SuccessMark() {
|
|
|
3155
3860
|
}
|
|
3156
3861
|
function OrderConfirmationPage({ orderId }) {
|
|
3157
3862
|
const config = useStorefrontConfig();
|
|
3158
|
-
const [order, setOrder] =
|
|
3159
|
-
const [loading, setLoading] =
|
|
3863
|
+
const [order, setOrder] = React6.useState(null);
|
|
3864
|
+
const [loading, setLoading] = React6.useState(true);
|
|
3160
3865
|
const money = (v) => formatMoney(v, config.currency, config.locale);
|
|
3161
3866
|
useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
|
|
3162
|
-
|
|
3867
|
+
React6.useEffect(() => {
|
|
3163
3868
|
let cancelled = false;
|
|
3164
3869
|
runtime.getShopProvider().getOrder(orderId).then((o) => {
|
|
3165
3870
|
if (!cancelled) setOrder(o);
|
|
@@ -3237,7 +3942,7 @@ function OrderConfirmationPage({ orderId }) {
|
|
|
3237
3942
|
/* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order })
|
|
3238
3943
|
] }),
|
|
3239
3944
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-8 space-y-3 text-left", children: order.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
|
|
3240
|
-
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3945
|
+
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
|
|
3241
3946
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
3242
3947
|
item.name,
|
|
3243
3948
|
" ",
|
|
@@ -3291,11 +3996,11 @@ function OrderConfirmationPage({ orderId }) {
|
|
|
3291
3996
|
function useMyOrders() {
|
|
3292
3997
|
const customerId = useSessionStore((s) => s.customerId);
|
|
3293
3998
|
const email = useSessionStore((s) => s.email);
|
|
3294
|
-
const [orders, setOrders] =
|
|
3295
|
-
const [loading, setLoading] =
|
|
3296
|
-
const [tick, setTick] =
|
|
3297
|
-
const refresh =
|
|
3298
|
-
|
|
3999
|
+
const [orders, setOrders] = React6.useState([]);
|
|
4000
|
+
const [loading, setLoading] = React6.useState(true);
|
|
4001
|
+
const [tick, setTick] = React6.useState(0);
|
|
4002
|
+
const refresh = React6.useCallback(() => setTick((t) => t + 1), []);
|
|
4003
|
+
React6.useEffect(() => {
|
|
3299
4004
|
let cancelled = false;
|
|
3300
4005
|
if (!customerId && !email) {
|
|
3301
4006
|
setOrders([]);
|
|
@@ -3317,12 +4022,12 @@ function useMyOrders() {
|
|
|
3317
4022
|
return { orders, loading, refresh };
|
|
3318
4023
|
}
|
|
3319
4024
|
function AuthForm() {
|
|
3320
|
-
const [mode, setMode] =
|
|
3321
|
-
const [email, setEmail] =
|
|
3322
|
-
const [password, setPassword] =
|
|
3323
|
-
const [name, setName] =
|
|
3324
|
-
const [busy, setBusy] =
|
|
3325
|
-
const [error, setError] =
|
|
4025
|
+
const [mode, setMode] = React6.useState("signin");
|
|
4026
|
+
const [email, setEmail] = React6.useState("");
|
|
4027
|
+
const [password, setPassword] = React6.useState("");
|
|
4028
|
+
const [name, setName] = React6.useState("");
|
|
4029
|
+
const [busy, setBusy] = React6.useState(false);
|
|
4030
|
+
const [error, setError] = React6.useState(null);
|
|
3326
4031
|
async function submit(e) {
|
|
3327
4032
|
e.preventDefault();
|
|
3328
4033
|
setError(null);
|
|
@@ -3445,7 +4150,7 @@ function OrdersPanel() {
|
|
|
3445
4150
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: new Date(order.createdAt).toLocaleDateString(config.locale) }),
|
|
3446
4151
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order, compact: true }) }),
|
|
3447
4152
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-3 flex flex-wrap gap-2", children: order.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2 rounded-lg border px-2 py-1 text-xs", children: [
|
|
3448
|
-
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4153
|
+
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
|
|
3449
4154
|
item.name,
|
|
3450
4155
|
" \xD7 ",
|
|
3451
4156
|
item.quantity
|
|
@@ -3464,9 +4169,9 @@ function OrdersPanel() {
|
|
|
3464
4169
|
}
|
|
3465
4170
|
function ProfilePanel() {
|
|
3466
4171
|
const session = useSessionStore();
|
|
3467
|
-
const [name, setName] =
|
|
3468
|
-
const [saving, setSaving] =
|
|
3469
|
-
const [saved, setSaved] =
|
|
4172
|
+
const [name, setName] = React6.useState(session.name ?? "");
|
|
4173
|
+
const [saving, setSaving] = React6.useState(false);
|
|
4174
|
+
const [saved, setSaved] = React6.useState(false);
|
|
3470
4175
|
async function save(e) {
|
|
3471
4176
|
e.preventDefault();
|
|
3472
4177
|
const trimmed = name.trim();
|
|
@@ -3562,7 +4267,7 @@ var SECTION_SUBTITLE = {
|
|
|
3562
4267
|
function MyPurchasesPage() {
|
|
3563
4268
|
const config = useStorefrontConfig();
|
|
3564
4269
|
const session = useSessionStore();
|
|
3565
|
-
const [section, setSection] =
|
|
4270
|
+
const [section, setSection] = React6.useState("orders");
|
|
3566
4271
|
useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
|
|
3567
4272
|
if (!session.email) {
|
|
3568
4273
|
return /* @__PURE__ */ jsxRuntime.jsx("main", { className: "mx-auto max-w-3xl px-4 py-12 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsx(AuthForm, {}) });
|
|
@@ -3625,7 +4330,7 @@ function PolicyPage({ kind }) {
|
|
|
3625
4330
|
] }) })
|
|
3626
4331
|
] });
|
|
3627
4332
|
}
|
|
3628
|
-
var StorefrontErrorBoundary = class extends
|
|
4333
|
+
var StorefrontErrorBoundary = class extends React6__default.default.Component {
|
|
3629
4334
|
constructor() {
|
|
3630
4335
|
super(...arguments);
|
|
3631
4336
|
this.state = { error: null };
|
|
@@ -3669,7 +4374,7 @@ var ACCENT = {
|
|
|
3669
4374
|
function ToastCard({ toast: toast2 }) {
|
|
3670
4375
|
const dismiss = useToastStore((s) => s.dismiss);
|
|
3671
4376
|
const Icon2 = ICON[toast2.variant];
|
|
3672
|
-
|
|
4377
|
+
React6.useEffect(() => {
|
|
3673
4378
|
const timer = window.setTimeout(() => dismiss(toast2.id), toast2.durationMs);
|
|
3674
4379
|
return () => window.clearTimeout(timer);
|
|
3675
4380
|
}, [toast2.id, toast2.durationMs, dismiss]);
|
|
@@ -3726,8 +4431,8 @@ function RouteSwitch({ path }) {
|
|
|
3726
4431
|
if (product?.slug) return /* @__PURE__ */ jsxRuntime.jsx(ProductDetailPage, { slug: product.slug });
|
|
3727
4432
|
const order = matchPath("/order/:id", path);
|
|
3728
4433
|
if (order?.id) return /* @__PURE__ */ jsxRuntime.jsx(OrderConfirmationPage, { orderId: order.id });
|
|
3729
|
-
if (matchPath("/checkout", path)) return /* @__PURE__ */ jsxRuntime.jsx(CheckoutPage, {});
|
|
3730
|
-
if (matchPath("/account", path)) return /* @__PURE__ */ jsxRuntime.jsx(MyPurchasesPage, {});
|
|
4434
|
+
if (matchPath("/checkout", path) && config.features.checkout) return /* @__PURE__ */ jsxRuntime.jsx(CheckoutPage, {});
|
|
4435
|
+
if (matchPath("/account", path) && config.features.accounts) return /* @__PURE__ */ jsxRuntime.jsx(MyPurchasesPage, {});
|
|
3731
4436
|
if (matchPath("/catalog", path)) return /* @__PURE__ */ jsxRuntime.jsx(CatalogPage, {});
|
|
3732
4437
|
if (matchPath("/privacy", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "privacy" });
|
|
3733
4438
|
if (matchPath("/terms", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "terms" });
|
|
@@ -3769,12 +4474,26 @@ function buildMockSeed(config) {
|
|
|
3769
4474
|
};
|
|
3770
4475
|
}
|
|
3771
4476
|
function initStorefrontRuntime(config) {
|
|
3772
|
-
|
|
4477
|
+
if (resolveConfig(config).features.accounts) {
|
|
4478
|
+
initCustomerAuth(resolveAuthAdapter(config.auth, {
|
|
4479
|
+
supabaseUrl: config.supabaseUrl,
|
|
4480
|
+
supabaseAnonKey: config.supabaseAnonKey
|
|
4481
|
+
}));
|
|
4482
|
+
}
|
|
3773
4483
|
if (config.provider) {
|
|
3774
4484
|
runtime.setShopProvider(config.provider);
|
|
3775
4485
|
} else {
|
|
3776
4486
|
runtime.setShopProvider(mock.createMockShopProvider(buildMockSeed(config)));
|
|
3777
4487
|
}
|
|
4488
|
+
const reconcileCart = () => {
|
|
4489
|
+
const provider = runtime.getShopProvider();
|
|
4490
|
+
void useCartStore.getState().reconcile(async (line) => {
|
|
4491
|
+
const product = await provider.getProduct(line.productId);
|
|
4492
|
+
return product != null;
|
|
4493
|
+
});
|
|
4494
|
+
};
|
|
4495
|
+
if (useCartStore.persist.hasHydrated()) reconcileCart();
|
|
4496
|
+
else useCartStore.persist.onFinishHydration(reconcileCart);
|
|
3778
4497
|
if (config.supabaseUrl || config.supabaseAnonKey) {
|
|
3779
4498
|
console.warn(
|
|
3780
4499
|
"@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path."
|
|
@@ -3785,14 +4504,20 @@ function StorefrontShell() {
|
|
|
3785
4504
|
const config = useStorefrontConfig();
|
|
3786
4505
|
const path = useHashPath();
|
|
3787
4506
|
const focused = isFocusedRoute(config, path);
|
|
3788
|
-
|
|
4507
|
+
const components = getStorefrontComponents(config);
|
|
4508
|
+
const chromeProps = { config, commerceMode: config.commerceMode };
|
|
4509
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3789
4510
|
config.theme && /* @__PURE__ */ jsxRuntime.jsx(StorefrontThemeStyle, { theme: config.theme }),
|
|
3790
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {}),
|
|
4511
|
+
!focused && (components.Header ? /* @__PURE__ */ jsxRuntime.jsx(components.Header, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {})),
|
|
3791
4512
|
/* @__PURE__ */ jsxRuntime.jsx(StorefrontErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(RouteSwitch, { path }) }, path),
|
|
3792
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
|
|
3793
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {}),
|
|
4513
|
+
!focused && config.features.cart && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
|
|
4514
|
+
!focused && (components.Footer ? /* @__PURE__ */ jsxRuntime.jsx(components.Footer, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {})),
|
|
3794
4515
|
/* @__PURE__ */ jsxRuntime.jsx(Toaster, {})
|
|
3795
4516
|
] });
|
|
4517
|
+
if (components.Shell) {
|
|
4518
|
+
return /* @__PURE__ */ jsxRuntime.jsx(components.Shell, { ...chromeProps, children: body });
|
|
4519
|
+
}
|
|
4520
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-background text-foreground", children: body });
|
|
3796
4521
|
}
|
|
3797
4522
|
function createStorefrontApp(config) {
|
|
3798
4523
|
const resolved = resolveConfig(config);
|
|
@@ -3803,6 +4528,37 @@ function createStorefrontApp(config) {
|
|
|
3803
4528
|
StorefrontApp.displayName = "StorefrontApp";
|
|
3804
4529
|
return StorefrontApp;
|
|
3805
4530
|
}
|
|
4531
|
+
function createStorefront(options) {
|
|
4532
|
+
const routes = [...options.pages ?? [], ...options.routes ?? []];
|
|
4533
|
+
const config = {
|
|
4534
|
+
...options.config,
|
|
4535
|
+
provider: options.provider ?? options.config.provider,
|
|
4536
|
+
components: {
|
|
4537
|
+
...options.config.components,
|
|
4538
|
+
...options.components
|
|
4539
|
+
},
|
|
4540
|
+
routes: routes.length > 0 ? routes : options.config.routes,
|
|
4541
|
+
home: options.sections ? { ...options.config.home ?? { sections: [] }, sections: options.sections } : options.config.home
|
|
4542
|
+
};
|
|
4543
|
+
return createStorefrontApp(config);
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4546
|
+
// src/define.ts
|
|
4547
|
+
function defineStorefrontConfig(config) {
|
|
4548
|
+
return config;
|
|
4549
|
+
}
|
|
4550
|
+
function defineStorefrontComponents(components) {
|
|
4551
|
+
return components;
|
|
4552
|
+
}
|
|
4553
|
+
function defineStorefrontRoutes(routes) {
|
|
4554
|
+
return routes;
|
|
4555
|
+
}
|
|
4556
|
+
function defineStorefrontSections(sections) {
|
|
4557
|
+
return sections;
|
|
4558
|
+
}
|
|
4559
|
+
function defineStorefrontApp(options) {
|
|
4560
|
+
return options;
|
|
4561
|
+
}
|
|
3806
4562
|
var runtimeConfigRegistry = /* @__PURE__ */ new Map();
|
|
3807
4563
|
function slug(name) {
|
|
3808
4564
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "app";
|
|
@@ -3861,7 +4617,7 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
3861
4617
|
provider: manifest.backend?.provider,
|
|
3862
4618
|
url: manifest.backend?.url
|
|
3863
4619
|
},
|
|
3864
|
-
|
|
4620
|
+
components: runtimeConfig?.components,
|
|
3865
4621
|
routes: runtimeConfig?.routes,
|
|
3866
4622
|
provider: runtimeConfig?.provider,
|
|
3867
4623
|
auth: runtimeConfig?.auth,
|
|
@@ -3869,9 +4625,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
3869
4625
|
};
|
|
3870
4626
|
}
|
|
3871
4627
|
function StorefrontScaffold({ manifest, surface }) {
|
|
3872
|
-
const config =
|
|
3873
|
-
const resolved =
|
|
3874
|
-
const inited =
|
|
4628
|
+
const config = React6__default.default.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
|
|
4629
|
+
const resolved = React6__default.default.useMemo(() => resolveConfig(config), [config]);
|
|
4630
|
+
const inited = React6__default.default.useRef(false);
|
|
3875
4631
|
if (!inited.current) {
|
|
3876
4632
|
initStorefrontRuntime(config);
|
|
3877
4633
|
inited.current = true;
|
|
@@ -4097,11 +4853,73 @@ var atelierTemplate = {
|
|
|
4097
4853
|
]
|
|
4098
4854
|
})
|
|
4099
4855
|
};
|
|
4856
|
+
var foodTemplate = {
|
|
4857
|
+
id: "food",
|
|
4858
|
+
inspiration: "Delivery / restaurant storefront",
|
|
4859
|
+
announcement: "ENTREGA GR\xC1TIS ACIMA DE R$ 150 \xB7 PE\xC7A J\xC1",
|
|
4860
|
+
theme: {
|
|
4861
|
+
name: "food",
|
|
4862
|
+
colors: {
|
|
4863
|
+
background: "30 33% 97%",
|
|
4864
|
+
foreground: "20 14% 15%",
|
|
4865
|
+
primary: "14 84% 50%",
|
|
4866
|
+
primaryForeground: "0 0% 100%",
|
|
4867
|
+
card: "30 33% 99%",
|
|
4868
|
+
cardForeground: "20 14% 15%",
|
|
4869
|
+
muted: "28 20% 92%",
|
|
4870
|
+
mutedForeground: "22 10% 42%",
|
|
4871
|
+
border: "28 18% 85%",
|
|
4872
|
+
headerBackground: "20 14% 11%",
|
|
4873
|
+
headerForeground: "30 24% 96%",
|
|
4874
|
+
announcementBackground: "14 84% 50%",
|
|
4875
|
+
announcementForeground: "0 0% 100%"
|
|
4876
|
+
},
|
|
4877
|
+
font: { heading: "Poppins", body: "Inter" },
|
|
4878
|
+
radius: "round",
|
|
4879
|
+
header: { variant: "classic" },
|
|
4880
|
+
productCard: { style: "card", imageAspect: "square" }
|
|
4881
|
+
},
|
|
4882
|
+
home: (storeName, images) => ({
|
|
4883
|
+
sections: [
|
|
4884
|
+
{
|
|
4885
|
+
type: "hero",
|
|
4886
|
+
variant: "banner",
|
|
4887
|
+
height: "tall",
|
|
4888
|
+
slides: [
|
|
4889
|
+
{ 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] }
|
|
4890
|
+
]
|
|
4891
|
+
},
|
|
4892
|
+
{
|
|
4893
|
+
type: "benefits",
|
|
4894
|
+
items: [
|
|
4895
|
+
{ icon: "Flame", title: "Feito na hora", text: "Preparado com capricho" },
|
|
4896
|
+
{ icon: "Truck", title: "Entrega r\xE1pida", text: "Frete gr\xE1tis acima de R$ 150" },
|
|
4897
|
+
{ icon: "CreditCard", title: "Pagamento f\xE1cil", text: "Pix, cart\xE3o e mais" }
|
|
4898
|
+
]
|
|
4899
|
+
},
|
|
4900
|
+
{ type: "categories", style: "tiles", title: "Nosso card\xE1pio" },
|
|
4901
|
+
{ type: "products", title: "Mais pedidos", eyebrow: "favoritos da casa", filter: "all", limit: 8 },
|
|
4902
|
+
{ type: "banner", title: "Combo do dia", eyebrow: "oferta", subtitle: "Sele\xE7\xE3o especial com desconto", cta: "Aproveitar", hue: 14, image: images?.banners?.[0] },
|
|
4903
|
+
{ type: "products", title: "Novidades no card\xE1pio", filter: "new", limit: 4 },
|
|
4904
|
+
{
|
|
4905
|
+
type: "testimonials",
|
|
4906
|
+
title: "O que dizem por a\xED",
|
|
4907
|
+
items: [
|
|
4908
|
+
{ quote: "Chegou quentinho e absurdo de bom. Virei cliente!", author: "Rafael M." },
|
|
4909
|
+
{ quote: "Sabor de restaurante em casa, sem complica\xE7\xE3o.", author: "Beatriz L." },
|
|
4910
|
+
{ quote: "Entrega r\xE1pida e por\xE7\xE3o generosa. Recomendo demais.", author: "Diego S." }
|
|
4911
|
+
]
|
|
4912
|
+
},
|
|
4913
|
+
{ type: "newsletter", title: "Receba nossas ofertas", subtitle: "Promo\xE7\xF5es e novidades do card\xE1pio, direto no seu e-mail." }
|
|
4914
|
+
]
|
|
4915
|
+
})
|
|
4916
|
+
};
|
|
4100
4917
|
var storefrontTemplates = {
|
|
4101
4918
|
mare: mareTemplate,
|
|
4102
4919
|
sertao: sertaoTemplate,
|
|
4103
4920
|
volt: voltTemplate,
|
|
4104
|
-
atelier: atelierTemplate
|
|
4921
|
+
atelier: atelierTemplate,
|
|
4922
|
+
food: foodTemplate
|
|
4105
4923
|
};
|
|
4106
4924
|
|
|
4107
4925
|
Object.defineProperty(exports, "getSupabaseClientOptional", {
|
|
@@ -4122,6 +4940,7 @@ exports.HeroSection = HeroSection;
|
|
|
4122
4940
|
exports.HomePage = HomePage;
|
|
4123
4941
|
exports.Link = Link;
|
|
4124
4942
|
exports.ManifestoBlock = ManifestoBlock;
|
|
4943
|
+
exports.MediaCarousel = MediaCarousel;
|
|
4125
4944
|
exports.MyPurchasesPage = MyPurchasesPage;
|
|
4126
4945
|
exports.NewsletterBand = NewsletterBand;
|
|
4127
4946
|
exports.OrderConfirmationPage = OrderConfirmationPage;
|
|
@@ -4129,24 +4948,35 @@ exports.OrderTrackingTimeline = OrderTrackingTimeline;
|
|
|
4129
4948
|
exports.Price = Price;
|
|
4130
4949
|
exports.ProductCard = ProductCard;
|
|
4131
4950
|
exports.ProductDetailPage = ProductDetailPage;
|
|
4951
|
+
exports.ProductEnquiryForm = ProductEnquiryForm;
|
|
4132
4952
|
exports.ProductGrid = ProductGrid;
|
|
4133
4953
|
exports.ProductOptionSelector = ProductOptionSelector;
|
|
4134
4954
|
exports.ProductRail = ProductRail;
|
|
4955
|
+
exports.ProductSlider = ProductSlider;
|
|
4135
4956
|
exports.PromoBanner = PromoBanner;
|
|
4136
4957
|
exports.QuantityInput = QuantityInput;
|
|
4137
4958
|
exports.Reveal = Reveal;
|
|
4959
|
+
exports.SmoothImage = SmoothImage;
|
|
4138
4960
|
exports.StorefrontFooter = StorefrontFooter;
|
|
4139
4961
|
exports.StorefrontHeader = StorefrontHeader;
|
|
4140
4962
|
exports.StorefrontScaffold = StorefrontScaffold;
|
|
4963
|
+
exports.StorefrontSections = StorefrontSections;
|
|
4141
4964
|
exports.StorefrontShell = StorefrontShell;
|
|
4142
4965
|
exports.StorefrontThemeStyle = StorefrontThemeStyle;
|
|
4143
4966
|
exports.TID = TID;
|
|
4144
4967
|
exports.Testimonials = Testimonials;
|
|
4145
4968
|
exports.atelierTemplate = atelierTemplate;
|
|
4146
4969
|
exports.bannerPlaceholder = bannerPlaceholder;
|
|
4970
|
+
exports.createStorefront = createStorefront;
|
|
4147
4971
|
exports.createStorefrontApp = createStorefrontApp;
|
|
4148
4972
|
exports.defineStorefront = defineStorefront;
|
|
4973
|
+
exports.defineStorefrontApp = defineStorefrontApp;
|
|
4974
|
+
exports.defineStorefrontComponents = defineStorefrontComponents;
|
|
4975
|
+
exports.defineStorefrontConfig = defineStorefrontConfig;
|
|
4976
|
+
exports.defineStorefrontRoutes = defineStorefrontRoutes;
|
|
4977
|
+
exports.defineStorefrontSections = defineStorefrontSections;
|
|
4149
4978
|
exports.establishCustomerSession = establishCustomerSession;
|
|
4979
|
+
exports.foodTemplate = foodTemplate;
|
|
4150
4980
|
exports.formatMoney = formatMoney;
|
|
4151
4981
|
exports.formatProductOptionSelection = formatProductOptionSelection;
|
|
4152
4982
|
exports.getCustomerAuthAdapter = getCustomerAuthAdapter;
|
|
@@ -4158,7 +4988,7 @@ exports.navigateTo = navigateTo;
|
|
|
4158
4988
|
exports.normalizeProductOptionSelection = normalizeProductOptionSelection;
|
|
4159
4989
|
exports.placeStorefrontOrder = placeStorefrontOrder;
|
|
4160
4990
|
exports.prefersReducedMotion = prefersReducedMotion;
|
|
4161
|
-
exports.
|
|
4991
|
+
exports.productCardComponentContract = productCardComponentContract;
|
|
4162
4992
|
exports.productOptionSelectionKey = productOptionSelectionKey;
|
|
4163
4993
|
exports.registerStorefrontBlocks = registerStorefrontBlocks;
|
|
4164
4994
|
exports.resolveAuthAdapter = resolveAuthAdapter;
|
|
@@ -4173,13 +5003,16 @@ exports.sertaoTemplate = sertaoTemplate;
|
|
|
4173
5003
|
exports.signInByEmail = signInByEmail;
|
|
4174
5004
|
exports.signOutCustomer = signOutCustomer;
|
|
4175
5005
|
exports.signUpCustomer = signUpCustomer;
|
|
4176
|
-
exports.
|
|
5006
|
+
exports.storefrontComponentContracts = storefrontComponentContracts;
|
|
4177
5007
|
exports.storefrontTemplates = storefrontTemplates;
|
|
4178
5008
|
exports.themeToCss = themeToCss;
|
|
5009
|
+
exports.useCart = useCart;
|
|
4179
5010
|
exports.useCartStore = useCartStore;
|
|
5011
|
+
exports.useCatalog = useCatalog;
|
|
4180
5012
|
exports.useCatalogStore = useCatalogStore;
|
|
4181
5013
|
exports.useCategories = useCategories;
|
|
4182
5014
|
exports.useDiscountValidator = useDiscountValidator;
|
|
5015
|
+
exports.useEnquiry = useEnquiry;
|
|
4183
5016
|
exports.useHashPath = useHashPath;
|
|
4184
5017
|
exports.useInView = useInView;
|
|
4185
5018
|
exports.useMyOrders = useMyOrders;
|
|
@@ -4188,7 +5021,10 @@ exports.useProduct = useProduct;
|
|
|
4188
5021
|
exports.useProducts = useProducts;
|
|
4189
5022
|
exports.useScrolled = useScrolled;
|
|
4190
5023
|
exports.useSessionStore = useSessionStore;
|
|
5024
|
+
exports.useStorefront = useStorefront;
|
|
5025
|
+
exports.useStorefrontActions = useStorefrontActions;
|
|
4191
5026
|
exports.useStorefrontConfig = useStorefrontConfig;
|
|
5027
|
+
exports.useStorefrontConfigOptional = useStorefrontConfigOptional;
|
|
4192
5028
|
exports.voltTemplate = voltTemplate;
|
|
4193
5029
|
//# sourceMappingURL=index.cjs.map
|
|
4194
5030
|
//# sourceMappingURL=index.cjs.map
|