@fayz-ai/storefront 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +904 -185
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +797 -95
- 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/sections.d.ts +45 -2
- package/dist/sections.d.ts.map +1 -1
- package/dist/testids.d.ts +20 -0
- package/dist/testids.d.ts.map +1 -1
- package/dist/theme.d.ts +4 -0
- package/dist/theme.d.ts.map +1 -1
- package/package.json +7 -9
- 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 +43 -38
- 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 +62 -9
- package/src/define.ts +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useStorefront.ts +58 -0
- package/src/index.ts +61 -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/scaffold.tsx +1 -1
- package/src/sections.ts +49 -2
- package/src/testids.ts +20 -0
- package/src/theme.ts +4 -0
- package/src/workflows/checkout.ts +1 -1
- 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,7 +227,7 @@ function matchPath(pattern, path) {
|
|
|
185
227
|
return params;
|
|
186
228
|
}
|
|
187
229
|
function Link({ to, children, ...rest }) {
|
|
188
|
-
return
|
|
230
|
+
return React6__default.default.createElement("a", { href: `#${to}`, ...rest }, children);
|
|
189
231
|
}
|
|
190
232
|
var RADIUS_MAP = {
|
|
191
233
|
none: { button: "0px", card: "0px", input: "0px" },
|
|
@@ -259,7 +301,7 @@ function googleFontsHref(theme) {
|
|
|
259
301
|
}
|
|
260
302
|
function StorefrontThemeStyle({ theme }) {
|
|
261
303
|
const fontsHref = googleFontsHref(theme);
|
|
262
|
-
|
|
304
|
+
React6.useEffect(() => {
|
|
263
305
|
if (!fontsHref) return;
|
|
264
306
|
const id = "sf-theme-fonts";
|
|
265
307
|
let link = document.getElementById(id);
|
|
@@ -271,7 +313,7 @@ function StorefrontThemeStyle({ theme }) {
|
|
|
271
313
|
}
|
|
272
314
|
link.href = fontsHref;
|
|
273
315
|
}, [fontsHref]);
|
|
274
|
-
return
|
|
316
|
+
return React6__default.default.createElement("style", { "data-sf-theme": theme.name }, themeToCss(theme));
|
|
275
317
|
}
|
|
276
318
|
|
|
277
319
|
// src/format.ts
|
|
@@ -418,9 +460,9 @@ var useCatalogStore = zustand.create()((set) => ({
|
|
|
418
460
|
reset: () => set(initial)
|
|
419
461
|
}));
|
|
420
462
|
function useCategories() {
|
|
421
|
-
const [categories, setCategories] =
|
|
422
|
-
const [loading, setLoading] =
|
|
423
|
-
|
|
463
|
+
const [categories, setCategories] = React6.useState([]);
|
|
464
|
+
const [loading, setLoading] = React6.useState(true);
|
|
465
|
+
React6.useEffect(() => {
|
|
424
466
|
let cancelled = false;
|
|
425
467
|
runtime.getShopProvider().listCategories().then((data) => {
|
|
426
468
|
if (!cancelled) setCategories(data);
|
|
@@ -439,9 +481,9 @@ function prefersReducedMotion() {
|
|
|
439
481
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
440
482
|
}
|
|
441
483
|
function useInView(margin = "0px 0px -8% 0px") {
|
|
442
|
-
const ref =
|
|
443
|
-
const [inView, setInView] =
|
|
444
|
-
|
|
484
|
+
const ref = React6.useRef(null);
|
|
485
|
+
const [inView, setInView] = React6.useState(() => prefersReducedMotion());
|
|
486
|
+
React6.useEffect(() => {
|
|
445
487
|
if (inView) return;
|
|
446
488
|
const el = ref.current;
|
|
447
489
|
if (!el || typeof IntersectionObserver === "undefined") {
|
|
@@ -476,8 +518,8 @@ function Reveal({ delay = 0, children, className, style, ...rest }) {
|
|
|
476
518
|
);
|
|
477
519
|
}
|
|
478
520
|
function useScrolled(threshold = 8) {
|
|
479
|
-
const [scrolled, setScrolled] =
|
|
480
|
-
|
|
521
|
+
const [scrolled, setScrolled] = React6.useState(false);
|
|
522
|
+
React6.useEffect(() => {
|
|
481
523
|
const onScroll = () => setScrolled(window.scrollY > threshold);
|
|
482
524
|
onScroll();
|
|
483
525
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
@@ -486,9 +528,9 @@ function useScrolled(threshold = 8) {
|
|
|
486
528
|
return scrolled;
|
|
487
529
|
}
|
|
488
530
|
function usePopOnChange(value) {
|
|
489
|
-
const [pop, setPop] =
|
|
490
|
-
const prev =
|
|
491
|
-
|
|
531
|
+
const [pop, setPop] = React6.useState(false);
|
|
532
|
+
const prev = React6.useRef(value);
|
|
533
|
+
React6.useEffect(() => {
|
|
492
534
|
if (prev.current !== value && value > 0) {
|
|
493
535
|
setPop(true);
|
|
494
536
|
const t = setTimeout(() => setPop(false), 400);
|
|
@@ -497,7 +539,7 @@ function usePopOnChange(value) {
|
|
|
497
539
|
prev.current = value;
|
|
498
540
|
return void 0;
|
|
499
541
|
}, [value]);
|
|
500
|
-
|
|
542
|
+
React6.useEffect(() => {
|
|
501
543
|
prev.current = value;
|
|
502
544
|
});
|
|
503
545
|
return pop;
|
|
@@ -519,6 +561,13 @@ var TID = {
|
|
|
519
561
|
hero: "home-hero",
|
|
520
562
|
heroPrev: "hero-prev",
|
|
521
563
|
heroNext: "hero-next",
|
|
564
|
+
mediaCarousel: "media-carousel",
|
|
565
|
+
mediaCarouselPrev: "media-carousel-prev",
|
|
566
|
+
mediaCarouselNext: "media-carousel-next",
|
|
567
|
+
productSlider: "product-slider",
|
|
568
|
+
productSliderViewport: "product-slider-viewport",
|
|
569
|
+
productSliderPrev: "product-slider-prev",
|
|
570
|
+
productSliderNext: "product-slider-next",
|
|
522
571
|
categoryShowcase: "category-showcase",
|
|
523
572
|
productRail: "product-rail",
|
|
524
573
|
benefits: "benefits-row",
|
|
@@ -553,6 +602,19 @@ var TID = {
|
|
|
553
602
|
productOptionValue: (id, value) => `product-option-${id.toLowerCase().replace(/\s+/g, "-")}-${value.toLowerCase().replace(/\s+/g, "-")}`,
|
|
554
603
|
pdpQtyInput: "pdp-qty-input",
|
|
555
604
|
pdpAddToCart: "pdp-add-to-cart",
|
|
605
|
+
pdpRoot: "pdp-root",
|
|
606
|
+
pdpGallery: "pdp-gallery",
|
|
607
|
+
pdpActions: "pdp-actions",
|
|
608
|
+
pdpEnquiryButton: "pdp-enquiry-button",
|
|
609
|
+
enquiryForm: "enquiry-form",
|
|
610
|
+
enquiryName: "enquiry-name",
|
|
611
|
+
enquiryEmail: "enquiry-email",
|
|
612
|
+
enquiryPhone: "enquiry-phone",
|
|
613
|
+
enquirySubject: "enquiry-subject",
|
|
614
|
+
enquiryMessage: "enquiry-message",
|
|
615
|
+
enquirySubmit: "enquiry-submit",
|
|
616
|
+
enquirySuccess: "enquiry-success",
|
|
617
|
+
enquiryError: "enquiry-error",
|
|
556
618
|
// cart
|
|
557
619
|
cartDrawer: "cart-drawer",
|
|
558
620
|
cartLine: "cart-line",
|
|
@@ -616,7 +678,7 @@ var headerDivider = {
|
|
|
616
678
|
var ANNOUNCEMENT_KEY = "fayz.storefront.announcement.dismissed";
|
|
617
679
|
function AnnouncementBar() {
|
|
618
680
|
const config = useStorefrontConfig();
|
|
619
|
-
const [dismissed, setDismissed] =
|
|
681
|
+
const [dismissed, setDismissed] = React6.useState(() => {
|
|
620
682
|
try {
|
|
621
683
|
return localStorage.getItem(ANNOUNCEMENT_KEY) === config.announcement;
|
|
622
684
|
} catch {
|
|
@@ -707,7 +769,7 @@ function HeaderActions() {
|
|
|
707
769
|
const count = useCartStore(selectCount);
|
|
708
770
|
const openDrawer = useCartStore((s) => s.openDrawer);
|
|
709
771
|
const session = useSessionStore();
|
|
710
|
-
const [accountOpen, setAccountOpen] =
|
|
772
|
+
const [accountOpen, setAccountOpen] = React6.useState(false);
|
|
711
773
|
const pop = usePopOnChange(count);
|
|
712
774
|
const firstName = session.name?.split(/\s+/)[0];
|
|
713
775
|
const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
|
|
@@ -780,7 +842,7 @@ function HeaderActions() {
|
|
|
780
842
|
}
|
|
781
843
|
)
|
|
782
844
|
] }),
|
|
783
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
845
|
+
config.features.cart && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
784
846
|
"button",
|
|
785
847
|
{
|
|
786
848
|
type: "button",
|
|
@@ -807,12 +869,13 @@ function NavLinks({ className }) {
|
|
|
807
869
|
const config = useStorefrontConfig();
|
|
808
870
|
const { categories } = useCategories();
|
|
809
871
|
const setCategoryId = useCatalogStore((s) => s.setCategoryId);
|
|
872
|
+
const showCategories = config.theme?.header?.showCategories !== false;
|
|
810
873
|
const goCategory = (categoryId) => {
|
|
811
874
|
useCatalogStore.getState().reset();
|
|
812
875
|
setCategoryId(categoryId);
|
|
813
876
|
navigateTo(config.catalogPath);
|
|
814
877
|
};
|
|
815
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex items-center gap-6 ${className ?? ""}`, children: [
|
|
878
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: `flex min-w-max items-center gap-6 ${className ?? ""}`, children: [
|
|
816
879
|
config.nav.map((link) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
817
880
|
Link,
|
|
818
881
|
{
|
|
@@ -823,7 +886,7 @@ function NavLinks({ className }) {
|
|
|
823
886
|
},
|
|
824
887
|
link.to
|
|
825
888
|
)),
|
|
826
|
-
categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
889
|
+
showCategories && categories.slice(0, 6).map((c) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
827
890
|
"button",
|
|
828
891
|
{
|
|
829
892
|
type: "button",
|
|
@@ -843,6 +906,7 @@ function StorefrontHeader() {
|
|
|
843
906
|
const config = useStorefrontConfig();
|
|
844
907
|
const variant = config.theme?.header?.variant ?? "classic";
|
|
845
908
|
const scrolled = useScrolled();
|
|
909
|
+
const showSearch = config.theme?.header?.showSearch !== false;
|
|
846
910
|
const logo = /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "sf-heading shrink-0 text-xl font-bold tracking-tight", children: config.logo ?? config.name });
|
|
847
911
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
848
912
|
"header",
|
|
@@ -856,21 +920,21 @@ function StorefrontHeader() {
|
|
|
856
920
|
// Rio/Flex pattern: centered logo row, nav row below
|
|
857
921
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
858
922
|
/* @__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" }),
|
|
923
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-xs sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
860
924
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
861
925
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
862
926
|
] }),
|
|
863
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
927
|
+
/* @__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
928
|
] })
|
|
865
929
|
) : variant === "search" ? (
|
|
866
930
|
// Brasília pattern: prominent search left, logo center, actions right; nav row below
|
|
867
931
|
/* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
868
932
|
/* @__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" }),
|
|
933
|
+
showSearch ? /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-full max-w-sm sm:block" }) : /* @__PURE__ */ jsxRuntime.jsx("div", {}),
|
|
870
934
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
871
935
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "justify-self-end", children: /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {}) })
|
|
872
936
|
] }),
|
|
873
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
937
|
+
/* @__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
938
|
] })
|
|
875
939
|
) : variant === "minimal" ? (
|
|
876
940
|
// Uyuni pattern: nav left, centered logo, actions right — single compact row
|
|
@@ -878,7 +942,7 @@ function StorefrontHeader() {
|
|
|
878
942
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-5", children: /* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden sm:flex" }) }),
|
|
879
943
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center", children: logo }),
|
|
880
944
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-3", children: [
|
|
881
|
-
/* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-44 lg:block" }),
|
|
945
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "hidden w-44 lg:block" }),
|
|
882
946
|
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
883
947
|
] })
|
|
884
948
|
] })
|
|
@@ -887,7 +951,7 @@ function StorefrontHeader() {
|
|
|
887
951
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6", children: [
|
|
888
952
|
logo,
|
|
889
953
|
/* @__PURE__ */ jsxRuntime.jsx(NavLinks, { className: "hidden md:flex" }),
|
|
890
|
-
/* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
|
|
954
|
+
showSearch && /* @__PURE__ */ jsxRuntime.jsx(SearchInput, { className: "ml-auto hidden w-full max-w-sm sm:block" }),
|
|
891
955
|
/* @__PURE__ */ jsxRuntime.jsx(HeaderActions, {})
|
|
892
956
|
] })
|
|
893
957
|
)
|
|
@@ -900,6 +964,17 @@ function StorefrontFooter() {
|
|
|
900
964
|
const { categories } = useCategories();
|
|
901
965
|
const visibleCategories = categories.slice(0, 8);
|
|
902
966
|
const hiddenCategoryCount = Math.max(categories.length - visibleCategories.length, 0);
|
|
967
|
+
const labels = {
|
|
968
|
+
categories: "Departamentos",
|
|
969
|
+
navigation: "Navega\xE7\xE3o",
|
|
970
|
+
contact: "Contato",
|
|
971
|
+
viewAll: "Ver todos",
|
|
972
|
+
purchases: "Minhas compras",
|
|
973
|
+
privacy: "Privacidade",
|
|
974
|
+
terms: "Termos",
|
|
975
|
+
returns: "Trocas e devolu\xE7\xF5es",
|
|
976
|
+
...config.footer?.labels
|
|
977
|
+
};
|
|
903
978
|
const goCategory = (categoryId) => {
|
|
904
979
|
useCatalogStore.getState().reset();
|
|
905
980
|
useCatalogStore.getState().setCategoryId(categoryId);
|
|
@@ -912,7 +987,7 @@ function StorefrontFooter() {
|
|
|
912
987
|
config.footer?.about && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm leading-relaxed text-muted-foreground", children: config.footer.about })
|
|
913
988
|
] }),
|
|
914
989
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
915
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.categories }),
|
|
916
991
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
|
|
917
992
|
visibleCategories.map((c) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
918
993
|
"button",
|
|
@@ -924,21 +999,22 @@ function StorefrontFooter() {
|
|
|
924
999
|
}
|
|
925
1000
|
) }, c.id)),
|
|
926
1001
|
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
|
-
|
|
1002
|
+
labels.viewAll,
|
|
1003
|
+
" (",
|
|
928
1004
|
categories.length,
|
|
929
1005
|
")"
|
|
930
1006
|
] }) })
|
|
931
1007
|
] })
|
|
932
1008
|
] }),
|
|
933
1009
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
934
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.navigation }),
|
|
935
1011
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm", children: [
|
|
936
1012
|
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:
|
|
1013
|
+
config.features.accounts && /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/account", className: "transition-opacity hover:opacity-70", children: labels.purchases }) })
|
|
938
1014
|
] })
|
|
939
1015
|
] }),
|
|
940
1016
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
941
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children:
|
|
1017
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: labels.contact }),
|
|
942
1018
|
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "space-y-2 text-sm text-muted-foreground", children: [
|
|
943
1019
|
config.footer?.contact?.phone && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.phone }),
|
|
944
1020
|
config.footer?.contact?.email && /* @__PURE__ */ jsxRuntime.jsx("li", { children: config.footer.contact.email }),
|
|
@@ -949,17 +1025,15 @@ function StorefrontFooter() {
|
|
|
949
1025
|
] }),
|
|
950
1026
|
/* @__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
1027
|
/* @__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:
|
|
1028
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/privacy", className: "transition-colors hover:text-foreground", children: labels.privacy }),
|
|
1029
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/terms", className: "transition-colors hover:text-foreground", children: labels.terms }),
|
|
1030
|
+
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/refunds", className: "transition-colors hover:text-foreground", children: labels.returns })
|
|
955
1031
|
] }),
|
|
956
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1032
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center", children: config.footer?.credit ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
957
1033
|
"\xA9 ",
|
|
958
1034
|
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
|
-
] })
|
|
1035
|
+
" \u2014 powered by Fayz"
|
|
1036
|
+
] }) })
|
|
963
1037
|
] }) })
|
|
964
1038
|
] });
|
|
965
1039
|
}
|
|
@@ -971,7 +1045,7 @@ var REASON_MESSAGE = {
|
|
|
971
1045
|
};
|
|
972
1046
|
function useDiscountValidator() {
|
|
973
1047
|
const config = useStorefrontConfig();
|
|
974
|
-
return
|
|
1048
|
+
return React6.useCallback(async (code) => {
|
|
975
1049
|
const normalized = code.trim().toUpperCase();
|
|
976
1050
|
if (!normalized) return { valid: false, percent: 0, message: "Informe um cupom." };
|
|
977
1051
|
const configDiscount = config.discounts?.find((d) => d.code.trim().toUpperCase() === normalized);
|
|
@@ -1052,14 +1126,61 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
|
|
|
1052
1126
|
)
|
|
1053
1127
|
] });
|
|
1054
1128
|
}
|
|
1129
|
+
function SmoothImage({
|
|
1130
|
+
imageLoading,
|
|
1131
|
+
loading = "lazy",
|
|
1132
|
+
decoding = "async",
|
|
1133
|
+
onLoad,
|
|
1134
|
+
style,
|
|
1135
|
+
...props
|
|
1136
|
+
}) {
|
|
1137
|
+
const config = useStorefrontConfigOptional();
|
|
1138
|
+
const resolved = {
|
|
1139
|
+
...config?.imageLoading,
|
|
1140
|
+
...imageLoading
|
|
1141
|
+
};
|
|
1142
|
+
const mode = resolved.mode ?? "fade";
|
|
1143
|
+
const durationMs = resolved.durationMs ?? 420;
|
|
1144
|
+
const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1145
|
+
const blur = resolved.blur ?? true;
|
|
1146
|
+
const ref = React6__default.default.useRef(null);
|
|
1147
|
+
const [loaded, setLoaded] = React6__default.default.useState(mode === "none");
|
|
1148
|
+
React6__default.default.useEffect(() => {
|
|
1149
|
+
if (mode === "none") {
|
|
1150
|
+
setLoaded(true);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true);
|
|
1154
|
+
}, [mode, props.src]);
|
|
1155
|
+
const revealStyle = mode === "none" ? {} : {
|
|
1156
|
+
opacity: loaded ? 1 : 0,
|
|
1157
|
+
filter: blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
1158
|
+
transition: `opacity ${durationMs}ms ${easing}, filter ${durationMs}ms ${easing}, transform ${durationMs}ms ${easing}`
|
|
1159
|
+
};
|
|
1160
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1161
|
+
"img",
|
|
1162
|
+
{
|
|
1163
|
+
...props,
|
|
1164
|
+
ref,
|
|
1165
|
+
loading,
|
|
1166
|
+
decoding,
|
|
1167
|
+
"data-sf-image-loaded": loaded ? "true" : "false",
|
|
1168
|
+
style: { ...style, ...revealStyle },
|
|
1169
|
+
onLoad: (event) => {
|
|
1170
|
+
setLoaded(true);
|
|
1171
|
+
onLoad?.(event);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1055
1176
|
function CartDrawer() {
|
|
1056
1177
|
const config = useStorefrontConfig();
|
|
1057
1178
|
const cart = useCartStore();
|
|
1058
1179
|
const validate = useDiscountValidator();
|
|
1059
|
-
const [code, setCode] =
|
|
1060
|
-
const [discountError, setDiscountError] =
|
|
1061
|
-
const [applying, setApplying] =
|
|
1062
|
-
|
|
1180
|
+
const [code, setCode] = React6.useState("");
|
|
1181
|
+
const [discountError, setDiscountError] = React6.useState(null);
|
|
1182
|
+
const [applying, setApplying] = React6.useState(false);
|
|
1183
|
+
React6.useEffect(() => {
|
|
1063
1184
|
if (!cart.isOpen) return;
|
|
1064
1185
|
const onKey = (e) => {
|
|
1065
1186
|
if (e.key === "Escape") cart.closeDrawer();
|
|
@@ -1151,7 +1272,7 @@ function CartDrawer() {
|
|
|
1151
1272
|
"data-line-id": line.lineId ?? line.productId,
|
|
1152
1273
|
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
1274
|
children: [
|
|
1154
|
-
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1275
|
+
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
1276
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col", children: [
|
|
1156
1277
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
1157
1278
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium leading-snug", children: line.name }),
|
|
@@ -1282,12 +1403,12 @@ function CartDrawer() {
|
|
|
1282
1403
|
] });
|
|
1283
1404
|
}
|
|
1284
1405
|
function useProducts(opts) {
|
|
1285
|
-
const [products, setProducts] =
|
|
1286
|
-
const [loading, setLoading] =
|
|
1287
|
-
const [error, setError] =
|
|
1288
|
-
const [nonce, setNonce] =
|
|
1406
|
+
const [products, setProducts] = React6.useState([]);
|
|
1407
|
+
const [loading, setLoading] = React6.useState(true);
|
|
1408
|
+
const [error, setError] = React6.useState(null);
|
|
1409
|
+
const [nonce, setNonce] = React6.useState(0);
|
|
1289
1410
|
const key = JSON.stringify(opts);
|
|
1290
|
-
|
|
1411
|
+
React6.useEffect(() => {
|
|
1291
1412
|
let cancelled = false;
|
|
1292
1413
|
setLoading(true);
|
|
1293
1414
|
runtime.getShopProvider().listProducts(JSON.parse(key)).then((data) => {
|
|
@@ -1321,36 +1442,91 @@ function Price({ value, compareAt, testId, compareTestId, className }) {
|
|
|
1321
1442
|
] });
|
|
1322
1443
|
}
|
|
1323
1444
|
|
|
1324
|
-
// src/
|
|
1325
|
-
var
|
|
1445
|
+
// src/component-selectors.ts
|
|
1446
|
+
var productCardComponentContract = {
|
|
1326
1447
|
root: { "data-testid": TID.productCard },
|
|
1327
1448
|
name: { "data-testid": TID.productCardName },
|
|
1328
1449
|
priceTestId: TID.productCardPrice,
|
|
1329
1450
|
addButton: { "data-testid": TID.productCardAdd }
|
|
1330
1451
|
};
|
|
1331
|
-
var
|
|
1332
|
-
productCard:
|
|
1452
|
+
var storefrontComponentContracts = {
|
|
1453
|
+
productCard: productCardComponentContract,
|
|
1454
|
+
productDetail: {
|
|
1455
|
+
root: { "data-testid": TID.pdpRoot },
|
|
1456
|
+
gallery: { "data-testid": TID.pdpGallery },
|
|
1457
|
+
name: { "data-testid": TID.pdpName },
|
|
1458
|
+
description: { "data-testid": TID.pdpDescription },
|
|
1459
|
+
actions: { "data-testid": TID.pdpActions },
|
|
1460
|
+
addButton: { "data-testid": TID.pdpAddToCart },
|
|
1461
|
+
enquiryButton: { "data-testid": TID.pdpEnquiryButton },
|
|
1462
|
+
enquiryForm: { "data-testid": TID.enquiryForm }
|
|
1463
|
+
}
|
|
1333
1464
|
};
|
|
1334
|
-
function
|
|
1465
|
+
function useStorefront() {
|
|
1466
|
+
return useStorefrontConfig();
|
|
1467
|
+
}
|
|
1468
|
+
function useCatalog() {
|
|
1335
1469
|
const config = useStorefrontConfig();
|
|
1336
|
-
const
|
|
1470
|
+
const catalog = useCatalogStore();
|
|
1471
|
+
return {
|
|
1472
|
+
...catalog,
|
|
1473
|
+
catalogPath: config.catalogPath,
|
|
1474
|
+
config
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
function useCart() {
|
|
1478
|
+
return useCartStore();
|
|
1479
|
+
}
|
|
1480
|
+
function useEnquiry() {
|
|
1481
|
+
const config = useStorefrontConfig();
|
|
1482
|
+
const createProductEnquiry = async (input) => {
|
|
1483
|
+
const provider = runtime.getShopProvider();
|
|
1484
|
+
if (!provider.createProductEnquiry) {
|
|
1485
|
+
throw new Error("The active shop provider does not support product enquiries.");
|
|
1486
|
+
}
|
|
1487
|
+
return provider.createProductEnquiry(input);
|
|
1488
|
+
};
|
|
1489
|
+
return {
|
|
1490
|
+
enquiry: config.enquiry,
|
|
1491
|
+
createProductEnquiry
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function useStorefrontActions() {
|
|
1495
|
+
const addItem = useCartStore((state) => state.addItem);
|
|
1496
|
+
const openDrawer = useCartStore((state) => state.openDrawer);
|
|
1497
|
+
const closeDrawer = useCartStore((state) => state.closeDrawer);
|
|
1498
|
+
const { createProductEnquiry } = useEnquiry();
|
|
1499
|
+
return {
|
|
1500
|
+
navigateTo,
|
|
1501
|
+
addToCart: (product, quantity, options) => addItem(product, quantity, options),
|
|
1502
|
+
openCart: openDrawer,
|
|
1503
|
+
closeCart: closeDrawer,
|
|
1504
|
+
createProductEnquiry
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
function ProductCard({ product, config: providedConfig, actions: providedActions }) {
|
|
1508
|
+
const runtimeConfig = useStorefrontConfig();
|
|
1509
|
+
const runtimeActions = useStorefrontActions();
|
|
1510
|
+
const config = providedConfig ?? runtimeConfig;
|
|
1511
|
+
const actions = providedActions ?? runtimeActions;
|
|
1337
1512
|
const hasOptions = getProductOptionGroups(product).length > 0;
|
|
1338
1513
|
const soldOut = product.inventoryCount <= 0;
|
|
1339
1514
|
const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
|
|
1340
1515
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
1341
1516
|
const cardStyle = config.theme?.productCard?.style ?? "card";
|
|
1342
1517
|
const aspect = config.theme?.productCard?.imageAspect === "portrait" ? "aspect-[3/4]" : "aspect-square";
|
|
1518
|
+
const purchaseMode = config.commerceMode === "checkout";
|
|
1343
1519
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1344
1520
|
"div",
|
|
1345
1521
|
{
|
|
1346
|
-
...
|
|
1522
|
+
...productCardComponentContract.root,
|
|
1347
1523
|
"data-slug": product.slug,
|
|
1348
1524
|
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
1525
|
style: { borderRadius: "var(--sf-radius-card)" },
|
|
1350
1526
|
children: [
|
|
1351
1527
|
/* @__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
1528
|
image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1353
|
-
|
|
1529
|
+
SmoothImage,
|
|
1354
1530
|
{
|
|
1355
1531
|
src: image.url,
|
|
1356
1532
|
alt: image.altText ?? product.name,
|
|
@@ -1383,33 +1559,37 @@ function ProductCard({ product }) {
|
|
|
1383
1559
|
Link,
|
|
1384
1560
|
{
|
|
1385
1561
|
to: `/product/${product.slug}`,
|
|
1386
|
-
...
|
|
1562
|
+
...productCardComponentContract.name,
|
|
1387
1563
|
className: "font-medium leading-snug hover:underline",
|
|
1388
1564
|
children: product.name
|
|
1389
1565
|
}
|
|
1390
1566
|
),
|
|
1391
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
|
|
1392
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1567
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center justify-between gap-3 pt-2", children: [
|
|
1568
|
+
purchaseMode ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1393
1569
|
Price,
|
|
1394
1570
|
{
|
|
1395
1571
|
value: product.price,
|
|
1396
1572
|
compareAt: product.compareAtPrice,
|
|
1397
|
-
testId:
|
|
1573
|
+
testId: productCardComponentContract.priceTestId
|
|
1398
1574
|
}
|
|
1399
|
-
),
|
|
1575
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": productCardComponentContract.priceTestId, className: "text-sm text-muted-foreground", children: config.commerceMode === "enquiry" ? config.enquiry.label : "Ver detalhes" }),
|
|
1400
1576
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1401
1577
|
"button",
|
|
1402
1578
|
{
|
|
1403
1579
|
type: "button",
|
|
1404
|
-
...
|
|
1405
|
-
disabled: soldOut,
|
|
1406
|
-
"aria-label": hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho`,
|
|
1580
|
+
...productCardComponentContract.addButton,
|
|
1581
|
+
disabled: purchaseMode && soldOut,
|
|
1582
|
+
"aria-label": purchaseMode ? hasOptions ? `Escolher op\xE7\xF5es de ${product.name}` : `Adicionar ${product.name} ao carrinho` : `${config.enquiry.label} ${product.name}`,
|
|
1407
1583
|
onClick: () => {
|
|
1584
|
+
if (!purchaseMode) {
|
|
1585
|
+
navigateTo(`/product/${product.slug}`);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1408
1588
|
if (hasOptions) {
|
|
1409
1589
|
navigateTo(`/product/${product.slug}`);
|
|
1410
1590
|
return;
|
|
1411
1591
|
}
|
|
1412
|
-
|
|
1592
|
+
actions.addToCart(product);
|
|
1413
1593
|
},
|
|
1414
1594
|
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
1595
|
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ShoppingBag, { className: "h-4 w-4" })
|
|
@@ -1423,14 +1603,25 @@ function ProductCard({ product }) {
|
|
|
1423
1603
|
}
|
|
1424
1604
|
function ProductGrid({ products, loading }) {
|
|
1425
1605
|
const config = useStorefrontConfig();
|
|
1426
|
-
const
|
|
1606
|
+
const actions = useStorefrontActions();
|
|
1607
|
+
const components = getStorefrontComponents(config);
|
|
1608
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
1427
1609
|
if (loading) {
|
|
1428
1610
|
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
1611
|
}
|
|
1430
1612
|
if (products.length === 0) {
|
|
1431
1613
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": TID.productGrid, className: "py-20 text-center text-muted-foreground", children: "Nenhum produto encontrado." });
|
|
1432
1614
|
}
|
|
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(
|
|
1615
|
+
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(
|
|
1616
|
+
ProductCardComponent,
|
|
1617
|
+
{
|
|
1618
|
+
product: p,
|
|
1619
|
+
config,
|
|
1620
|
+
commerceMode: config.commerceMode,
|
|
1621
|
+
actions
|
|
1622
|
+
},
|
|
1623
|
+
p.id
|
|
1624
|
+
)) });
|
|
1434
1625
|
}
|
|
1435
1626
|
function FiltersPanel() {
|
|
1436
1627
|
const { categories } = useCategories();
|
|
@@ -1535,10 +1726,10 @@ function FiltersPanel() {
|
|
|
1535
1726
|
}
|
|
1536
1727
|
function useStorefrontHead(input) {
|
|
1537
1728
|
const { title, description } = input;
|
|
1538
|
-
|
|
1729
|
+
React6.useEffect(() => {
|
|
1539
1730
|
if (title) document.title = title;
|
|
1540
1731
|
}, [title]);
|
|
1541
|
-
|
|
1732
|
+
React6.useEffect(() => {
|
|
1542
1733
|
if (!description) return;
|
|
1543
1734
|
let tag = document.head.querySelector('meta[name="description"]');
|
|
1544
1735
|
if (!tag) {
|
|
@@ -1557,9 +1748,9 @@ var SORT_MAP = {
|
|
|
1557
1748
|
};
|
|
1558
1749
|
var SORTS = ["newest", "price-asc", "price-desc", "name"];
|
|
1559
1750
|
function useCatalogUrlSync() {
|
|
1560
|
-
const hydrated =
|
|
1751
|
+
const hydrated = React6.useRef(false);
|
|
1561
1752
|
const catalog = useCatalogStore();
|
|
1562
|
-
|
|
1753
|
+
React6.useEffect(() => {
|
|
1563
1754
|
if (hydrated.current) return;
|
|
1564
1755
|
hydrated.current = true;
|
|
1565
1756
|
const q = window.location.hash.split("?")[1];
|
|
@@ -1574,7 +1765,7 @@ function useCatalogUrlSync() {
|
|
|
1574
1765
|
if (p.has("min")) s.setPriceMin(Number(p.get("min")) || null);
|
|
1575
1766
|
if (p.has("max")) s.setPriceMax(Number(p.get("max")) || null);
|
|
1576
1767
|
}, []);
|
|
1577
|
-
|
|
1768
|
+
React6.useEffect(() => {
|
|
1578
1769
|
const p = new URLSearchParams();
|
|
1579
1770
|
if (catalog.search) p.set("search", catalog.search);
|
|
1580
1771
|
if (catalog.categoryId) p.set("category", catalog.categoryId);
|
|
@@ -1602,7 +1793,7 @@ function CatalogPage() {
|
|
|
1602
1793
|
...SORT_MAP[catalog.sort]
|
|
1603
1794
|
};
|
|
1604
1795
|
const { products, loading, error, reload } = useProducts(providerOpts);
|
|
1605
|
-
const filtered =
|
|
1796
|
+
const filtered = React6.useMemo(
|
|
1606
1797
|
() => products.filter((p) => {
|
|
1607
1798
|
if (catalog.priceMin != null && p.price < catalog.priceMin) return false;
|
|
1608
1799
|
if (catalog.priceMax != null && p.price > catalog.priceMax) return false;
|
|
@@ -1667,10 +1858,17 @@ function HeroSection({
|
|
|
1667
1858
|
slides,
|
|
1668
1859
|
height = "tall"
|
|
1669
1860
|
}) {
|
|
1670
|
-
const [index, setIndex] =
|
|
1671
|
-
const
|
|
1861
|
+
const [index, setIndex] = React6.useState(0);
|
|
1862
|
+
const [loadedSlides, setLoadedSlides] = React6.useState({});
|
|
1863
|
+
const config = useStorefrontConfig();
|
|
1864
|
+
const interacted = React6.useRef(false);
|
|
1672
1865
|
const hClass = height === "tall" ? "h-[72vh] min-h-[440px]" : "h-[48vh] min-h-[340px]";
|
|
1673
|
-
|
|
1866
|
+
const revealImages = config.imageLoading.mode !== "none";
|
|
1867
|
+
const revealStyle = (loaded) => revealImages ? {
|
|
1868
|
+
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
1869
|
+
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
1870
|
+
} : void 0;
|
|
1871
|
+
React6.useEffect(() => {
|
|
1674
1872
|
if (variant !== "slider" || slides.length < 2 || prefersReducedMotion()) return;
|
|
1675
1873
|
const timer = setInterval(() => {
|
|
1676
1874
|
if (!interacted.current) setIndex((i) => (i + 1) % slides.length);
|
|
@@ -1690,7 +1888,7 @@ function HeroSection({
|
|
|
1690
1888
|
className: "group relative block h-[42vh] min-h-[320px] overflow-hidden",
|
|
1691
1889
|
children: [
|
|
1692
1890
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1693
|
-
|
|
1891
|
+
SmoothImage,
|
|
1694
1892
|
{
|
|
1695
1893
|
src: slideImage(slide, 900, 700),
|
|
1696
1894
|
alt: slide.title,
|
|
@@ -1722,7 +1920,11 @@ function HeroSection({
|
|
|
1722
1920
|
src: slideImage(slide),
|
|
1723
1921
|
alt: slide.title,
|
|
1724
1922
|
"aria-hidden": i !== index,
|
|
1725
|
-
|
|
1923
|
+
loading: i === 0 ? "eager" : "lazy",
|
|
1924
|
+
decoding: "async",
|
|
1925
|
+
onLoad: () => setLoadedSlides((current) => ({ ...current, [i]: true })),
|
|
1926
|
+
style: revealStyle(Boolean(loadedSlides[i])),
|
|
1927
|
+
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
1928
|
},
|
|
1727
1929
|
i
|
|
1728
1930
|
)),
|
|
@@ -1784,7 +1986,7 @@ function CategoryShowcase({ style, title }) {
|
|
|
1784
1986
|
title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
|
|
1785
1987
|
/* @__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
1988
|
/* @__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
|
-
|
|
1989
|
+
SmoothImage,
|
|
1788
1990
|
{
|
|
1789
1991
|
src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 200, 200),
|
|
1790
1992
|
alt: c.name,
|
|
@@ -1799,7 +2001,7 @@ function CategoryShowcase({ style, title }) {
|
|
|
1799
2001
|
title && /* @__PURE__ */ jsxRuntime.jsx(Reveal, { children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading mb-8 text-center text-3xl font-bold", children: title }) }),
|
|
1800
2002
|
/* @__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
2003
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1802
|
-
|
|
2004
|
+
SmoothImage,
|
|
1803
2005
|
{
|
|
1804
2006
|
src: c.imageUrl ?? bannerPlaceholder(c.name, 30 + i * 70, 70 + i * 70, 480, 600),
|
|
1805
2007
|
alt: c.name,
|
|
@@ -1821,11 +2023,13 @@ function ProductRail({
|
|
|
1821
2023
|
limit = 4
|
|
1822
2024
|
}) {
|
|
1823
2025
|
const config = useStorefrontConfig();
|
|
1824
|
-
const
|
|
2026
|
+
const actions = useStorefrontActions();
|
|
2027
|
+
const components = getStorefrontComponents(config);
|
|
2028
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
1825
2029
|
const { products, loading } = useProducts(
|
|
1826
2030
|
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
1827
2031
|
);
|
|
1828
|
-
const visible =
|
|
2032
|
+
const visible = React6.useMemo(() => {
|
|
1829
2033
|
let list = products;
|
|
1830
2034
|
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
1831
2035
|
return list.slice(0, limit);
|
|
@@ -1837,7 +2041,15 @@ function ProductRail({
|
|
|
1837
2041
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: title }),
|
|
1838
2042
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: subtitle })
|
|
1839
2043
|
] }),
|
|
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(
|
|
2044
|
+
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(
|
|
2045
|
+
ProductCardComponent,
|
|
2046
|
+
{
|
|
2047
|
+
product: p,
|
|
2048
|
+
config,
|
|
2049
|
+
commerceMode: config.commerceMode,
|
|
2050
|
+
actions
|
|
2051
|
+
}
|
|
2052
|
+
) }, p.id)) }),
|
|
1841
2053
|
/* @__PURE__ */ jsxRuntime.jsx(Reveal, { className: "mt-10 text-center", delay: 200, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1842
2054
|
Link,
|
|
1843
2055
|
{
|
|
@@ -1849,6 +2061,201 @@ function ProductRail({
|
|
|
1849
2061
|
) })
|
|
1850
2062
|
] });
|
|
1851
2063
|
}
|
|
2064
|
+
var heightClass = {
|
|
2065
|
+
medium: "h-[58vh] min-h-[420px]",
|
|
2066
|
+
tall: "h-[74vh] min-h-[520px]",
|
|
2067
|
+
screen: "min-h-[calc(100vh-64px)]"
|
|
2068
|
+
};
|
|
2069
|
+
var alignClass = {
|
|
2070
|
+
start: "items-start text-left",
|
|
2071
|
+
center: "items-center text-center",
|
|
2072
|
+
end: "items-end text-right"
|
|
2073
|
+
};
|
|
2074
|
+
function MediaCarousel({
|
|
2075
|
+
items,
|
|
2076
|
+
height = "tall",
|
|
2077
|
+
autoplayMs = 7e3,
|
|
2078
|
+
overlay = "dark",
|
|
2079
|
+
className = ""
|
|
2080
|
+
}) {
|
|
2081
|
+
const [index, setIndex] = React6.useState(0);
|
|
2082
|
+
const [loadedItems, setLoadedItems] = React6.useState({});
|
|
2083
|
+
const config = useStorefrontConfig();
|
|
2084
|
+
const interacted = React6.useRef(false);
|
|
2085
|
+
const revealImages = config.imageLoading.mode !== "none";
|
|
2086
|
+
const revealStyle = (loaded) => revealImages ? {
|
|
2087
|
+
filter: config.imageLoading.blur && !loaded ? "blur(10px)" : "blur(0px)",
|
|
2088
|
+
transition: `opacity 700ms ease, filter ${config.imageLoading.durationMs}ms ${config.imageLoading.easing}`
|
|
2089
|
+
} : void 0;
|
|
2090
|
+
React6.useEffect(() => {
|
|
2091
|
+
if (items.length < 2 || autoplayMs <= 0 || prefersReducedMotion()) return;
|
|
2092
|
+
const timer = setInterval(() => {
|
|
2093
|
+
if (!interacted.current) setIndex((i) => (i + 1) % items.length);
|
|
2094
|
+
}, autoplayMs);
|
|
2095
|
+
return () => clearInterval(timer);
|
|
2096
|
+
}, [autoplayMs, items.length]);
|
|
2097
|
+
if (items.length === 0) return null;
|
|
2098
|
+
const active = items[Math.min(index, items.length - 1)];
|
|
2099
|
+
const goTo = (next) => {
|
|
2100
|
+
interacted.current = true;
|
|
2101
|
+
setIndex((next + items.length) % items.length);
|
|
2102
|
+
};
|
|
2103
|
+
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" : "";
|
|
2104
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.mediaCarousel, className: `relative isolate overflow-hidden ${heightClass[height]} ${className}`, children: [
|
|
2105
|
+
items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2106
|
+
"img",
|
|
2107
|
+
{
|
|
2108
|
+
src: item.image,
|
|
2109
|
+
alt: item.imageAlt ?? item.title,
|
|
2110
|
+
"aria-hidden": i !== index,
|
|
2111
|
+
loading: i === 0 ? "eager" : "lazy",
|
|
2112
|
+
decoding: "async",
|
|
2113
|
+
onLoad: () => setLoadedItems((current) => ({ ...current, [i]: true })),
|
|
2114
|
+
style: revealStyle(Boolean(loadedItems[i])),
|
|
2115
|
+
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"}`
|
|
2116
|
+
},
|
|
2117
|
+
`${item.title}-${i}`
|
|
2118
|
+
)),
|
|
2119
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: `absolute inset-0 -z-10 ${overlayClass}` }),
|
|
2120
|
+
/* @__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: [
|
|
2121
|
+
active.eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-4 text-xs font-semibold uppercase tracking-[0.24em] text-white/80", children: active.eyebrow }),
|
|
2122
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "sf-heading text-5xl font-normal leading-[0.95] sm:text-7xl lg:text-8xl", children: active.title }),
|
|
2123
|
+
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 }),
|
|
2124
|
+
active.cta && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2125
|
+
Link,
|
|
2126
|
+
{
|
|
2127
|
+
to: active.href ?? "/catalog",
|
|
2128
|
+
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",
|
|
2129
|
+
children: active.cta
|
|
2130
|
+
}
|
|
2131
|
+
)
|
|
2132
|
+
] }) }),
|
|
2133
|
+
items.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2134
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2135
|
+
"button",
|
|
2136
|
+
{
|
|
2137
|
+
type: "button",
|
|
2138
|
+
"aria-label": "Previous slide",
|
|
2139
|
+
"data-testid": TID.mediaCarouselPrev,
|
|
2140
|
+
onClick: () => goTo(index - 1),
|
|
2141
|
+
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",
|
|
2142
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
|
|
2143
|
+
}
|
|
2144
|
+
),
|
|
2145
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2146
|
+
"button",
|
|
2147
|
+
{
|
|
2148
|
+
type: "button",
|
|
2149
|
+
"aria-label": "Next slide",
|
|
2150
|
+
"data-testid": TID.mediaCarouselNext,
|
|
2151
|
+
onClick: () => goTo(index + 1),
|
|
2152
|
+
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",
|
|
2153
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
|
|
2154
|
+
}
|
|
2155
|
+
),
|
|
2156
|
+
/* @__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(
|
|
2157
|
+
"button",
|
|
2158
|
+
{
|
|
2159
|
+
type: "button",
|
|
2160
|
+
"aria-label": `Go to slide ${i + 1}`,
|
|
2161
|
+
onClick: () => goTo(i),
|
|
2162
|
+
className: `h-2 rounded-full transition-all ${i === index ? "w-8 bg-white" : "w-2 bg-white/55 hover:bg-white"}`
|
|
2163
|
+
},
|
|
2164
|
+
`${item.title}-dot-${i}`
|
|
2165
|
+
)) })
|
|
2166
|
+
] })
|
|
2167
|
+
] });
|
|
2168
|
+
}
|
|
2169
|
+
function ProductSlider({
|
|
2170
|
+
title,
|
|
2171
|
+
subtitle,
|
|
2172
|
+
eyebrow,
|
|
2173
|
+
filter = "all",
|
|
2174
|
+
categoryName,
|
|
2175
|
+
collection,
|
|
2176
|
+
limit = 10,
|
|
2177
|
+
cta = "View all",
|
|
2178
|
+
href,
|
|
2179
|
+
className = ""
|
|
2180
|
+
}) {
|
|
2181
|
+
const config = useStorefrontConfig();
|
|
2182
|
+
const actions = useStorefrontActions();
|
|
2183
|
+
const viewportRef = React6.useRef(null);
|
|
2184
|
+
const components = getStorefrontComponents(config);
|
|
2185
|
+
const ProductCardComponent = components.ProductCard ?? ProductCard;
|
|
2186
|
+
const { products, loading } = useProducts(
|
|
2187
|
+
filter === "new" ? { status: "active", orderBy: "created_at", order: "desc" } : { status: "active" }
|
|
2188
|
+
);
|
|
2189
|
+
const visible = React6.useMemo(() => {
|
|
2190
|
+
let list = products;
|
|
2191
|
+
if (filter === "sale") list = list.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price);
|
|
2192
|
+
if (categoryName) list = list.filter((p) => p.categoryName === categoryName);
|
|
2193
|
+
if (collection) list = list.filter((p) => p.metadata?.collection === collection);
|
|
2194
|
+
return list.slice(0, limit);
|
|
2195
|
+
}, [categoryName, collection, filter, limit, products]);
|
|
2196
|
+
const scroll = (direction) => {
|
|
2197
|
+
viewportRef.current?.scrollBy({ left: direction * Math.max(320, viewportRef.current.clientWidth * 0.75), behavior: "smooth" });
|
|
2198
|
+
};
|
|
2199
|
+
if (!loading && visible.length === 0) return null;
|
|
2200
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.productSlider, className: `mx-auto max-w-7xl px-4 py-14 sm:px-6 ${className}`, children: [
|
|
2201
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between", children: [
|
|
2202
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-2xl", children: [
|
|
2203
|
+
eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-[0.2em] text-primary", children: eyebrow }),
|
|
2204
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-normal leading-tight tracking-normal sm:text-5xl", children: title }),
|
|
2205
|
+
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 leading-7 text-muted-foreground", children: subtitle })
|
|
2206
|
+
] }),
|
|
2207
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2209
|
+
"button",
|
|
2210
|
+
{
|
|
2211
|
+
type: "button",
|
|
2212
|
+
"aria-label": "Previous products",
|
|
2213
|
+
"data-testid": TID.productSliderPrev,
|
|
2214
|
+
onClick: () => scroll(-1),
|
|
2215
|
+
className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
|
|
2216
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-5 w-5" })
|
|
2217
|
+
}
|
|
2218
|
+
),
|
|
2219
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2220
|
+
"button",
|
|
2221
|
+
{
|
|
2222
|
+
type: "button",
|
|
2223
|
+
"aria-label": "Next products",
|
|
2224
|
+
"data-testid": TID.productSliderNext,
|
|
2225
|
+
onClick: () => scroll(1),
|
|
2226
|
+
className: "inline-flex h-10 w-10 items-center justify-center border border-border bg-background transition hover:bg-foreground hover:text-background",
|
|
2227
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronRight, { className: "h-5 w-5" })
|
|
2228
|
+
}
|
|
2229
|
+
)
|
|
2230
|
+
] })
|
|
2231
|
+
] }),
|
|
2232
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2233
|
+
"div",
|
|
2234
|
+
{
|
|
2235
|
+
ref: viewportRef,
|
|
2236
|
+
"data-testid": TID.productSliderViewport,
|
|
2237
|
+
className: "flex snap-x gap-5 overflow-x-auto pb-5 [scrollbar-width:thin]",
|
|
2238
|
+
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(
|
|
2239
|
+
ProductCardComponent,
|
|
2240
|
+
{
|
|
2241
|
+
product,
|
|
2242
|
+
config,
|
|
2243
|
+
commerceMode: config.commerceMode,
|
|
2244
|
+
actions
|
|
2245
|
+
}
|
|
2246
|
+
) }, product.id))
|
|
2247
|
+
}
|
|
2248
|
+
),
|
|
2249
|
+
cta && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2250
|
+
Link,
|
|
2251
|
+
{
|
|
2252
|
+
to: href ?? config.catalogPath,
|
|
2253
|
+
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",
|
|
2254
|
+
children: cta
|
|
2255
|
+
}
|
|
2256
|
+
)
|
|
2257
|
+
] });
|
|
2258
|
+
}
|
|
1852
2259
|
function Icon({ name, className }) {
|
|
1853
2260
|
const C = LucideIcons__namespace[name];
|
|
1854
2261
|
return C ? /* @__PURE__ */ jsxRuntime.jsx(C, { className }) : null;
|
|
@@ -1873,7 +2280,7 @@ function PromoBanner({
|
|
|
1873
2280
|
}) {
|
|
1874
2281
|
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-testid": TID.promoBanner, className: "group relative my-12 overflow-hidden", children: [
|
|
1875
2282
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1876
|
-
|
|
2283
|
+
SmoothImage,
|
|
1877
2284
|
{
|
|
1878
2285
|
src: image ?? bannerPlaceholder(title, hue, hue + 50, 1600, 420),
|
|
1879
2286
|
alt: title,
|
|
@@ -1927,8 +2334,8 @@ function Testimonials({ title, items }) {
|
|
|
1927
2334
|
] }) });
|
|
1928
2335
|
}
|
|
1929
2336
|
function NewsletterBand({ title, subtitle }) {
|
|
1930
|
-
const [email, setEmail] =
|
|
1931
|
-
const [done, setDone] =
|
|
2337
|
+
const [email, setEmail] = React6.useState("");
|
|
2338
|
+
const [done, setDone] = React6.useState(false);
|
|
1932
2339
|
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
2340
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-3xl font-bold", children: title ?? "Receba nossas novidades" }),
|
|
1934
2341
|
subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 opacity-90", children: subtitle }),
|
|
@@ -1976,8 +2383,10 @@ function registerStorefrontBlocks() {
|
|
|
1976
2383
|
if (registered) return;
|
|
1977
2384
|
registered = true;
|
|
1978
2385
|
core.registerBlock("hero", HeroSection, { source: "sdk", label: "Hero" });
|
|
2386
|
+
core.registerBlock("mediaCarousel", MediaCarousel, { source: "sdk", label: "Media carousel" });
|
|
1979
2387
|
core.registerBlock("categories", CategoryShowcase, { source: "sdk", label: "Categories" });
|
|
1980
2388
|
core.registerBlock("products", ProductRail, { source: "sdk", label: "Product rail" });
|
|
2389
|
+
core.registerBlock("productSlider", ProductSlider, { source: "sdk", label: "Product slider" });
|
|
1981
2390
|
core.registerBlock("benefits", BenefitsRow, { source: "sdk", label: "Benefits row" });
|
|
1982
2391
|
core.registerBlock("banner", PromoBanner, { source: "sdk", label: "Promo banner" });
|
|
1983
2392
|
core.registerBlock("manifesto", ManifestoBlock, { source: "sdk", label: "Manifesto" });
|
|
@@ -1991,14 +2400,17 @@ function sectionsToBlocks(sections) {
|
|
|
1991
2400
|
return { type, id: `${type}-${i}`, props };
|
|
1992
2401
|
});
|
|
1993
2402
|
}
|
|
1994
|
-
function
|
|
2403
|
+
function StorefrontSections({ sections }) {
|
|
1995
2404
|
return /* @__PURE__ */ jsxRuntime.jsx("main", { children: core.renderBlocks(sectionsToBlocks(sections)) });
|
|
1996
2405
|
}
|
|
2406
|
+
function HomePage({ sections }) {
|
|
2407
|
+
return /* @__PURE__ */ jsxRuntime.jsx(StorefrontSections, { sections });
|
|
2408
|
+
}
|
|
1997
2409
|
function useProduct(slug2) {
|
|
1998
|
-
const [product, setProduct] =
|
|
1999
|
-
const [loading, setLoading] =
|
|
2000
|
-
const [error, setError] =
|
|
2001
|
-
|
|
2410
|
+
const [product, setProduct] = React6.useState(null);
|
|
2411
|
+
const [loading, setLoading] = React6.useState(true);
|
|
2412
|
+
const [error, setError] = React6.useState(null);
|
|
2413
|
+
React6.useEffect(() => {
|
|
2002
2414
|
let cancelled = false;
|
|
2003
2415
|
setLoading(true);
|
|
2004
2416
|
runtime.getShopProvider().listProducts({ slug: slug2, status: "active", limit: 1 }).then((data) => {
|
|
@@ -2105,28 +2517,218 @@ function ProductSpecs({ product }) {
|
|
|
2105
2517
|
}
|
|
2106
2518
|
function RelatedProducts({ product }) {
|
|
2107
2519
|
const config = useStorefrontConfig();
|
|
2108
|
-
const
|
|
2520
|
+
const actions = useStorefrontActions();
|
|
2521
|
+
const components = getStorefrontComponents(config);
|
|
2522
|
+
const Card = components.ProductCard ?? ProductCard;
|
|
2109
2523
|
const opts = product.categoryId ? { categoryId: product.categoryId, status: "active", limit: 5 } : { status: "active", limit: 5 };
|
|
2110
2524
|
const { products, loading } = useProducts(opts);
|
|
2111
2525
|
const related = products.filter((p) => p.id !== product.id).slice(0, 4);
|
|
2112
2526
|
if (loading || related.length === 0) return null;
|
|
2113
2527
|
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "mt-16", children: [
|
|
2114
2528
|
/* @__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(
|
|
2529
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6 grid grid-cols-2 gap-5 md:grid-cols-4", children: related.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2530
|
+
Card,
|
|
2531
|
+
{
|
|
2532
|
+
product: p,
|
|
2533
|
+
config,
|
|
2534
|
+
commerceMode: config.commerceMode,
|
|
2535
|
+
actions
|
|
2536
|
+
},
|
|
2537
|
+
p.id
|
|
2538
|
+
)) })
|
|
2116
2539
|
] });
|
|
2117
2540
|
}
|
|
2541
|
+
function defaultSubject(prefix, product) {
|
|
2542
|
+
return `${prefix}: ${product.name}`;
|
|
2543
|
+
}
|
|
2544
|
+
function buildWhatsAppHref(url, product) {
|
|
2545
|
+
const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`);
|
|
2546
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
2547
|
+
return `${url}${sep}text=${text}`;
|
|
2548
|
+
}
|
|
2549
|
+
function ProductEnquiryForm({ product, onSuccess }) {
|
|
2550
|
+
const config = useStorefrontConfig();
|
|
2551
|
+
const [customerName, setCustomerName] = React6.useState("");
|
|
2552
|
+
const [customerEmail, setCustomerEmail] = React6.useState("");
|
|
2553
|
+
const [customerPhone, setCustomerPhone] = React6.useState("");
|
|
2554
|
+
const [subject, setSubject] = React6.useState(defaultSubject(config.enquiry.subjectPrefix, product));
|
|
2555
|
+
const [message, setMessage] = React6.useState(`I would like more information about ${product.name}.`);
|
|
2556
|
+
const [status, setStatus] = React6.useState("idle");
|
|
2557
|
+
const [error, setError] = React6.useState(null);
|
|
2558
|
+
const submit = async (event) => {
|
|
2559
|
+
event.preventDefault();
|
|
2560
|
+
setStatus("submitting");
|
|
2561
|
+
setError(null);
|
|
2562
|
+
try {
|
|
2563
|
+
const provider = runtime.getShopProvider();
|
|
2564
|
+
if (provider.createProductEnquiry) {
|
|
2565
|
+
await provider.createProductEnquiry({
|
|
2566
|
+
productId: product.id,
|
|
2567
|
+
productName: product.name,
|
|
2568
|
+
productSlug: product.slug,
|
|
2569
|
+
customerName,
|
|
2570
|
+
customerEmail,
|
|
2571
|
+
customerPhone: customerPhone || void 0,
|
|
2572
|
+
subject,
|
|
2573
|
+
message,
|
|
2574
|
+
sourceUrl: typeof window === "undefined" ? void 0 : window.location.href,
|
|
2575
|
+
metadata: { sku: product.sku, categoryName: product.categoryName }
|
|
2576
|
+
});
|
|
2577
|
+
} else if (config.enquiry.email) {
|
|
2578
|
+
window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
|
|
2579
|
+
}
|
|
2580
|
+
setStatus("success");
|
|
2581
|
+
onSuccess?.();
|
|
2582
|
+
} catch (e) {
|
|
2583
|
+
setStatus("error");
|
|
2584
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
2585
|
+
}
|
|
2586
|
+
};
|
|
2587
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2588
|
+
"form",
|
|
2589
|
+
{
|
|
2590
|
+
...storefrontComponentContracts.productDetail.enquiryForm,
|
|
2591
|
+
onSubmit: submit,
|
|
2592
|
+
className: "space-y-4",
|
|
2593
|
+
children: [
|
|
2594
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
2595
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
|
|
2596
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your name" }),
|
|
2597
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2598
|
+
"input",
|
|
2599
|
+
{
|
|
2600
|
+
"data-testid": TID.enquiryName,
|
|
2601
|
+
required: true,
|
|
2602
|
+
value: customerName,
|
|
2603
|
+
onChange: (e) => setCustomerName(e.target.value),
|
|
2604
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2605
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2606
|
+
}
|
|
2607
|
+
)
|
|
2608
|
+
] }),
|
|
2609
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "space-y-1.5 text-sm font-medium", children: [
|
|
2610
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Your email" }),
|
|
2611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2612
|
+
"input",
|
|
2613
|
+
{
|
|
2614
|
+
"data-testid": TID.enquiryEmail,
|
|
2615
|
+
required: true,
|
|
2616
|
+
type: "email",
|
|
2617
|
+
value: customerEmail,
|
|
2618
|
+
onChange: (e) => setCustomerEmail(e.target.value),
|
|
2619
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2620
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2621
|
+
}
|
|
2622
|
+
)
|
|
2623
|
+
] })
|
|
2624
|
+
] }),
|
|
2625
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2626
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2627
|
+
"Phone",
|
|
2628
|
+
config.enquiry.requirePhone ? "" : " (optional)"
|
|
2629
|
+
] }),
|
|
2630
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2631
|
+
"input",
|
|
2632
|
+
{
|
|
2633
|
+
"data-testid": TID.enquiryPhone,
|
|
2634
|
+
required: config.enquiry.requirePhone,
|
|
2635
|
+
value: customerPhone,
|
|
2636
|
+
onChange: (e) => setCustomerPhone(e.target.value),
|
|
2637
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2638
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2639
|
+
}
|
|
2640
|
+
)
|
|
2641
|
+
] }),
|
|
2642
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2643
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Subject" }),
|
|
2644
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2645
|
+
"input",
|
|
2646
|
+
{
|
|
2647
|
+
"data-testid": TID.enquirySubject,
|
|
2648
|
+
required: true,
|
|
2649
|
+
value: subject,
|
|
2650
|
+
onChange: (e) => setSubject(e.target.value),
|
|
2651
|
+
className: "w-full border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2652
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2653
|
+
}
|
|
2654
|
+
)
|
|
2655
|
+
] }),
|
|
2656
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1.5 text-sm font-medium", children: [
|
|
2657
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Message" }),
|
|
2658
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2659
|
+
"textarea",
|
|
2660
|
+
{
|
|
2661
|
+
"data-testid": TID.enquiryMessage,
|
|
2662
|
+
required: true,
|
|
2663
|
+
rows: 5,
|
|
2664
|
+
value: message,
|
|
2665
|
+
onChange: (e) => setMessage(e.target.value),
|
|
2666
|
+
className: "w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary",
|
|
2667
|
+
style: { borderRadius: "var(--sf-radius-input)" }
|
|
2668
|
+
}
|
|
2669
|
+
)
|
|
2670
|
+
] }),
|
|
2671
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
2672
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2673
|
+
"button",
|
|
2674
|
+
{
|
|
2675
|
+
"data-testid": TID.enquirySubmit,
|
|
2676
|
+
type: "submit",
|
|
2677
|
+
disabled: status === "submitting",
|
|
2678
|
+
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",
|
|
2679
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2680
|
+
children: [
|
|
2681
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Send, { className: "h-4 w-4" }),
|
|
2682
|
+
status === "submitting" ? "Sending..." : "Send enquiry"
|
|
2683
|
+
]
|
|
2684
|
+
}
|
|
2685
|
+
),
|
|
2686
|
+
config.enquiry.whatsappUrl && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2687
|
+
"a",
|
|
2688
|
+
{
|
|
2689
|
+
href: buildWhatsAppHref(config.enquiry.whatsappUrl, product),
|
|
2690
|
+
target: "_blank",
|
|
2691
|
+
rel: "noreferrer",
|
|
2692
|
+
className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2693
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2694
|
+
children: [
|
|
2695
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.MessageCircle, { className: "h-4 w-4" }),
|
|
2696
|
+
"WhatsApp"
|
|
2697
|
+
]
|
|
2698
|
+
}
|
|
2699
|
+
),
|
|
2700
|
+
config.enquiry.email && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2701
|
+
"a",
|
|
2702
|
+
{
|
|
2703
|
+
href: `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`,
|
|
2704
|
+
className: "inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2705
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2706
|
+
children: [
|
|
2707
|
+
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Mail, { className: "h-4 w-4" }),
|
|
2708
|
+
"Email"
|
|
2709
|
+
]
|
|
2710
|
+
}
|
|
2711
|
+
)
|
|
2712
|
+
] }),
|
|
2713
|
+
status === "success" && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquirySuccess, className: "text-sm font-medium text-emerald-600", children: config.enquiry.successMessage }),
|
|
2714
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": TID.enquiryError, className: "text-sm font-medium text-red-600", children: error })
|
|
2715
|
+
]
|
|
2716
|
+
}
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2118
2719
|
function ProductDetailPage({ slug: slug2 }) {
|
|
2119
2720
|
const config = useStorefrontConfig();
|
|
2721
|
+
const actions = useStorefrontActions();
|
|
2120
2722
|
const { product, loading } = useProduct(slug2);
|
|
2121
2723
|
const addItem = useCartStore((s) => s.addItem);
|
|
2122
|
-
const [qty, setQty] =
|
|
2123
|
-
const [selectedOptions, setSelectedOptions] =
|
|
2724
|
+
const [qty, setQty] = React6.useState(1);
|
|
2725
|
+
const [selectedOptions, setSelectedOptions] = React6.useState({});
|
|
2124
2726
|
const optionGroups = product ? getProductOptionGroups(product) : [];
|
|
2125
2727
|
useStorefrontHead({
|
|
2126
2728
|
title: product ? `${product.name} \u2014 ${config.name}` : config.name,
|
|
2127
2729
|
description: product?.description
|
|
2128
2730
|
});
|
|
2129
|
-
|
|
2731
|
+
React6.useEffect(() => {
|
|
2130
2732
|
if (!product) return;
|
|
2131
2733
|
const defaults = Object.fromEntries(
|
|
2132
2734
|
getProductOptionGroups(product).map((group) => [group.label, group.values[0] ?? ""])
|
|
@@ -2152,21 +2754,56 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2152
2754
|
const soldOut = product.inventoryCount <= 0;
|
|
2153
2755
|
const lowStock = !soldOut && product.inventoryCount <= 5;
|
|
2154
2756
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
|
|
2757
|
+
const components = getStorefrontComponents(config);
|
|
2758
|
+
const ProductDetailComponent = components.ProductDetail;
|
|
2759
|
+
const addToCart = () => addItem(product, qty, selectedOptions);
|
|
2760
|
+
const openEnquiry = () => {
|
|
2761
|
+
document.getElementById("storefront-product-enquiry")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
2762
|
+
};
|
|
2155
2763
|
const discountPct = product.compareAtPrice && product.compareAtPrice > product.price ? Math.round((1 - product.price / product.compareAtPrice) * 100) : 0;
|
|
2156
|
-
|
|
2764
|
+
if (ProductDetailComponent) {
|
|
2765
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2766
|
+
ProductDetailComponent,
|
|
2767
|
+
{
|
|
2768
|
+
product,
|
|
2769
|
+
config,
|
|
2770
|
+
commerceMode: config.commerceMode,
|
|
2771
|
+
primaryImage: image,
|
|
2772
|
+
optionGroups,
|
|
2773
|
+
selectedOptions,
|
|
2774
|
+
setSelectedOptions,
|
|
2775
|
+
quantity: qty,
|
|
2776
|
+
setQuantity: setQty,
|
|
2777
|
+
soldOut,
|
|
2778
|
+
lowStock,
|
|
2779
|
+
actions,
|
|
2780
|
+
addToCart,
|
|
2781
|
+
openEnquiry
|
|
2782
|
+
}
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("main", { ...storefrontComponentContracts.productDetail.root, className: "mx-auto max-w-5xl px-4 py-8 sm:px-6", children: [
|
|
2157
2786
|
/* @__PURE__ */ jsxRuntime.jsxs(Link, { to: "/", className: "mb-6 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [
|
|
2158
2787
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.ChevronLeft, { className: "h-4 w-4" }),
|
|
2159
2788
|
" Continuar comprando"
|
|
2160
2789
|
] }),
|
|
2161
2790
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid animate-fade-up gap-10 md:grid-cols-2", children: [
|
|
2162
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2163
|
-
"
|
|
2791
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2792
|
+
"div",
|
|
2164
2793
|
{
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2794
|
+
...storefrontComponentContracts.productDetail.gallery,
|
|
2795
|
+
className: "group overflow-hidden border bg-muted",
|
|
2796
|
+
style: { borderRadius: "var(--sf-radius-card)" },
|
|
2797
|
+
children: image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2798
|
+
SmoothImage,
|
|
2799
|
+
{
|
|
2800
|
+
src: image.url,
|
|
2801
|
+
alt: image.altText ?? product.name,
|
|
2802
|
+
className: "aspect-square w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
2803
|
+
}
|
|
2804
|
+
)
|
|
2168
2805
|
}
|
|
2169
|
-
)
|
|
2806
|
+
),
|
|
2170
2807
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col py-2 lg:sticky lg:top-24 lg:self-start", children: [
|
|
2171
2808
|
/* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
2172
2809
|
/* @__PURE__ */ jsxRuntime.jsx(Link, { to: "/", className: "transition-colors hover:text-foreground", children: "In\xEDcio" }),
|
|
@@ -2178,7 +2815,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2178
2815
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: product.name })
|
|
2179
2816
|
] }),
|
|
2180
2817
|
/* @__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: [
|
|
2818
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center gap-3", children: [
|
|
2182
2819
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xl", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2183
2820
|
Price,
|
|
2184
2821
|
{
|
|
@@ -2195,7 +2832,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2195
2832
|
] })
|
|
2196
2833
|
] }),
|
|
2197
2834
|
/* @__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: [
|
|
2835
|
+
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
2836
|
soldOut ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2 font-medium text-muted-foreground", children: [
|
|
2200
2837
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full bg-muted-foreground" }),
|
|
2201
2838
|
" Sem estoque"
|
|
@@ -2221,7 +2858,7 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2221
2858
|
onChange: setSelectedOptions
|
|
2222
2859
|
}
|
|
2223
2860
|
),
|
|
2224
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2861
|
+
/* @__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
2862
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2226
2863
|
QuantityInput,
|
|
2227
2864
|
{
|
|
@@ -2237,14 +2874,32 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2237
2874
|
type: "button",
|
|
2238
2875
|
"data-testid": TID.pdpAddToCart,
|
|
2239
2876
|
disabled: soldOut,
|
|
2240
|
-
onClick:
|
|
2877
|
+
onClick: addToCart,
|
|
2241
2878
|
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
2879
|
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2243
2880
|
children: soldOut ? "Esgotado" : "Adicionar ao carrinho"
|
|
2244
2881
|
}
|
|
2245
2882
|
)
|
|
2246
|
-
] })
|
|
2247
|
-
|
|
2883
|
+
] }) : config.commerceMode === "enquiry" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2884
|
+
"button",
|
|
2885
|
+
{
|
|
2886
|
+
type: "button",
|
|
2887
|
+
...storefrontComponentContracts.productDetail.enquiryButton,
|
|
2888
|
+
onClick: openEnquiry,
|
|
2889
|
+
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",
|
|
2890
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2891
|
+
children: config.enquiry.label
|
|
2892
|
+
}
|
|
2893
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2894
|
+
Link,
|
|
2895
|
+
{
|
|
2896
|
+
to: config.catalogPath,
|
|
2897
|
+
className: "inline-flex border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted",
|
|
2898
|
+
style: { borderRadius: "var(--sf-radius-button)" },
|
|
2899
|
+
children: "Voltar ao cat\xE1logo"
|
|
2900
|
+
}
|
|
2901
|
+
) }),
|
|
2902
|
+
config.commerceMode === "checkout" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
|
|
2248
2903
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
|
|
2249
2904
|
/* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Truck, { className: "h-5 w-5 text-primary" }),
|
|
2250
2905
|
" Envio r\xE1pido"
|
|
@@ -2260,6 +2915,11 @@ function ProductDetailPage({ slug: slug2 }) {
|
|
|
2260
2915
|
] })
|
|
2261
2916
|
] })
|
|
2262
2917
|
] }),
|
|
2918
|
+
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: [
|
|
2919
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sf-heading text-2xl font-semibold", children: "Need more information?" }),
|
|
2920
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-muted-foreground", children: "Send an enquiry about this product." }),
|
|
2921
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(ProductEnquiryForm, { product }) })
|
|
2922
|
+
] }) }),
|
|
2263
2923
|
/* @__PURE__ */ jsxRuntime.jsx(ProductSpecs, { product }),
|
|
2264
2924
|
/* @__PURE__ */ jsxRuntime.jsx(RelatedProducts, { product })
|
|
2265
2925
|
] });
|
|
@@ -2305,11 +2965,11 @@ async function placeStorefrontOrder({
|
|
|
2305
2965
|
return { order, customerId: customerId ?? order.customerId ?? "" };
|
|
2306
2966
|
}
|
|
2307
2967
|
function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
|
|
2308
|
-
const [email, setEmail] =
|
|
2309
|
-
const [password, setPassword] =
|
|
2310
|
-
const [busy, setBusy] =
|
|
2311
|
-
const [error, setError] =
|
|
2312
|
-
|
|
2968
|
+
const [email, setEmail] = React6.useState(defaultEmail);
|
|
2969
|
+
const [password, setPassword] = React6.useState("");
|
|
2970
|
+
const [busy, setBusy] = React6.useState(false);
|
|
2971
|
+
const [error, setError] = React6.useState(null);
|
|
2972
|
+
React6.useEffect(() => {
|
|
2313
2973
|
const onKey = (event) => {
|
|
2314
2974
|
if (event.key === "Escape") onClose();
|
|
2315
2975
|
};
|
|
@@ -2471,13 +3131,13 @@ function CheckoutPage() {
|
|
|
2471
3131
|
const session = useSessionStore();
|
|
2472
3132
|
const validateDiscount2 = useDiscountValidator();
|
|
2473
3133
|
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] =
|
|
3134
|
+
const [selectedAddressId, setSelectedAddressId] = React6.useState(DEFAULT_ADDRESS.id);
|
|
3135
|
+
const [selectedPaymentId, setSelectedPaymentId] = React6.useState(DEFAULT_PAYMENT.id);
|
|
3136
|
+
const [savedAddresses, setSavedAddresses] = React6.useState(SAVED_ADDRESSES);
|
|
3137
|
+
const [savedPaymentMethods, setSavedPaymentMethods] = React6.useState(SAVED_PAYMENT_METHODS);
|
|
3138
|
+
const [addressMode, setAddressMode] = React6.useState("saved");
|
|
3139
|
+
const [paymentMode, setPaymentMode] = React6.useState("saved");
|
|
3140
|
+
const [form, setForm] = React6.useState({
|
|
2481
3141
|
email: session.email ?? "",
|
|
2482
3142
|
name: session.name ?? "",
|
|
2483
3143
|
street: DEFAULT_ADDRESS.street,
|
|
@@ -2487,20 +3147,20 @@ function CheckoutPage() {
|
|
|
2487
3147
|
expiry: DEFAULT_PAYMENT.expiry,
|
|
2488
3148
|
cvc: DEFAULT_PAYMENT.cvc
|
|
2489
3149
|
});
|
|
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] =
|
|
3150
|
+
const [error, setError] = React6.useState(null);
|
|
3151
|
+
const [discountCode, setDiscountCode] = React6.useState("");
|
|
3152
|
+
const [discountError, setDiscountError] = React6.useState(null);
|
|
3153
|
+
const [discountSuccess, setDiscountSuccess] = React6.useState(null);
|
|
3154
|
+
const [applyingDiscount, setApplyingDiscount] = React6.useState(false);
|
|
3155
|
+
const [placing, setPlacing] = React6.useState(false);
|
|
3156
|
+
const [processingStep, setProcessingStep] = React6.useState(0);
|
|
3157
|
+
const [showSignin, setShowSignin] = React6.useState(false);
|
|
2498
3158
|
const subtotal = selectSubtotal(cart);
|
|
2499
3159
|
const discountTotal = selectDiscountTotal(cart);
|
|
2500
3160
|
const shipping = selectShipping(cart, config);
|
|
2501
3161
|
const total = selectTotal(cart, config);
|
|
2502
3162
|
const money = (value) => formatMoney(value, config.currency, config.locale);
|
|
2503
|
-
|
|
3163
|
+
React6.useEffect(() => {
|
|
2504
3164
|
if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
|
|
2505
3165
|
}, [cart.lines.length, config.catalogPath, placing]);
|
|
2506
3166
|
const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
|
|
@@ -2815,7 +3475,7 @@ function CheckoutPage() {
|
|
|
2815
3475
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
|
|
2816
3476
|
/* @__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
3477
|
/* @__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(
|
|
3478
|
+
line.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: line.imageUrl, alt: line.name, className: "h-full w-full object-cover" }),
|
|
2819
3479
|
/* @__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
3480
|
] }),
|
|
2821
3481
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -3155,11 +3815,11 @@ function SuccessMark() {
|
|
|
3155
3815
|
}
|
|
3156
3816
|
function OrderConfirmationPage({ orderId }) {
|
|
3157
3817
|
const config = useStorefrontConfig();
|
|
3158
|
-
const [order, setOrder] =
|
|
3159
|
-
const [loading, setLoading] =
|
|
3818
|
+
const [order, setOrder] = React6.useState(null);
|
|
3819
|
+
const [loading, setLoading] = React6.useState(true);
|
|
3160
3820
|
const money = (v) => formatMoney(v, config.currency, config.locale);
|
|
3161
3821
|
useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
|
|
3162
|
-
|
|
3822
|
+
React6.useEffect(() => {
|
|
3163
3823
|
let cancelled = false;
|
|
3164
3824
|
runtime.getShopProvider().getOrder(orderId).then((o) => {
|
|
3165
3825
|
if (!cancelled) setOrder(o);
|
|
@@ -3237,7 +3897,7 @@ function OrderConfirmationPage({ orderId }) {
|
|
|
3237
3897
|
/* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order })
|
|
3238
3898
|
] }),
|
|
3239
3899
|
/* @__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(
|
|
3900
|
+
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
|
|
3241
3901
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
3242
3902
|
item.name,
|
|
3243
3903
|
" ",
|
|
@@ -3291,11 +3951,11 @@ function OrderConfirmationPage({ orderId }) {
|
|
|
3291
3951
|
function useMyOrders() {
|
|
3292
3952
|
const customerId = useSessionStore((s) => s.customerId);
|
|
3293
3953
|
const email = useSessionStore((s) => s.email);
|
|
3294
|
-
const [orders, setOrders] =
|
|
3295
|
-
const [loading, setLoading] =
|
|
3296
|
-
const [tick, setTick] =
|
|
3297
|
-
const refresh =
|
|
3298
|
-
|
|
3954
|
+
const [orders, setOrders] = React6.useState([]);
|
|
3955
|
+
const [loading, setLoading] = React6.useState(true);
|
|
3956
|
+
const [tick, setTick] = React6.useState(0);
|
|
3957
|
+
const refresh = React6.useCallback(() => setTick((t) => t + 1), []);
|
|
3958
|
+
React6.useEffect(() => {
|
|
3299
3959
|
let cancelled = false;
|
|
3300
3960
|
if (!customerId && !email) {
|
|
3301
3961
|
setOrders([]);
|
|
@@ -3317,12 +3977,12 @@ function useMyOrders() {
|
|
|
3317
3977
|
return { orders, loading, refresh };
|
|
3318
3978
|
}
|
|
3319
3979
|
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] =
|
|
3980
|
+
const [mode, setMode] = React6.useState("signin");
|
|
3981
|
+
const [email, setEmail] = React6.useState("");
|
|
3982
|
+
const [password, setPassword] = React6.useState("");
|
|
3983
|
+
const [name, setName] = React6.useState("");
|
|
3984
|
+
const [busy, setBusy] = React6.useState(false);
|
|
3985
|
+
const [error, setError] = React6.useState(null);
|
|
3326
3986
|
async function submit(e) {
|
|
3327
3987
|
e.preventDefault();
|
|
3328
3988
|
setError(null);
|
|
@@ -3445,7 +4105,7 @@ function OrdersPanel() {
|
|
|
3445
4105
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: new Date(order.createdAt).toLocaleDateString(config.locale) }),
|
|
3446
4106
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(OrderTrackingTimeline, { order, compact: true }) }),
|
|
3447
4107
|
/* @__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(
|
|
4108
|
+
item.imageUrl && /* @__PURE__ */ jsxRuntime.jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-6 w-6 rounded object-cover" }),
|
|
3449
4109
|
item.name,
|
|
3450
4110
|
" \xD7 ",
|
|
3451
4111
|
item.quantity
|
|
@@ -3464,9 +4124,9 @@ function OrdersPanel() {
|
|
|
3464
4124
|
}
|
|
3465
4125
|
function ProfilePanel() {
|
|
3466
4126
|
const session = useSessionStore();
|
|
3467
|
-
const [name, setName] =
|
|
3468
|
-
const [saving, setSaving] =
|
|
3469
|
-
const [saved, setSaved] =
|
|
4127
|
+
const [name, setName] = React6.useState(session.name ?? "");
|
|
4128
|
+
const [saving, setSaving] = React6.useState(false);
|
|
4129
|
+
const [saved, setSaved] = React6.useState(false);
|
|
3470
4130
|
async function save(e) {
|
|
3471
4131
|
e.preventDefault();
|
|
3472
4132
|
const trimmed = name.trim();
|
|
@@ -3562,7 +4222,7 @@ var SECTION_SUBTITLE = {
|
|
|
3562
4222
|
function MyPurchasesPage() {
|
|
3563
4223
|
const config = useStorefrontConfig();
|
|
3564
4224
|
const session = useSessionStore();
|
|
3565
|
-
const [section, setSection] =
|
|
4225
|
+
const [section, setSection] = React6.useState("orders");
|
|
3566
4226
|
useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
|
|
3567
4227
|
if (!session.email) {
|
|
3568
4228
|
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 +4285,7 @@ function PolicyPage({ kind }) {
|
|
|
3625
4285
|
] }) })
|
|
3626
4286
|
] });
|
|
3627
4287
|
}
|
|
3628
|
-
var StorefrontErrorBoundary = class extends
|
|
4288
|
+
var StorefrontErrorBoundary = class extends React6__default.default.Component {
|
|
3629
4289
|
constructor() {
|
|
3630
4290
|
super(...arguments);
|
|
3631
4291
|
this.state = { error: null };
|
|
@@ -3669,7 +4329,7 @@ var ACCENT = {
|
|
|
3669
4329
|
function ToastCard({ toast: toast2 }) {
|
|
3670
4330
|
const dismiss = useToastStore((s) => s.dismiss);
|
|
3671
4331
|
const Icon2 = ICON[toast2.variant];
|
|
3672
|
-
|
|
4332
|
+
React6.useEffect(() => {
|
|
3673
4333
|
const timer = window.setTimeout(() => dismiss(toast2.id), toast2.durationMs);
|
|
3674
4334
|
return () => window.clearTimeout(timer);
|
|
3675
4335
|
}, [toast2.id, toast2.durationMs, dismiss]);
|
|
@@ -3726,8 +4386,8 @@ function RouteSwitch({ path }) {
|
|
|
3726
4386
|
if (product?.slug) return /* @__PURE__ */ jsxRuntime.jsx(ProductDetailPage, { slug: product.slug });
|
|
3727
4387
|
const order = matchPath("/order/:id", path);
|
|
3728
4388
|
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, {});
|
|
4389
|
+
if (matchPath("/checkout", path) && config.features.checkout) return /* @__PURE__ */ jsxRuntime.jsx(CheckoutPage, {});
|
|
4390
|
+
if (matchPath("/account", path) && config.features.accounts) return /* @__PURE__ */ jsxRuntime.jsx(MyPurchasesPage, {});
|
|
3731
4391
|
if (matchPath("/catalog", path)) return /* @__PURE__ */ jsxRuntime.jsx(CatalogPage, {});
|
|
3732
4392
|
if (matchPath("/privacy", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "privacy" });
|
|
3733
4393
|
if (matchPath("/terms", path)) return /* @__PURE__ */ jsxRuntime.jsx(PolicyPage, { kind: "terms" });
|
|
@@ -3769,7 +4429,12 @@ function buildMockSeed(config) {
|
|
|
3769
4429
|
};
|
|
3770
4430
|
}
|
|
3771
4431
|
function initStorefrontRuntime(config) {
|
|
3772
|
-
|
|
4432
|
+
if (resolveConfig(config).features.accounts) {
|
|
4433
|
+
initCustomerAuth(resolveAuthAdapter(config.auth, {
|
|
4434
|
+
supabaseUrl: config.supabaseUrl,
|
|
4435
|
+
supabaseAnonKey: config.supabaseAnonKey
|
|
4436
|
+
}));
|
|
4437
|
+
}
|
|
3773
4438
|
if (config.provider) {
|
|
3774
4439
|
runtime.setShopProvider(config.provider);
|
|
3775
4440
|
} else {
|
|
@@ -3785,14 +4450,20 @@ function StorefrontShell() {
|
|
|
3785
4450
|
const config = useStorefrontConfig();
|
|
3786
4451
|
const path = useHashPath();
|
|
3787
4452
|
const focused = isFocusedRoute(config, path);
|
|
3788
|
-
|
|
4453
|
+
const components = getStorefrontComponents(config);
|
|
4454
|
+
const chromeProps = { config, commerceMode: config.commerceMode };
|
|
4455
|
+
const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3789
4456
|
config.theme && /* @__PURE__ */ jsxRuntime.jsx(StorefrontThemeStyle, { theme: config.theme }),
|
|
3790
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {}),
|
|
4457
|
+
!focused && (components.Header ? /* @__PURE__ */ jsxRuntime.jsx(components.Header, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontHeader, {})),
|
|
3791
4458
|
/* @__PURE__ */ jsxRuntime.jsx(StorefrontErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(RouteSwitch, { path }) }, path),
|
|
3792
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
|
|
3793
|
-
!focused && /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {}),
|
|
4459
|
+
!focused && config.features.cart && /* @__PURE__ */ jsxRuntime.jsx(CartDrawer, {}),
|
|
4460
|
+
!focused && (components.Footer ? /* @__PURE__ */ jsxRuntime.jsx(components.Footer, { ...chromeProps }) : /* @__PURE__ */ jsxRuntime.jsx(StorefrontFooter, {})),
|
|
3794
4461
|
/* @__PURE__ */ jsxRuntime.jsx(Toaster, {})
|
|
3795
4462
|
] });
|
|
4463
|
+
if (components.Shell) {
|
|
4464
|
+
return /* @__PURE__ */ jsxRuntime.jsx(components.Shell, { ...chromeProps, children: body });
|
|
4465
|
+
}
|
|
4466
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-background text-foreground", children: body });
|
|
3796
4467
|
}
|
|
3797
4468
|
function createStorefrontApp(config) {
|
|
3798
4469
|
const resolved = resolveConfig(config);
|
|
@@ -3803,6 +4474,37 @@ function createStorefrontApp(config) {
|
|
|
3803
4474
|
StorefrontApp.displayName = "StorefrontApp";
|
|
3804
4475
|
return StorefrontApp;
|
|
3805
4476
|
}
|
|
4477
|
+
function createStorefront(options) {
|
|
4478
|
+
const routes = [...options.pages ?? [], ...options.routes ?? []];
|
|
4479
|
+
const config = {
|
|
4480
|
+
...options.config,
|
|
4481
|
+
provider: options.provider ?? options.config.provider,
|
|
4482
|
+
components: {
|
|
4483
|
+
...options.config.components,
|
|
4484
|
+
...options.components
|
|
4485
|
+
},
|
|
4486
|
+
routes: routes.length > 0 ? routes : options.config.routes,
|
|
4487
|
+
home: options.sections ? { ...options.config.home ?? { sections: [] }, sections: options.sections } : options.config.home
|
|
4488
|
+
};
|
|
4489
|
+
return createStorefrontApp(config);
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
// src/define.ts
|
|
4493
|
+
function defineStorefrontConfig(config) {
|
|
4494
|
+
return config;
|
|
4495
|
+
}
|
|
4496
|
+
function defineStorefrontComponents(components) {
|
|
4497
|
+
return components;
|
|
4498
|
+
}
|
|
4499
|
+
function defineStorefrontRoutes(routes) {
|
|
4500
|
+
return routes;
|
|
4501
|
+
}
|
|
4502
|
+
function defineStorefrontSections(sections) {
|
|
4503
|
+
return sections;
|
|
4504
|
+
}
|
|
4505
|
+
function defineStorefrontApp(options) {
|
|
4506
|
+
return options;
|
|
4507
|
+
}
|
|
3806
4508
|
var runtimeConfigRegistry = /* @__PURE__ */ new Map();
|
|
3807
4509
|
function slug(name) {
|
|
3808
4510
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "app";
|
|
@@ -3861,7 +4563,7 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
3861
4563
|
provider: manifest.backend?.provider,
|
|
3862
4564
|
url: manifest.backend?.url
|
|
3863
4565
|
},
|
|
3864
|
-
|
|
4566
|
+
components: runtimeConfig?.components,
|
|
3865
4567
|
routes: runtimeConfig?.routes,
|
|
3866
4568
|
provider: runtimeConfig?.provider,
|
|
3867
4569
|
auth: runtimeConfig?.auth,
|
|
@@ -3869,9 +4571,9 @@ function manifestToStorefrontConfig(manifest, surfaceName) {
|
|
|
3869
4571
|
};
|
|
3870
4572
|
}
|
|
3871
4573
|
function StorefrontScaffold({ manifest, surface }) {
|
|
3872
|
-
const config =
|
|
3873
|
-
const resolved =
|
|
3874
|
-
const inited =
|
|
4574
|
+
const config = React6__default.default.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface]);
|
|
4575
|
+
const resolved = React6__default.default.useMemo(() => resolveConfig(config), [config]);
|
|
4576
|
+
const inited = React6__default.default.useRef(false);
|
|
3875
4577
|
if (!inited.current) {
|
|
3876
4578
|
initStorefrontRuntime(config);
|
|
3877
4579
|
inited.current = true;
|
|
@@ -4122,6 +4824,7 @@ exports.HeroSection = HeroSection;
|
|
|
4122
4824
|
exports.HomePage = HomePage;
|
|
4123
4825
|
exports.Link = Link;
|
|
4124
4826
|
exports.ManifestoBlock = ManifestoBlock;
|
|
4827
|
+
exports.MediaCarousel = MediaCarousel;
|
|
4125
4828
|
exports.MyPurchasesPage = MyPurchasesPage;
|
|
4126
4829
|
exports.NewsletterBand = NewsletterBand;
|
|
4127
4830
|
exports.OrderConfirmationPage = OrderConfirmationPage;
|
|
@@ -4129,23 +4832,33 @@ exports.OrderTrackingTimeline = OrderTrackingTimeline;
|
|
|
4129
4832
|
exports.Price = Price;
|
|
4130
4833
|
exports.ProductCard = ProductCard;
|
|
4131
4834
|
exports.ProductDetailPage = ProductDetailPage;
|
|
4835
|
+
exports.ProductEnquiryForm = ProductEnquiryForm;
|
|
4132
4836
|
exports.ProductGrid = ProductGrid;
|
|
4133
4837
|
exports.ProductOptionSelector = ProductOptionSelector;
|
|
4134
4838
|
exports.ProductRail = ProductRail;
|
|
4839
|
+
exports.ProductSlider = ProductSlider;
|
|
4135
4840
|
exports.PromoBanner = PromoBanner;
|
|
4136
4841
|
exports.QuantityInput = QuantityInput;
|
|
4137
4842
|
exports.Reveal = Reveal;
|
|
4843
|
+
exports.SmoothImage = SmoothImage;
|
|
4138
4844
|
exports.StorefrontFooter = StorefrontFooter;
|
|
4139
4845
|
exports.StorefrontHeader = StorefrontHeader;
|
|
4140
4846
|
exports.StorefrontScaffold = StorefrontScaffold;
|
|
4847
|
+
exports.StorefrontSections = StorefrontSections;
|
|
4141
4848
|
exports.StorefrontShell = StorefrontShell;
|
|
4142
4849
|
exports.StorefrontThemeStyle = StorefrontThemeStyle;
|
|
4143
4850
|
exports.TID = TID;
|
|
4144
4851
|
exports.Testimonials = Testimonials;
|
|
4145
4852
|
exports.atelierTemplate = atelierTemplate;
|
|
4146
4853
|
exports.bannerPlaceholder = bannerPlaceholder;
|
|
4854
|
+
exports.createStorefront = createStorefront;
|
|
4147
4855
|
exports.createStorefrontApp = createStorefrontApp;
|
|
4148
4856
|
exports.defineStorefront = defineStorefront;
|
|
4857
|
+
exports.defineStorefrontApp = defineStorefrontApp;
|
|
4858
|
+
exports.defineStorefrontComponents = defineStorefrontComponents;
|
|
4859
|
+
exports.defineStorefrontConfig = defineStorefrontConfig;
|
|
4860
|
+
exports.defineStorefrontRoutes = defineStorefrontRoutes;
|
|
4861
|
+
exports.defineStorefrontSections = defineStorefrontSections;
|
|
4149
4862
|
exports.establishCustomerSession = establishCustomerSession;
|
|
4150
4863
|
exports.formatMoney = formatMoney;
|
|
4151
4864
|
exports.formatProductOptionSelection = formatProductOptionSelection;
|
|
@@ -4158,7 +4871,7 @@ exports.navigateTo = navigateTo;
|
|
|
4158
4871
|
exports.normalizeProductOptionSelection = normalizeProductOptionSelection;
|
|
4159
4872
|
exports.placeStorefrontOrder = placeStorefrontOrder;
|
|
4160
4873
|
exports.prefersReducedMotion = prefersReducedMotion;
|
|
4161
|
-
exports.
|
|
4874
|
+
exports.productCardComponentContract = productCardComponentContract;
|
|
4162
4875
|
exports.productOptionSelectionKey = productOptionSelectionKey;
|
|
4163
4876
|
exports.registerStorefrontBlocks = registerStorefrontBlocks;
|
|
4164
4877
|
exports.resolveAuthAdapter = resolveAuthAdapter;
|
|
@@ -4173,13 +4886,16 @@ exports.sertaoTemplate = sertaoTemplate;
|
|
|
4173
4886
|
exports.signInByEmail = signInByEmail;
|
|
4174
4887
|
exports.signOutCustomer = signOutCustomer;
|
|
4175
4888
|
exports.signUpCustomer = signUpCustomer;
|
|
4176
|
-
exports.
|
|
4889
|
+
exports.storefrontComponentContracts = storefrontComponentContracts;
|
|
4177
4890
|
exports.storefrontTemplates = storefrontTemplates;
|
|
4178
4891
|
exports.themeToCss = themeToCss;
|
|
4892
|
+
exports.useCart = useCart;
|
|
4179
4893
|
exports.useCartStore = useCartStore;
|
|
4894
|
+
exports.useCatalog = useCatalog;
|
|
4180
4895
|
exports.useCatalogStore = useCatalogStore;
|
|
4181
4896
|
exports.useCategories = useCategories;
|
|
4182
4897
|
exports.useDiscountValidator = useDiscountValidator;
|
|
4898
|
+
exports.useEnquiry = useEnquiry;
|
|
4183
4899
|
exports.useHashPath = useHashPath;
|
|
4184
4900
|
exports.useInView = useInView;
|
|
4185
4901
|
exports.useMyOrders = useMyOrders;
|
|
@@ -4188,7 +4904,10 @@ exports.useProduct = useProduct;
|
|
|
4188
4904
|
exports.useProducts = useProducts;
|
|
4189
4905
|
exports.useScrolled = useScrolled;
|
|
4190
4906
|
exports.useSessionStore = useSessionStore;
|
|
4907
|
+
exports.useStorefront = useStorefront;
|
|
4908
|
+
exports.useStorefrontActions = useStorefrontActions;
|
|
4191
4909
|
exports.useStorefrontConfig = useStorefrontConfig;
|
|
4910
|
+
exports.useStorefrontConfigOptional = useStorefrontConfigOptional;
|
|
4192
4911
|
exports.voltTemplate = voltTemplate;
|
|
4193
4912
|
//# sourceMappingURL=index.cjs.map
|
|
4194
4913
|
//# sourceMappingURL=index.cjs.map
|