@sellhapi/mcp 0.0.0-stage → 0.1.2
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 +147 -2
- package/dist/client.d.ts +33 -0
- package/dist/client.js +99 -0
- package/dist/hosted.d.ts +25 -0
- package/dist/hosted.js +241 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +54 -0
- package/dist/resources/build-rules.d.ts +5 -0
- package/dist/resources/build-rules.js +157 -0
- package/dist/resources/capability-patterns.d.ts +15 -0
- package/dist/resources/capability-patterns.js +366 -0
- package/dist/resources/design-direction-schema.d.ts +11 -0
- package/dist/resources/design-direction-schema.js +252 -0
- package/dist/resources/index.d.ts +9 -0
- package/dist/resources/index.js +55 -0
- package/dist/resources/runtime-hooks.d.ts +5 -0
- package/dist/resources/runtime-hooks.js +290 -0
- package/dist/resources/wireframe-schema.d.ts +8 -0
- package/dist/resources/wireframe-schema.js +220 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +27 -0
- package/dist/tools/commerce.d.ts +9 -0
- package/dist/tools/commerce.js +86 -0
- package/dist/tools/content.d.ts +8 -0
- package/dist/tools/content.js +49 -0
- package/dist/tools/intelligence.d.ts +5 -0
- package/dist/tools/intelligence.js +39 -0
- package/dist/tools/studio.d.ts +5 -0
- package/dist/tools/studio.js +342 -0
- package/dist/util.d.ts +16 -0
- package/dist/util.js +21 -0
- package/package.json +36 -4
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DesignDirection schema resource.
|
|
4
|
+
*
|
|
5
|
+
* Exposes the exact DesignDirection TypeScript interface + field documentation
|
|
6
|
+
* so AI clients can generate a valid direction JSON themselves (zero SellHapi
|
|
7
|
+
* Claude cost) and submit it via the apply_design_direction tool.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the interface in generate2.controller.ts. Keep in sync when that
|
|
10
|
+
* interface changes.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DESIGN_DIRECTION_SCHEMA = void 0;
|
|
14
|
+
exports.DESIGN_DIRECTION_SCHEMA = `
|
|
15
|
+
# SellHapi Design Direction Schema
|
|
16
|
+
|
|
17
|
+
Use this schema to generate a \`DesignDirection\` JSON object and submit it via
|
|
18
|
+
\`apply_design_direction\`. This path costs SellHapi nothing — your own LLM does
|
|
19
|
+
the work. Only fall back to \`set_design_direction\` if you cannot generate
|
|
20
|
+
structured JSON reliably.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Top-level object
|
|
25
|
+
|
|
26
|
+
\`\`\`typescript
|
|
27
|
+
{
|
|
28
|
+
businessType: string; // REQUIRED. e.g. "fashion", "bakery", "photography"
|
|
29
|
+
capabilityIds: string[]; // REQUIRED. Active capability IDs. See /api/mcp/capabilities.
|
|
30
|
+
capabilitySlots?: CapabilitySlot[]; // Optional. Richer slot-per-purpose model (see below).
|
|
31
|
+
pages: Page[]; // REQUIRED. Non-empty. One entry per storefront page.
|
|
32
|
+
visualDirection: VisualDirection; // REQUIRED. All visual tokens.
|
|
33
|
+
businessContext: BusinessContext; // REQUIRED. Business identity.
|
|
34
|
+
storefrontConfig?: StorefrontConfig; // Optional. Only for storefront-mode projects.
|
|
35
|
+
displayModes?: DisplayModes; // Optional. Product display preferences.
|
|
36
|
+
}
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Page
|
|
42
|
+
|
|
43
|
+
Each page the storefront will have. Minimum 1, typically 3–6.
|
|
44
|
+
|
|
45
|
+
\`\`\`typescript
|
|
46
|
+
{
|
|
47
|
+
slug: string; // URL-safe identifier. e.g. "home", "products", "about", "contact"
|
|
48
|
+
title: string; // Display name. e.g. "Home", "Shop", "About Us"
|
|
49
|
+
sections: string[]; // Section labels in order. e.g. ["hero", "product-grid", "testimonials", "newsletter"]
|
|
50
|
+
}
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
**Section label conventions:**
|
|
54
|
+
- Use lowercase kebab-case
|
|
55
|
+
- Should describe content purpose: "hero", "featured-products", "services-list", "booking-cta",
|
|
56
|
+
"testimonials", "faq", "team", "newsletter", "about-story", "contact-form", "events-grid"
|
|
57
|
+
- Capability-specific sections should reference the capability:
|
|
58
|
+
"appointment-booking", "donation-campaigns", "membership-tiers", "events-list"
|
|
59
|
+
|
|
60
|
+
**Common page structures:**
|
|
61
|
+
|
|
62
|
+
Physical products store:
|
|
63
|
+
\`\`\`
|
|
64
|
+
home: ["hero", "featured-products", "collections", "testimonials", "newsletter"]
|
|
65
|
+
products: ["filter-bar", "product-grid", "pagination"]
|
|
66
|
+
about: ["brand-story", "values", "team"]
|
|
67
|
+
contact: ["contact-form", "location-map"]
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
Service business:
|
|
71
|
+
\`\`\`
|
|
72
|
+
home: ["hero", "services-overview", "how-it-works", "testimonials", "booking-cta"]
|
|
73
|
+
services: ["services-list", "pricing"]
|
|
74
|
+
about: ["our-story", "team", "credentials"]
|
|
75
|
+
contact: ["contact-form", "faq"]
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## VisualDirection
|
|
81
|
+
|
|
82
|
+
All fields except the optional ones are required.
|
|
83
|
+
|
|
84
|
+
\`\`\`typescript
|
|
85
|
+
{
|
|
86
|
+
// Descriptive aesthetic — free text. e.g. "Minimal luxury", "Vibrant street market"
|
|
87
|
+
style: string;
|
|
88
|
+
|
|
89
|
+
primaryColor: string; // Main brand color — hex. e.g. "#1a1a2e"
|
|
90
|
+
accentColor: string; // CTA / highlight color — hex. e.g. "#e94560"
|
|
91
|
+
backgroundColor: string; // Page background — hex. Can be dark, cream, off-white.
|
|
92
|
+
surfaceColor: string; // Card / panel background — hex.
|
|
93
|
+
textColor: string; // Primary text color — hex.
|
|
94
|
+
|
|
95
|
+
headingFont: string; // Google Fonts name for headings. e.g. "Playfair Display", "Space Grotesk"
|
|
96
|
+
bodyFont: string; // Google Fonts name for body copy. e.g. "Inter", "DM Sans"
|
|
97
|
+
fontStyle: string; // Description. e.g. "editorial serif headings with clean sans body"
|
|
98
|
+
|
|
99
|
+
// Enum values — use exactly as written:
|
|
100
|
+
cardStyle: "flat" | "soft-shadow" | "bordered" | "glassmorphism";
|
|
101
|
+
buttonStyle: "pill" | "rounded" | "sharp" | "outline-first";
|
|
102
|
+
visualEffect: "flat" | "subtle-gradient" | "glassmorphism" | "textured";
|
|
103
|
+
|
|
104
|
+
mood: string; // e.g. "sophisticated and aspirational", "warm and approachable"
|
|
105
|
+
|
|
106
|
+
// Optional — include when you have reference site analysis or strong design intent:
|
|
107
|
+
layoutPattern?: string; // e.g. "full-bleed hero, 3-col card grid, alternating split sections"
|
|
108
|
+
spacingRhythm?: string; // e.g. "generous whitespace, section padding 80–120px"
|
|
109
|
+
typographyScale?: string; // e.g. "oversized display ~72px, 17px body, tight heading tracking"
|
|
110
|
+
componentNotes?: string; // e.g. "rounded cards with drop shadow, pill category tags"
|
|
111
|
+
distinctiveElements?: string; // e.g. "full-bleed photography, sticky glassmorphic nav"
|
|
112
|
+
}
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
**Color guidance:**
|
|
116
|
+
- All color fields must be valid hex strings (#rrggbb or #rgb)
|
|
117
|
+
- Ensure sufficient contrast: textColor vs backgroundColor ≥ 4.5:1 (WCAG AA)
|
|
118
|
+
- primaryColor and accentColor should be visually distinct
|
|
119
|
+
- surfaceColor is usually a slightly lighter/darker variant of backgroundColor
|
|
120
|
+
|
|
121
|
+
**Font guidance:**
|
|
122
|
+
- Use Google Fonts names exactly as they appear on fonts.google.com
|
|
123
|
+
- Pairing examples: "Playfair Display" + "Inter", "Space Grotesk" + "DM Sans",
|
|
124
|
+
"Syne" + "Outfit", "Cormorant Garamond" + "Jost"
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## BusinessContext
|
|
129
|
+
|
|
130
|
+
\`\`\`typescript
|
|
131
|
+
{
|
|
132
|
+
name: string; // REQUIRED. Business name. e.g. "Adire Lagos"
|
|
133
|
+
tagline: string; // One-line value prop. e.g. "Authentic Nigerian fashion for the bold"
|
|
134
|
+
targetAudience: string; // e.g. "Nigerian professionals aged 25–40 who value craftsmanship"
|
|
135
|
+
uniqueSellingPoints: string[]; // 2–4 bullets. e.g. ["Handcrafted in Lagos", "Free delivery over ₦15,000"]
|
|
136
|
+
}
|
|
137
|
+
\`\`\`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## CapabilitySlot (optional, more expressive than capabilityIds alone)
|
|
142
|
+
|
|
143
|
+
\`\`\`typescript
|
|
144
|
+
{
|
|
145
|
+
purpose: string; // Human-readable job. e.g. "Let customers book sessions"
|
|
146
|
+
capability: string; // Selected capability ID. e.g. "appointments"
|
|
147
|
+
alternatives: string[]; // Valid swaps for this slot. Always include the selected capability.
|
|
148
|
+
}
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## StorefrontConfig (optional)
|
|
154
|
+
|
|
155
|
+
\`\`\`typescript
|
|
156
|
+
{
|
|
157
|
+
storeType: "fashion" | "beauty" | "food" | "electronics" | "general";
|
|
158
|
+
vendorOverrides: string[]; // Free-text additions. e.g. ["Feature collections on homepage"]
|
|
159
|
+
}
|
|
160
|
+
\`\`\`
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## DisplayModes (optional — only for stores with physical_products)
|
|
165
|
+
|
|
166
|
+
\`\`\`typescript
|
|
167
|
+
{
|
|
168
|
+
productGrid: boolean; // Main product grid (useProducts)
|
|
169
|
+
collections: boolean; // Product collections (useProductCollections)
|
|
170
|
+
sections: boolean; // Category sections (useSections)
|
|
171
|
+
bundles: boolean; // Bundles & deals (useStorefrontBundles)
|
|
172
|
+
}
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Full example — Lagos fashion store
|
|
178
|
+
|
|
179
|
+
\`\`\`json
|
|
180
|
+
{
|
|
181
|
+
"businessType": "fashion",
|
|
182
|
+
"capabilityIds": ["physical_products", "content", "customer_accounts"],
|
|
183
|
+
"pages": [
|
|
184
|
+
{
|
|
185
|
+
"slug": "home",
|
|
186
|
+
"title": "Home",
|
|
187
|
+
"sections": ["hero", "featured-products", "collections", "brand-story", "testimonials", "newsletter"]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"slug": "shop",
|
|
191
|
+
"title": "Shop",
|
|
192
|
+
"sections": ["filter-bar", "product-grid"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"slug": "about",
|
|
196
|
+
"title": "About",
|
|
197
|
+
"sections": ["brand-story", "values", "team"]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"slug": "contact",
|
|
201
|
+
"title": "Contact",
|
|
202
|
+
"sections": ["contact-form", "faq"]
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"visualDirection": {
|
|
206
|
+
"style": "Minimal luxury",
|
|
207
|
+
"primaryColor": "#1a1a2e",
|
|
208
|
+
"accentColor": "#c9a84c",
|
|
209
|
+
"backgroundColor": "#fafaf8",
|
|
210
|
+
"surfaceColor": "#ffffff",
|
|
211
|
+
"textColor": "#1a1a2e",
|
|
212
|
+
"headingFont": "Cormorant Garamond",
|
|
213
|
+
"bodyFont": "Inter",
|
|
214
|
+
"fontStyle": "editorial serif headings with clean sans body",
|
|
215
|
+
"cardStyle": "soft-shadow",
|
|
216
|
+
"buttonStyle": "sharp",
|
|
217
|
+
"visualEffect": "flat",
|
|
218
|
+
"mood": "sophisticated and aspirational",
|
|
219
|
+
"layoutPattern": "full-bleed hero, 2-col editorial split, 3-col product grid",
|
|
220
|
+
"spacingRhythm": "generous whitespace, 100–120px section padding",
|
|
221
|
+
"typographyScale": "oversized display 72px, 16px body, tight tracking on headings"
|
|
222
|
+
},
|
|
223
|
+
"businessContext": {
|
|
224
|
+
"name": "Adire Lagos",
|
|
225
|
+
"tagline": "Authentic Nigerian fashion for the bold",
|
|
226
|
+
"targetAudience": "Nigerian professionals aged 25–40 who value craftsmanship",
|
|
227
|
+
"uniqueSellingPoints": [
|
|
228
|
+
"Handcrafted adire fabric from Abeokuta",
|
|
229
|
+
"Free delivery over ₦15,000",
|
|
230
|
+
"30-day returns"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"storefrontConfig": {
|
|
234
|
+
"storeType": "fashion",
|
|
235
|
+
"vendorOverrides": []
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Submission
|
|
243
|
+
|
|
244
|
+
Once you have generated the direction JSON and the user has reviewed it:
|
|
245
|
+
|
|
246
|
+
\`\`\`
|
|
247
|
+
apply_design_direction(projectId, direction)
|
|
248
|
+
\`\`\`
|
|
249
|
+
|
|
250
|
+
The server validates the shape, filters any unrecognised capabilityIds, and stores
|
|
251
|
+
the direction on the project. Status advances to "directing".
|
|
252
|
+
`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Resources — teach AI clients how to write valid SellHapi storefront code.
|
|
3
|
+
*
|
|
4
|
+
* These resources are automatically loaded by AI clients when they connect.
|
|
5
|
+
* They contain everything needed to generate React code that passes BuildGuard
|
|
6
|
+
* and correctly uses the @sellhapi/studio-runtime hooks.
|
|
7
|
+
*/
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare function registerResources(server: McpServer): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP Resources — teach AI clients how to write valid SellHapi storefront code.
|
|
4
|
+
*
|
|
5
|
+
* These resources are automatically loaded by AI clients when they connect.
|
|
6
|
+
* They contain everything needed to generate React code that passes BuildGuard
|
|
7
|
+
* and correctly uses the @sellhapi/studio-runtime hooks.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.registerResources = registerResources;
|
|
11
|
+
const runtime_hooks_js_1 = require("./runtime-hooks.js");
|
|
12
|
+
const build_rules_js_1 = require("./build-rules.js");
|
|
13
|
+
const capability_patterns_js_1 = require("./capability-patterns.js");
|
|
14
|
+
const design_direction_schema_js_1 = require("./design-direction-schema.js");
|
|
15
|
+
const wireframe_schema_js_1 = require("./wireframe-schema.js");
|
|
16
|
+
function registerResources(server) {
|
|
17
|
+
// ── Runtime Hooks ──────────────────────────────────────────────────────────
|
|
18
|
+
server.registerResource("runtime-hooks", "sellhapi://runtime-hooks", {
|
|
19
|
+
description: "Complete reference for all @sellhapi/studio-runtime hooks — signatures, return shapes, and usage examples. Read this BEFORE generating any storefront code.",
|
|
20
|
+
mimeType: "text/plain",
|
|
21
|
+
}, async () => ({
|
|
22
|
+
contents: [{ uri: "sellhapi://runtime-hooks", text: runtime_hooks_js_1.RUNTIME_HOOKS, mimeType: "text/plain" }],
|
|
23
|
+
}));
|
|
24
|
+
// ── Build Rules ────────────────────────────────────────────────────────────
|
|
25
|
+
server.registerResource("build-rules", "sellhapi://build-rules", {
|
|
26
|
+
description: "BuildGuard validation rules — what code patterns are allowed and forbidden. Code that violates these rules will be rejected by push_code.",
|
|
27
|
+
mimeType: "text/plain",
|
|
28
|
+
}, async () => ({
|
|
29
|
+
contents: [{ uri: "sellhapi://build-rules", text: build_rules_js_1.BUILD_RULES, mimeType: "text/plain" }],
|
|
30
|
+
}));
|
|
31
|
+
// ── Design Direction Schema ────────────────────────────────────────────────
|
|
32
|
+
server.registerResource("design-direction-schema", "sellhapi://design-direction-schema", {
|
|
33
|
+
description: "Complete DesignDirection JSON schema with field documentation, enum values, font pairing guidance, and a full example. Read this before calling apply_design_direction — generate the direction object yourself (free) instead of using set_design_direction (costs Claude API credits).",
|
|
34
|
+
mimeType: "text/plain",
|
|
35
|
+
}, async () => ({
|
|
36
|
+
contents: [{ uri: "sellhapi://design-direction-schema", text: design_direction_schema_js_1.DESIGN_DIRECTION_SCHEMA, mimeType: "text/plain" }],
|
|
37
|
+
}));
|
|
38
|
+
// ── Wireframe Schema ───────────────────────────────────────────────────────
|
|
39
|
+
server.registerResource("wireframe-schema", "sellhapi://wireframe-schema", {
|
|
40
|
+
description: "Wireframe shape, section conventions, nav/footer rules, data-hook reference table, and user-presentation format. Read this before generating wireframes. The wireframe is the user's checkpoint — present each page to the user for approval before calling apply_wireframe.",
|
|
41
|
+
mimeType: "text/plain",
|
|
42
|
+
}, async () => ({
|
|
43
|
+
contents: [{ uri: "sellhapi://wireframe-schema", text: wireframe_schema_js_1.WIREFRAME_SCHEMA, mimeType: "text/plain" }],
|
|
44
|
+
}));
|
|
45
|
+
// ── Capability Patterns ────────────────────────────────────────────────────
|
|
46
|
+
// Fetched live from sell_api on each read — auto-generated from STUDIO_PROMPT_REGISTRY.
|
|
47
|
+
// Falls back to the bundled static copy if the API is unreachable.
|
|
48
|
+
server.registerResource("capability-patterns", "sellhapi://capability-patterns", {
|
|
49
|
+
description: "Per-capability data shapes, anti-patterns, hook checklists, and UX flows. Auto-generated from the live server registry — always up to date. Covers: products, digital products, services, appointments, events, donations, memberships, content, listings, customer auth.",
|
|
50
|
+
mimeType: "text/plain",
|
|
51
|
+
}, async () => {
|
|
52
|
+
const text = await (0, capability_patterns_js_1.buildCapabilityPatterns)();
|
|
53
|
+
return { contents: [{ uri: "sellhapi://capability-patterns", text, mimeType: "text/plain" }] };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime hooks reference — extracted from @sellhapi/studio-runtime.
|
|
3
|
+
* This resource teaches AI clients the exact hook signatures and return shapes.
|
|
4
|
+
*/
|
|
5
|
+
export declare const RUNTIME_HOOKS = "\n# @sellhapi/studio-runtime \u2014 Hook Reference\n\nAll imports come from \"@sellhapi/studio-runtime\". No other network/data access is allowed.\n\n## IMPORTANT RULES\n- All prices are in kobo (divide by 100 for Naira display): product.priceKobo / 100\n- product.images is string[] \u2014 each element IS the URL. NEVER access .url on it.\n- Always include type: 'physical' | 'digital' when calling addItem()\n- If you use useCart/addItem, you MUST render <CartDrawer /> in App.tsx\n- FetchState<T> = { data: T; loading: boolean; error: string | null }\n\n---\n\n## ROUTING (required for multi-page storefronts)\n\nuseHashRouter()\n Returns: { route: string; params: Record<string, string>; navigate: (route: string) => void }\n Usage: const { route, navigate } = useHashRouter();\n Routes: \"#/\" (home), \"#/product/:id\", \"#/digital/:id\", \"#/services/:id\", etc.\n\n---\n\n## PHYSICAL PRODUCTS\n\nuseProducts(options?)\n Options: { limit?: number; categoryId?: string; collectionId?: string; sectionId?: string; sort?: \"newest\"|\"price_asc\"|\"price_desc\"; search?: string; ids?: string[] }\n Returns: FetchState<StoreProduct[]>\n StoreProduct: {\n id, name, priceKobo, imageUrl, images: string[], category,\n type: \"physical\"|\"digital\", inStock,\n originalPriceKobo: number | undefined, // strikethrough price \u2014 show when > priceKobo\n discountBadge: string | null, // pre-formatted e.g. \"20% Off\" \u2014 show as badge\n priceRange: { minKobo: number; maxKobo: number } | null, // when variants differ in price\n variants: StoreProductVariant[],\n }\n WARNING: compareAtPriceKobo does NOT exist. Use originalPriceKobo for strikethrough price.\n WARNING: images is string[] \u2014 each element IS the URL. Never access .url on it.\n WARNING: some image URLs contain spaces \u2014 always encode: u.includes(' ') ? u.split('/').map(encodeURIComponent).join('/') : u\n\nuseProduct(productId: string)\n Returns: FetchState<StoreProductDetail | null>\n StoreProductDetail: extends StoreProduct + { description, variants: StoreProductVariant[] }\n StoreProductVariant: { id, options: Array<{ field: string; value: string }>, priceKobo?: number, stock?: number | null }\n WARNING: variants have NO size/color/name fields \u2014 iterate options array for labels\n Variant label helper: v.options?.map(o => o.value).join(' / ') || 'Standard'\n\nuseCategories()\n Returns: FetchState<StoreCategory[]>\n StoreCategory: { name: string; count: number }\n\nuseProductCollections()\n Returns: FetchState<ProductCollection[]>\n ProductCollection: { id, name, coverImage: string | null }\n\nuseSections()\n Returns: FetchState<StoreSection[]>\nuseSection(sectionId: string)\n Returns: FetchState<StoreProduct[]>\n\n---\n\n## CART & CHECKOUT\n\nuseCart()\n Returns: {\n items: CartItem[]; count: number; totalKobo: number; savingsKobo: number; isEmpty: boolean;\n addItem: (item: { productId, name, priceKobo, imageUrl?, variantId?, variantLabel?, quantity?, type: \"physical\"|\"digital\" }) => void;\n removeItem: (productId, variantId?) => void;\n updateQty: (productId, quantity, variantId?) => void;\n clear: () => void;\n openCart: () => void; // MUST call after addItem \u2014 opens the cart drawer\n openCheckout: () => void;\n }\n\nCartDrawer \u2014 REQUIRED component if using cart. Import from \"@sellhapi/studio-runtime\".\n Usage: <CartDrawer /> in App.tsx root. Without it, openCart() does nothing.\n\nuseCartCheckout()\n Returns: { initiate: (customer: { name, email, phone, address? }, options?: { deliveryMethod?, shippingRateId?, discountCode?, note? }) => Promise<CommerceActionResult>; loading; error }\n CommerceActionResult: { paymentLink: string } \u2014 redirect to paymentLink\n\nuseProductCheckout()\n Returns: { initiate: (input: { productId, quantity, variantId?, customer: { name, email, phone, address? }, deliveryMethod? }) => Promise<CommerceActionResult>; loading; error }\n Use for single-product \"Buy Now\" flows only. For multi-product stores, use cart.\n\nuseShippingRates()\n Returns: FetchState<ShippingRate[]>\n ShippingRate: { id, name, amountKobo, estimatedDays }\n\nuseValidateDiscount()\n Returns: { validate: (code: string) => Promise<DiscountValidationResult>; validating; error }\n\nuseCheckoutConfig()\n Returns: FetchState<CheckoutConfig>\n CheckoutConfig: { deliveryEnabled, pickupEnabled, deliveryOnly, pickupOnly }\n\n---\n\n## DIGITAL PRODUCTS\n\nuseDigitalProducts(options?)\n Options: { limit?: number }\n Returns: FetchState<StoreProduct[]> (filtered to type=\"digital\")\n\nuseDigitalProduct(productId: string)\n Returns: FetchState<StoreProductDetail | null>\n\n---\n\n## SERVICES\n\nuseServiceListings(options?)\n Options: { limit?: number; category?: string }\n Returns: FetchState<StorefrontServiceListing[]>\n StorefrontServiceListing: { id, title, description, coverImage, category, priceKobo, availability, addOns: ServiceAddOn[], subServices: SubService[] }\n\nuseServiceListing(listingId: string)\n Returns: FetchState<StorefrontServiceListing | null>\n\nuseCheckoutService()\n Returns: { checkout: (input: { listingId, customer: { name, email, phone }, addOnIds?, subServiceIds? }) => Promise<{ paymentLink }>; loading; error }\n\nuseInitiateEngagement()\n Returns: { submit: (input: { listingId, customer: { name, email, phone }, message? }) => Promise<void>; submitting; submitted; error }\n\n---\n\n## APPOINTMENTS\n\nuseServices(options?)\n Options: { limit?: number }\n Returns: FetchState<StorefrontService[]>\n StorefrontService: { id, name, description, durationMinutes, priceKobo, imageUrl, category }\n\nuseAvailableDates(options)\n Options: { serviceId: string; month: string } (month format: \"YYYY-MM\")\n Returns: FetchState<string[]> \u2014 array of date strings \"YYYY-MM-DD\"\n\nuseAvailability(options)\n Options: { serviceId: string; from: string; to: string } (both \"YYYY-MM-DD\")\n CRITICAL: to must always be from + 1 day \u2014 NEVER set from === to, it returns no slots\n to = new Date(+new Date(from) + 86400000).toISOString().slice(0, 10)\n Returns: FetchState<AvailabilitySlot[]>\n AvailabilitySlot: { id, startTime, endTime, status: \"available\"|\"claimed\"|\"booked\"|\"blocked\" }\n FILTER: only show slots where status === \"available\" \u2014 the hook returns all statuses\n\nuseBookAppointment()\n Returns: { book: (input: { serviceId, dateTime: string, customerName, customerEmail, customerPhone }) => Promise<CommerceActionResult>; loading; error }\n dateTime: slot.startTime (ISO datetime string from useAvailability result)\n WARNING: NEVER pass slotId \u2014 pass dateTime: slot.startTime\n WARNING: NEVER use initiate() for appointments \u2014 use book(). Returns { paymentLink } \u2014 redirect with window.location.href\n\n---\n\n## EVENTS\n\nuseEvents(options?)\n Options: { limit?: number }\n Returns: FetchState<StorefrontEvent[]>\n StorefrontEvent: { id, name, description, startsAt, endsAt, location, imageUrl, ticketTypes: StorefrontTicketType[] }\n WARNING: event.date and event.endDate do NOT exist \u2014 use startsAt and endsAt (ISO strings)\n StorefrontTicketType: { id, name, priceKobo, quantity, sold, available }\n\nuseEvent(eventId: string)\n Returns: FetchState<StorefrontEvent | null>\n\nusePurchaseTickets()\n Returns: { initiate: (input: { eventId, ticketOrders: [{ ticketTypeId: string, quantity: number }], customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }\n WARNING: param is ticketOrders not tickets\n\nuseCreateRSVP()\n Returns: { submit: (input: { eventId, name, email, phone? }) => Promise<void>; loading; success; error }\n\n---\n\n## DONATIONS\n\nuseDonationCampaigns(options?)\n Options: { limit?: number; ids?: string[] }\n Returns: FetchState<StorefrontDonationCampaign[]>\n StorefrontDonationCampaign: { id, name, description, imageUrl, goalAmountKobo: number|null, raisedAmountKobo: number }\n WARNING: .raised, .raisedAmount, .goalAmount DO NOT EXIST. Use .raisedAmountKobo and .goalAmountKobo only.\n\nuseDonationCampaign(campaignId: string)\n Returns: FetchState<StorefrontDonationCampaign | null>\n\nuseDonate()\n Returns: { initiate: (input: { campaignId, amountKobo, customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }\n\n---\n\n## MEMBERSHIPS\n\nuseMemberships()\n Returns: FetchState<StorefrontMembershipTier[]>\n StorefrontMembershipTier: { id, name, description, priceKobo, billingPeriod: \"monthly\"|\"yearly\"|\"one_time\", benefits: string[]|null, imageUrl: string|null, isFeatured }\n WARNING: .price, .amount, .features, .perks DO NOT EXIST. Use .priceKobo and .benefits only.\n WARNING: billingPeriod is \"yearly\" not \"annual\".\n\nuseSubscribeMembership()\n Returns: { initiate: (input: { tierId, customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }\n\n---\n\n## CONTENT & CMS\n\nuseContentItems(options: { type: string })\n Type values: \"image\", \"video\", \"testimonial\", \"faq\", \"stat\", \"feature\", \"team_member\", \"logo_cloud\", \"pricing\"\n Returns: FetchState<ContentItem[]>\n\nConvenience aliases (all return FetchState<ContentItem[]>):\n useContentImages(), useContentVideos(), useContentTestimonials(), useContentFaqs(),\n useContentStats(), useContentFeatures(), useContentTeamMembers(), useContentLogoCloud(), useContentPricing()\n WARNING: useContentTeam() does NOT exist \u2014 use useContentTeamMembers()\n\nuseArticles(options?)\n Options: { limit?: number; category?: string; tag?: string }\n Returns: FetchState<StorefrontArticle[]>\n\nuseArticle(slug: string)\n Returns: FetchState<StorefrontArticle | null>\n\nuseContentForm(formId: string)\n Returns: { form: ContentFormSchema | null; loading; error }\n ContentFormSchema: { id, name, fields: ContentFormField[] }\n ContentFormField: { id, label, type, required, options?, placeholder? }\n\nuseSubmitForm(formId: string)\n Returns: { submit: (data: Record<string, unknown>) => Promise<void>; submitting; submitted; error }\n\nuseMailingListSubscribe()\n Returns: { subscribe: (email: string) => Promise<void>; subscribing; subscribed; error }\n\n---\n\n## LISTINGS\n\nuseListings(options?)\n Options: { listingTypeId?: string; page?: number; limit?: number; filters?: Record<string, string> }\n Returns: { listings: StorefrontListing[]; total; page; totalPages; loading; error }\n\nuseListingDetail(slugOrId: string)\n Returns: { listing: StorefrontListingDetail | null; related: StorefrontListing[]; loading; error }\n\nuseListingFilters(listingTypeId?: string)\n Returns: { fieldDefinitions; params; setParam; reset; loading; error }\n\nAction hooks (each takes listingId):\n useListingEnquiry(id) \u2014 { submit: ({ name, email?, phone?, message? }) => Promise<void>; loading; success; error }\n useListingApplication(id) \u2014 { submit: ({ name, email, phone?, coverNote?, resumeUrl? }) => Promise<void>; loading; success; error }\n useListingPayment(id) \u2014 { checkout: (customer, fieldData?) => Promise<void>; loading; error }\n useListingReserve(id) \u2014 { reserve: (data?) => Promise<void>; loading; success; alreadyReserved; error }\n useListingDownload(id) \u2014 { download: () => Promise<void>; downloadUrl; loading; error }\n\n---\n\n## CUSTOMER ACCOUNTS\n\nuseCustomerAuth()\n Returns: { customer: { id, name, email, phone, address } | null; isLoggedIn: boolean; isLoading: boolean;\n signUp: (name, email, password) => Promise<void>; signIn: (email, password) => Promise<void>; signOut: () => void; error }\n WARNING: field is isLoggedIn, NOT isAuthenticated.\n\nuseMyOrders() \u2014 { orders: MyOrder[]; loading; error }\nuseMyAppointments() \u2014 { appointments: MyAppointment[]; loading; error }\nuseMyPurchases() \u2014 { purchases: MyPurchase[]; loading; error }\nuseMyMembership() \u2014 { membership: MyMembership | null; loading; error }\nuseMyProfile() \u2014 { profile: { name, email, phone, address } | null; update: ({ name, phone, address }) => Promise<void>; updating; updateError }\n\n---\n\n## ORDER TRACKING\n\nuseOrderStatus() \u2014 { status: OrderStatusData | null; loading; error }\nuseOrderLookup() \u2014 { lookup: (orderRef, email) => Promise<OrderLookupItem[]>; items; loading; error }\n\n---\n\n## COMPONENTS (import from \"@sellhapi/studio-runtime\")\n\nCartDrawer \u2014 renders the slide-out cart drawer. REQUIRED if using useCart().\n";
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime hooks reference — extracted from @sellhapi/studio-runtime.
|
|
4
|
+
* This resource teaches AI clients the exact hook signatures and return shapes.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.RUNTIME_HOOKS = void 0;
|
|
8
|
+
exports.RUNTIME_HOOKS = `
|
|
9
|
+
# @sellhapi/studio-runtime — Hook Reference
|
|
10
|
+
|
|
11
|
+
All imports come from "@sellhapi/studio-runtime". No other network/data access is allowed.
|
|
12
|
+
|
|
13
|
+
## IMPORTANT RULES
|
|
14
|
+
- All prices are in kobo (divide by 100 for Naira display): product.priceKobo / 100
|
|
15
|
+
- product.images is string[] — each element IS the URL. NEVER access .url on it.
|
|
16
|
+
- Always include type: 'physical' | 'digital' when calling addItem()
|
|
17
|
+
- If you use useCart/addItem, you MUST render <CartDrawer /> in App.tsx
|
|
18
|
+
- FetchState<T> = { data: T; loading: boolean; error: string | null }
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ROUTING (required for multi-page storefronts)
|
|
23
|
+
|
|
24
|
+
useHashRouter()
|
|
25
|
+
Returns: { route: string; params: Record<string, string>; navigate: (route: string) => void }
|
|
26
|
+
Usage: const { route, navigate } = useHashRouter();
|
|
27
|
+
Routes: "#/" (home), "#/product/:id", "#/digital/:id", "#/services/:id", etc.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## PHYSICAL PRODUCTS
|
|
32
|
+
|
|
33
|
+
useProducts(options?)
|
|
34
|
+
Options: { limit?: number; categoryId?: string; collectionId?: string; sectionId?: string; sort?: "newest"|"price_asc"|"price_desc"; search?: string; ids?: string[] }
|
|
35
|
+
Returns: FetchState<StoreProduct[]>
|
|
36
|
+
StoreProduct: {
|
|
37
|
+
id, name, priceKobo, imageUrl, images: string[], category,
|
|
38
|
+
type: "physical"|"digital", inStock,
|
|
39
|
+
originalPriceKobo: number | undefined, // strikethrough price — show when > priceKobo
|
|
40
|
+
discountBadge: string | null, // pre-formatted e.g. "20% Off" — show as badge
|
|
41
|
+
priceRange: { minKobo: number; maxKobo: number } | null, // when variants differ in price
|
|
42
|
+
variants: StoreProductVariant[],
|
|
43
|
+
}
|
|
44
|
+
WARNING: compareAtPriceKobo does NOT exist. Use originalPriceKobo for strikethrough price.
|
|
45
|
+
WARNING: images is string[] — each element IS the URL. Never access .url on it.
|
|
46
|
+
WARNING: some image URLs contain spaces — always encode: u.includes(' ') ? u.split('/').map(encodeURIComponent).join('/') : u
|
|
47
|
+
|
|
48
|
+
useProduct(productId: string)
|
|
49
|
+
Returns: FetchState<StoreProductDetail | null>
|
|
50
|
+
StoreProductDetail: extends StoreProduct + { description, variants: StoreProductVariant[] }
|
|
51
|
+
StoreProductVariant: { id, options: Array<{ field: string; value: string }>, priceKobo?: number, stock?: number | null }
|
|
52
|
+
WARNING: variants have NO size/color/name fields — iterate options array for labels
|
|
53
|
+
Variant label helper: v.options?.map(o => o.value).join(' / ') || 'Standard'
|
|
54
|
+
|
|
55
|
+
useCategories()
|
|
56
|
+
Returns: FetchState<StoreCategory[]>
|
|
57
|
+
StoreCategory: { name: string; count: number }
|
|
58
|
+
|
|
59
|
+
useProductCollections()
|
|
60
|
+
Returns: FetchState<ProductCollection[]>
|
|
61
|
+
ProductCollection: { id, name, coverImage: string | null }
|
|
62
|
+
|
|
63
|
+
useSections()
|
|
64
|
+
Returns: FetchState<StoreSection[]>
|
|
65
|
+
useSection(sectionId: string)
|
|
66
|
+
Returns: FetchState<StoreProduct[]>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## CART & CHECKOUT
|
|
71
|
+
|
|
72
|
+
useCart()
|
|
73
|
+
Returns: {
|
|
74
|
+
items: CartItem[]; count: number; totalKobo: number; savingsKobo: number; isEmpty: boolean;
|
|
75
|
+
addItem: (item: { productId, name, priceKobo, imageUrl?, variantId?, variantLabel?, quantity?, type: "physical"|"digital" }) => void;
|
|
76
|
+
removeItem: (productId, variantId?) => void;
|
|
77
|
+
updateQty: (productId, quantity, variantId?) => void;
|
|
78
|
+
clear: () => void;
|
|
79
|
+
openCart: () => void; // MUST call after addItem — opens the cart drawer
|
|
80
|
+
openCheckout: () => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
CartDrawer — REQUIRED component if using cart. Import from "@sellhapi/studio-runtime".
|
|
84
|
+
Usage: <CartDrawer /> in App.tsx root. Without it, openCart() does nothing.
|
|
85
|
+
|
|
86
|
+
useCartCheckout()
|
|
87
|
+
Returns: { initiate: (customer: { name, email, phone, address? }, options?: { deliveryMethod?, shippingRateId?, discountCode?, note? }) => Promise<CommerceActionResult>; loading; error }
|
|
88
|
+
CommerceActionResult: { paymentLink: string } — redirect to paymentLink
|
|
89
|
+
|
|
90
|
+
useProductCheckout()
|
|
91
|
+
Returns: { initiate: (input: { productId, quantity, variantId?, customer: { name, email, phone, address? }, deliveryMethod? }) => Promise<CommerceActionResult>; loading; error }
|
|
92
|
+
Use for single-product "Buy Now" flows only. For multi-product stores, use cart.
|
|
93
|
+
|
|
94
|
+
useShippingRates()
|
|
95
|
+
Returns: FetchState<ShippingRate[]>
|
|
96
|
+
ShippingRate: { id, name, amountKobo, estimatedDays }
|
|
97
|
+
|
|
98
|
+
useValidateDiscount()
|
|
99
|
+
Returns: { validate: (code: string) => Promise<DiscountValidationResult>; validating; error }
|
|
100
|
+
|
|
101
|
+
useCheckoutConfig()
|
|
102
|
+
Returns: FetchState<CheckoutConfig>
|
|
103
|
+
CheckoutConfig: { deliveryEnabled, pickupEnabled, deliveryOnly, pickupOnly }
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## DIGITAL PRODUCTS
|
|
108
|
+
|
|
109
|
+
useDigitalProducts(options?)
|
|
110
|
+
Options: { limit?: number }
|
|
111
|
+
Returns: FetchState<StoreProduct[]> (filtered to type="digital")
|
|
112
|
+
|
|
113
|
+
useDigitalProduct(productId: string)
|
|
114
|
+
Returns: FetchState<StoreProductDetail | null>
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## SERVICES
|
|
119
|
+
|
|
120
|
+
useServiceListings(options?)
|
|
121
|
+
Options: { limit?: number; category?: string }
|
|
122
|
+
Returns: FetchState<StorefrontServiceListing[]>
|
|
123
|
+
StorefrontServiceListing: { id, title, description, coverImage, category, priceKobo, availability, addOns: ServiceAddOn[], subServices: SubService[] }
|
|
124
|
+
|
|
125
|
+
useServiceListing(listingId: string)
|
|
126
|
+
Returns: FetchState<StorefrontServiceListing | null>
|
|
127
|
+
|
|
128
|
+
useCheckoutService()
|
|
129
|
+
Returns: { checkout: (input: { listingId, customer: { name, email, phone }, addOnIds?, subServiceIds? }) => Promise<{ paymentLink }>; loading; error }
|
|
130
|
+
|
|
131
|
+
useInitiateEngagement()
|
|
132
|
+
Returns: { submit: (input: { listingId, customer: { name, email, phone }, message? }) => Promise<void>; submitting; submitted; error }
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## APPOINTMENTS
|
|
137
|
+
|
|
138
|
+
useServices(options?)
|
|
139
|
+
Options: { limit?: number }
|
|
140
|
+
Returns: FetchState<StorefrontService[]>
|
|
141
|
+
StorefrontService: { id, name, description, durationMinutes, priceKobo, imageUrl, category }
|
|
142
|
+
|
|
143
|
+
useAvailableDates(options)
|
|
144
|
+
Options: { serviceId: string; month: string } (month format: "YYYY-MM")
|
|
145
|
+
Returns: FetchState<string[]> — array of date strings "YYYY-MM-DD"
|
|
146
|
+
|
|
147
|
+
useAvailability(options)
|
|
148
|
+
Options: { serviceId: string; from: string; to: string } (both "YYYY-MM-DD")
|
|
149
|
+
CRITICAL: to must always be from + 1 day — NEVER set from === to, it returns no slots
|
|
150
|
+
to = new Date(+new Date(from) + 86400000).toISOString().slice(0, 10)
|
|
151
|
+
Returns: FetchState<AvailabilitySlot[]>
|
|
152
|
+
AvailabilitySlot: { id, startTime, endTime, status: "available"|"claimed"|"booked"|"blocked" }
|
|
153
|
+
FILTER: only show slots where status === "available" — the hook returns all statuses
|
|
154
|
+
|
|
155
|
+
useBookAppointment()
|
|
156
|
+
Returns: { book: (input: { serviceId, dateTime: string, customerName, customerEmail, customerPhone }) => Promise<CommerceActionResult>; loading; error }
|
|
157
|
+
dateTime: slot.startTime (ISO datetime string from useAvailability result)
|
|
158
|
+
WARNING: NEVER pass slotId — pass dateTime: slot.startTime
|
|
159
|
+
WARNING: NEVER use initiate() for appointments — use book(). Returns { paymentLink } — redirect with window.location.href
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## EVENTS
|
|
164
|
+
|
|
165
|
+
useEvents(options?)
|
|
166
|
+
Options: { limit?: number }
|
|
167
|
+
Returns: FetchState<StorefrontEvent[]>
|
|
168
|
+
StorefrontEvent: { id, name, description, startsAt, endsAt, location, imageUrl, ticketTypes: StorefrontTicketType[] }
|
|
169
|
+
WARNING: event.date and event.endDate do NOT exist — use startsAt and endsAt (ISO strings)
|
|
170
|
+
StorefrontTicketType: { id, name, priceKobo, quantity, sold, available }
|
|
171
|
+
|
|
172
|
+
useEvent(eventId: string)
|
|
173
|
+
Returns: FetchState<StorefrontEvent | null>
|
|
174
|
+
|
|
175
|
+
usePurchaseTickets()
|
|
176
|
+
Returns: { initiate: (input: { eventId, ticketOrders: [{ ticketTypeId: string, quantity: number }], customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }
|
|
177
|
+
WARNING: param is ticketOrders not tickets
|
|
178
|
+
|
|
179
|
+
useCreateRSVP()
|
|
180
|
+
Returns: { submit: (input: { eventId, name, email, phone? }) => Promise<void>; loading; success; error }
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## DONATIONS
|
|
185
|
+
|
|
186
|
+
useDonationCampaigns(options?)
|
|
187
|
+
Options: { limit?: number; ids?: string[] }
|
|
188
|
+
Returns: FetchState<StorefrontDonationCampaign[]>
|
|
189
|
+
StorefrontDonationCampaign: { id, name, description, imageUrl, goalAmountKobo: number|null, raisedAmountKobo: number }
|
|
190
|
+
WARNING: .raised, .raisedAmount, .goalAmount DO NOT EXIST. Use .raisedAmountKobo and .goalAmountKobo only.
|
|
191
|
+
|
|
192
|
+
useDonationCampaign(campaignId: string)
|
|
193
|
+
Returns: FetchState<StorefrontDonationCampaign | null>
|
|
194
|
+
|
|
195
|
+
useDonate()
|
|
196
|
+
Returns: { initiate: (input: { campaignId, amountKobo, customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## MEMBERSHIPS
|
|
201
|
+
|
|
202
|
+
useMemberships()
|
|
203
|
+
Returns: FetchState<StorefrontMembershipTier[]>
|
|
204
|
+
StorefrontMembershipTier: { id, name, description, priceKobo, billingPeriod: "monthly"|"yearly"|"one_time", benefits: string[]|null, imageUrl: string|null, isFeatured }
|
|
205
|
+
WARNING: .price, .amount, .features, .perks DO NOT EXIST. Use .priceKobo and .benefits only.
|
|
206
|
+
WARNING: billingPeriod is "yearly" not "annual".
|
|
207
|
+
|
|
208
|
+
useSubscribeMembership()
|
|
209
|
+
Returns: { initiate: (input: { tierId, customer: { name, email, phone } }) => Promise<CommerceActionResult>; loading; error }
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## CONTENT & CMS
|
|
214
|
+
|
|
215
|
+
useContentItems(options: { type: string })
|
|
216
|
+
Type values: "image", "video", "testimonial", "faq", "stat", "feature", "team_member", "logo_cloud", "pricing"
|
|
217
|
+
Returns: FetchState<ContentItem[]>
|
|
218
|
+
|
|
219
|
+
Convenience aliases (all return FetchState<ContentItem[]>):
|
|
220
|
+
useContentImages(), useContentVideos(), useContentTestimonials(), useContentFaqs(),
|
|
221
|
+
useContentStats(), useContentFeatures(), useContentTeamMembers(), useContentLogoCloud(), useContentPricing()
|
|
222
|
+
WARNING: useContentTeam() does NOT exist — use useContentTeamMembers()
|
|
223
|
+
|
|
224
|
+
useArticles(options?)
|
|
225
|
+
Options: { limit?: number; category?: string; tag?: string }
|
|
226
|
+
Returns: FetchState<StorefrontArticle[]>
|
|
227
|
+
|
|
228
|
+
useArticle(slug: string)
|
|
229
|
+
Returns: FetchState<StorefrontArticle | null>
|
|
230
|
+
|
|
231
|
+
useContentForm(formId: string)
|
|
232
|
+
Returns: { form: ContentFormSchema | null; loading; error }
|
|
233
|
+
ContentFormSchema: { id, name, fields: ContentFormField[] }
|
|
234
|
+
ContentFormField: { id, label, type, required, options?, placeholder? }
|
|
235
|
+
|
|
236
|
+
useSubmitForm(formId: string)
|
|
237
|
+
Returns: { submit: (data: Record<string, unknown>) => Promise<void>; submitting; submitted; error }
|
|
238
|
+
|
|
239
|
+
useMailingListSubscribe()
|
|
240
|
+
Returns: { subscribe: (email: string) => Promise<void>; subscribing; subscribed; error }
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## LISTINGS
|
|
245
|
+
|
|
246
|
+
useListings(options?)
|
|
247
|
+
Options: { listingTypeId?: string; page?: number; limit?: number; filters?: Record<string, string> }
|
|
248
|
+
Returns: { listings: StorefrontListing[]; total; page; totalPages; loading; error }
|
|
249
|
+
|
|
250
|
+
useListingDetail(slugOrId: string)
|
|
251
|
+
Returns: { listing: StorefrontListingDetail | null; related: StorefrontListing[]; loading; error }
|
|
252
|
+
|
|
253
|
+
useListingFilters(listingTypeId?: string)
|
|
254
|
+
Returns: { fieldDefinitions; params; setParam; reset; loading; error }
|
|
255
|
+
|
|
256
|
+
Action hooks (each takes listingId):
|
|
257
|
+
useListingEnquiry(id) — { submit: ({ name, email?, phone?, message? }) => Promise<void>; loading; success; error }
|
|
258
|
+
useListingApplication(id) — { submit: ({ name, email, phone?, coverNote?, resumeUrl? }) => Promise<void>; loading; success; error }
|
|
259
|
+
useListingPayment(id) — { checkout: (customer, fieldData?) => Promise<void>; loading; error }
|
|
260
|
+
useListingReserve(id) — { reserve: (data?) => Promise<void>; loading; success; alreadyReserved; error }
|
|
261
|
+
useListingDownload(id) — { download: () => Promise<void>; downloadUrl; loading; error }
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## CUSTOMER ACCOUNTS
|
|
266
|
+
|
|
267
|
+
useCustomerAuth()
|
|
268
|
+
Returns: { customer: { id, name, email, phone, address } | null; isLoggedIn: boolean; isLoading: boolean;
|
|
269
|
+
signUp: (name, email, password) => Promise<void>; signIn: (email, password) => Promise<void>; signOut: () => void; error }
|
|
270
|
+
WARNING: field is isLoggedIn, NOT isAuthenticated.
|
|
271
|
+
|
|
272
|
+
useMyOrders() — { orders: MyOrder[]; loading; error }
|
|
273
|
+
useMyAppointments() — { appointments: MyAppointment[]; loading; error }
|
|
274
|
+
useMyPurchases() — { purchases: MyPurchase[]; loading; error }
|
|
275
|
+
useMyMembership() — { membership: MyMembership | null; loading; error }
|
|
276
|
+
useMyProfile() — { profile: { name, email, phone, address } | null; update: ({ name, phone, address }) => Promise<void>; updating; updateError }
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## ORDER TRACKING
|
|
281
|
+
|
|
282
|
+
useOrderStatus() — { status: OrderStatusData | null; loading; error }
|
|
283
|
+
useOrderLookup() — { lookup: (orderRef, email) => Promise<OrderLookupItem[]>; items; loading; error }
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## COMPONENTS (import from "@sellhapi/studio-runtime")
|
|
288
|
+
|
|
289
|
+
CartDrawer — renders the slide-out cart drawer. REQUIRED if using useCart().
|
|
290
|
+
`;
|