@fayz-ai/storefront 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +3 -1
  2. package/dist/auth.d.ts +12 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/blocks.d.ts +3 -3
  5. package/dist/blocks.d.ts.map +1 -1
  6. package/dist/component-contracts.d.ts +124 -0
  7. package/dist/component-contracts.d.ts.map +1 -0
  8. package/dist/component-selectors.d.ts +61 -0
  9. package/dist/component-selectors.d.ts.map +1 -0
  10. package/dist/components/CartDrawer.d.ts.map +1 -1
  11. package/dist/components/ProductCard.d.ts +2 -5
  12. package/dist/components/ProductCard.d.ts.map +1 -1
  13. package/dist/components/ProductEnquiryForm.d.ts +8 -0
  14. package/dist/components/ProductEnquiryForm.d.ts.map +1 -0
  15. package/dist/components/ProductGrid.d.ts.map +1 -1
  16. package/dist/components/RelatedProducts.d.ts +1 -1
  17. package/dist/components/RelatedProducts.d.ts.map +1 -1
  18. package/dist/components/SmoothImage.d.ts +7 -0
  19. package/dist/components/SmoothImage.d.ts.map +1 -0
  20. package/dist/components/StorefrontFooter.d.ts.map +1 -1
  21. package/dist/components/StorefrontHeader.d.ts.map +1 -1
  22. package/dist/components/sections/CategoryShowcase.d.ts.map +1 -1
  23. package/dist/components/sections/HeroSection.d.ts.map +1 -1
  24. package/dist/components/sections/MediaCarousel.d.ts +11 -0
  25. package/dist/components/sections/MediaCarousel.d.ts.map +1 -0
  26. package/dist/components/sections/MiscSections.d.ts.map +1 -1
  27. package/dist/components/sections/ProductRail.d.ts.map +1 -1
  28. package/dist/components/sections/ProductSlider.d.ts +15 -0
  29. package/dist/components/sections/ProductSlider.d.ts.map +1 -0
  30. package/dist/config.d.ts +38 -21
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/createStorefrontApp.d.ts +12 -0
  33. package/dist/createStorefrontApp.d.ts.map +1 -1
  34. package/dist/define.d.ts +17 -0
  35. package/dist/define.d.ts.map +1 -0
  36. package/dist/hooks/index.d.ts +1 -0
  37. package/dist/hooks/index.d.ts.map +1 -1
  38. package/dist/hooks/useStorefront.d.ts +35 -0
  39. package/dist/hooks/useStorefront.d.ts.map +1 -0
  40. package/dist/index.cjs +1113 -277
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +21 -11
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1006 -188
  45. package/dist/index.js.map +1 -1
  46. package/dist/pages/CheckoutPage.d.ts.map +1 -1
  47. package/dist/pages/HomePage.d.ts +5 -2
  48. package/dist/pages/HomePage.d.ts.map +1 -1
  49. package/dist/pages/MyPurchasesPage.d.ts.map +1 -1
  50. package/dist/pages/OrderConfirmationPage.d.ts.map +1 -1
  51. package/dist/pages/ProductDetailPage.d.ts.map +1 -1
  52. package/dist/presets.d.ts +5 -0
  53. package/dist/presets.d.ts.map +1 -1
  54. package/dist/sections.d.ts +45 -2
  55. package/dist/sections.d.ts.map +1 -1
  56. package/dist/stores/cart.store.d.ts +5 -0
  57. package/dist/stores/cart.store.d.ts.map +1 -1
  58. package/dist/testids.d.ts +21 -0
  59. package/dist/testids.d.ts.map +1 -1
  60. package/dist/theme.d.ts +21 -1
  61. package/dist/theme.d.ts.map +1 -1
  62. package/dist/workflows/checkout.d.ts.map +1 -1
  63. package/package.json +10 -6
  64. package/src/auth.ts +47 -10
  65. package/src/blocks.tsx +7 -3
  66. package/src/component-contracts.ts +140 -0
  67. package/src/component-selectors.ts +31 -0
  68. package/src/components/CartDrawer.tsx +2 -1
  69. package/src/components/ProductCard.tsx +39 -23
  70. package/src/components/ProductEnquiryForm.tsx +175 -0
  71. package/src/components/ProductGrid.tsx +12 -3
  72. package/src/components/RelatedProducts.tsx +13 -4
  73. package/src/components/SmoothImage.tsx +59 -0
  74. package/src/components/StorefrontFooter.tsx +24 -14
  75. package/src/components/StorefrontHeader.tsx +74 -42
  76. package/src/components/sections/CategoryShowcase.tsx +3 -2
  77. package/src/components/sections/HeroSection.tsx +18 -2
  78. package/src/components/sections/MediaCarousel.tsx +141 -0
  79. package/src/components/sections/MiscSections.tsx +2 -1
  80. package/src/components/sections/ProductRail.tsx +11 -3
  81. package/src/components/sections/ProductSlider.tsx +119 -0
  82. package/src/config.ts +65 -24
  83. package/src/createStorefrontApp.tsx +80 -10
  84. package/src/define.ts +34 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/useStorefront.ts +58 -0
  87. package/src/index.ts +62 -11
  88. package/src/pages/CheckoutPage.tsx +2 -1
  89. package/src/pages/HomePage.tsx +6 -2
  90. package/src/pages/MyPurchasesPage.tsx +2 -1
  91. package/src/pages/OrderConfirmationPage.tsx +2 -1
  92. package/src/pages/ProductDetailPage.tsx +138 -62
  93. package/src/presets.ts +66 -0
  94. package/src/scaffold.tsx +1 -1
  95. package/src/sections.ts +49 -2
  96. package/src/stores/cart.store.ts +16 -1
  97. package/src/testids.ts +21 -0
  98. package/src/theme.ts +36 -2
  99. package/src/workflows/checkout.ts +5 -2
  100. package/dist/slot-contracts.d.ts +0 -34
  101. package/dist/slot-contracts.d.ts.map +0 -1
  102. package/src/slot-contracts.ts +0 -20
