@fayz-ai/storefront 0.2.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/auth.d.ts +29 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/blocks.d.ts +6 -0
  6. package/dist/blocks.d.ts.map +1 -0
  7. package/dist/components/CartDrawer.d.ts +3 -0
  8. package/dist/components/CartDrawer.d.ts.map +1 -0
  9. package/dist/components/CustomerAccountShell.d.ts +13 -0
  10. package/dist/components/CustomerAccountShell.d.ts.map +1 -0
  11. package/dist/components/FiltersPanel.d.ts +3 -0
  12. package/dist/components/FiltersPanel.d.ts.map +1 -0
  13. package/dist/components/OrderTrackingTimeline.d.ts +7 -0
  14. package/dist/components/OrderTrackingTimeline.d.ts.map +1 -0
  15. package/dist/components/Price.d.ts +11 -0
  16. package/dist/components/Price.d.ts.map +1 -0
  17. package/dist/components/ProductCard.d.ts +7 -0
  18. package/dist/components/ProductCard.d.ts.map +1 -0
  19. package/dist/components/ProductGrid.d.ts +7 -0
  20. package/dist/components/ProductGrid.d.ts.map +1 -0
  21. package/dist/components/ProductOptionSelector.d.ts +9 -0
  22. package/dist/components/ProductOptionSelector.d.ts.map +1 -0
  23. package/dist/components/ProductSpecs.d.ts +8 -0
  24. package/dist/components/ProductSpecs.d.ts.map +1 -0
  25. package/dist/components/QuantityInput.d.ts +12 -0
  26. package/dist/components/QuantityInput.d.ts.map +1 -0
  27. package/dist/components/RelatedProducts.d.ts +9 -0
  28. package/dist/components/RelatedProducts.d.ts.map +1 -0
  29. package/dist/components/SignInModal.d.ts +19 -0
  30. package/dist/components/SignInModal.d.ts.map +1 -0
  31. package/dist/components/StorefrontErrorBoundary.d.ts +20 -0
  32. package/dist/components/StorefrontErrorBoundary.d.ts.map +1 -0
  33. package/dist/components/StorefrontFooter.d.ts +3 -0
  34. package/dist/components/StorefrontFooter.d.ts.map +1 -0
  35. package/dist/components/StorefrontHeader.d.ts +3 -0
  36. package/dist/components/StorefrontHeader.d.ts.map +1 -0
  37. package/dist/components/Toaster.d.ts +3 -0
  38. package/dist/components/Toaster.d.ts.map +1 -0
  39. package/dist/components/sections/CategoryShowcase.d.ts +6 -0
  40. package/dist/components/sections/CategoryShowcase.d.ts.map +1 -0
  41. package/dist/components/sections/HeroSection.d.ts +9 -0
  42. package/dist/components/sections/HeroSection.d.ts.map +1 -0
  43. package/dist/components/sections/MiscSections.d.ts +33 -0
  44. package/dist/components/sections/MiscSections.d.ts.map +1 -0
  45. package/dist/components/sections/ProductRail.d.ts +9 -0
  46. package/dist/components/sections/ProductRail.d.ts.map +1 -0
  47. package/dist/config.d.ts +149 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/createStorefrontApp.d.ts +16 -0
  50. package/dist/createStorefrontApp.d.ts.map +1 -0
  51. package/dist/format.d.ts +3 -0
  52. package/dist/format.d.ts.map +1 -0
  53. package/dist/hooks/index.d.ts +6 -0
  54. package/dist/hooks/index.d.ts.map +1 -0
  55. package/dist/hooks/useCategories.d.ts +6 -0
  56. package/dist/hooks/useCategories.d.ts.map +1 -0
  57. package/dist/hooks/useDiscountValidator.d.ts +14 -0
  58. package/dist/hooks/useDiscountValidator.d.ts.map +1 -0
  59. package/dist/hooks/useMyOrders.d.ts +7 -0
  60. package/dist/hooks/useMyOrders.d.ts.map +1 -0
  61. package/dist/hooks/useProduct.d.ts +7 -0
  62. package/dist/hooks/useProduct.d.ts.map +1 -0
  63. package/dist/hooks/useProducts.d.ts +8 -0
  64. package/dist/hooks/useProducts.d.ts.map +1 -0
  65. package/dist/hooks/useStorefrontHead.d.ts +11 -0
  66. package/dist/hooks/useStorefrontHead.d.ts.map +1 -0
  67. package/dist/index.cjs +4186 -0
  68. package/dist/index.cjs.map +1 -0
  69. package/dist/index.d.ts +55 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +4085 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/motion.d.ts +19 -0
  74. package/dist/motion.d.ts.map +1 -0
  75. package/dist/order-status.d.ts +10 -0
  76. package/dist/order-status.d.ts.map +1 -0
  77. package/dist/pages/CatalogPage.d.ts +3 -0
  78. package/dist/pages/CatalogPage.d.ts.map +1 -0
  79. package/dist/pages/CheckoutPage.d.ts +3 -0
  80. package/dist/pages/CheckoutPage.d.ts.map +1 -0
  81. package/dist/pages/HomePage.d.ts +6 -0
  82. package/dist/pages/HomePage.d.ts.map +1 -0
  83. package/dist/pages/MyPurchasesPage.d.ts +3 -0
  84. package/dist/pages/MyPurchasesPage.d.ts.map +1 -0
  85. package/dist/pages/NotFoundPage.d.ts +4 -0
  86. package/dist/pages/NotFoundPage.d.ts.map +1 -0
  87. package/dist/pages/OrderConfirmationPage.d.ts +5 -0
  88. package/dist/pages/OrderConfirmationPage.d.ts.map +1 -0
  89. package/dist/pages/PolicyPage.d.ts +15 -0
  90. package/dist/pages/PolicyPage.d.ts.map +1 -0
  91. package/dist/pages/ProductDetailPage.d.ts +5 -0
  92. package/dist/pages/ProductDetailPage.d.ts.map +1 -0
  93. package/dist/presets.d.ts +33 -0
  94. package/dist/presets.d.ts.map +1 -0
  95. package/dist/product-options.d.ts +12 -0
  96. package/dist/product-options.d.ts.map +1 -0
  97. package/dist/router.d.ts +285 -0
  98. package/dist/router.d.ts.map +1 -0
  99. package/dist/scaffold.d.ts +14 -0
  100. package/dist/scaffold.d.ts.map +1 -0
  101. package/dist/sections.d.ts +84 -0
  102. package/dist/sections.d.ts.map +1 -0
  103. package/dist/slot-contracts.d.ts +34 -0
  104. package/dist/slot-contracts.d.ts.map +1 -0
  105. package/dist/stores/cart.store.d.ts +58 -0
  106. package/dist/stores/cart.store.d.ts.map +1 -0
  107. package/dist/stores/catalog.store.d.ts +18 -0
  108. package/dist/stores/catalog.store.d.ts.map +1 -0
  109. package/dist/stores/session.store.d.ts +23 -0
  110. package/dist/stores/session.store.d.ts.map +1 -0
  111. package/dist/stores/toast.store.d.ts +27 -0
  112. package/dist/stores/toast.store.d.ts.map +1 -0
  113. package/dist/testids.d.ts +93 -0
  114. package/dist/testids.d.ts.map +1 -0
  115. package/dist/theme.d.ts +59 -0
  116. package/dist/theme.d.ts.map +1 -0
  117. package/dist/workflows/checkout.d.ts +33 -0
  118. package/dist/workflows/checkout.d.ts.map +1 -0
  119. package/package.json +56 -0
  120. package/src/auth.ts +134 -0
  121. package/src/blocks.tsx +47 -0
  122. package/src/components/CartDrawer.tsx +250 -0
  123. package/src/components/CustomerAccountShell.tsx +102 -0
  124. package/src/components/FiltersPanel.tsx +109 -0
  125. package/src/components/OrderTrackingTimeline.tsx +135 -0
  126. package/src/components/Price.tsx +32 -0
  127. package/src/components/ProductCard.tsx +104 -0
  128. package/src/components/ProductGrid.tsx +34 -0
  129. package/src/components/ProductOptionSelector.tsx +50 -0
  130. package/src/components/ProductSpecs.tsx +75 -0
  131. package/src/components/QuantityInput.tsx +52 -0
  132. package/src/components/RelatedProducts.tsx +31 -0
  133. package/src/components/SignInModal.tsx +120 -0
  134. package/src/components/StorefrontErrorBoundary.tsx +51 -0
  135. package/src/components/StorefrontFooter.tsx +100 -0
  136. package/src/components/StorefrontHeader.tsx +342 -0
  137. package/src/components/Toaster.tsx +60 -0
  138. package/src/components/sections/CategoryShowcase.tsx +79 -0
  139. package/src/components/sections/HeroSection.tsx +177 -0
  140. package/src/components/sections/MiscSections.tsx +157 -0
  141. package/src/components/sections/ProductRail.tsx +73 -0
  142. package/src/config.ts +176 -0
  143. package/src/createStorefrontApp.tsx +171 -0
  144. package/src/format.ts +7 -0
  145. package/src/hooks/index.ts +5 -0
  146. package/src/hooks/useCategories.ts +26 -0
  147. package/src/hooks/useDiscountValidator.ts +43 -0
  148. package/src/hooks/useMyOrders.ts +41 -0
  149. package/src/hooks/useProduct.ts +36 -0
  150. package/src/hooks/useProducts.ts +39 -0
  151. package/src/hooks/useStorefrontHead.ts +25 -0
  152. package/src/index.ts +132 -0
  153. package/src/motion.tsx +94 -0
  154. package/src/order-status.ts +33 -0
  155. package/src/pages/CatalogPage.tsx +118 -0
  156. package/src/pages/CheckoutPage.tsx +578 -0
  157. package/src/pages/HomePage.tsx +12 -0
  158. package/src/pages/MyPurchasesPage.tsx +310 -0
  159. package/src/pages/NotFoundPage.tsx +25 -0
  160. package/src/pages/OrderConfirmationPage.tsx +189 -0
  161. package/src/pages/PolicyPage.tsx +47 -0
  162. package/src/pages/ProductDetailPage.tsx +184 -0
  163. package/src/presets.ts +258 -0
  164. package/src/product-options.ts +63 -0
  165. package/src/router.ts +49 -0
  166. package/src/scaffold.tsx +109 -0
  167. package/src/sections.ts +69 -0
  168. package/src/slot-contracts.ts +20 -0
  169. package/src/stores/cart.store.ts +149 -0
  170. package/src/stores/catalog.store.ts +39 -0
  171. package/src/stores/session.store.ts +28 -0
  172. package/src/stores/toast.store.ts +43 -0
  173. package/src/testids.ts +103 -0
  174. package/src/theme.ts +160 -0
  175. package/src/workflows/checkout.ts +91 -0
