@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.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/auth.d.ts +29 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/blocks.d.ts +6 -0
- package/dist/blocks.d.ts.map +1 -0
- package/dist/components/CartDrawer.d.ts +3 -0
- package/dist/components/CartDrawer.d.ts.map +1 -0
- package/dist/components/CustomerAccountShell.d.ts +13 -0
- package/dist/components/CustomerAccountShell.d.ts.map +1 -0
- package/dist/components/FiltersPanel.d.ts +3 -0
- package/dist/components/FiltersPanel.d.ts.map +1 -0
- package/dist/components/OrderTrackingTimeline.d.ts +7 -0
- package/dist/components/OrderTrackingTimeline.d.ts.map +1 -0
- package/dist/components/Price.d.ts +11 -0
- package/dist/components/Price.d.ts.map +1 -0
- package/dist/components/ProductCard.d.ts +7 -0
- package/dist/components/ProductCard.d.ts.map +1 -0
- package/dist/components/ProductGrid.d.ts +7 -0
- package/dist/components/ProductGrid.d.ts.map +1 -0
- package/dist/components/ProductOptionSelector.d.ts +9 -0
- package/dist/components/ProductOptionSelector.d.ts.map +1 -0
- package/dist/components/ProductSpecs.d.ts +8 -0
- package/dist/components/ProductSpecs.d.ts.map +1 -0
- package/dist/components/QuantityInput.d.ts +12 -0
- package/dist/components/QuantityInput.d.ts.map +1 -0
- package/dist/components/RelatedProducts.d.ts +9 -0
- package/dist/components/RelatedProducts.d.ts.map +1 -0
- package/dist/components/SignInModal.d.ts +19 -0
- package/dist/components/SignInModal.d.ts.map +1 -0
- package/dist/components/StorefrontErrorBoundary.d.ts +20 -0
- package/dist/components/StorefrontErrorBoundary.d.ts.map +1 -0
- package/dist/components/StorefrontFooter.d.ts +3 -0
- package/dist/components/StorefrontFooter.d.ts.map +1 -0
- package/dist/components/StorefrontHeader.d.ts +3 -0
- package/dist/components/StorefrontHeader.d.ts.map +1 -0
- package/dist/components/Toaster.d.ts +3 -0
- package/dist/components/Toaster.d.ts.map +1 -0
- package/dist/components/sections/CategoryShowcase.d.ts +6 -0
- package/dist/components/sections/CategoryShowcase.d.ts.map +1 -0
- package/dist/components/sections/HeroSection.d.ts +9 -0
- package/dist/components/sections/HeroSection.d.ts.map +1 -0
- package/dist/components/sections/MiscSections.d.ts +33 -0
- package/dist/components/sections/MiscSections.d.ts.map +1 -0
- package/dist/components/sections/ProductRail.d.ts +9 -0
- package/dist/components/sections/ProductRail.d.ts.map +1 -0
- package/dist/config.d.ts +149 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/createStorefrontApp.d.ts +16 -0
- package/dist/createStorefrontApp.d.ts.map +1 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useCategories.d.ts +6 -0
- package/dist/hooks/useCategories.d.ts.map +1 -0
- package/dist/hooks/useDiscountValidator.d.ts +14 -0
- package/dist/hooks/useDiscountValidator.d.ts.map +1 -0
- package/dist/hooks/useMyOrders.d.ts +7 -0
- package/dist/hooks/useMyOrders.d.ts.map +1 -0
- package/dist/hooks/useProduct.d.ts +7 -0
- package/dist/hooks/useProduct.d.ts.map +1 -0
- package/dist/hooks/useProducts.d.ts +8 -0
- package/dist/hooks/useProducts.d.ts.map +1 -0
- package/dist/hooks/useStorefrontHead.d.ts +11 -0
- package/dist/hooks/useStorefrontHead.d.ts.map +1 -0
- package/dist/index.cjs +4186 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4085 -0
- package/dist/index.js.map +1 -0
- package/dist/motion.d.ts +19 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/order-status.d.ts +10 -0
- package/dist/order-status.d.ts.map +1 -0
- package/dist/pages/CatalogPage.d.ts +3 -0
- package/dist/pages/CatalogPage.d.ts.map +1 -0
- package/dist/pages/CheckoutPage.d.ts +3 -0
- package/dist/pages/CheckoutPage.d.ts.map +1 -0
- package/dist/pages/HomePage.d.ts +6 -0
- package/dist/pages/HomePage.d.ts.map +1 -0
- package/dist/pages/MyPurchasesPage.d.ts +3 -0
- package/dist/pages/MyPurchasesPage.d.ts.map +1 -0
- package/dist/pages/NotFoundPage.d.ts +4 -0
- package/dist/pages/NotFoundPage.d.ts.map +1 -0
- package/dist/pages/OrderConfirmationPage.d.ts +5 -0
- package/dist/pages/OrderConfirmationPage.d.ts.map +1 -0
- package/dist/pages/PolicyPage.d.ts +15 -0
- package/dist/pages/PolicyPage.d.ts.map +1 -0
- package/dist/pages/ProductDetailPage.d.ts +5 -0
- package/dist/pages/ProductDetailPage.d.ts.map +1 -0
- package/dist/presets.d.ts +33 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/product-options.d.ts +12 -0
- package/dist/product-options.d.ts.map +1 -0
- package/dist/router.d.ts +285 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/scaffold.d.ts +14 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/sections.d.ts +84 -0
- package/dist/sections.d.ts.map +1 -0
- package/dist/slot-contracts.d.ts +34 -0
- package/dist/slot-contracts.d.ts.map +1 -0
- package/dist/stores/cart.store.d.ts +58 -0
- package/dist/stores/cart.store.d.ts.map +1 -0
- package/dist/stores/catalog.store.d.ts +18 -0
- package/dist/stores/catalog.store.d.ts.map +1 -0
- package/dist/stores/session.store.d.ts +23 -0
- package/dist/stores/session.store.d.ts.map +1 -0
- package/dist/stores/toast.store.d.ts +27 -0
- package/dist/stores/toast.store.d.ts.map +1 -0
- package/dist/testids.d.ts +93 -0
- package/dist/testids.d.ts.map +1 -0
- package/dist/theme.d.ts +59 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/workflows/checkout.d.ts +33 -0
- package/dist/workflows/checkout.d.ts.map +1 -0
- package/package.json +56 -0
- package/src/auth.ts +134 -0
- package/src/blocks.tsx +47 -0
- package/src/components/CartDrawer.tsx +250 -0
- package/src/components/CustomerAccountShell.tsx +102 -0
- package/src/components/FiltersPanel.tsx +109 -0
- package/src/components/OrderTrackingTimeline.tsx +135 -0
- package/src/components/Price.tsx +32 -0
- package/src/components/ProductCard.tsx +104 -0
- package/src/components/ProductGrid.tsx +34 -0
- package/src/components/ProductOptionSelector.tsx +50 -0
- package/src/components/ProductSpecs.tsx +75 -0
- package/src/components/QuantityInput.tsx +52 -0
- package/src/components/RelatedProducts.tsx +31 -0
- package/src/components/SignInModal.tsx +120 -0
- package/src/components/StorefrontErrorBoundary.tsx +51 -0
- package/src/components/StorefrontFooter.tsx +100 -0
- package/src/components/StorefrontHeader.tsx +342 -0
- package/src/components/Toaster.tsx +60 -0
- package/src/components/sections/CategoryShowcase.tsx +79 -0
- package/src/components/sections/HeroSection.tsx +177 -0
- package/src/components/sections/MiscSections.tsx +157 -0
- package/src/components/sections/ProductRail.tsx +73 -0
- package/src/config.ts +176 -0
- package/src/createStorefrontApp.tsx +171 -0
- package/src/format.ts +7 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useCategories.ts +26 -0
- package/src/hooks/useDiscountValidator.ts +43 -0
- package/src/hooks/useMyOrders.ts +41 -0
- package/src/hooks/useProduct.ts +36 -0
- package/src/hooks/useProducts.ts +39 -0
- package/src/hooks/useStorefrontHead.ts +25 -0
- package/src/index.ts +132 -0
- package/src/motion.tsx +94 -0
- package/src/order-status.ts +33 -0
- package/src/pages/CatalogPage.tsx +118 -0
- package/src/pages/CheckoutPage.tsx +578 -0
- package/src/pages/HomePage.tsx +12 -0
- package/src/pages/MyPurchasesPage.tsx +310 -0
- package/src/pages/NotFoundPage.tsx +25 -0
- package/src/pages/OrderConfirmationPage.tsx +189 -0
- package/src/pages/PolicyPage.tsx +47 -0
- package/src/pages/ProductDetailPage.tsx +184 -0
- package/src/presets.ts +258 -0
- package/src/product-options.ts +63 -0
- package/src/router.ts +49 -0
- package/src/scaffold.tsx +109 -0
- package/src/sections.ts +69 -0
- package/src/slot-contracts.ts +20 -0
- package/src/stores/cart.store.ts +149 -0
- package/src/stores/catalog.store.ts +39 -0
- package/src/stores/session.store.ts +28 -0
- package/src/stores/toast.store.ts +43 -0
- package/src/testids.ts +103 -0
- package/src/theme.ts +160 -0
- 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"}
|
package/dist/theme.d.ts
ADDED
|
@@ -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
|
+
}
|