package/src/config.ts CHANGED
@@ -1,14 +1,10 @@
1
1
  import React, { createContext, useContext } from 'react'
2
- import type { Product } from '@fayz-ai/shop/types'
3
2
  import type { ShopProvider } from '@fayz-ai/shop/provider'
4
3
  import type { MockShopSeed } from '@fayz-ai/shop/mock'
5
4
  import type { StorefrontTheme } from './theme'
6
5
  import type { HomeConfig, NavLink, FooterConfig } from './sections'
7
- import type { StorefrontAuthAdapter } from './auth'
8
-
9
- export interface ProductCardSlotProps {
10
- product: Product
11
- }
6
+ import type { StorefrontAuthConfig } from './auth'
7
+ import type { StorefrontComponents } from './component-contracts'
12
8
 
13
9
  export type StorefrontRouteKind =
14
10
  | 'home'
@@ -28,6 +24,15 @@ export interface StorefrontRouteComponentProps {
28
24
  }
29
25
 
30
26
  export type StorefrontRouteChrome = 'default' | 'focused'
27
+ export type StorefrontCommerceMode = 'checkout' | 'catalog' | 'enquiry'
28
+ export type StorefrontImageLoadingMode = 'fade' | 'none'
29
+
30
+ export interface StorefrontImageLoadingConfig {
31
+ mode?: StorefrontImageLoadingMode
32
+ durationMs?: number
33
+ easing?: string
34
+ blur?: boolean
35
+ }
31
36
 
32
37
  export interface StorefrontRouteDefinition {
33
38
  /**
@@ -54,22 +59,21 @@ export interface StorefrontRouteDefinition {
54
59
  component: React.ComponentType<StorefrontRouteComponentProps>
55
60
  }
56
61
 
57
- export interface StorefrontSlots {
58
- /**
59
- * Product card renderer used by catalog grids and product rails.
60
- *
61
- * Custom renderers must preserve `productCardSlotContract` selectors so
62
- * checkout smoke tests, QA, and agents can still operate the storefront.
63
- */
64
- ProductCard?: React.ComponentType<ProductCardSlotProps>
65
- }
66
-
67
62
  export interface StorefrontDiscountConfig {
68
63
  code: string
69
64
  percent: number
70
65
  title?: string
71
66
  }
72
67
 
