@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../src/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"CheckoutPage.d.ts","sourceRoot":"","sources":["../../src/pages/CheckoutPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAgGlD,wBAAgB,YAAY,sBAke3B"}
|
package/dist/pages/HomePage.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { StorefrontSection } from '../sections';
|
|
3
|
+
export declare function StorefrontSections({ sections }: {
|
|
4
|
+
sections: readonly StorefrontSection[];
|
|
5
|
+
}): React.JSX.Element;
|
|
3
6
|
export declare function HomePage({ sections }: {
|
|
4
|
-
sections:
|
|
7
|
+
sections: readonly StorefrontSection[];
|
|
5
8
|
}): React.JSX.Element;
|
|
6
9
|
//# sourceMappingURL=HomePage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HomePage.d.ts","sourceRoot":"","sources":["../../src/pages/HomePage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"HomePage.d.ts","sourceRoot":"","sources":["../../src/pages/HomePage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAOpD,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,qBAE1F;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAAE,qBAEhF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyPurchasesPage.d.ts","sourceRoot":"","sources":["../../src/pages/MyPurchasesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"MyPurchasesPage.d.ts","sourceRoot":"","sources":["../../src/pages/MyPurchasesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AA2RvC,wBAAgB,eAAe,sBA2B9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderConfirmationPage.d.ts","sourceRoot":"","sources":["../../src/pages/OrderConfirmationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"OrderConfirmationPage.d.ts","sourceRoot":"","sources":["../../src/pages/OrderConfirmationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAoDlD,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAyIrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductDetailPage.d.ts","sourceRoot":"","sources":["../../src/pages/ProductDetailPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ProductDetailPage.d.ts","sourceRoot":"","sources":["../../src/pages/ProductDetailPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAmBlD,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,qBAgP3D"}
|
package/dist/sections.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HeroVariant } from './theme';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
export interface HeroSlide {
|
|
3
4
|
title: string;
|
|
4
5
|
subtitle?: string;
|
|
@@ -10,11 +11,28 @@ export interface HeroSlide {
|
|
|
10
11
|
/** Editorial boxed copy block (Uyuni) instead of free overlay */
|
|
11
12
|
boxed?: boolean;
|
|
12
13
|
}
|
|
13
|
-
export
|
|
14
|
+
export interface MediaCarouselItem {
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
eyebrow?: string;
|
|
18
|
+
cta?: string;
|
|
19
|
+
href?: string;
|
|
20
|
+
image: string;
|
|
21
|
+
imageAlt?: string;
|
|
22
|
+
align?: 'start' | 'center' | 'end';
|
|
23
|
+
}
|
|
24
|
+
export type StorefrontSection = {
|
|
14
25
|
type: 'hero';
|
|
15
26
|
variant: HeroVariant;
|
|
16
27
|
slides: HeroSlide[];
|
|
17
28
|
height?: 'tall' | 'medium';
|
|
29
|
+
} | {
|
|
30
|
+
type: 'mediaCarousel';
|
|
31
|
+
items: MediaCarouselItem[];
|
|
32
|
+
height?: 'medium' | 'tall' | 'screen';
|
|
33
|
+
autoplayMs?: number;
|
|
34
|
+
overlay?: 'dark' | 'soft' | 'none';
|
|
35
|
+
className?: string;
|
|
18
36
|
} | {
|
|
19
37
|
type: 'categories';
|
|
20
38
|
style: 'bubbles' | 'tiles';
|
|
@@ -28,6 +46,18 @@ export type HomeSection = {
|
|
|
28
46
|
/** 'sale' = compareAtPrice set; 'new' = newest first; 'all' = catalog order */
|
|
29
47
|
filter?: 'sale' | 'new' | 'all';
|
|
30
48
|
limit?: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'productSlider';
|
|
51
|
+
title: string;
|
|
52
|
+
subtitle?: string;
|
|
53
|
+
eyebrow?: string;
|
|
54
|
+
filter?: 'sale' | 'new' | 'all';
|
|
55
|
+
categoryName?: string;
|
|
56
|
+
collection?: string;
|
|
57
|
+
limit?: number;
|
|
58
|
+
cta?: string;
|
|
59
|
+
href?: string;
|
|
60
|
+
className?: string;
|
|
31
61
|
} | {
|
|
32
62
|
type: 'benefits';
|
|
33
63
|
items: Array<{
|
|
@@ -60,8 +90,10 @@ export type HomeSection = {
|
|
|
60
90
|
title?: string;
|
|
61
91
|
subtitle?: string;
|
|
62
92
|
};
|
|
93
|
+
/** @deprecated Use StorefrontSection. Kept for existing generated stores. */
|
|
94
|
+
export type HomeSection = StorefrontSection;
|
|
63
95
|
export interface HomeConfig {
|
|
64
|
-
sections:
|
|
96
|
+
sections: readonly StorefrontSection[];
|
|
65
97
|
}
|
|
66
98
|
export interface NavLink {
|
|
67
99
|
label: string;
|
|
@@ -78,6 +110,17 @@ export interface FooterConfig {
|
|
|
78
110
|
label: string;
|
|
79
111
|
href: string;
|
|
80
112
|
}>;
|
|
113
|
+
credit?: React.ReactNode;
|
|
114
|
+
labels?: {
|
|
115
|
+
categories?: string;
|
|
116
|
+
navigation?: string;
|
|
117
|
+
contact?: string;
|
|
118
|
+
viewAll?: string;
|
|
119
|
+
purchases?: string;
|
|
120
|
+
privacy?: string;
|
|
121
|
+
terms?: string;
|
|
122
|
+
returns?: string;
|
|
123
|
+
};
|
|
81
124
|
}
|
|
82
125
|
/** Wide gradient SVG data-URI — deterministic hero/banner art, no network. */
|
|
83
126
|
export declare function bannerPlaceholder(label: string, hueA: number, hueB: number, w?: number, h?: number): string;
|
package/dist/sections.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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"}
|
package/dist/testids.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export declare const TID: {
|
|
|
11
11
|
readonly hero: "home-hero";
|
|
12
12
|
readonly heroPrev: "hero-prev";
|
|
13
13
|
readonly heroNext: "hero-next";
|
|
14
|
+
readonly mediaCarousel: "media-carousel";
|
|
15
|
+
readonly mediaCarouselPrev: "media-carousel-prev";
|
|
16
|
+
readonly mediaCarouselNext: "media-carousel-next";
|
|
17
|
+
readonly productSlider: "product-slider";
|
|
18
|
+
readonly productSliderViewport: "product-slider-viewport";
|
|
19
|
+
readonly productSliderPrev: "product-slider-prev";
|
|
20
|
+
readonly productSliderNext: "product-slider-next";
|
|
14
21
|
readonly categoryShowcase: "category-showcase";
|
|
15
22
|
readonly productRail: "product-rail";
|
|
16
23
|
readonly benefits: "benefits-row";
|
|
@@ -42,6 +49,19 @@ export declare const TID: {
|
|
|
42
49
|
readonly productOptionValue: (id: string, value: string) => string;
|
|
43
50
|
readonly pdpQtyInput: "pdp-qty-input";
|
|
44
51
|
readonly pdpAddToCart: "pdp-add-to-cart";
|
|
52
|
+
readonly pdpRoot: "pdp-root";
|
|
53
|
+
readonly pdpGallery: "pdp-gallery";
|
|
54
|
+
readonly pdpActions: "pdp-actions";
|
|
55
|
+
readonly pdpEnquiryButton: "pdp-enquiry-button";
|
|
56
|
+
readonly enquiryForm: "enquiry-form";
|
|
57
|
+
readonly enquiryName: "enquiry-name";
|
|
58
|
+
readonly enquiryEmail: "enquiry-email";
|
|
59
|
+
readonly enquiryPhone: "enquiry-phone";
|
|
60
|
+
readonly enquirySubject: "enquiry-subject";
|
|
61
|
+
readonly enquiryMessage: "enquiry-message";
|
|
62
|
+
readonly enquirySubmit: "enquiry-submit";
|
|
63
|
+
readonly enquirySuccess: "enquiry-success";
|
|
64
|
+
readonly enquiryError: "enquiry-error";
|
|
45
65
|
readonly cartDrawer: "cart-drawer";
|
|
46
66
|
readonly cartLine: "cart-line";
|
|
47
67
|
readonly lineQty: "line-qty";
|
package/dist/testids.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testids.d.ts","sourceRoot":"","sources":["../src/testids.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;8BAQG,MAAM;iCACH,MAAM
|
|
1
|
+
{"version":3,"file":"testids.d.ts","sourceRoot":"","sources":["../src/testids.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;8BAQG,MAAM;iCACH,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAgCH,MAAM;;;;;;;;;;sCAWJ,MAAM;sCACN,MAAM,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAiDpB,MAAM;;;;;;;;;;;;;;;;;CAkBxB,CAAA"}
|
package/dist/theme.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export interface StorefrontTheme {
|
|
|
41
41
|
variant?: HeaderVariant;
|
|
42
42
|
/** Uppercase nav links (premium retail tone) */
|
|
43
43
|
uppercaseNav?: boolean;
|
|
44
|
+
/** Show category shortcuts after primary nav links. Default true. */
|
|
45
|
+
showCategories?: boolean;
|
|
46
|
+
/** Show the storefront search input in the header. Default true. */
|
|
47
|
+
showSearch?: boolean;
|
|
44
48
|
};
|
|
45
49
|
productCard?: {
|
|
46
50
|
style?: CardStyle;
|
package/dist/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAA;AAaxC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAA;AACzE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,uBAAuB;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,yBAAyB;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,gDAAgD;QAChD,YAAY,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAA;AAaxC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAA;AACzE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,uBAAuB;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,yBAAyB;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,gDAAgD;QAChD,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,qEAAqE;QACrE,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,oEAAoE;QACpE,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAA;IACD,WAAW,CAAC,EAAE;QACZ,KAAK,CAAC,EAAE,SAAS,CAAA;QACjB,WAAW,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;KACpC,CAAA;IACD,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAyBD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAuCzD;AAYD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,eAAe,CAAA;CAAE;;8CAiBzE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fayz-ai/storefront",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Fayz SDK storefront kit — Shopify-style customer-facing ecommerce template components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"@supabase/supabase-js": "^2.45.0",
|
|
41
41
|
"lucide-react": "^0.400.0",
|
|
42
42
|
"zustand": "^4.5.0",
|
|
43
|
-
"@fayz-ai/
|
|
44
|
-
"@fayz-ai/
|
|
45
|
-
"@fayz-ai/
|
|
46
|
-
"@fayz-ai/
|
|
47
|
-
"@fayz-ai/
|
|
43
|
+
"@fayz-ai/core": "^0.6.6",
|
|
44
|
+
"@fayz-ai/plugin-auth": "^0.1.2",
|
|
45
|
+
"@fayz-ai/shop": "^0.7.0",
|
|
46
|
+
"@fayz-ai/ui": "^0.6.6",
|
|
47
|
+
"@fayz-ai/sdk": "^0.6.6",
|
|
48
|
+
"@fayz-ai/auth": "^0.6.6"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -63,9 +64,6 @@
|
|
|
63
64
|
"template"
|
|
64
65
|
],
|
|
65
66
|
"license": "MIT",
|
|
66
|
-
"publishConfig": {
|
|
67
|
-
"access": "public"
|
|
68
|
-
},
|
|
69
67
|
"scripts": {
|
|
70
68
|
"build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
|
|
71
69
|
"dev": "tsup --watch",
|
package/src/auth.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { AuthAdapter, AuthUser } from '@fayz-ai/core'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createAuthRuntime,
|
|
4
|
+
createMockAuthAdapter,
|
|
5
|
+
type AuthPluginOptions,
|
|
6
|
+
} from '@fayz-ai/plugin-auth'
|
|
3
7
|
import { getShopProvider } from '@fayz-ai/shop/runtime'
|
|
4
8
|
import { useSessionStore } from './stores/session.store'
|
|
5
9
|
|
|
@@ -11,20 +15,53 @@ import { useSessionStore } from './stores/session.store'
|
|
|
11
15
|
// and purchase history attach to a customer.
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
|
|
14
|
-
export type StorefrontAuthAdapter = AuthAdapter | 'mock' | 'supabase'
|
|
18
|
+
export type StorefrontAuthAdapter = AuthAdapter | 'mock' | 'supabase' | (string & {})
|
|
19
|
+
|
|
20
|
+
export interface StorefrontAuthConfig {
|
|
21
|
+
adapter?: AuthPluginOptions['adapter']
|
|
22
|
+
provider?: AuthPluginOptions['provider']
|
|
23
|
+
routes?: AuthPluginOptions['routes']
|
|
24
|
+
supabase?: AuthPluginOptions['supabase']
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
let _adapter: AuthAdapter | null = null
|
|
17
28
|
|
|
29
|
+
function isAuthAdapter(value: unknown): value is AuthAdapter {
|
|
30
|
+
return Boolean(
|
|
31
|
+
value &&
|
|
32
|
+
typeof value === 'object' &&
|
|
33
|
+
'getSession' in value &&
|
|
34
|
+
'signIn' in value &&
|
|
35
|
+
'signOut' in value,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeAuthConfig(
|
|
40
|
+
configured: StorefrontAuthAdapter | StorefrontAuthConfig | undefined,
|
|
41
|
+
): StorefrontAuthConfig {
|
|
42
|
+
if (!configured) return {}
|
|
43
|
+
if (typeof configured === 'string' || isAuthAdapter(configured)) return { adapter: configured }
|
|
44
|
+
return configured
|
|
45
|
+
}
|
|
46
|
+
|
|
18
47
|
export function resolveAuthAdapter(
|
|
19
|
-
configured: StorefrontAuthAdapter | undefined,
|
|
48
|
+
configured: StorefrontAuthAdapter | StorefrontAuthConfig | undefined,
|
|
49
|
+
fallback?: { supabaseUrl?: string; supabaseAnonKey?: string },
|
|
20
50
|
): AuthAdapter {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
const config = normalizeAuthConfig(configured)
|
|
52
|
+
return createAuthRuntime({
|
|
53
|
+
provider: config.provider ?? config.adapter ?? (fallback?.supabaseUrl ? 'supabase' : 'mock'),
|
|
54
|
+
adapter: config.adapter,
|
|
55
|
+
routes: config.routes,
|
|
56
|
+
requireAuth: false,
|
|
57
|
+
supabase: {
|
|
58
|
+
url: config.supabase?.url ?? fallback?.supabaseUrl,
|
|
59
|
+
anonKey: config.supabase?.anonKey ?? fallback?.supabaseAnonKey,
|
|
60
|
+
client: config.supabase?.client,
|
|
61
|
+
callbackUrl: config.supabase?.callbackUrl,
|
|
62
|
+
resetPasswordUrl: config.supabase?.resetPasswordUrl,
|
|
63
|
+
},
|
|
64
|
+
}).adapter
|
|
28
65
|
}
|
|
29
66
|
|
|
30
67
|
/** Called once by createStorefrontApp. Restores any persisted session. */
|
package/src/blocks.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { registerBlock } from '@fayz-ai/core'
|
|
2
2
|
import type { BlockNode } from '@fayz-ai/core'
|
|
3
|
-
import type {
|
|
3
|
+
import type { StorefrontSection } from './sections'
|
|
4
4
|
import { HeroSection } from './components/sections/HeroSection'
|
|
5
5
|
import { CategoryShowcase } from './components/sections/CategoryShowcase'
|
|
6
6
|
import { ProductRail } from './components/sections/ProductRail'
|
|
7
|
+
import { MediaCarousel } from './components/sections/MediaCarousel'
|
|
8
|
+
import { ProductSlider } from './components/sections/ProductSlider'
|
|
7
9
|
import {
|
|
8
10
|
BenefitsRow,
|
|
9
11
|
PromoBanner,
|
|
@@ -26,8 +28,10 @@ export function registerStorefrontBlocks(): void {
|
|
|
26
28
|
if (registered) return
|
|
27
29
|
registered = true
|
|
28
30
|
registerBlock('hero', HeroSection, { source: 'sdk', label: 'Hero' })
|
|
31
|
+
registerBlock('mediaCarousel', MediaCarousel, { source: 'sdk', label: 'Media carousel' })
|
|
29
32
|
registerBlock('categories', CategoryShowcase, { source: 'sdk', label: 'Categories' })
|
|
30
33
|
registerBlock('products', ProductRail, { source: 'sdk', label: 'Product rail' })
|
|
34
|
+
registerBlock('productSlider', ProductSlider, { source: 'sdk', label: 'Product slider' })
|
|
31
35
|
registerBlock('benefits', BenefitsRow, { source: 'sdk', label: 'Benefits row' })
|
|
32
36
|
registerBlock('banner', PromoBanner, { source: 'sdk', label: 'Promo banner' })
|
|
33
37
|
registerBlock('manifesto', ManifestoBlock, { source: 'sdk', label: 'Manifesto' })
|
|
@@ -38,8 +42,8 @@ export function registerStorefrontBlocks(): void {
|
|
|
38
42
|
// Register on import so any consumer of the storefront package has the blocks.
|
|
39
43
|
registerStorefrontBlocks()
|
|
40
44
|
|
|
41
|
-
/** Convert the
|
|
42
|
-
export function sectionsToBlocks(sections:
|
|
45
|
+
/** Convert the typed storefront section blueprint into a generic block tree. */
|
|
46
|
+
export function sectionsToBlocks(sections: readonly StorefrontSection[]): BlockNode[] {
|
|
43
47
|
return sections.map((section, i) => {
|
|
44
48
|
const { type, ...props } = section
|
|
45
49
|
return { type, id: `${type}-${i}`, props: props as Record<string, unknown> }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import type { Category, CreateProductEnquiryInput, Product, ProductEnquiry, ProductImage } from '@fayz-ai/shop/types'
|
|
3
|
+
import type { ProductOptionGroup, ProductOptionSelection } from './product-options'
|
|
4
|
+
import type { ResolvedStorefrontConfig, StorefrontCommerceMode } from './config'
|
|
5
|
+
import type { MediaCarouselItem } from './sections'
|
|
6
|
+
|
|
7
|
+
export interface StorefrontComponentBaseProps {
|
|
8
|
+
config: ResolvedStorefrontConfig
|
|
9
|
+
commerceMode: StorefrontCommerceMode
|
|
10
|
+
className?: string
|
|
11
|
+
testId?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StorefrontActions {
|
|
15
|
+
navigateTo: (to: string) => void
|
|
16
|
+
addToCart: (product: Product, quantity?: number, options?: ProductOptionSelection) => void
|
|
17
|
+
openCart: () => void
|
|
18
|
+
closeCart: () => void
|
|
19
|
+
createProductEnquiry: (input: CreateProductEnquiryInput) => Promise<ProductEnquiry>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ProductCardProps extends StorefrontComponentBaseProps {
|
|
23
|
+
product: Product
|
|
24
|
+
actions: Pick<StorefrontActions, 'navigateTo' | 'addToCart' | 'openCart'>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProductDetailProps extends StorefrontComponentBaseProps {
|
|
28
|
+
product: Product
|
|
29
|
+
primaryImage?: ProductImage
|
|
30
|
+
optionGroups: ProductOptionGroup[]
|
|
31
|
+
selectedOptions: ProductOptionSelection
|
|
32
|
+
setSelectedOptions: React.Dispatch<React.SetStateAction<ProductOptionSelection>>
|
|
33
|
+
quantity: number
|
|
34
|
+
setQuantity: React.Dispatch<React.SetStateAction<number>>
|
|
35
|
+
soldOut: boolean
|
|
36
|
+
lowStock: boolean
|
|
37
|
+
actions: Pick<StorefrontActions, 'addToCart' | 'createProductEnquiry' | 'navigateTo'>
|
|
38
|
+
addToCart: () => void
|
|
39
|
+
openEnquiry: () => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ProductGalleryProps extends StorefrontComponentBaseProps {
|
|
43
|
+
product: Product
|
|
44
|
+
images: ProductImage[]
|
|
45
|
+
primaryImage?: ProductImage
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ProductPriceProps extends StorefrontComponentBaseProps {
|
|
49
|
+
product: Product
|
|
50
|
+
value: number
|
|
51
|
+
compareAt?: number | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProductActionsProps extends StorefrontComponentBaseProps {
|
|
55
|
+
product: Product
|
|
56
|
+
quantity: number
|
|
57
|
+
setQuantity: React.Dispatch<React.SetStateAction<number>>
|
|
58
|
+
soldOut: boolean
|
|
59
|
+
actions: Pick<StorefrontActions, 'addToCart' | 'createProductEnquiry' | 'navigateTo'>
|
|
60
|
+
addToCart: () => void
|
|
61
|
+
openEnquiry: () => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ProductEnquiryFormContractProps extends StorefrontComponentBaseProps {
|
|
65
|
+
product: Product
|
|
66
|
+
onSuccess?: () => void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CategoryCardProps extends StorefrontComponentBaseProps {
|
|
70
|
+
category: Category
|
|
71
|
+
actions: Pick<StorefrontActions, 'navigateTo'>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CollectionSectionProps extends StorefrontComponentBaseProps {
|
|
75
|
+
title: string
|
|
76
|
+
subtitle?: string
|
|
77
|
+
products: Product[]
|
|
78
|
+
category?: Category
|
|
79
|
+
children?: React.ReactNode
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HeroSectionContractProps extends StorefrontComponentBaseProps {
|
|
83
|
+
title: string
|
|
84
|
+
subtitle?: string
|
|
85
|
+
eyebrow?: string
|
|
86
|
+
image?: string
|
|
87
|
+
cta?: string
|
|
88
|
+
href?: string
|
|
89
|
+
children?: React.ReactNode
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface MediaCarouselProps extends StorefrontComponentBaseProps {
|
|
93
|
+
items: MediaCarouselItem[]
|
|
94
|
+
height?: 'medium' | 'tall' | 'screen'
|
|
95
|
+
autoplayMs?: number
|
|
96
|
+
overlay?: 'dark' | 'soft' | 'none'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ProductSliderProps extends StorefrontComponentBaseProps {
|
|
100
|
+
title: string
|
|
101
|
+
subtitle?: string
|
|
102
|
+
eyebrow?: string
|
|
103
|
+
filter?: 'sale' | 'new' | 'all'
|
|
104
|
+
categoryName?: string
|
|
105
|
+
collection?: string
|
|
106
|
+
limit?: number
|
|
107
|
+
cta?: string
|
|
108
|
+
href?: string
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface StorefrontChromeProps extends StorefrontComponentBaseProps {
|
|
112
|
+
children?: React.ReactNode
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface StorefrontStateProps extends StorefrontComponentBaseProps {
|
|
116
|
+
title?: string
|
|
117
|
+
message?: string
|
|
118
|
+
children?: React.ReactNode
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface StorefrontComponents {
|
|
122
|
+
ProductCard?: React.ComponentType<ProductCardProps>
|
|
123
|
+
ProductDetail?: React.ComponentType<ProductDetailProps>
|
|
124
|
+
ProductGallery?: React.ComponentType<ProductGalleryProps>
|
|
125
|
+
ProductPrice?: React.ComponentType<ProductPriceProps>
|
|
126
|
+
ProductActions?: React.ComponentType<ProductActionsProps>
|
|
127
|
+
ProductEnquiryForm?: React.ComponentType<ProductEnquiryFormContractProps>
|
|
128
|
+
CategoryCard?: React.ComponentType<CategoryCardProps>
|
|
129
|
+
CollectionSection?: React.ComponentType<CollectionSectionProps>
|
|
130
|
+
HeroSection?: React.ComponentType<HeroSectionContractProps>
|
|
131
|
+
MediaCarousel?: React.ComponentType<MediaCarouselProps>
|
|
132
|
+
ProductSlider?: React.ComponentType<ProductSliderProps>
|
|
133
|
+
Header?: React.ComponentType<StorefrontChromeProps>
|
|
134
|
+
Footer?: React.ComponentType<StorefrontChromeProps>
|
|
135
|
+
Shell?: React.ComponentType<StorefrontChromeProps>
|
|
136
|
+
EmptyState?: React.ComponentType<StorefrontStateProps>
|
|
137
|
+
LoadingState?: React.ComponentType<StorefrontStateProps>
|
|
138
|
+
ErrorState?: React.ComponentType<StorefrontStateProps>
|
|
139
|
+
|
|
140
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TID } from './testids'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable selectors that custom storefront components must preserve.
|
|
5
|
+
*
|
|
6
|
+
* Apps can fully customize the visual tree, but replacement components still
|
|
7
|
+
* need these anchors so smoke tests, QA, and agents can operate stores.
|
|
8
|
+
*/
|
|
9
|
+
export const productCardComponentContract = {
|
|
10
|
+
root: { 'data-testid': TID.productCard },
|
|
11
|
+
name: { 'data-testid': TID.productCardName },
|
|
12
|
+
priceTestId: TID.productCardPrice,
|
|
13
|
+
addButton: { 'data-testid': TID.productCardAdd },
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
export const storefrontComponentContracts = {
|
|
17
|
+
productCard: productCardComponentContract,
|
|
18
|
+
productDetail: {
|
|
19
|
+
root: { 'data-testid': TID.pdpRoot },
|
|
20
|
+
gallery: { 'data-testid': TID.pdpGallery },
|
|
21
|
+
name: { 'data-testid': TID.pdpName },
|
|
22
|
+
description: { 'data-testid': TID.pdpDescription },
|
|
23
|
+
actions: { 'data-testid': TID.pdpActions },
|
|
24
|
+
addButton: { 'data-testid': TID.pdpAddToCart },
|
|
25
|
+
enquiryButton: { 'data-testid': TID.pdpEnquiryButton },
|
|
26
|
+
enquiryForm: { 'data-testid': TID.enquiryForm },
|
|
27
|
+
},
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
export type ProductCardComponentContract = typeof productCardComponentContract
|
|
31
|
+
export type ProductDetailComponentContract = typeof storefrontComponentContracts.productDetail
|
|
@@ -14,6 +14,7 @@ import { navigateTo } from '../router'
|
|
|
14
14
|
import { formatMoney } from '../format'
|
|
15
15
|
import { QuantityInput } from './QuantityInput'
|
|
16
16
|
import { TID } from '../testids'
|
|
17
|
+
import { SmoothImage } from './SmoothImage'
|
|
17
18
|
|
|
18
19
|
export function CartDrawer() {
|
|
19
20
|
const config = useStorefrontConfig()
|
|
@@ -127,7 +128,7 @@ export function CartDrawer() {
|
|
|
127
128
|
}`}
|
|
128
129
|
>
|
|
129
130
|
{line.imageUrl && (
|
|
130
|
-
<
|
|
131
|
+
<SmoothImage src={line.imageUrl} alt={line.name} className="h-20 w-20 shrink-0 rounded-lg border object-cover" />
|
|
131
132
|
)}
|
|
132
133
|
<div className="flex flex-1 flex-col">
|
|
133
134
|
<div className="flex items-start justify-between gap-2">
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { ShoppingBag } from 'lucide-react'
|
|
3
|
-
import type { Product } from '@fayz-ai/shop/types'
|
|
4
|
-
import { useCartStore } from '../stores/cart.store'
|
|
5
3
|
import { useStorefrontConfig } from '../config'
|
|
6
4
|
import { Link, navigateTo } from '../router'
|
|
7
5
|
import { getProductOptionGroups } from '../product-options'
|
|
8
6
|
import { Price } from './Price'
|
|
9
7
|
import { TID } from '../testids'
|
|
10
|
-
import {
|
|
8
|
+
import { productCardComponentContract } from '../component-selectors'
|
|
9
|
+
import { useStorefrontActions } from '../hooks/useStorefront'
|
|
10
|
+
import type { ProductCardProps } from '../component-contracts'
|
|
11
|
+
import { SmoothImage } from './SmoothImage'
|
|
11
12
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const config = useStorefrontConfig()
|
|
18
|
-
const addItem = useCartStore((s) => s.addItem)
|
|
13
|
+
export function ProductCard({ product, config: providedConfig, actions: providedActions }: ProductCardProps) {
|
|
14
|
+
const runtimeConfig = useStorefrontConfig()
|
|
15
|
+
const runtimeActions = useStorefrontActions()
|
|
16
|
+
const config = providedConfig ?? runtimeConfig
|
|
17
|
+
const actions = providedActions ?? runtimeActions
|
|
19
18
|
const hasOptions = getProductOptionGroups(product).length > 0
|
|
20
19
|
const soldOut = product.inventoryCount <= 0
|
|
21
20
|
const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price
|
|
22
21
|
const image = product.images.find((i) => i.isPrimary) ?? product.images[0]
|
|
23
22
|
const cardStyle = config.theme?.productCard?.style ?? 'card'
|
|
24
23
|
const aspect = config.theme?.productCard?.imageAspect === 'portrait' ? 'aspect-[3/4]' : 'aspect-square'
|
|
24
|
+
const purchaseMode = config.commerceMode === 'checkout'
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
27
|
<div
|
|
28
|
-
{...
|
|
28
|
+
{...productCardComponentContract.root}
|
|
29
29
|
data-slug={product.slug}
|
|
30
30
|
className={`group flex flex-col overflow-hidden transition-all duration-300 ${
|
|
31
31
|
cardStyle === 'editorial'
|
|
@@ -36,7 +36,7 @@ export function ProductCard({ product }: ProductCardProps) {
|
|
|
36
36
|
>
|
|
37
37
|
<Link to={`/product/${product.slug}`} className={`relative block ${aspect} overflow-hidden bg-muted`} style={{ borderRadius: cardStyle === 'editorial' ? 'var(--sf-radius-card)' : undefined }}>
|
|
38
38
|
{image && (
|
|
39
|
-
<
|
|
39
|
+
<SmoothImage
|
|
40
40
|
src={image.url}
|
|
41
41
|
alt={image.altText ?? product.name}
|
|
42
42
|
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
@@ -67,23 +67,39 @@ export function ProductCard({ product }: ProductCardProps) {
|
|
|
67
67
|
<span className="text-xs text-muted-foreground">{product.categoryName}</span>
|
|
68
68
|
<Link
|
|
69
69
|
to={`/product/${product.slug}`}
|
|
70
|
-
{...
|
|
70
|
+
{...productCardComponentContract.name}
|
|
71
71
|
className="font-medium leading-snug hover:underline"
|
|
72
72
|
>
|
|
73
73
|
{product.name}
|
|
74
74
|
</Link>
|
|
75
|
-
<div className="mt-auto flex items-center justify-between pt-2">
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
<div className="mt-auto flex items-center justify-between gap-3 pt-2">
|
|
76
|
+
{purchaseMode ? (
|
|
77
|
+
<Price
|
|
78
|
+
value={product.price}
|
|
79
|
+
compareAt={product.compareAtPrice}
|
|
80
|
+
testId={productCardComponentContract.priceTestId}
|
|
81
|
+
/>
|
|
82
|
+
) : (
|
|
83
|
+
<span data-testid={productCardComponentContract.priceTestId} className="text-sm text-muted-foreground">
|
|
84
|
+
{config.commerceMode === 'enquiry' ? config.enquiry.label : 'Ver detalhes'}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
81
87
|
<button
|
|
82
88
|
type="button"
|
|
83
|
-
{...
|
|
84
|
-
disabled={soldOut}
|
|
85
|
-
aria-label={
|
|
89
|
+
{...productCardComponentContract.addButton}
|
|
90
|
+
disabled={purchaseMode && soldOut}
|
|
91
|
+
aria-label={
|
|
92
|
+
purchaseMode
|
|
93
|
+
? hasOptions
|
|
94
|
+
? `Escolher opções de ${product.name}`
|
|
95
|
+
: `Adicionar ${product.name} ao carrinho`
|
|
96
|
+
: `${config.enquiry.label} ${product.name}`
|
|
97
|
+
}
|
|
86
98
|
onClick={() => {
|
|
99
|
+
if (!purchaseMode) {
|
|
100
|
+
navigateTo(`/product/${product.slug}`)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
87
103
|
// Products with variants (size/color) must pick an option on the PDP
|
|
88
104
|
// — quick-adding would create an option-less line. Others add inline
|
|
89
105
|
// (addItem auto-opens the cart drawer).
|
|
@@ -91,7 +107,7 @@ export function ProductCard({ product }: ProductCardProps) {
|
|
|
91
107
|
navigateTo(`/product/${product.slug}`)
|
|
92
108
|
return
|
|
93
109
|
}
|
|
94
|
-
|
|
110
|
+
actions.addToCart(product)
|
|
95
111
|
}}
|
|
96
112
|
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"
|
|
97
113
|
>
|