@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.
Files changed (94) hide show
  1. package/dist/auth.d.ts +12 -2
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/blocks.d.ts +3 -3
  4. package/dist/blocks.d.ts.map +1 -1
  5. package/dist/component-contracts.d.ts +124 -0
  6. package/dist/component-contracts.d.ts.map +1 -0
  7. package/dist/component-selectors.d.ts +61 -0
  8. package/dist/component-selectors.d.ts.map +1 -0
  9. package/dist/components/CartDrawer.d.ts.map +1 -1
  10. package/dist/components/ProductCard.d.ts +2 -5
  11. package/dist/components/ProductCard.d.ts.map +1 -1
  12. package/dist/components/ProductEnquiryForm.d.ts +8 -0
  13. package/dist/components/ProductEnquiryForm.d.ts.map +1 -0
  14. package/dist/components/ProductGrid.d.ts.map +1 -1
  15. package/dist/components/RelatedProducts.d.ts +1 -1
  16. package/dist/components/RelatedProducts.d.ts.map +1 -1
  17. package/dist/components/SmoothImage.d.ts +7 -0
  18. package/dist/components/SmoothImage.d.ts.map +1 -0
  19. package/dist/components/StorefrontFooter.d.ts.map +1 -1
  20. package/dist/components/StorefrontHeader.d.ts.map +1 -1
  21. package/dist/components/sections/CategoryShowcase.d.ts.map +1 -1
  22. package/dist/components/sections/HeroSection.d.ts.map +1 -1
  23. package/dist/components/sections/MediaCarousel.d.ts +11 -0
  24. package/dist/components/sections/MediaCarousel.d.ts.map +1 -0
  25. package/dist/components/sections/MiscSections.d.ts.map +1 -1
  26. package/dist/components/sections/ProductRail.d.ts.map +1 -1
  27. package/dist/components/sections/ProductSlider.d.ts +15 -0
  28. package/dist/components/sections/ProductSlider.d.ts.map +1 -0
  29. package/dist/config.d.ts +38 -21
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/createStorefrontApp.d.ts +12 -0
  32. package/dist/createStorefrontApp.d.ts.map +1 -1
  33. package/dist/define.d.ts +17 -0
  34. package/dist/define.d.ts.map +1 -0
  35. package/dist/hooks/index.d.ts +1 -0
  36. package/dist/hooks/index.d.ts.map +1 -1
  37. package/dist/hooks/useStorefront.d.ts +35 -0
  38. package/dist/hooks/useStorefront.d.ts.map +1 -0
  39. package/dist/index.cjs +904 -185
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +20 -10
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +797 -95
  44. package/dist/index.js.map +1 -1
  45. package/dist/pages/CheckoutPage.d.ts.map +1 -1
  46. package/dist/pages/HomePage.d.ts +5 -2
  47. package/dist/pages/HomePage.d.ts.map +1 -1
  48. package/dist/pages/MyPurchasesPage.d.ts.map +1 -1
  49. package/dist/pages/OrderConfirmationPage.d.ts.map +1 -1
  50. package/dist/pages/ProductDetailPage.d.ts.map +1 -1
  51. package/dist/sections.d.ts +45 -2
  52. package/dist/sections.d.ts.map +1 -1
  53. package/dist/testids.d.ts +20 -0
  54. package/dist/testids.d.ts.map +1 -1
  55. package/dist/theme.d.ts +4 -0
  56. package/dist/theme.d.ts.map +1 -1
  57. package/package.json +7 -9
  58. package/src/auth.ts +47 -10
  59. package/src/blocks.tsx +7 -3
  60. package/src/component-contracts.ts +140 -0
  61. package/src/component-selectors.ts +31 -0
  62. package/src/components/CartDrawer.tsx +2 -1
  63. package/src/components/ProductCard.tsx +39 -23
  64. package/src/components/ProductEnquiryForm.tsx +175 -0
  65. package/src/components/ProductGrid.tsx +12 -3
  66. package/src/components/RelatedProducts.tsx +13 -4
  67. package/src/components/SmoothImage.tsx +59 -0
  68. package/src/components/StorefrontFooter.tsx +24 -14
  69. package/src/components/StorefrontHeader.tsx +43 -38
  70. package/src/components/sections/CategoryShowcase.tsx +3 -2
  71. package/src/components/sections/HeroSection.tsx +18 -2
  72. package/src/components/sections/MediaCarousel.tsx +141 -0
  73. package/src/components/sections/MiscSections.tsx +2 -1
  74. package/src/components/sections/ProductRail.tsx +11 -3
  75. package/src/components/sections/ProductSlider.tsx +119 -0
  76. package/src/config.ts +65 -24
  77. package/src/createStorefrontApp.tsx +62 -9
  78. package/src/define.ts +34 -0
  79. package/src/hooks/index.ts +1 -0
  80. package/src/hooks/useStorefront.ts +58 -0
  81. package/src/index.ts +61 -11
  82. package/src/pages/CheckoutPage.tsx +2 -1
  83. package/src/pages/HomePage.tsx +6 -2
  84. package/src/pages/MyPurchasesPage.tsx +2 -1
  85. package/src/pages/OrderConfirmationPage.tsx +2 -1
  86. package/src/pages/ProductDetailPage.tsx +138 -62
  87. package/src/scaffold.tsx +1 -1
  88. package/src/sections.ts +49 -2
  89. package/src/testids.ts +20 -0
  90. package/src/theme.ts +4 -0
  91. package/src/workflows/checkout.ts +1 -1
  92. package/dist/slot-contracts.d.ts +0 -34
  93. package/dist/slot-contracts.d.ts.map +0 -1
  94. 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;AA+FlD,wBAAgB,YAAY,sBAke3B"}
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"}
@@ -1,6 +1,9 @@
1
1
  import React from 'react';