68
+ export interface StorefrontEnquiryConfig {
69
+ label?: string
70
+ successMessage?: string
71
+ subjectPrefix?: string
72
+ email?: string
73
+ whatsappUrl?: string
74
+ requirePhone?: boolean
75
+ }
76
+
73
77
  export interface StorefrontConfig {
74
78
  /** Store display name (header, page title) */
75
79
  name: string
@@ -81,6 +85,8 @@ export interface StorefrontConfig {
81
85
  locale?: string
82
86
  /** Visual identity: colors, fonts, radius, header/card personality */
83
87
  theme?: StorefrontTheme
88
+ /** Checkout-first default, catalog-only browsing, or product enquiry flow. */
89
+ commerceMode?: StorefrontCommerceMode
84
90
  /** Promo message above the header (e.g. 'FRETE GRÁTIS ACIMA DE R$ 300') */
85
91
  announcement?: string
86
92
  /** Home page blueprint. When set, `/` renders it and the catalog moves to /catalog. */
@@ -108,7 +114,7 @@ export interface StorefrontConfig {
108
114
  */
109
115
  legal?: { privacy?: string; terms?: string; returns?: string }
110
116
  /** Customer auth — same AuthAdapter contract as createSaasApp. Default: mock. */
111
- auth?: { adapter?: StorefrontAuthAdapter }
117
+ auth?: StorefrontAuthConfig
112
118
  /** Shipping pricing — default { flatRate: 0 } */
113
119
  shipping?: { flatRate?: number; freeAbove?: number }
114
120
  /**
@@ -117,12 +123,16 @@ export interface StorefrontConfig {
117
123
  * a real payment webhook confirms settlement.
118
124
  */
119
125
  payments?: { mode?: 'mock' | 'pix-mercadopago' }
120
- /** Feature toggles discounts default off; accounts default on */
121
- features?: { discounts?: boolean; accounts?: boolean }
122
- /** Code-level customization slots. Kept out of serialized manifests. */
123
- slots?: StorefrontSlots
126
+ /** Product enquiry behavior for catalog/enquiry stores. */
127
+ enquiry?: StorefrontEnquiryConfig
128
+ /** Feature toggles defaults depend on commerceMode. */
129
+ features?: { discounts?: boolean; accounts?: boolean; cart?: boolean; checkout?: boolean }
130
+ /** Global storefront image reveal behavior. Default is a soft fade. */
131
+ imageLoading?: StorefrontImageLoadingConfig
132
+ /** Code-level component replacements. Kept out of serialized manifests. */
133
+ components?: StorefrontComponents
124
134
  /** Code-level route overrides/custom routes. Kept out of serialized manifests. */
125
- routes?: StorefrontRouteDefinition[]
135
+ routes?: readonly StorefrontRouteDefinition[]
126
136
  }
127
137
 
128
138
  export interface ResolvedStorefrontConfig extends StorefrontConfig {
@@ -130,7 +140,11 @@ export interface ResolvedStorefrontConfig extends StorefrontConfig {
130
140
  locale: string
131
141
  shipping: { flatRate: number; freeAbove?: number }
132
142
  payments: { mode: 'mock' | 'pix-mercadopago' }
133
- features: { discounts: boolean; accounts: boolean }
143
+ commerceMode: StorefrontCommerceMode
144
+ enquiry: Required<Pick<StorefrontEnquiryConfig, 'label' | 'successMessage' | 'subjectPrefix'>> &
145
+ Omit<StorefrontEnquiryConfig, 'label' | 'successMessage' | 'subjectPrefix'>
146
+ features: { discounts: boolean; accounts: boolean; cart: boolean; checkout: boolean }
147
+ imageLoading: Required<StorefrontImageLoadingConfig>
134
148
  /** Where the full product list lives ('/' without a home page, '/catalog' with one) */
135
149
  catalogPath: string
136
150
  nav: NavLink[]
@@ -138,12 +152,23 @@ export interface ResolvedStorefrontConfig extends StorefrontConfig {
138
152
 
139
153
  export function resolveConfig(config: StorefrontConfig): ResolvedStorefrontConfig {
140
154
  const catalogPath = config.home ? '/catalog' : '/'
155
+ const commerceMode = config.commerceMode ?? 'checkout'
156
+ const checkoutEnabled = commerceMode === 'checkout'
141
157
  return {
142
158
  ...config,
143
159
  currency: config.currency ?? 'BRL',
144
160
  locale: config.locale ?? 'pt-BR',
145
161
  shipping: { flatRate: config.shipping?.flatRate ?? 0, freeAbove: config.shipping?.freeAbove },
146
162
  payments: { mode: config.payments?.mode ?? 'mock' },
163
+ commerceMode,
164
+ enquiry: {
165
+ label: config.enquiry?.label ?? 'Contact me',
166
+ successMessage: config.enquiry?.successMessage ?? 'Thanks. We received your enquiry.',
167
+ subjectPrefix: config.enquiry?.subjectPrefix ?? 'Product enquiry',
168
+ email: config.enquiry?.email,
169
+ whatsappUrl: config.enquiry?.whatsappUrl,
170
+ requirePhone: config.enquiry?.requirePhone,
171
+ },
147
172
  features: {
148
173
  // Auto-enable the coupon UI when the store actually ships discount codes
149
174
  // (via config.discounts or a seeded catalog), unless explicitly overridden —
@@ -151,7 +176,15 @@ export function resolveConfig(config: StorefrontConfig): ResolvedStorefrontConfi
151
176
  discounts:
152
177
  config.features?.discounts ??
153
178
  Boolean(config.discounts?.length || config.catalog?.discounts?.length),
154
- accounts: config.features?.accounts ?? true,
179
+ accounts: config.features?.accounts ?? checkoutEnabled,
180
+ cart: config.features?.cart ?? checkoutEnabled,
181
+ checkout: config.features?.checkout ?? checkoutEnabled,
182
+ },
183
+ imageLoading: {
184
+ mode: config.imageLoading?.mode ?? 'fade',
185
+ durationMs: config.imageLoading?.durationMs ?? 420,
186
+ easing: config.imageLoading?.easing ?? 'cubic-bezier(0.22, 1, 0.36, 1)',
187
+ blur: config.imageLoading?.blur ?? true,
155
188
  },
156
189
  catalogPath,
157
190
  nav:
@@ -174,3 +207,11 @@ export function useStorefrontConfig(): ResolvedStorefrontConfig {
174
207
  if (!ctx) throw new Error('useStorefrontConfig must be used inside createStorefrontApp')
175
208
  return ctx
176
209
  }
210
+
211
+ export function useStorefrontConfigOptional(): ResolvedStorefrontConfig | null {
212
+ return useContext(StorefrontConfigContext)
213
+ }
214
+
215
+ export function getStorefrontComponents(config: ResolvedStorefrontConfig): StorefrontComponents {
216
+ return config.components ?? {}
217
+ }
@@ -1,12 +1,16 @@
1
1
  import React from 'react'
2
- import { setShopProvider } from '@fayz-ai/shop/runtime'
2
+ import { setShopProvider, getShopProvider } from '@fayz-ai/shop/runtime'
3
3
  import { createMockShopProvider } from '@fayz-ai/shop/mock'
4
4
  import type { MockShopSeed } from '@fayz-ai/shop/mock'
5
5
  import type { Discount } from '@fayz-ai/shop/types'
6
+ import type { ShopProvider } from '@fayz-ai/shop/provider'
6
7
  import { initCustomerAuth, resolveAuthAdapter } from './auth'
7
- import { StorefrontConfigProvider, resolveConfig, useStorefrontConfig } from './config'
8
+ import { getStorefrontComponents, StorefrontConfigProvider, resolveConfig, useStorefrontConfig } from './config'
8
9
  import type { StorefrontConfig, StorefrontDiscountConfig } from './config'
10
+ import type { StorefrontComponents } from './component-contracts'
11
+ import type { StorefrontSection } from './sections'
9
12
  import { useHashPath, matchPath } from './router'
13
+ import { useCartStore } from './stores/cart.store'
10
14
  import { StorefrontThemeStyle } from './theme'
11
15
  import { StorefrontHeader } from './components/StorefrontHeader'
12
16
  import { StorefrontFooter } from './components/StorefrontFooter'
@@ -51,8 +55,8 @@ function RouteSwitch({ path }: { path: string }) {
51
55
  const order = matchPath('/order/:id', path)
52
56
  if (order?.id) return <OrderConfirmationPage orderId={order.id} />
53
57
 
54
- if (matchPath('/checkout', path)) return <CheckoutPage />
55
- if (matchPath('/account', path)) return <MyPurchasesPage />
58
+ if (matchPath('/checkout', path) && config.features.checkout) return <CheckoutPage />
59
+ if (matchPath('/account', path) && config.features.accounts) return <MyPurchasesPage />
56
60
  if (matchPath('/catalog', path)) return <CatalogPage />
57
61
  if (matchPath('/privacy', path)) return <PolicyPage kind="privacy" />
58
62
  if (matchPath('/terms', path)) return <PolicyPage kind="terms" />
@@ -112,7 +116,12 @@ function buildMockSeed(config: StorefrontConfig): MockShopSeed | undefined {
112
116
  }
113
117
 
114
118
  export function initStorefrontRuntime(config: StorefrontConfig): void {
115
- initCustomerAuth(resolveAuthAdapter(config.auth?.adapter))
119
+ if (resolveConfig(config).features.accounts) {
120
+ initCustomerAuth(resolveAuthAdapter(config.auth, {
121
+ supabaseUrl: config.supabaseUrl,
122
+ supabaseAnonKey: config.supabaseAnonKey,
123
+ }))
124
+ }
116
125
 
117
126
  if (config.provider) {
118
127
  setShopProvider(config.provider)
@@ -120,6 +129,22 @@ export function initStorefrontRuntime(config: StorefrontConfig): void {
120
129
  setShopProvider(createMockShopProvider(buildMockSeed(config)))
121
130
  }
122
131
 
132
+ // Drop any persisted cart line the active backend can't resolve (e.g. a cart
133
+ // built in an earlier mock-mode session, whose ids don't exist live) so stale
134
+ // ids never reach checkout and 400 the order. Keeps a line on any error so a
135
+ // transient failure never discards a valid item. Runs AFTER the persisted cart
136
+ // has hydrated — zustand/persist rehydrates asynchronously, so reconciling at
137
+ // init would see an empty cart and no-op.
138
+ const reconcileCart = () => {
139
+ const provider = getShopProvider()
140
+ void useCartStore.getState().reconcile(async (line) => {
141
+ const product = await provider.getProduct(line.productId)
142
+ return product != null
143
+ })
144
+ }
145
+ if (useCartStore.persist.hasHydrated()) reconcileCart()
146
+ else useCartStore.persist.onFinishHydration(reconcileCart)
147
+
123
148
  if (config.supabaseUrl || config.supabaseAnonKey) {
124
149
  console.warn(
125
150
  '@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path.',
@@ -133,16 +158,34 @@ export function StorefrontShell() {
133
158
  const config = useStorefrontConfig()
134
159
  const path = useHashPath()
135
160
  const focused = isFocusedRoute(config, path)
136
- return (
137
- <div className="min-h-screen bg-background text-foreground">
161
+ const components = getStorefrontComponents(config)
162
+ const chromeProps = { config, commerceMode: config.commerceMode }
163
+ const body = (
164
+ <>
138
165
  {config.theme && <StorefrontThemeStyle theme={config.theme} />}
139
- {!focused && <StorefrontHeader />}
166
+ {!focused &&
167
+ (components.Header ? <components.Header {...chromeProps} /> : <StorefrontHeader />)}
140
168
  <StorefrontErrorBoundary key={path}>
141
169
  <RouteSwitch path={path} />
142
170
  </StorefrontErrorBoundary>
143
- {!focused && <CartDrawer />}
144
- {!focused && <StorefrontFooter />}
171
+ {!focused && config.features.cart && <CartDrawer />}
172
+ {!focused &&
173
+ (components.Footer ? <components.Footer {...chromeProps} /> : <StorefrontFooter />)}
145
174
  <Toaster />
175
+ </>
176
+ )
177
+
178
+ if (components.Shell) {
179
+ return (
180
+ <components.Shell {...chromeProps}>
181
+ {body}
182
+ </components.Shell>
183
+ )
184
+ }
185
+
186
+ return (
187
+ <div className="min-h-screen bg-background text-foreground">
188
+ {body}
146
189
  </div>
147
190
  )
148
191
  }
@@ -169,3 +212,30 @@ export function createStorefrontApp(config: StorefrontConfig): React.ComponentTy
169
212
  StorefrontApp.displayName = 'StorefrontApp'
170
213
  return StorefrontApp
171
214
  }
215
+
216
+ export interface CreateStorefrontOptions {
217
+ config: StorefrontConfig
218
+ provider?: ShopProvider
219
+ components?: StorefrontComponents
220
+ routes?: StorefrontConfig['routes']
221
+ pages?: StorefrontConfig['routes']
222
+ sections?: readonly StorefrontSection[]
223
+ }
224
+
225
+ export function createStorefront(options: CreateStorefrontOptions): React.ComponentType {
226
+ const routes = [...(options.pages ?? []), ...(options.routes ?? [])]
227
+ const config: StorefrontConfig = {
228
+ ...options.config,
229
+ provider: options.provider ?? options.config.provider,
230
+ components: {
231
+ ...options.config.components,
232
+ ...options.components,
233
+ },
234
+ routes: routes.length > 0 ? routes : options.config.routes,
235
+ home: options.sections
236
+ ? { ...(options.config.home ?? { sections: [] }), sections: options.sections }
237
+ : options.config.home,
238
+ }
239
+
240
+ return createStorefrontApp(config)
241
+ }
package/src/define.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type {
2
+ StorefrontConfig,
3
+ StorefrontRouteDefinition,
4
+ } from './config'
5
+ import type { StorefrontComponents } from './component-contracts'
6
+ import type { StorefrontSection } from './sections'
7
+ import type { CreateStorefrontOptions } from './createStorefrontApp'
8
+
9
+ /**
10
+ * Identity helpers for generated/custom storefronts.
11
+ *
12
+ * They do not add runtime behavior; they make the intended client-app shape
13
+ * explicit and keep TypeScript inference tight across config, components, routes,
14
+ * sections and app creation options.
15
+ */
16
+ export function defineStorefrontConfig<const T extends StorefrontConfig>(config: T): T {
17
+ return config
18
+ }
19
+
20
+ export function defineStorefrontComponents<const T extends StorefrontComponents>(components: T): T {
21
+ return components
22
+ }
23
+
24
+ export function defineStorefrontRoutes<const T extends readonly StorefrontRouteDefinition[]>(routes: T): T {
25
+ return routes
26
+ }
27
+
28
+ export function defineStorefrontSections<const T extends readonly StorefrontSection[]>(sections: T): T {
29
+ return sections
30
+ }
31
+
32
+ export function defineStorefrontApp<const T extends CreateStorefrontOptions>(options: T): T {
33
+ return options
34
+ }
@@ -1,5 +1,6 @@
1
1
  export { useProducts } from './useProducts'
2
2
  export { useProduct } from './useProduct'
3
3
  export { useCategories } from './useCategories'
4
+ export { useStorefront, useCatalog, useCart, useEnquiry, useStorefrontActions } from './useStorefront'
4
5
  export { useMyOrders } from './useMyOrders'
5
6
  export { useDiscountValidator } from './useDiscountValidator'
@@ -0,0 +1,58 @@
1
+ import { getShopProvider } from '@fayz-ai/shop/runtime'
2
+ import type { CreateProductEnquiryInput } from '@fayz-ai/shop/types'
3
+ import { useStorefrontConfig } from '../config'
4
+ import { navigateTo } from '../router'
5
+ import { useCatalogStore } from '../stores/catalog.store'
6
+ import { useCartStore } from '../stores/cart.store'
7
+ import type { ProductOptionSelection } from '../product-options'
8
+ import type { Product } from '@fayz-ai/shop/types'
9
+
10
+ export function useStorefront() {
11
+ return useStorefrontConfig()
12
+ }
13
+
14
+ export function useCatalog() {
15
+ const config = useStorefrontConfig()
16
+ const catalog = useCatalogStore()
17
+ return {
18
+ ...catalog,
19
+ catalogPath: config.catalogPath,
20
+ config,
21
+ }
22
+ }
23
+
24
+ export function useCart() {
25
+ return useCartStore()
26
+ }
27
+
28
+ export function useEnquiry() {
29
+ const config = useStorefrontConfig()
30
+ const createProductEnquiry = async (input: CreateProductEnquiryInput) => {
31
+ const provider = getShopProvider()
32
+ if (!provider.createProductEnquiry) {
33
+ throw new Error('The active shop provider does not support product enquiries.')
34
+ }
35
+ return provider.createProductEnquiry(input)
36
+ }
37
+
38
+ return {
39
+ enquiry: config.enquiry,
40
+ createProductEnquiry,
41
+ }
42
+ }
43
+
44
+ export function useStorefrontActions() {
45
+ const addItem = useCartStore((state) => state.addItem)
46
+ const openDrawer = useCartStore((state) => state.openDrawer)
47
+ const closeDrawer = useCartStore((state) => state.closeDrawer)
48
+ const { createProductEnquiry } = useEnquiry()
49
+
50
+ return {
51
+ navigateTo,
52
+ addToCart: (product: Product, quantity?: number, options?: ProductOptionSelection) =>
53
+ addItem(product, quantity, options),
54
+ openCart: openDrawer,
55
+ closeCart: closeDrawer,
56
+ createProductEnquiry,
57
+ }
58
+ }
package/src/index.ts CHANGED
@@ -1,15 +1,44 @@
1
1
  // Factory
2
- export { createStorefrontApp, initStorefrontRuntime, StorefrontShell } from './createStorefrontApp'
2
+ export { createStorefront, createStorefrontApp, initStorefrontRuntime, StorefrontShell } from './createStorefrontApp'
3
+ export type { CreateStorefrontOptions } from './createStorefrontApp'
4
+ export {
5
+ defineStorefrontConfig,
6
+ defineStorefrontComponents,
7
+ defineStorefrontRoutes,
8
+ defineStorefrontSections,
9
+ defineStorefrontApp,
10
+ } from './define'
3
11
  export type {
4
12
  StorefrontConfig,
5
13
  ResolvedStorefrontConfig,
6
14
  StorefrontRouteComponentProps,
7
15
  StorefrontRouteChrome,
16
+ StorefrontCommerceMode,
17
+ StorefrontImageLoadingConfig,
18
+ StorefrontImageLoadingMode,
8
19
  StorefrontRouteDefinition,
9
20
  StorefrontRouteKind,
10
21
  StorefrontRouteParams,
22
+ StorefrontEnquiryConfig,
11
23
  } from './config'
12
- export { useStorefrontConfig } from './config'
24
+ export type {
25
+ StorefrontActions,
26
+ StorefrontComponents,
27
+ ProductCardProps,
28
+ ProductDetailProps,
29
+ ProductGalleryProps,
30
+ ProductPriceProps,
31
+ ProductActionsProps,
32
+ ProductEnquiryFormContractProps,
33
+ CategoryCardProps,
34
+ CollectionSectionProps,
35
+ HeroSectionContractProps,
36
+ MediaCarouselProps as MediaCarouselContractProps,
37
+ ProductSliderProps as ProductSliderContractProps,
38
+ StorefrontChromeProps,
39
+ StorefrontStateProps,
40
+ } from './component-contracts'
41
+ export { useStorefrontConfig, useStorefrontConfigOptional } from './config'
13
42
 
14
43
  // Manifest path (renderApp(defineStorefront(config)) / storefront scaffold)
15
44
  export { defineStorefront, StorefrontScaffold } from './scaffold'
@@ -41,17 +70,31 @@ export {
41
70
  getCustomerAuthAdapter,
42
71
  resolveAuthAdapter,
43
72
  } from './auth'
44
- export type { StorefrontAuthAdapter, EstablishSessionOptions } from './auth'
73
+ export type { StorefrontAuthAdapter, StorefrontAuthConfig, EstablishSessionOptions } from './auth'
45
74
  export { useCatalogStore } from './stores/catalog.store'
46
75
  export type { CatalogState, CatalogSort } from './stores/catalog.store'
47
76
 
48
77
  // Hooks
49
- export { useProducts, useProduct, useCategories, useMyOrders, useDiscountValidator } from './hooks'
78
+ export {
79
+ useProducts,
80
+ useProduct,
81
+ useCategories,
82
+ useStorefront,
83
+ useCatalog,
84
+ useCart,
85
+ useEnquiry,
86
+ useStorefrontActions,
87
+ useMyOrders,
88
+ useDiscountValidator,
89
+ } from './hooks'
50
90
 
51
91
  // Components (for custom layouts)
52
92
  export { StorefrontHeader } from './components/StorefrontHeader'
93
+ export { SmoothImage } from './components/SmoothImage'
94
+ export type { SmoothImageProps } from './components/SmoothImage'
53
95
  export { ProductCard } from './components/ProductCard'
54
- export type { ProductCardProps } from './components/ProductCard'
96
+ export { ProductEnquiryForm } from './components/ProductEnquiryForm'
97
+ export type { ProductEnquiryFormProps } from './components/ProductEnquiryForm'
55
98
  export { OrderTrackingTimeline } from './components/OrderTrackingTimeline'
56
99
  export { ProductGrid } from './components/ProductGrid'
57
100
  export { FiltersPanel } from './components/FiltersPanel'
@@ -93,7 +136,7 @@ export type {
93
136
  HeroVariant,
94
137
  CardStyle,
95
138
  } from './theme'
96
- export type { HomeSection, HomeConfig, HeroSlide, NavLink, FooterConfig } from './sections'
139
+ export type { StorefrontSection, HomeSection, HomeConfig, HeroSlide, MediaCarouselItem, NavLink, FooterConfig } from './sections'
97
140
  export { bannerPlaceholder } from './sections'
98
141
  export {
99
142
  storefrontTemplates,
@@ -101,14 +144,19 @@ export {
101
144
  sertaoTemplate,
102
145
  voltTemplate,
103
146
  atelierTemplate,
147
+ foodTemplate,
104
148
  } from './presets'
105
149
  export type { StorefrontTemplate, StorefrontTemplateId } from './presets'
106
150
 
107
151
  // Home sections (for custom compositions)
108
- export { HomePage } from './pages/HomePage'
152
+ export { HomePage, StorefrontSections } from './pages/HomePage'
109
153
  export { HeroSection } from './components/sections/HeroSection'
154
+ export { MediaCarousel } from './components/sections/MediaCarousel'
155
+ export type { MediaCarouselProps } from './components/sections/MediaCarousel'
110
156
  export { CategoryShowcase } from './components/sections/CategoryShowcase'
111
157
  export { ProductRail } from './components/sections/ProductRail'
158
+ export { ProductSlider } from './components/sections/ProductSlider'
159
+ export type { ProductSliderProps } from './components/sections/ProductSlider'
112
160
  export {
113
161
  BenefitsRow,
114
162
  PromoBanner,
@@ -126,10 +174,13 @@ export type { RevealProps } from './motion'
126
174
  export { formatMoney, roundCents } from './format'
127
175
  export { TID } from './testids'
128
176
  export {
129
- productCardSlotContract,
130
- storefrontSlotContracts,
131
- } from './slot-contracts'
132
- export type { ProductCardSlotContract } from './slot-contracts'
177
+ productCardComponentContract,
178
+ storefrontComponentContracts,
179
+ } from './component-selectors'
180
+ export type {
181
+ ProductCardComponentContract,
182
+ ProductDetailComponentContract,
183
+ } from './component-selectors'
133
184
 
134
185
  // ---------------------------------------------------------------------------
135
186
  // Front-door re-exports: storefront apps depend on @fayz-ai/storefront alone.
@@ -18,6 +18,7 @@ import { useDiscountValidator } from '../hooks/useDiscountValidator'
18
18
  import { toast } from '../stores/toast.store'
19
19
  import { useStorefrontHead } from '../hooks/useStorefrontHead'
20
20
  import { SignInModal } from '../components/SignInModal'
21
+ import { SmoothImage } from '../components/SmoothImage'
21
22
 
22
23
  interface CheckoutForm {
23
24
  email: string
@@ -471,7 +472,7 @@ export function CheckoutPage() {
471
472
  {cart.lines.map((line) => (
472
473
  <li key={line.lineId ?? line.productId} className="flex gap-3 text-sm">
473
474
  <div className="relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background">
474
- {line.imageUrl && <img src={line.imageUrl} alt={line.name} className="h-full w-full object-cover" />}
475
+ {line.imageUrl && <SmoothImage src={line.imageUrl} alt={line.name} className="h-full w-full object-cover" />}
475
476
  <span className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted-foreground px-1.5 text-[10px] font-bold text-background">
476
477
  {line.quantity}
477
478
  </span>
@@ -1,12 +1,16 @@
1
1
  import React from 'react'
2
2
  import { renderBlocks } from '@fayz-ai/core'
3
- import type { HomeSection } from '../sections'
3
+ import type { StorefrontSection } from '../sections'
4
4
  import { sectionsToBlocks } from '../blocks'
5
5
 
6
6
  // The home page is now a block tree resolved through the universal block
7
7
  // registry (see ../blocks). Section components and their props are unchanged;
8
8
  // only the dispatch mechanism moved from a switch to the registry, so the same
9
9
  // composition works from a pure-data AppManifest.
10
- export function HomePage({ sections }: { sections: HomeSection[] }) {
10
+ export function StorefrontSections({ sections }: { sections: readonly StorefrontSection[] }) {
11
11
  return <main>{renderBlocks(sectionsToBlocks(sections))}</main>
12
12
  }
13
+
14
+ export function HomePage({ sections }: { sections: readonly StorefrontSection[] }) {
15
+ return <StorefrontSections sections={sections} />
16
+ }
@@ -12,6 +12,7 @@ import { CustomerAccountShell, type AccountSection } from '../components/Custome
12
12
  import { OrderTrackingTimeline } from '../components/OrderTrackingTimeline'
13
13
  import { financialStatusBadge } from '../order-status'
14
14
  import { useStorefrontHead } from '../hooks/useStorefrontHead'
15
+ import { SmoothImage } from '../components/SmoothImage'
15
16
 
16
17
  function AuthForm() {
17
18
  const [mode, setMode] = useState<'signin' | 'signup'>('signin')
@@ -155,7 +156,7 @@ function OrdersPanel() {
155
156
  <ul className="mt-3 flex flex-wrap gap-2">
156
157
  {order.items.map((item) => (
157
158
  <li key={item.id} className="flex items-center gap-2 rounded-lg border px-2 py-1 text-xs">
158
- {item.imageUrl && <img src={item.imageUrl} alt={item.name} className="h-6 w-6 rounded object-cover" />}
159
+ {item.imageUrl && <SmoothImage src={item.imageUrl} alt={item.name} className="h-6 w-6 rounded object-cover" />}
159
160
  {item.name} × {item.quantity}
160
161
  </li>
161
162
  ))}
@@ -10,6 +10,7 @@ import { CustomerAccountShell } from '../components/CustomerAccountShell'
10
10
  import { OrderTrackingTimeline } from '../components/OrderTrackingTimeline'
11
11
  import { financialStatusBadge } from '../order-status'
12
12
  import { useStorefrontHead } from '../hooks/useStorefrontHead'
13
+ import { SmoothImage } from '../components/SmoothImage'
13
14
 
14
15
  const CONFETTI_COLORS = ['hsl(var(--primary))', '#f59e0b', '#10b981', '#3b82f6', '#ec4899']
15
16
 
@@ -139,7 +140,7 @@ export function OrderConfirmationPage({ orderId }: { orderId: string }) {
139
140
  <ul className="mt-8 space-y-3 text-left">
140
141
  {order.items.map((item) => (
141
142
  <li key={item.id} className="flex items-center gap-3 text-sm">
142
- {item.imageUrl && <img src={item.imageUrl} alt={item.name} className="h-12 w-12 rounded-lg border object-cover" />}
143
+ {item.imageUrl && <SmoothImage src={item.imageUrl} alt={item.name} className="h-12 w-12 rounded-lg border object-cover" />}
143
144
  <span className="flex-1">
144
145
  {item.name} <span className="text-muted-foreground">× {item.quantity}</span>
145
146
  </span>