@fayz-ai/storefront 0.7.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.
- package/README.md +3 -1
- package/dist/components/StorefrontHeader.d.ts.map +1 -1
- package/dist/createStorefrontApp.d.ts.map +1 -1
- package/dist/index.cjs +215 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +215 -99
- package/dist/index.js.map +1 -1
- package/dist/presets.d.ts +5 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/stores/cart.store.d.ts +5 -0
- package/dist/stores/cart.store.d.ts.map +1 -1
- package/dist/testids.d.ts +1 -0
- package/dist/testids.d.ts.map +1 -1
- package/dist/theme.d.ts +18 -2
- package/dist/theme.d.ts.map +1 -1
- package/dist/workflows/checkout.d.ts.map +1 -1
- package/package.json +13 -7
- package/src/components/StorefrontHeader.tsx +35 -8
- package/src/createStorefrontApp.tsx +18 -1
- package/src/index.ts +1 -0
- package/src/presets.ts +66 -0
- package/src/stores/cart.store.ts +16 -1
- package/src/testids.ts +1 -0
- package/src/theme.ts +33 -3
- package/src/workflows/checkout.ts +4 -1
package/dist/presets.d.ts
CHANGED
|
@@ -23,11 +23,16 @@ export declare const sertaoTemplate: StorefrontTemplate;
|
|
|
23
23
|
export declare const voltTemplate: StorefrontTemplate;
|
|
24
24
|
/** atelier — classic premium retail (Flex): white, near-black, gold accent, slider hero, trust badges. */
|
|
25
25
|
export declare const atelierTemplate: StorefrontTemplate;
|
|
26
|
+
/** food — warm appetizing delivery (steakhouse / burger / comfort food): dark inviting
|
|
27
|
+
* header, ember CTA, cream canvas, rounded cards, menu-first home with "mais pedidos"
|
|
28
|
+
* rail and social proof. Built for restaurant/food stores — override to go fully dark. */
|
|
29
|
+
export declare const foodTemplate: StorefrontTemplate;
|
|
26
30
|
export declare const storefrontTemplates: {
|
|
27
31
|
readonly mare: StorefrontTemplate;
|
|
28
32
|
readonly sertao: StorefrontTemplate;
|
|
29
33
|
readonly volt: StorefrontTemplate;
|
|
30
34
|
readonly atelier: StorefrontTemplate;
|
|
35
|
+
readonly food: StorefrontTemplate;
|
|
31
36
|
};
|
|
32
37
|
export type StorefrontTemplateId = keyof typeof storefrontTemplates;
|
|
33
38
|
//# sourceMappingURL=presets.d.ts.map
|
package/dist/presets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAS5C;qFACqF;AACrF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,eAAe,CAAA;IACtB,0EAA0E;IAC1E,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,KAAK,UAAU,CAAA;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,yFAAyF;AACzF,eAAO,MAAM,YAAY,EAAE,kBA0C1B,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,cAAc,EAAE,kBA2D5B,CAAA;AAED,kHAAkH;AAClH,eAAO,MAAM,YAAY,EAAE,kBAkD1B,CAAA;AAED,0GAA0G;AAC1G,eAAO,MAAM,eAAe,EAAE,kBA4D7B,CAAA;AAED,eAAO,MAAM,mBAAmB
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAS5C;qFACqF;AACrF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,eAAe,CAAA;IACtB,0EAA0E;IAC1E,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,KAAK,UAAU,CAAA;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,yFAAyF;AACzF,eAAO,MAAM,YAAY,EAAE,kBA0C1B,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,cAAc,EAAE,kBA2D5B,CAAA;AAED,kHAAkH;AAClH,eAAO,MAAM,YAAY,EAAE,kBAkD1B,CAAA;AAED,0GAA0G;AAC1G,eAAO,MAAM,eAAe,EAAE,kBA4D7B,CAAA;AAED;;2FAE2F;AAC3F,eAAO,MAAM,YAAY,EAAE,kBA4D1B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAA;AAEV,MAAM,MAAM,oBAAoB,GAAG,MAAM,OAAO,mBAAmB,CAAA"}
|
|
@@ -30,6 +30,11 @@ export interface CartState {
|
|
|
30
30
|
clear(): void;
|
|
31
31
|
openDrawer(): void;
|
|
32
32
|
closeDrawer(): void;
|
|
33
|
+
/** Drop persisted lines the active backend can no longer resolve (e.g. a cart
|
|
34
|
+
* built in an earlier mock-mode session, whose ids don't exist live). The
|
|
35
|
+
* resolver returns false only for a definitive "not found"; on error it must
|
|
36
|
+
* return true so a transient failure never discards a legit line. */
|
|
37
|
+
reconcile(resolve: (line: CartLine) => Promise<boolean>): Promise<void>;
|
|
33
38
|
}
|
|
34
39
|
export declare const useCartStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<CartState>, "persist"> & {
|
|
35
40
|
persist: {
|
|
@@ -1 +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;
|
|
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;IACnB;;;0EAGsE;IACtE,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxE;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;EAoFxB,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"}
|
package/dist/testids.d.ts
CHANGED
package/dist/testids.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testids.d.ts","sourceRoot":"","sources":["../src/testids.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG
|
|
1
|
+
{"version":3,"file":"testids.d.ts","sourceRoot":"","sources":["../src/testids.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;;8BASG,MAAM;iCACH,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAgCH,MAAM;;;;;;;;;;sCAWJ,MAAM;sCACN,MAAM,SAAS,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAiDpB,MAAM;;;;;;;;;;;;;;;;;CAkBxB,CAAA"}
|
package/dist/theme.d.ts
CHANGED
|
@@ -20,13 +20,21 @@ export interface StorefrontThemeColors {
|
|
|
20
20
|
/** Announcement bar */
|
|
21
21
|
announcementBackground?: string;
|
|
22
22
|
announcementForeground?: string;
|
|
23
|
+
/** Secondary brand accent (e.g. a gourmet gold beside the primary). Emitted
|
|
24
|
+
* as `--sf-accent` / `--sf-accent-foreground` and the `accent` tailwind token. */
|
|
25
|
+
accent?: string;
|
|
26
|
+
accentForeground?: string;
|
|
23
27
|
}
|
|
24
28
|
export interface StorefrontThemeFont {
|
|
25
29
|
/** Display font for headings, e.g. 'Marcellus' */
|
|
26
30
|
heading: string;
|
|
27
31
|
/** Body font, e.g. 'Rubik' */
|
|
28
32
|
body: string;
|
|
29
|
-
/**
|
|
33
|
+
/** Optional oversized display face for hero words (emitted as `--sf-font-display`). */
|
|
34
|
+
display?: string;
|
|
35
|
+
/** Optional serif accent for quotes/editorial (emitted as `--sf-font-serif`). */
|
|
36
|
+
serif?: string;
|
|
37
|
+
/** Google Fonts families to load (defaults to heading+body+display+serif) */
|
|
30
38
|
googleFonts?: string[];
|
|
31
39
|
/** Fallback stack — default sans-serif; use 'serif' for editorial themes */
|
|
32
40
|
fallback?: 'sans-serif' | 'serif';
|
|
@@ -43,8 +51,11 @@ export interface StorefrontTheme {
|
|
|
43
51
|
uppercaseNav?: boolean;
|
|
44
52
|
/** Show category shortcuts after primary nav links. Default true. */
|
|
45
53
|
showCategories?: boolean;
|
|
46
|
-
/** Show the storefront search
|
|
54
|
+
/** Show the storefront search in the header. Default true. */
|
|
47
55
|
showSearch?: boolean;
|
|
56
|
+
/** Header search presentation: a full input ('full', default) or a compact
|
|
57
|
+
* icon button that expands into an input on click ('icon'). */
|
|
58
|
+
searchStyle?: 'full' | 'icon';
|
|
48
59
|
};
|
|
49
60
|
productCard?: {
|
|
50
61
|
style?: CardStyle;
|
|
@@ -52,6 +63,11 @@ export interface StorefrontTheme {
|
|
|
52
63
|
};
|
|
53
64
|
/** Uppercase CTA buttons with letter-spacing (Uyuni/Flex tone) */
|
|
54
65
|
uppercaseButtons?: boolean;
|
|
66
|
+
/** Brand-token passthrough. Each entry is emitted verbatim as a `--<key>` CSS
|
|
67
|
+
* custom property, so a store can add gradients, glows, extra colors, or
|
|
68
|
+
* letter-spacing the fixed schema doesn't enumerate — without editing the SDK.
|
|
69
|
+
* e.g. { 'grad-ember': 'linear-gradient(...)', 'glow-ember': '0 0 0 1px ...' }. */
|
|
70
|
+
tokens?: Record<string, string>;
|
|
55
71
|
}
|
|
56
72
|
export declare function themeToCss(theme: StorefrontTheme): string;
|
|
57
73
|
/** Injects theme CSS variables + Google Fonts. Mount once inside the app root. */
|
package/dist/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAA;AAaxC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;AACjE,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAA;AACzE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AACvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;AAE5C,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,uBAAuB;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAA;
|
|
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;IAC/B;uFACmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,yBAAyB;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,gDAAgD;QAChD,YAAY,CAAC,EAAE,OAAO,CAAA;QACtB,qEAAqE;QACrE,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,8DAA8D;QAC9D,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB;wEACgE;QAChE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAC9B,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;IAC1B;;;wFAGoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AA2BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CA+CzD;AAgBD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,eAAe,CAAA;CAAE;;8CAiBzE"}
|
|
@@ -1 +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,
|
|
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,CAoCjE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fayz-ai/storefront",
|
|
3
|
-
"
|
|
3
|
+
"fayz": {
|
|
4
|
+
"status": "beta"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.8.0",
|
|
4
7
|
"description": "Fayz SDK storefront kit — Shopify-style customer-facing ecommerce template components",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"main": "./dist/index.cjs",
|
|
@@ -40,12 +43,12 @@
|
|
|
40
43
|
"@supabase/supabase-js": "^2.45.0",
|
|
41
44
|
"lucide-react": "^0.400.0",
|
|
42
45
|
"zustand": "^4.5.0",
|
|
43
|
-
"@fayz-ai/
|
|
44
|
-
"@fayz-ai/
|
|
45
|
-
"@fayz-ai/
|
|
46
|
-
"@fayz-ai/
|
|
47
|
-
"@fayz-ai/
|
|
48
|
-
"@fayz-ai/
|
|
46
|
+
"@fayz-ai/auth": "^0.8.0",
|
|
47
|
+
"@fayz-ai/core": "^0.8.0",
|
|
48
|
+
"@fayz-ai/plugin-auth": "^0.8.0",
|
|
49
|
+
"@fayz-ai/shop": "^0.8.0",
|
|
50
|
+
"@fayz-ai/ui": "^0.8.0",
|
|
51
|
+
"@fayz-ai/sdk": "^0.8.0"
|
|
49
52
|
},
|
|
50
53
|
"peerDependencies": {
|
|
51
54
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -64,6 +67,9 @@
|
|
|
64
67
|
"template"
|
|
65
68
|
],
|
|
66
69
|
"license": "MIT",
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public"
|
|
72
|
+
},
|
|
67
73
|
"scripts": {
|
|
68
74
|
"build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --noEmit false",
|
|
69
75
|
"dev": "tsup --watch",
|
|
@@ -99,24 +99,48 @@ function UtilityBar() {
|
|
|
99
99
|
)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function SearchInput({ className }: { className?: string }) {
|
|
102
|
+
function SearchInput({ className, iconOnly = false }: { className?: string; iconOnly?: boolean }) {
|
|
103
103
|
const config = useStorefrontConfig()
|
|
104
104
|
const search = useCatalogStore((s) => s.search)
|
|
105
105
|
const setSearch = useCatalogStore((s) => s.setSearch)
|
|
106
106
|
const path = useHashPath()
|
|
107
|
+
const [open, setOpen] = useState(false)
|
|
108
|
+
|
|
109
|
+
// Icon-only: a compact search button that expands into the input on click and
|
|
110
|
+
// collapses again on Escape / blur-when-empty.
|
|
111
|
+
if (iconOnly && !open) {
|
|
112
|
+
return (
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
aria-label="Buscar produtos"
|
|
116
|
+
data-testid={TID.headerSearchToggle}
|
|
117
|
+
onClick={() => setOpen(true)}
|
|
118
|
+
className="rounded-full p-2.5 transition-opacity hover:opacity-70"
|
|
119
|
+
>
|
|
120
|
+
<Search className="h-5 w-5" />
|
|
121
|
+
</button>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
107
124
|
|
|
108
125
|
return (
|
|
109
|
-
<div className={`relative ${className ?? ''}`}>
|
|
126
|
+
<div className={`relative ${iconOnly ? 'w-full max-w-xs' : (className ?? '')}`}>
|
|
110
127
|
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 opacity-60" />
|
|
111
128
|
<input
|
|
112
129
|
data-testid={TID.headerSearch}
|
|
113
130
|
type="search"
|
|
114
131
|
placeholder="Buscar produtos..."
|
|
115
132
|
value={search}
|
|
133
|
+
autoFocus={iconOnly}
|
|
116
134
|
onChange={(e) => {
|
|
117
135
|
setSearch(e.target.value)
|
|
118
136
|
if (path !== config.catalogPath) navigateTo(config.catalogPath)
|
|
119
137
|
}}
|
|
138
|
+
onKeyDown={(e) => {
|
|
139
|
+
if (e.key === 'Escape' && iconOnly) setOpen(false)
|
|
140
|
+
}}
|
|
141
|
+
onBlur={() => {
|
|
142
|
+
if (iconOnly && !search) setOpen(false)
|
|
143
|
+
}}
|
|
120
144
|
className="w-full border bg-white/10 py-2 pl-9 pr-4 text-sm outline-none transition-colors placeholder:opacity-60 focus:border-primary focus:bg-white focus:text-gray-900"
|
|
121
145
|
style={{
|
|
122
146
|
borderRadius: 'var(--sf-radius-input)',
|
|
@@ -277,6 +301,7 @@ export function StorefrontHeader() {
|
|
|
277
301
|
const variant = config.theme?.header?.variant ?? 'classic'
|
|
278
302
|
const scrolled = useScrolled()
|
|
279
303
|
const showSearch = config.theme?.header?.showSearch !== false
|
|
304
|
+
const iconSearch = config.theme?.header?.searchStyle === 'icon'
|
|
280
305
|
const logo = (
|
|
281
306
|
<Link to="/" className="sf-heading shrink-0 text-xl font-bold tracking-tight">
|
|
282
307
|
{config.logo ?? config.name}
|
|
@@ -295,7 +320,7 @@ export function StorefrontHeader() {
|
|
|
295
320
|
// Rio/Flex pattern: centered logo row, nav row below
|
|
296
321
|
<>
|
|
297
322
|
<div className="mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6">
|
|
298
|
-
{showSearch ? <SearchInput className="hidden w-full max-w-xs sm:block" /> : <div />}
|
|
323
|
+
{showSearch ? <SearchInput iconOnly={iconSearch} className="hidden w-full max-w-xs sm:block" /> : <div />}
|
|
299
324
|
<div className="text-center">{logo}</div>
|
|
300
325
|
<div className="justify-self-end">
|
|
301
326
|
<HeaderActions />
|
|
@@ -309,7 +334,7 @@ export function StorefrontHeader() {
|
|
|
309
334
|
// Brasília pattern: prominent search left, logo center, actions right; nav row below
|
|
310
335
|
<>
|
|
311
336
|
<div className="mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center gap-6 px-4 sm:px-6">
|
|
312
|
-
{showSearch ? <SearchInput className="hidden w-full max-w-sm sm:block" /> : <div />}
|
|
337
|
+
{showSearch ? <SearchInput iconOnly={iconSearch} className="hidden w-full max-w-sm sm:block" /> : <div />}
|
|
313
338
|
<div className="text-center">{logo}</div>
|
|
314
339
|
<div className="justify-self-end">
|
|
315
340
|
<HeaderActions />
|
|
@@ -329,17 +354,19 @@ export function StorefrontHeader() {
|
|
|
329
354
|
</div>
|
|
330
355
|
<div className="text-center">{logo}</div>
|
|
331
356
|
<div className="flex items-center justify-end gap-3">
|
|
332
|
-
{showSearch && <SearchInput className="hidden w-44 lg:block" />}
|
|
357
|
+
{showSearch && <SearchInput iconOnly={iconSearch} className="hidden w-44 lg:block" />}
|
|
333
358
|
<HeaderActions />
|
|
334
359
|
</div>
|
|
335
360
|
</div>
|
|
336
361
|
) : (
|
|
337
|
-
// classic: logo left,
|
|
362
|
+
// classic: logo left, nav, then a right-aligned search + actions cluster
|
|
338
363
|
<div className="mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 sm:px-6">
|
|
339
364
|
{logo}
|
|
340
365
|
<NavLinks className="hidden md:flex" />
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
<div className="ml-auto flex items-center gap-2 sm:gap-3">
|
|
367
|
+
{showSearch && <SearchInput iconOnly={iconSearch} className={iconSearch ? '' : 'hidden w-full max-w-sm sm:block'} />}
|
|
368
|
+
<HeaderActions />
|
|
369
|
+
</div>
|
|
343
370
|
</div>
|
|
344
371
|
)}
|
|
345
372
|
</header>
|
|
@@ -1,5 +1,5 @@
|
|
|
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'
|
|
@@ -10,6 +10,7 @@ import type { StorefrontConfig, StorefrontDiscountConfig } from './config'
|
|
|
10
10
|
import type { StorefrontComponents } from './component-contracts'
|
|
11
11
|
import type { StorefrontSection } from './sections'
|
|
12
12
|
import { useHashPath, matchPath } from './router'
|
|
13
|
+
import { useCartStore } from './stores/cart.store'
|
|
13
14
|
import { StorefrontThemeStyle } from './theme'
|
|
14
15
|
import { StorefrontHeader } from './components/StorefrontHeader'
|
|
15
16
|
import { StorefrontFooter } from './components/StorefrontFooter'
|
|
@@ -128,6 +129,22 @@ export function initStorefrontRuntime(config: StorefrontConfig): void {
|
|
|
128
129
|
setShopProvider(createMockShopProvider(buildMockSeed(config)))
|
|
129
130
|
}
|
|
130
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
|
+
|
|
131
148
|
if (config.supabaseUrl || config.supabaseAnonKey) {
|
|
132
149
|
console.warn(
|
|
133
150
|
'@fayz-ai/shop: supabaseUrl/supabaseAnonKey are legacy fields. Pass an explicit provider/adapter or use the Fayz SDK broker path.',
|
package/src/index.ts
CHANGED
package/src/presets.ts
CHANGED
|
@@ -248,11 +248,77 @@ export const atelierTemplate: StorefrontTemplate = {
|
|
|
248
248
|
}),
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/** food — warm appetizing delivery (steakhouse / burger / comfort food): dark inviting
|
|
252
|
+
* header, ember CTA, cream canvas, rounded cards, menu-first home with "mais pedidos"
|
|
253
|
+
* rail and social proof. Built for restaurant/food stores — override to go fully dark. */
|
|
254
|
+
export const foodTemplate: StorefrontTemplate = {
|
|
255
|
+
id: 'food',
|
|
256
|
+
inspiration: 'Delivery / restaurant storefront',
|
|
257
|
+
announcement: 'ENTREGA GRÁTIS ACIMA DE R$ 150 · PEÇA JÁ',
|
|
258
|
+
theme: {
|
|
259
|
+
name: 'food',
|
|
260
|
+
colors: {
|
|
261
|
+
background: '30 33% 97%',
|
|
262
|
+
foreground: '20 14% 15%',
|
|
263
|
+
primary: '14 84% 50%',
|
|
264
|
+
primaryForeground: '0 0% 100%',
|
|
265
|
+
card: '30 33% 99%',
|
|
266
|
+
cardForeground: '20 14% 15%',
|
|
267
|
+
muted: '28 20% 92%',
|
|
268
|
+
mutedForeground: '22 10% 42%',
|
|
269
|
+
border: '28 18% 85%',
|
|
270
|
+
headerBackground: '20 14% 11%',
|
|
271
|
+
headerForeground: '30 24% 96%',
|
|
272
|
+
announcementBackground: '14 84% 50%',
|
|
273
|
+
announcementForeground: '0 0% 100%',
|
|
274
|
+
},
|
|
275
|
+
font: { heading: 'Poppins', body: 'Inter' },
|
|
276
|
+
radius: 'round',
|
|
277
|
+
header: { variant: 'classic' },
|
|
278
|
+
productCard: { style: 'card', imageAspect: 'square' },
|
|
279
|
+
},
|
|
280
|
+
home: (storeName, images) => ({
|
|
281
|
+
sections: [
|
|
282
|
+
{
|
|
283
|
+
type: 'hero',
|
|
284
|
+
variant: 'banner',
|
|
285
|
+
height: 'tall',
|
|
286
|
+
slides: [
|
|
287
|
+
{ title: 'Sabor de verdade, na sua porta', subtitle: `${storeName} — feito na hora, do jeito que você ama.`, cta: 'Ver cardápio', href: '/catalog', hue: 20, image: images?.hero?.[0] },
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
type: 'benefits',
|
|
292
|
+
items: [
|
|
293
|
+
{ icon: 'Flame', title: 'Feito na hora', text: 'Preparado com capricho' },
|
|
294
|
+
{ icon: 'Truck', title: 'Entrega rápida', text: 'Frete grátis acima de R$ 150' },
|
|
295
|
+
{ icon: 'CreditCard', title: 'Pagamento fácil', text: 'Pix, cartão e mais' },
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
{ type: 'categories', style: 'tiles', title: 'Nosso cardápio' },
|
|
299
|
+
{ type: 'products', title: 'Mais pedidos', eyebrow: 'favoritos da casa', filter: 'all', limit: 8 },
|
|
300
|
+
{ type: 'banner', title: 'Combo do dia', eyebrow: 'oferta', subtitle: 'Seleção especial com desconto', cta: 'Aproveitar', hue: 14, image: images?.banners?.[0] },
|
|
301
|
+
{ type: 'products', title: 'Novidades no cardápio', filter: 'new', limit: 4 },
|
|
302
|
+
{
|
|
303
|
+
type: 'testimonials',
|
|
304
|
+
title: 'O que dizem por aí',
|
|
305
|
+
items: [
|
|
306
|
+
{ quote: 'Chegou quentinho e absurdo de bom. Virei cliente!', author: 'Rafael M.' },
|
|
307
|
+
{ quote: 'Sabor de restaurante em casa, sem complicação.', author: 'Beatriz L.' },
|
|
308
|
+
{ quote: 'Entrega rápida e porção generosa. Recomendo demais.', author: 'Diego S.' },
|
|
309
|
+
],
|
|
310
|
+
},
|
|
311
|
+
{ type: 'newsletter', title: 'Receba nossas ofertas', subtitle: 'Promoções e novidades do cardápio, direto no seu e-mail.' },
|
|
312
|
+
],
|
|
313
|
+
}),
|
|
314
|
+
}
|
|
315
|
+
|
|
251
316
|
export const storefrontTemplates = {
|
|
252
317
|
mare: mareTemplate,
|
|
253
318
|
sertao: sertaoTemplate,
|
|
254
319
|
volt: voltTemplate,
|
|
255
320
|
atelier: atelierTemplate,
|
|
321
|
+
food: foodTemplate,
|
|
256
322
|
} as const
|
|
257
323
|
|
|
258
324
|
export type StorefrontTemplateId = keyof typeof storefrontTemplates
|
package/src/stores/cart.store.ts
CHANGED
|
@@ -40,11 +40,16 @@ export interface CartState {
|
|
|
40
40
|
clear(): void
|
|
41
41
|
openDrawer(): void
|
|
42
42
|
closeDrawer(): void
|
|
43
|
+
/** Drop persisted lines the active backend can no longer resolve (e.g. a cart
|
|
44
|
+
* built in an earlier mock-mode session, whose ids don't exist live). The
|
|
45
|
+
* resolver returns false only for a definitive "not found"; on error it must
|
|
46
|
+
* return true so a transient failure never discards a legit line. */
|
|
47
|
+
reconcile(resolve: (line: CartLine) => Promise<boolean>): Promise<void>
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
export const useCartStore = create<CartState>()(
|
|
46
51
|
persist(
|
|
47
|
-
(set) => ({
|
|
52
|
+
(set, get) => ({
|
|
48
53
|
lines: [],
|
|
49
54
|
discountCode: null,
|
|
50
55
|
discountPercent: 0,
|
|
@@ -106,6 +111,16 @@ export const useCartStore = create<CartState>()(
|
|
|
106
111
|
clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
|
|
107
112
|
openDrawer: () => set({ isOpen: true }),
|
|
108
113
|
closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
|
|
114
|
+
|
|
115
|
+
reconcile: async (resolve) => {
|
|
116
|
+
const lines = get().lines
|
|
117
|
+
if (lines.length === 0) return
|
|
118
|
+
const keep = await Promise.all(
|
|
119
|
+
lines.map((line) => resolve(line).catch(() => true)),
|
|
120
|
+
)
|
|
121
|
+
const kept = lines.filter((_, i) => keep[i])
|
|
122
|
+
if (kept.length !== lines.length) set({ lines: kept })
|
|
123
|
+
},
|
|
109
124
|
}),
|
|
110
125
|
{
|
|
111
126
|
name: 'fayz.storefront.cart.v1',
|
package/src/testids.ts
CHANGED
package/src/theme.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface StorefrontThemeColors {
|
|
|
33
33
|
/** Announcement bar */
|
|
34
34
|
announcementBackground?: string
|
|
35
35
|
announcementForeground?: string
|
|
36
|
+
/** Secondary brand accent (e.g. a gourmet gold beside the primary). Emitted
|
|
37
|
+
* as `--sf-accent` / `--sf-accent-foreground` and the `accent` tailwind token. */
|
|
38
|
+
accent?: string
|
|
39
|
+
accentForeground?: string
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
export interface StorefrontThemeFont {
|
|
@@ -40,7 +44,11 @@ export interface StorefrontThemeFont {
|
|
|
40
44
|
heading: string
|
|
41
45
|
/** Body font, e.g. 'Rubik' */
|
|
42
46
|
body: string
|
|
43
|
-
/**
|
|
47
|
+
/** Optional oversized display face for hero words (emitted as `--sf-font-display`). */
|
|
48
|
+
display?: string
|
|
49
|
+
/** Optional serif accent for quotes/editorial (emitted as `--sf-font-serif`). */
|
|
50
|
+
serif?: string
|
|
51
|
+
/** Google Fonts families to load (defaults to heading+body+display+serif) */
|
|
44
52
|
googleFonts?: string[]
|
|
45
53
|
/** Fallback stack — default sans-serif; use 'serif' for editorial themes */
|
|
46
54
|
fallback?: 'sans-serif' | 'serif'
|
|
@@ -58,8 +66,11 @@ export interface StorefrontTheme {
|
|
|
58
66
|
uppercaseNav?: boolean
|
|
59
67
|
/** Show category shortcuts after primary nav links. Default true. */
|
|
60
68
|
showCategories?: boolean
|
|
61
|
-
/** Show the storefront search
|
|
69
|
+
/** Show the storefront search in the header. Default true. */
|
|
62
70
|
showSearch?: boolean
|
|
71
|
+
/** Header search presentation: a full input ('full', default) or a compact
|
|
72
|
+
* icon button that expands into an input on click ('icon'). */
|
|
73
|
+
searchStyle?: 'full' | 'icon'
|
|
63
74
|
}
|
|
64
75
|
productCard?: {
|
|
65
76
|
style?: CardStyle
|
|
@@ -67,6 +78,11 @@ export interface StorefrontTheme {
|
|
|
67
78
|
}
|
|
68
79
|
/** Uppercase CTA buttons with letter-spacing (Uyuni/Flex tone) */
|
|
69
80
|
uppercaseButtons?: boolean
|
|
81
|
+
/** Brand-token passthrough. Each entry is emitted verbatim as a `--<key>` CSS
|
|
82
|
+
* custom property, so a store can add gradients, glows, extra colors, or
|
|
83
|
+
* letter-spacing the fixed schema doesn't enumerate — without editing the SDK.
|
|
84
|
+
* e.g. { 'grad-ember': 'linear-gradient(...)', 'glow-ember': '0 0 0 1px ...' }. */
|
|
85
|
+
tokens?: Record<string, string>
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
const RADIUS_MAP: Record<StorefrontRadius, { button: string; card: string; input: string }> = {
|
|
@@ -90,6 +106,8 @@ const COLOR_VAR_MAP: Record<keyof StorefrontThemeColors, string> = {
|
|
|
90
106
|
headerForeground: '--sf-header-fg',
|
|
91
107
|
announcementBackground: '--sf-announcement-bg',
|
|
92
108
|
announcementForeground: '--sf-announcement-fg',
|
|
109
|
+
accent: '--sf-accent',
|
|
110
|
+
accentForeground: '--sf-accent-foreground',
|
|
93
111
|
}
|
|
94
112
|
|
|
95
113
|
export function themeToCss(theme: StorefrontTheme): string {
|
|
@@ -111,6 +129,14 @@ export function themeToCss(theme: StorefrontTheme): string {
|
|
|
111
129
|
const fallback = theme.font.fallback ?? 'sans-serif'
|
|
112
130
|
lines.push(`--font-family: '${theme.font.body}', ${fallback};`)
|
|
113
131
|
lines.push(`--sf-font-heading: '${theme.font.heading}', ${fallback};`)
|
|
132
|
+
if (theme.font.display) lines.push(`--sf-font-display: '${theme.font.display}', ${fallback};`)
|
|
133
|
+
if (theme.font.serif) lines.push(`--sf-font-serif: '${theme.font.serif}', serif;`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Brand-token passthrough — emit each entry as a raw CSS custom property.
|
|
137
|
+
for (const [key, value] of Object.entries(theme.tokens ?? {})) {
|
|
138
|
+
const name = key.startsWith('--') ? key : `--${key}`
|
|
139
|
+
lines.push(`${name}: ${value};`)
|
|
114
140
|
}
|
|
115
141
|
|
|
116
142
|
const radius = RADIUS_MAP[theme.radius ?? 'soft']
|
|
@@ -135,7 +161,11 @@ export function themeToCss(theme: StorefrontTheme): string {
|
|
|
135
161
|
|
|
136
162
|
function googleFontsHref(theme: StorefrontTheme): string | null {
|
|
137
163
|
if (!theme.font) return null
|
|
138
|
-
const families =
|
|
164
|
+
const families =
|
|
165
|
+
theme.font.googleFonts ??
|
|
166
|
+
[theme.font.heading, theme.font.body, theme.font.display, theme.font.serif].filter(
|
|
167
|
+
(f): f is string => Boolean(f),
|
|
168
|
+
)
|
|
139
169
|
const unique = [...new Set(families)]
|
|
140
170
|
const params = unique
|
|
141
171
|
.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, '+')}:wght@300;400;500;600;700`)
|
|
@@ -84,7 +84,10 @@ export async function placeStorefrontOrder({
|
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
if (markPaid) {
|
|
87
|
-
|
|
87
|
+
// Prefer the RPC seam — anon storefronts have no UPDATE grant on the
|
|
88
|
+
// orders table, so a direct updateOrder 401s on pool backends.
|
|
89
|
+
if (provider.confirmPayment) await provider.confirmPayment(order.id)
|
|
90
|
+
else await provider.updateOrder(order.id, { financialStatus: 'paid' })
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
return { order, customerId: customerId ?? order.customerId ?? '' }
|