2
- import type { HomeSection } from '../sections';
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: HomeSection[];
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,WAAW,EAAE,MAAM,aAAa,CAAA;AAO9C,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,qBAEjE"}
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;AA0RvC,wBAAgB,eAAe,sBA2B9B"}
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;AAmDlD,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,qBAyIrE"}
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;AAelD,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,qBAwK3D"}
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"}
@@ -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 type HomeSection = {
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: HomeSection[];
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;
@@ -1 +1 @@
1
- {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;CACnC;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,GACvF;IACE,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,EAAE,iBAAiB,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GACD;IACE,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAA;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAClF;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACjI;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GACzF;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE7D,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,iBAAiB,CAAA;AAE3C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC/C,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;CACF;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,SAAO,EAAE,CAAC,SAAM,GAAG,MAAM,CAYtG"}
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";
@@ -1 +1 @@
1
- {"version":3,"file":"testids.d.ts","sourceRoot":"","sources":["../src/testids.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;8BAQG,MAAM;iCACH,MAAM;;;;;;;;;;;;;;;;;;;;;;oCAyBH,MAAM;;;;;;;;;;sCAWJ,MAAM;sCACN,MAAM,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAoCpB,MAAM;;;;;;;;;;;;;;;;;CAkBxB,CAAA"}
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;
@@ -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;KACvB,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"}
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.6.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/auth": "^0.6.0",
44
- "@fayz-ai/core": "^0.6.0",
45
- "@fayz-ai/sdk": "^0.6.0",
46
- "@fayz-ai/shop": "^0.6.0",
47
- "@fayz-ai/ui": "^0.6.0"
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 { createMockAuthAdapter } from '@fayz-ai/auth'
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
- if (configured && configured !== 'mock' && configured !== 'supabase') return configured
22
- if (configured === 'supabase') {
23
- console.warn(
24
- '@fayz-ai/shop: auth.adapter="supabase" is legacy. Pass an explicit AuthAdapter or use the Fayz SDK broker path.',
25
- )
26
- }
27
- return createMockAuthAdapter()
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 { HomeSection } from './sections'
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 legacy HomeSection[] blueprint into a generic block tree. */
42
- export function sectionsToBlocks(sections: HomeSection[]): BlockNode[] {
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
- <img src={line.imageUrl} alt={line.name} className="h-20 w-20 shrink-0 rounded-lg border object-cover" />
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 { productCardSlotContract } from '../slot-contracts'
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 interface ProductCardProps {
13
- product: Product
14
- }
15
-
16
- export function ProductCard({ product }: ProductCardProps) {
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
- {...productCardSlotContract.root}
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
- <img
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
- {...productCardSlotContract.name}
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
- <Price
77
- value={product.price}
78
- compareAt={product.compareAtPrice}
79
- testId={productCardSlotContract.priceTestId}
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
- {...productCardSlotContract.addButton}
84
- disabled={soldOut}
85
- aria-label={hasOptions ? `Escolher opções de ${product.name}` : `Adicionar ${product.name} ao carrinho`}
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
- addItem(product)
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
  >