@@ -0,0 +1,58 @@
1
+ import type { Product } from '@fayz-ai/shop/types';
2
+ import type { ResolvedStorefrontConfig } from '../config';
3
+ import { type ProductOptionSelection } from '../product-options';
4
+ export interface CartLine {
5
+ lineId: string;
6
+ productId: string;
7
+ slug: string;
8
+ name: string;
9
+ sku: string | null;
10
+ options: ProductOptionSelection;
11
+ optionsLabel: string;
12
+ unitPrice: number;
13
+ quantity: number;
14
+ imageUrl: string | null;
15
+ /** inventoryCount at add time — clamps quantity */
16
+ maxQuantity: number;
17
+ }
18
+ export interface CartState {
19
+ lines: CartLine[];
20
+ discountCode: string | null;
21
+ discountPercent: number;
22
+ isOpen: boolean;
23
+ /** Line most recently added — lets the drawer briefly highlight it. */
24
+ justAddedLineId: string | null;
25
+ addItem(product: Product, qty?: number, options?: ProductOptionSelection): void;
26
+ setQuantity(lineId: string, qty: number): void;
27
+ removeItem(lineId: string): void;
28
+ applyDiscount(code: string, percent: number): void;
29
+ clearDiscount(): void;
30
+ clear(): void;
31
+ openDrawer(): void;
32
+ closeDrawer(): void;
33
+ }
34
+ export declare const useCartStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<CartState>, "persist"> & {
35
+ persist: {
36
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<CartState, {
37
+ lines: CartLine[];
38
+ discountCode: string | null;
39
+ discountPercent: number;
40
+ }>>) => void;
41
+ clearStorage: () => void;
42
+ rehydrate: () => Promise<void> | void;
43
+ hasHydrated: () => boolean;
44
+ onHydrate: (fn: (state: CartState) => void) => () => void;
45
+ onFinishHydration: (fn: (state: CartState) => void) => () => void;
46
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<CartState, {
47
+ lines: CartLine[];
48
+ discountCode: string | null;
49
+ discountPercent: number;
50
+ }>>;
51
+ };
52
+ }>;
53
+ export declare const selectCount: (s: Pick<CartState, "lines">) => number;
54
+ export declare const selectSubtotal: (s: Pick<CartState, "lines">) => number;
55
+ export declare const selectDiscountTotal: (s: Pick<CartState, "lines" | "discountPercent">) => number;
56
+ export declare const selectShipping: (s: Pick<CartState, "lines">, cfg: ResolvedStorefrontConfig) => number;
57
+ export declare const selectTotal: (s: Pick<CartState, "lines" | "discountPercent">, cfg: ResolvedStorefrontConfig) => number;
58
+ //# sourceMappingURL=cart.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cart.store.d.ts","sourceRoot":"","sources":["../../src/stores/cart.store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AACzD,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;AAE3B,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,OAAO,EAAE,sBAAsB,CAAA;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,OAAO,CAAA;IACf,uEAAuE;IACvE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAA;IAC/E,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAClD,aAAa,IAAI,IAAI,CAAA;IACrB,KAAK,IAAI,IAAI,CAAA;IACb,UAAU,IAAI,IAAI,CAAA;IAClB,WAAW,IAAI,IAAI,CAAA;CACpB;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;EA0ExB,CAAA;AAOD,eAAO,MAAM,WAAW,GAAI,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,KAAG,MACb,CAAA;AAE7C,eAAO,MAAM,cAAc,GAAI,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,KAAG,MACc,CAAA;AAE3E,eAAO,MAAM,mBAAmB,GAAI,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,iBAAiB,CAAC,KAAG,MAC3B,CAAA;AAE3D,eAAO,MAAM,cAAc,GACzB,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC3B,KAAK,wBAAwB,KAC5B,MAKF,CAAA;AAED,eAAO,MAAM,WAAW,GACtB,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,iBAAiB,CAAC,EAC/C,KAAK,wBAAwB,KAC5B,MAC8E,CAAA"}
@@ -0,0 +1,18 @@
1
+ export type CatalogSort = 'newest' | 'price-asc' | 'price-desc' | 'name';
2
+ export interface CatalogState {
3
+ search: string;
4
+ categoryId: string | null;
5
+ priceMin: number | null;
6
+ priceMax: number | null;
7
+ inStockOnly: boolean;
8
+ sort: CatalogSort;
9
+ setSearch(search: string): void;
10
+ setCategoryId(categoryId: string | null): void;
11
+ setPriceMin(priceMin: number | null): void;
12
+ setPriceMax(priceMax: number | null): void;
13
+ setInStockOnly(inStockOnly: boolean): void;
14
+ setSort(sort: CatalogSort): void;
15
+ reset(): void;
16
+ }
17
+ export declare const useCatalogStore: import("zustand").UseBoundStore<import("zustand").StoreApi<CatalogState>>;
18
+ //# sourceMappingURL=catalog.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.store.d.ts","sourceRoot":"","sources":["../../src/stores/catalog.store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,MAAM,CAAA;AAExE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,OAAO,CAAA;IACpB,IAAI,EAAE,WAAW,CAAA;IACjB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC9C,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC1C,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC1C,cAAc,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAAA;IAC1C,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IAChC,KAAK,IAAI,IAAI,CAAA;CACd;AAWD,eAAO,MAAM,eAAe,2EASzB,CAAA"}
@@ -0,0 +1,23 @@
1
+ export interface SessionState {
2
+ customerId: string | null;
3
+ email: string | null;
4
+ name: string | null;
5
+ setSession(session: {
6
+ customerId: string;
7
+ email: string;
8
+ name?: string | null;
9
+ }): void;
10
+ signOut(): void;
11
+ }
12
+ export declare const useSessionStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<SessionState>, "persist"> & {
13
+ persist: {
14
+ setOptions: (options: Partial<import("zustand/middleware").PersistOptions<SessionState, SessionState>>) => void;
15
+ clearStorage: () => void;
16
+ rehydrate: () => Promise<void> | void;
17
+ hasHydrated: () => boolean;
18
+ onHydrate: (fn: (state: SessionState) => void) => () => void;
19
+ onFinishHydration: (fn: (state: SessionState) => void) => () => void;
20
+ getOptions: () => Partial<import("zustand/middleware").PersistOptions<SessionState, SessionState>>;
21
+ };
22
+ }>;
23
+ //# sourceMappingURL=session.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.store.d.ts","sourceRoot":"","sources":["../../src/stores/session.store.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,UAAU,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IACtF,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,eAAO,MAAM,eAAe;;;;;;;;;;EAY3B,CAAA"}
@@ -0,0 +1,27 @@
1
+ export type ToastVariant = 'success' | 'error' | 'info';
2
+ export interface Toast {
3
+ id: string;
4
+ title: string;
5
+ description?: string;
6
+ variant: ToastVariant;
7
+ durationMs: number;
8
+ }
9
+ interface ToastState {
10
+ toasts: Toast[];
11
+ push(input: {
12
+ title: string;
13
+ description?: string;
14
+ variant?: ToastVariant;
15
+ durationMs?: number;
16
+ }): string;
17
+ dismiss(id: string): void;
18
+ }
19
+ export declare const useToastStore: import("zustand").UseBoundStore<import("zustand").StoreApi<ToastState>>;
20
+ /** Imperative toast API — callable from components, store actions, or workflows. */
21
+ export declare const toast: {
22
+ success: (title: string, description?: string) => string;
23
+ error: (title: string, description?: string) => string;
24
+ info: (title: string, description?: string) => string;
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=toast.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast.store.d.ts","sourceRoot":"","sources":["../../src/stores/toast.store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAA;AAEvD,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,YAAY,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,IAAI,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,YAAY,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;IACzG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAID,eAAO,MAAM,aAAa,yEAevB,CAAA;AAEH,oFAAoF;AACpF,eAAO,MAAM,KAAK;qBACC,MAAM,gBAAgB,MAAM;mBAC9B,MAAM,gBAAgB,MAAM;kBAC7B,MAAM,gBAAgB,MAAM;CAC3C,CAAA"}
@@ -0,0 +1,93 @@
1
+ export declare const TID: {
2
+ readonly headerSearch: "header-search";
3
+ readonly cartButton: "cart-button";
4
+ readonly cartCount: "cart-count";
5
+ readonly accountLink: "account-link";
6
+ readonly accountDropdown: "account-dropdown";
7
+ readonly announcementBar: "announcement-bar";
8
+ readonly navLink: (label: string) => string;
9
+ readonly navCategory: (slug: string) => string;
10
+ readonly accountName: "account-name";
11
+ readonly hero: "home-hero";
12
+ readonly heroPrev: "hero-prev";
13
+ readonly heroNext: "hero-next";
14
+ readonly categoryShowcase: "category-showcase";
15
+ readonly productRail: "product-rail";
16
+ readonly benefits: "benefits-row";
17
+ readonly promoBanner: "promo-banner";
18
+ readonly manifesto: "manifesto";
19
+ readonly testimonials: "testimonials";
20
+ readonly newsletter: "newsletter";
21
+ readonly newsletterEmail: "newsletter-email";
22
+ readonly newsletterSubmit: "newsletter-submit";
23
+ readonly newsletterDone: "newsletter-done";
24
+ readonly productGrid: "product-grid";
25
+ readonly productCard: "product-card";
26
+ readonly productCardName: "product-card-name";
27
+ readonly productCardPrice: "product-card-price";
28
+ readonly productCardAdd: "product-card-add";
29
+ readonly badgeSale: "badge-sale";
30
+ readonly badgeSoldout: "badge-soldout";
31
+ readonly filterCategory: (slug: string) => string;
32
+ readonly filterPriceMin: "filter-price-min";
33
+ readonly filterPriceMax: "filter-price-max";
34
+ readonly filterInstock: "filter-instock";
35
+ readonly sortSelect: "sort-select";
36
+ readonly filtersClear: "filters-clear";
37
+ readonly pdpName: "pdp-name";
38
+ readonly pdpPrice: "pdp-price";
39
+ readonly pdpComparePrice: "pdp-compare-price";
40
+ readonly pdpDescription: "pdp-description";
41
+ readonly productOptionGroup: (id: string) => string;
42
+ readonly productOptionValue: (id: string, value: string) => string;
43
+ readonly pdpQtyInput: "pdp-qty-input";
44
+ readonly pdpAddToCart: "pdp-add-to-cart";
45
+ readonly cartDrawer: "cart-drawer";
46
+ readonly cartLine: "cart-line";
47
+ readonly lineQty: "line-qty";
48
+ readonly lineInc: "line-inc";
49
+ readonly lineDec: "line-dec";
50
+ readonly lineRemove: "line-remove";
51
+ readonly cartSubtotal: "cart-subtotal";
52
+ readonly discountInput: "discount-input";
53
+ readonly discountApply: "discount-apply";
54
+ readonly discountRow: "discount-row";
55
+ readonly discountError: "discount-error";
56
+ readonly cartShipping: "cart-shipping";
57
+ readonly shippingProgress: "shipping-progress";
58
+ readonly cartTotal: "cart-total";
59
+ readonly goCheckout: "go-checkout";
60
+ readonly cartEmpty: "cart-empty";
61
+ readonly checkoutEmail: "checkout-email";
62
+ readonly checkoutName: "checkout-name";
63
+ readonly checkoutStreet: "checkout-street";
64
+ readonly checkoutCity: "checkout-city";
65
+ readonly checkoutZip: "checkout-zip";
66
+ readonly checkoutCard: "checkout-card";
67
+ readonly checkoutExpiry: "checkout-expiry";
68
+ readonly checkoutCvc: "checkout-cvc";
69
+ readonly placeOrder: "place-order";
70
+ readonly checkoutError: "checkout-error";
71
+ readonly checkoutProcessing: "checkout-processing";
72
+ readonly orderNumber: "order-number";
73
+ readonly orderStatus: "order-status";
74
+ readonly orderTracking: "order-tracking";
75
+ readonly orderTrackingStep: (step: string) => string;
76
+ readonly orderTotal: "order-total";
77
+ readonly viewPurchases: "view-purchases";
78
+ readonly customerAccountShell: "customer-account-shell";
79
+ readonly customerAccountMenu: "customer-account-menu";
80
+ readonly orderDetail: "order-detail";
81
+ readonly authTabSignin: "auth-tab-signin";
82
+ readonly authTabSignup: "auth-tab-signup";
83
+ readonly signinEmail: "signin-email";
84
+ readonly signinPassword: "signin-password";
85
+ readonly signinName: "signin-name";
86
+ readonly signinSubmit: "signin-submit";
87
+ readonly authError: "auth-error";
88
+ readonly purchasesList: "purchases-list";
89
+ readonly purchaseItem: "purchase-item";
90
+ readonly purchasesEmpty: "purchases-empty";
91
+ readonly signout: "signout";
92
+ };
93
+ //# sourceMappingURL=testids.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ export type StorefrontRadius = 'none' | 'soft' | 'round' | 'pill';
3
+ export type HeaderVariant = 'classic' | 'centered' | 'search' | 'minimal';
4
+ export type HeroVariant = 'banner' | 'split' | 'slider';
5
+ export type CardStyle = 'card' | 'editorial';
6
+ export interface StorefrontThemeColors {
7
+ /** HSL triplets, e.g. '40 20% 94%' — same convention as SaasTheme */
8
+ background?: string;
9
+ foreground?: string;
10
+ primary?: string;
11
+ primaryForeground?: string;
12
+ card?: string;
13
+ cardForeground?: string;
14
+ muted?: string;
15
+ mutedForeground?: string;
16
+ border?: string;
17
+ /** Header treatment — dark headers (Brasília) set these */
18
+ headerBackground?: string;
19
+ headerForeground?: string;
20
+ /** Announcement bar */
21
+ announcementBackground?: string;
22
+ announcementForeground?: string;
23
+ }
24
+ export interface StorefrontThemeFont {
25
+ /** Display font for headings, e.g. 'Marcellus' */
26
+ heading: string;
27
+ /** Body font, e.g. 'Rubik' */
28
+ body: string;
29
+ /** Google Fonts families to load (defaults to heading+body) */
30
+ googleFonts?: string[];
31
+ /** Fallback stack — default sans-serif; use 'serif' for editorial themes */
32
+ fallback?: 'sans-serif' | 'serif';
33
+ }
34
+ export interface StorefrontTheme {
35
+ name: string;
36
+ colors?: StorefrontThemeColors;
37
+ font?: StorefrontThemeFont;
38
+ radius?: StorefrontRadius;
39
+ /** Layout personality */
40
+ header?: {
41
+ variant?: HeaderVariant;
42
+ /** Uppercase nav links (premium retail tone) */
43
+ uppercaseNav?: boolean;
44
+ };
45
+ productCard?: {
46
+ style?: CardStyle;
47
+ imageAspect?: 'square' | 'portrait';
48
+ };
49
+ /** Uppercase CTA buttons with letter-spacing (Uyuni/Flex tone) */
50
+ uppercaseButtons?: boolean;
51
+ }
52
+ export declare function themeToCss(theme: StorefrontTheme): string;
53
+ /** Injects theme CSS variables + Google Fonts. Mount once inside the app root. */
54
+ export declare function StorefrontThemeStyle({ theme }: {
55
+ theme: StorefrontTheme;
56
+ }): React.ReactElement<{
57
+ 'data-sf-theme': string;
58
+ }, string | React.JSXElementConstructor<any>>;
59
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,33 @@
1
+ import type { Order } from '@fayz-ai/shop/types';
2
+ import type { ResolvedStorefrontConfig } from '../config';
3
+ import type { CartState } from '../stores/cart.store';
4
+ import type { SessionState } from '../stores/session.store';
5
+ export interface StorefrontCheckoutCustomer {
6
+ email: string;
7
+ name: string;
8
+ }
9
+ export interface StorefrontCheckoutAddress {
10
+ street?: string;
11
+ city?: string;
12
+ zip?: string;
13
+ notes?: string;
14
+ }
15
+ export interface PlaceStorefrontOrderInput {
16
+ config: ResolvedStorefrontConfig;
17
+ cart: Pick<CartState, 'lines' | 'discountCode' | 'discountPercent'>;
18
+ session?: Pick<SessionState, 'customerId' | 'email'>;
19
+ customer: StorefrontCheckoutCustomer;
20
+ address?: StorefrontCheckoutAddress;
21
+ markPaid?: boolean;
22
+ }
23
+ export interface PlaceStorefrontOrderResult {
24
+ order: Order;
25
+ customerId: string;
26
+ }
27
+ /**
28
+ * Storefront checkout primitive for custom/generated checkout screens.
29
+ * It owns the order payload math and customer-session handshake so app-owned
30
+ * routes can customize UX without copying provider/business logic.
31
+ */
32
+ export declare function placeStorefrontOrder({ config, cart, session, customer, address, markPaid, }: PlaceStorefrontOrderInput): Promise<PlaceStorefrontOrderResult>;
33
+ //# sourceMappingURL=checkout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/workflows/checkout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,wBAAwB,CAAA;IAChC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,GAAG,cAAc,GAAG,iBAAiB,CAAC,CAAA;IACnE,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,CAAA;IACpD,QAAQ,EAAE,0BAA0B,CAAA;IACpC,OAAO,CAAC,EAAE,yBAAyB,CAAA;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;CACnB;AAaD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,QAAgB,GACjB,EAAE,yBAAyB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAiCjE"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@fayz-ai/storefront",
3
+ "version": "0.2.0",
4
+ "description": "Fayz SDK storefront kit — Shopify-style customer-facing ecommerce template components",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "source": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "dependencies": {
22
+ "@supabase/supabase-js": "^2.45.0",
23
+ "lucide-react": "^0.400.0",
24
+ "zustand": "^4.5.0",
25
+ "@fayz-ai/shop": "^0.2.0",
26
+ "@fayz-ai/core": "^0.2.0",
27
+ "@fayz-ai/auth": "^0.2.0",
28
+ "@fayz-ai/ui": "^0.2.0"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^18.0.0 || ^19.0.0",
32
+ "react-dom": "^18.0.0 || ^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.3.0",
36
+ "react": "^18.3.0",
37
+ "tsup": "^8.2.0",
38
+ "typescript": "^5.5.0"
39
+ },
40
+ "keywords": [
41
+ "fayz",
42
+ "storefront",
43
+ "ecommerce",
44
+ "template"
45
+ ],
46
+ "license": "MIT",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
52
+ "dev": "tsup --watch",
53
+ "typecheck": "tsc --noEmit",
54
+ "clean": "rm -rf dist"
55
+ }
56
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,134 @@
1
+ import type { AuthAdapter, AuthUser } from '@fayz-ai/core'
2
+ import { createMockAuthAdapter } from '@fayz-ai/auth'
3
+ import { getShopProvider } from '@fayz-ai/shop/runtime'
4
+ import { useSessionStore } from './stores/session.store'
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Customer auth — the SAME AuthAdapter contract createSaasApp uses, so the
8
+ // mock and Supabase adapters from @fayz-ai/auth serve both the saas admin and
9
+ // the storefront customer. The storefront adds one layer on top: linking the
10
+ // auth identity to a ShopCustomer record (find-or-create by email) so orders
11
+ // and purchase history attach to a customer.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export type StorefrontAuthAdapter = AuthAdapter | 'mock' | 'supabase'
15
+
16
+ let _adapter: AuthAdapter | null = null
17
+
18
+ export function resolveAuthAdapter(
19
+ configured: StorefrontAuthAdapter | undefined,
20
+ ): 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()
28
+ }
29
+
30
+ /** Called once by createStorefrontApp. Restores any persisted session. */
31
+ export function initCustomerAuth(adapter: AuthAdapter): void {
32
+ _adapter = adapter
33
+ adapter
34
+ .getSession()
35
+ .then((existing) => {
36
+ if (!existing) return
37
+ const store = useSessionStore.getState()
38
+ // Re-link only when the persisted shop session doesn't match the auth user
39
+ if (store.email !== existing.user.email) {
40
+ void linkCustomer(existing.user)
41
+ }
42
+ })
43
+ .catch(() => {})
44
+ adapter.onAuthStateChange((user) => {
45
+ if (!user) useSessionStore.getState().signOut()
46
+ })
47
+ }
48
+
49
+ export function getCustomerAuthAdapter(): AuthAdapter {
50
+ if (!_adapter) _adapter = createMockAuthAdapter()
51
+ return _adapter
52
+ }
53
+
54
+ /** Resolve (find-or-create + server-side auth link) the ShopCustomer for an email. */
55
+ async function resolveShopCustomer(email: string, name: string) {
56
+ const provider = getShopProvider()
57
+ // Preferred: provider.resolveCustomer find-or-creates AND links auth.uid
58
+ // server-side (RLS-safe). Falls back to generic CRUD for legacy providers.
59
+ if (provider.resolveCustomer) return provider.resolveCustomer({ email, name })
60
+ const matches = await provider.listCustomers({ search: email, limit: 10 })
61
+ const existing = matches.find((c) => (c.email ?? '').toLowerCase() === email)
62
+ if (existing) return existing
63
+ const parts = name.trim().split(/\s+/).filter(Boolean)
64
+ return provider.createCustomer({
65
+ firstName: parts[0] ?? email.split('@')[0] ?? 'Cliente',
66
+ lastName: parts.slice(1).join(' '),
67
+ email,
68
+ })
69
+ }
70
+
71
+ /** Find-or-create the ShopCustomer for an auth identity and persist the session. */
72
+ async function linkCustomer(user: AuthUser, fullName?: string): Promise<{ customerId: string }> {
73
+ const email = (user.email ?? '').trim().toLowerCase()
74
+ const name = fullName ?? user.fullName ?? ''
75
+ const customer = await resolveShopCustomer(email, name)
76
+ useSessionStore.getState().setSession({
77
+ customerId: customer.id,
78
+ email,
79
+ name: name || `${customer.firstName} ${customer.lastName}`.trim(),
80
+ })
81
+ return { customerId: customer.id }
82
+ }
83
+
84
+ export interface EstablishSessionOptions {
85
+ /** Provided → adapter.signIn; omitted → passwordless customer identity via signUp (mock-friendly). */
86
+ password?: string
87
+ name?: string
88
+ }
89
+
90
+ /**
91
+ * Sign a customer in through the auth adapter and link the ShopCustomer.
92
+ * Used by the account page sign-in AND by checkout (so a buyer is signed in
93
+ * for "my purchases" immediately after purchase) — one auth path, two doors.
94
+ */
95
+ export async function establishCustomerSession(
96
+ email: string,
97
+ opts?: EstablishSessionOptions,
98
+ ): Promise<{ customerId: string }> {
99
+ const normalized = email.trim().toLowerCase()
100
+ if (opts?.password) {
101
+ const adapter = getCustomerAuthAdapter()
102
+ const session = await adapter.signIn(normalized, opts.password)
103
+ return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, opts.name)
104
+ }
105
+ // Guest / passwordless checkout: do NOT create an auth account (that made
106
+ // returning guests fail with "user already registered" on Supabase). Just
107
+ // resolve the ShopCustomer and persist the local session for order history.
108
+ return linkCustomer({ id: '', email: normalized } as AuthUser, opts?.name)
109
+ }
110
+
111
+ /** Convenience wrapper — passwordless email sign-in (guest checkout path). */
112
+ export function signInByEmail(email: string, name?: string): Promise<{ customerId: string }> {
113
+ return establishCustomerSession(email, { name })
114
+ }
115
+
116
+ /** Explicit account creation (account page "Criar conta" form). */
117
+ export async function signUpCustomer(
118
+ email: string,
119
+ password: string,
120
+ name: string,
121
+ ): Promise<{ customerId: string }> {
122
+ const adapter = getCustomerAuthAdapter()
123
+ const normalized = email.trim().toLowerCase()
124
+ const session = await adapter.signUp(normalized, password, name)
125
+ return linkCustomer(session.user.email ? session.user : { ...session.user, email: normalized }, name)
126
+ }
127
+
128
+ export async function signOutCustomer(): Promise<void> {
129
+ try {
130
+ await getCustomerAuthAdapter().signOut()
131
+ } finally {
132
+ useSessionStore.getState().signOut()
133
+ }
134
+ }
package/src/blocks.tsx ADDED
@@ -0,0 +1,47 @@
1
+ import { registerBlock } from '@fayz-ai/core'
2
+ import type { BlockNode } from '@fayz-ai/core'
3
+ import type { HomeSection } from './sections'
4
+ import { HeroSection } from './components/sections/HeroSection'
5
+ import { CategoryShowcase } from './components/sections/CategoryShowcase'
6
+ import { ProductRail } from './components/sections/ProductRail'
7
+ import {
8
+ BenefitsRow,
9
+ PromoBanner,
10
+ ManifestoBlock,
11
+ Testimonials,
12
+ NewsletterBand,
13
+ } from './components/sections/MiscSections'
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Storefront blocks — the section components registered into the universal
17
+ // block registry. This replaces HomePage's hardcoded switch: a home page is now
18
+ // a block tree (`{ type, props }[]`) resolved through the registry, identical
19
+ // to how admin pages will compose. Section prop shapes are unchanged, so the
20
+ // components are registered directly.
21
+ // ---------------------------------------------------------------------------
22
+
23
+ let registered = false
24
+
25
+ export function registerStorefrontBlocks(): void {
26
+ if (registered) return
27
+ registered = true
28
+ registerBlock('hero', HeroSection, { source: 'sdk', label: 'Hero' })
29
+ registerBlock('categories', CategoryShowcase, { source: 'sdk', label: 'Categories' })
30
+ registerBlock('products', ProductRail, { source: 'sdk', label: 'Product rail' })
31
+ registerBlock('benefits', BenefitsRow, { source: 'sdk', label: 'Benefits row' })
32
+ registerBlock('banner', PromoBanner, { source: 'sdk', label: 'Promo banner' })
33
+ registerBlock('manifesto', ManifestoBlock, { source: 'sdk', label: 'Manifesto' })
34
+ registerBlock('testimonials', Testimonials, { source: 'sdk', label: 'Testimonials' })
35
+ registerBlock('newsletter', NewsletterBand, { source: 'sdk', label: 'Newsletter band' })
36
+ }
37
+
38
+ // Register on import so any consumer of the storefront package has the blocks.
39
+ registerStorefrontBlocks()
40
+
41
+ /** Convert the legacy HomeSection[] blueprint into a generic block tree. */
42
+ export function sectionsToBlocks(sections: HomeSection[]): BlockNode[] {
43
+ return sections.map((section, i) => {
44
+ const { type, ...props } = section
45
+ return { type, id: `${type}-${i}`, props: props as Record<string, unknown> }
46
+ })
47
+ }