@sellhapi/studio-runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/AppointmentBooker.d.ts +7 -0
  2. package/dist/components/AppointmentBooker.d.ts.map +1 -0
  3. package/dist/components/ArticleCard.d.ts +6 -0
  4. package/dist/components/ArticleCard.d.ts.map +1 -0
  5. package/dist/components/ArticleGrid.d.ts +8 -0
  6. package/dist/components/ArticleGrid.d.ts.map +1 -0
  7. package/dist/components/DonationCampaignGrid.d.ts +7 -0
  8. package/dist/components/DonationCampaignGrid.d.ts.map +1 -0
  9. package/dist/components/DonationForm.d.ts +9 -0
  10. package/dist/components/DonationForm.d.ts.map +1 -0
  11. package/dist/components/EventCard.d.ts +6 -0
  12. package/dist/components/EventCard.d.ts.map +1 -0
  13. package/dist/components/EventGrid.d.ts +8 -0
  14. package/dist/components/EventGrid.d.ts.map +1 -0
  15. package/dist/components/MembershipTiers.d.ts +6 -0
  16. package/dist/components/MembershipTiers.d.ts.map +1 -0
  17. package/dist/components/ProductCard.d.ts +6 -0
  18. package/dist/components/ProductCard.d.ts.map +1 -0
  19. package/dist/components/ProductGrid.d.ts +9 -0
  20. package/dist/components/ProductGrid.d.ts.map +1 -0
  21. package/dist/components/RSVPForm.d.ts +7 -0
  22. package/dist/components/RSVPForm.d.ts.map +1 -0
  23. package/dist/context.d.ts +26 -0
  24. package/dist/context.d.ts.map +1 -0
  25. package/dist/hooks/useArticles.d.ts +12 -0
  26. package/dist/hooks/useArticles.d.ts.map +1 -0
  27. package/dist/hooks/useBookAppointment.d.ts +26 -0
  28. package/dist/hooks/useBookAppointment.d.ts.map +1 -0
  29. package/dist/hooks/useCart.d.ts +63 -0
  30. package/dist/hooks/useCart.d.ts.map +1 -0
  31. package/dist/hooks/useCheckoutConfig.d.ts +21 -0
  32. package/dist/hooks/useCheckoutConfig.d.ts.map +1 -0
  33. package/dist/hooks/useCollection.d.ts +12 -0
  34. package/dist/hooks/useCollection.d.ts.map +1 -0
  35. package/dist/hooks/useContentForm.d.ts +31 -0
  36. package/dist/hooks/useContentForm.d.ts.map +1 -0
  37. package/dist/hooks/useContentItems.d.ts +122 -0
  38. package/dist/hooks/useContentItems.d.ts.map +1 -0
  39. package/dist/hooks/useCreateRSVP.d.ts +10 -0
  40. package/dist/hooks/useCreateRSVP.d.ts.map +1 -0
  41. package/dist/hooks/useDigitalProducts.d.ts +8 -0
  42. package/dist/hooks/useDigitalProducts.d.ts.map +1 -0
  43. package/dist/hooks/useDonate.d.ts +8 -0
  44. package/dist/hooks/useDonate.d.ts.map +1 -0
  45. package/dist/hooks/useDonationCampaigns.d.ts +10 -0
  46. package/dist/hooks/useDonationCampaigns.d.ts.map +1 -0
  47. package/dist/hooks/useEvents.d.ts +10 -0
  48. package/dist/hooks/useEvents.d.ts.map +1 -0
  49. package/dist/hooks/useFetch.d.ts +14 -0
  50. package/dist/hooks/useFetch.d.ts.map +1 -0
  51. package/dist/hooks/useMemberships.d.ts +5 -0
  52. package/dist/hooks/useMemberships.d.ts.map +1 -0
  53. package/dist/hooks/useProductCheckout.d.ts +15 -0
  54. package/dist/hooks/useProductCheckout.d.ts.map +1 -0
  55. package/dist/hooks/useProducts.d.ts +19 -0
  56. package/dist/hooks/useProducts.d.ts.map +1 -0
  57. package/dist/hooks/usePurchaseTickets.d.ts +8 -0
  58. package/dist/hooks/usePurchaseTickets.d.ts.map +1 -0
  59. package/dist/hooks/useServices.d.ts +8 -0
  60. package/dist/hooks/useServices.d.ts.map +1 -0
  61. package/dist/hooks/useShippingRates.d.ts +12 -0
  62. package/dist/hooks/useShippingRates.d.ts.map +1 -0
  63. package/dist/hooks/useStudioEditMode.d.ts +19 -0
  64. package/dist/hooks/useStudioEditMode.d.ts.map +1 -0
  65. package/dist/hooks/useSubscribeMembership.d.ts +8 -0
  66. package/dist/hooks/useSubscribeMembership.d.ts.map +1 -0
  67. package/dist/hooks/useValidateDiscount.d.ts +21 -0
  68. package/dist/hooks/useValidateDiscount.d.ts.map +1 -0
  69. package/dist/index.cjs +1594 -0
  70. package/dist/index.d.ts +104 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +1571 -0
  73. package/dist/types/index.d.ts +162 -0
  74. package/dist/types/index.d.ts.map +1 -0
  75. package/package.json +34 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1594 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AppointmentBooker: () => AppointmentBooker,
24
+ ArticleCard: () => ArticleCard,
25
+ ArticleGrid: () => ArticleGrid,
26
+ DonationCampaignGrid: () => DonationCampaignGrid,
27
+ DonationForm: () => DonationForm,
28
+ EventCard: () => EventCard,
29
+ EventGrid: () => EventGrid,
30
+ MembershipTiers: () => MembershipTiers,
31
+ ProductCard: () => ProductCard,
32
+ ProductGrid: () => ProductGrid,
33
+ RSVPForm: () => RSVPForm,
34
+ SellHapiProvider: () => SellHapiProvider,
35
+ useArticle: () => useArticle,
36
+ useArticles: () => useArticles,
37
+ useAvailability: () => useAvailability,
38
+ useBookAppointment: () => useBookAppointment,
39
+ useCart: () => useCart,
40
+ useCartCheckout: () => useCartCheckout,
41
+ useCheckout: () => useProductCheckout,
42
+ useCheckoutConfig: () => useCheckoutConfig,
43
+ useCollection: () => useCollection,
44
+ useContentAudio: () => useContentAudio,
45
+ useContentBrandStats: () => useContentBrandStats,
46
+ useContentDocuments: () => useContentDocuments,
47
+ useContentFaqs: () => useContentFaqs,
48
+ useContentForm: () => useContentForm,
49
+ useContentImages: () => useContentImages,
50
+ useContentItems: () => useContentItems,
51
+ useContentPortfolio: () => useContentPortfolio,
52
+ useContentPress: () => useContentPress,
53
+ useContentSocialLinks: () => useContentSocialLinks,
54
+ useContentTeamMembers: () => useContentTeamMembers,
55
+ useContentTestimonials: () => useContentTestimonials,
56
+ useContentVideoUrls: () => useContentVideoUrls,
57
+ useContentVideos: () => useContentVideos,
58
+ useCreateRSVP: () => useCreateRSVP,
59
+ useDigitalProducts: () => useDigitalProducts,
60
+ useDonate: () => useDonate,
61
+ useDonationCampaign: () => useDonationCampaign,
62
+ useDonationCampaigns: () => useDonationCampaigns,
63
+ useEvent: () => useEvent,
64
+ useEvents: () => useEvents,
65
+ useMemberships: () => useMemberships,
66
+ useProduct: () => useProduct,
67
+ useProductCheckout: () => useProductCheckout,
68
+ useProducts: () => useProducts,
69
+ usePurchaseTickets: () => usePurchaseTickets,
70
+ useSellHapi: () => useSellHapi,
71
+ useServices: () => useServices,
72
+ useShippingRates: () => useShippingRates,
73
+ useStudioEditMode: () => useStudioEditMode,
74
+ useSubmitForm: () => useSubmitForm,
75
+ useSubscribeMembership: () => useSubscribeMembership,
76
+ useValidateDiscount: () => useValidateDiscount
77
+ });
78
+ module.exports = __toCommonJS(index_exports);
79
+
80
+ // src/context.tsx
81
+ var import_react = require("react");
82
+ var import_jsx_runtime = require("react/jsx-runtime");
83
+ var SellHapiContext = (0, import_react.createContext)(null);
84
+ function SellHapiProvider({
85
+ workspaceId,
86
+ apiBase = "https://api.sellhapi.com",
87
+ vendorSlug,
88
+ children
89
+ }) {
90
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SellHapiContext.Provider, { value: { workspaceId, apiBase, vendorSlug }, children });
91
+ }
92
+ function useSellHapi() {
93
+ const ctx = (0, import_react.useContext)(SellHapiContext);
94
+ if (!ctx) {
95
+ throw new Error(
96
+ "[sellhapi/studio-runtime] useSellHapi must be used inside <SellHapiProvider>. Ensure your storefront root is wrapped with SellHapiProvider."
97
+ );
98
+ }
99
+ return ctx;
100
+ }
101
+
102
+ // src/hooks/useProducts.ts
103
+ var import_react3 = require("react");
104
+
105
+ // src/hooks/useFetch.ts
106
+ var import_react2 = require("react");
107
+ function useFetch(url, defaultValue) {
108
+ const [data, setData] = (0, import_react2.useState)(defaultValue);
109
+ const [loading, setLoading] = (0, import_react2.useState)(true);
110
+ const [error, setError] = (0, import_react2.useState)(null);
111
+ const urlRef = (0, import_react2.useRef)(url);
112
+ urlRef.current = url;
113
+ (0, import_react2.useEffect)(() => {
114
+ let cancelled = false;
115
+ setLoading(true);
116
+ setError(null);
117
+ fetch(url).then(async (res) => {
118
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
119
+ const json = await res.json();
120
+ if (!cancelled) {
121
+ setData(json.data ?? defaultValue);
122
+ setLoading(false);
123
+ }
124
+ }).catch((err) => {
125
+ if (!cancelled) {
126
+ setError(err instanceof Error ? err.message : "Unknown error");
127
+ setLoading(false);
128
+ }
129
+ });
130
+ return () => {
131
+ cancelled = true;
132
+ };
133
+ }, [url]);
134
+ return { data, loading, error };
135
+ }
136
+ function buildQueryUrl(apiBase, workspaceId, capabilityId, queryId, filters = {}) {
137
+ const params = new URLSearchParams({ capabilityId, queryId });
138
+ for (const [k, v] of Object.entries(filters)) {
139
+ if (v !== void 0) params.set(k, String(v));
140
+ }
141
+ return `${apiBase}/api/storefront/${workspaceId}/content/query?${params.toString()}`;
142
+ }
143
+
144
+ // src/hooks/useProducts.ts
145
+ function useProducts(options = {}) {
146
+ const { workspaceId, apiBase } = useSellHapi();
147
+ const idsKey = options.ids?.join(",");
148
+ const url = (0, import_react3.useMemo)(
149
+ () => buildQueryUrl(apiBase, workspaceId, "physical_products", "list_products", {
150
+ limit: options.limit,
151
+ categoryId: options.categoryId,
152
+ ...idsKey ? { ids: idsKey } : {}
153
+ }),
154
+ // eslint-disable-next-line react-hooks/exhaustive-deps
155
+ [apiBase, workspaceId, options.limit, options.categoryId, idsKey]
156
+ );
157
+ return useFetch(url, []);
158
+ }
159
+ function useProduct(_productId) {
160
+ return { data: null, loading: false, error: null };
161
+ }
162
+
163
+ // src/hooks/useDigitalProducts.ts
164
+ var import_react4 = require("react");
165
+ function useDigitalProducts(options = {}) {
166
+ const { workspaceId, apiBase } = useSellHapi();
167
+ const url = (0, import_react4.useMemo)(
168
+ () => buildQueryUrl(apiBase, workspaceId, "digital_products", "list_digital_products", {
169
+ limit: options.limit
170
+ }),
171
+ [apiBase, workspaceId, options.limit]
172
+ );
173
+ return useFetch(url, []);
174
+ }
175
+
176
+ // src/hooks/useEvents.ts
177
+ var import_react5 = require("react");
178
+ function useEvents(options = {}) {
179
+ const { workspaceId, apiBase } = useSellHapi();
180
+ const url = (0, import_react5.useMemo)(
181
+ () => buildQueryUrl(apiBase, workspaceId, "events", "list_events", {
182
+ limit: options.limit
183
+ }),
184
+ [apiBase, workspaceId, options.limit]
185
+ );
186
+ return useFetch(url, []);
187
+ }
188
+ function useEvent(eventId) {
189
+ const { workspaceId, apiBase } = useSellHapi();
190
+ const url = `${apiBase}/api/events/storefront/events/${encodeURIComponent(eventId)}?vendorId=${encodeURIComponent(workspaceId)}`;
191
+ return useFetch(url, null);
192
+ }
193
+
194
+ // src/hooks/useMemberships.ts
195
+ var import_react6 = require("react");
196
+ function useMemberships() {
197
+ const { workspaceId, apiBase } = useSellHapi();
198
+ const url = (0, import_react6.useMemo)(
199
+ () => buildQueryUrl(apiBase, workspaceId, "memberships", "list_membership_tiers"),
200
+ [apiBase, workspaceId]
201
+ );
202
+ return useFetch(url, []);
203
+ }
204
+
205
+ // src/hooks/useServices.ts
206
+ var import_react7 = require("react");
207
+ function useServices(options = {}) {
208
+ const { workspaceId, apiBase } = useSellHapi();
209
+ const url = (0, import_react7.useMemo)(
210
+ () => buildQueryUrl(apiBase, workspaceId, "appointments", "list_services", {
211
+ limit: options.limit
212
+ }),
213
+ [apiBase, workspaceId, options.limit]
214
+ );
215
+ return useFetch(url, []);
216
+ }
217
+
218
+ // src/hooks/useArticles.ts
219
+ var import_react8 = require("react");
220
+ function useArticles(options = {}) {
221
+ const { workspaceId, apiBase } = useSellHapi();
222
+ const url = (0, import_react8.useMemo)(() => {
223
+ const params = new URLSearchParams();
224
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
225
+ if (options.category !== void 0) params.set("category", options.category);
226
+ if (options.tag !== void 0) params.set("tag", options.tag);
227
+ const qs = params.toString();
228
+ return `${apiBase}/api/storefront/${workspaceId}/content/articles${qs ? `?${qs}` : ""}`;
229
+ }, [apiBase, workspaceId, options.limit, options.category, options.tag]);
230
+ return useFetch(url, []);
231
+ }
232
+ function useArticle(slug) {
233
+ const { workspaceId, apiBase } = useSellHapi();
234
+ const url = `${apiBase}/api/storefront/${workspaceId}/content/articles/${encodeURIComponent(slug)}`;
235
+ return useFetch(url, null);
236
+ }
237
+
238
+ // src/hooks/useDonationCampaigns.ts
239
+ var import_react9 = require("react");
240
+ function useDonationCampaigns(options = {}) {
241
+ const { workspaceId, apiBase } = useSellHapi();
242
+ const url = (0, import_react9.useMemo)(
243
+ () => buildQueryUrl(apiBase, workspaceId, "donations", "list_active_campaigns", {
244
+ limit: options.limit
245
+ }),
246
+ [apiBase, workspaceId, options.limit]
247
+ );
248
+ return useFetch(url, []);
249
+ }
250
+ function useDonationCampaign(campaignId) {
251
+ const { workspaceId, apiBase } = useSellHapi();
252
+ const url = `${apiBase}/api/donations/storefront/campaigns/${encodeURIComponent(campaignId)}?vendorId=${encodeURIComponent(workspaceId)}`;
253
+ return useFetch(url, null);
254
+ }
255
+
256
+ // src/hooks/useCreateRSVP.ts
257
+ var import_react10 = require("react");
258
+ function useCreateRSVP() {
259
+ const { workspaceId, apiBase } = useSellHapi();
260
+ const [loading, setLoading] = (0, import_react10.useState)(false);
261
+ const [error, setError] = (0, import_react10.useState)(null);
262
+ const [success, setSuccess] = (0, import_react10.useState)(false);
263
+ const submit = (0, import_react10.useCallback)(async (input) => {
264
+ setLoading(true);
265
+ setError(null);
266
+ try {
267
+ const res = await fetch(
268
+ `${apiBase}/api/events-tickets/storefront/events/${encodeURIComponent(input.eventId)}/rsvp`,
269
+ {
270
+ method: "POST",
271
+ headers: { "Content-Type": "application/json" },
272
+ body: JSON.stringify({ ...input, workspaceId })
273
+ }
274
+ );
275
+ if (!res.ok) {
276
+ const body = await res.json().catch(() => ({}));
277
+ throw new Error(body.message ?? `HTTP ${res.status}`);
278
+ }
279
+ setSuccess(true);
280
+ } catch (err) {
281
+ setError(err instanceof Error ? err.message : "Could not complete RSVP");
282
+ } finally {
283
+ setLoading(false);
284
+ }
285
+ }, [apiBase, workspaceId]);
286
+ return { submit, loading, error, success };
287
+ }
288
+
289
+ // src/hooks/useBookAppointment.ts
290
+ var import_react11 = require("react");
291
+ function useAvailability(options) {
292
+ const { workspaceId, apiBase } = useSellHapi();
293
+ const url = (0, import_react11.useMemo)(() => {
294
+ if (!options.serviceId || !options.from || !options.to) return null;
295
+ const p = new URLSearchParams({
296
+ vendorId: workspaceId,
297
+ serviceId: options.serviceId,
298
+ from: options.from,
299
+ to: options.to
300
+ });
301
+ if (options.staffMemberId) p.set("staffMemberId", options.staffMemberId);
302
+ return `${apiBase}/api/appointments/storefront/availability?${p.toString()}`;
303
+ }, [apiBase, workspaceId, options.serviceId, options.staffMemberId, options.from, options.to]);
304
+ return useFetch(url ?? "", []);
305
+ }
306
+ function useBookAppointment() {
307
+ const { workspaceId, apiBase } = useSellHapi();
308
+ const [loading, setLoading] = (0, import_react11.useState)(false);
309
+ const [error, setError] = (0, import_react11.useState)(null);
310
+ const initiate = (0, import_react11.useCallback)(async (input) => {
311
+ setLoading(true);
312
+ setError(null);
313
+ try {
314
+ const isSimple = "customerName" in input || "customerEmail" in input || "dateTime" in input;
315
+ const body = isSimple ? {
316
+ vendorId: workspaceId,
317
+ serviceId: input.serviceId,
318
+ dateTime: input.dateTime,
319
+ customer: {
320
+ name: input.customerName,
321
+ email: input.customerEmail,
322
+ phone: input.customerPhone ?? "N/A"
323
+ },
324
+ note: input.notes
325
+ } : { vendorId: workspaceId, ...input };
326
+ const res = await fetch(`${apiBase}/api/appointments/storefront/book`, {
327
+ method: "POST",
328
+ headers: { "Content-Type": "application/json" },
329
+ body: JSON.stringify(body)
330
+ });
331
+ const resBody = await res.json();
332
+ if (!res.ok) throw new Error(resBody.error ?? resBody.message ?? `HTTP ${res.status}`);
333
+ return { paymentLink: resBody.paymentLink, orderId: resBody.orderId };
334
+ } catch (err) {
335
+ const msg = err instanceof Error ? err.message : "Booking failed";
336
+ setError(msg);
337
+ throw err;
338
+ } finally {
339
+ setLoading(false);
340
+ }
341
+ }, [apiBase, workspaceId]);
342
+ return { initiate, book: initiate, loading, error };
343
+ }
344
+
345
+ // src/hooks/usePurchaseTickets.ts
346
+ var import_react12 = require("react");
347
+ function usePurchaseTickets() {
348
+ const { workspaceId, apiBase } = useSellHapi();
349
+ const [loading, setLoading] = (0, import_react12.useState)(false);
350
+ const [error, setError] = (0, import_react12.useState)(null);
351
+ const initiate = (0, import_react12.useCallback)(async (input) => {
352
+ setLoading(true);
353
+ setError(null);
354
+ try {
355
+ const res = await fetch(`${apiBase}/api/events/storefront/purchase`, {
356
+ method: "POST",
357
+ headers: { "Content-Type": "application/json" },
358
+ body: JSON.stringify({ vendorId: workspaceId, ...input })
359
+ });
360
+ const body = await res.json();
361
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
362
+ return { paymentLink: body.paymentLink, orderId: body.orderId };
363
+ } catch (err) {
364
+ const msg = err instanceof Error ? err.message : "Ticket purchase failed";
365
+ setError(msg);
366
+ throw err;
367
+ } finally {
368
+ setLoading(false);
369
+ }
370
+ }, [apiBase, workspaceId]);
371
+ return { initiate, loading, error };
372
+ }
373
+
374
+ // src/hooks/useDonate.ts
375
+ var import_react13 = require("react");
376
+ function useDonate() {
377
+ const { workspaceId, apiBase } = useSellHapi();
378
+ const [loading, setLoading] = (0, import_react13.useState)(false);
379
+ const [error, setError] = (0, import_react13.useState)(null);
380
+ const initiate = (0, import_react13.useCallback)(async (input) => {
381
+ setLoading(true);
382
+ setError(null);
383
+ try {
384
+ const res = await fetch(`${apiBase}/api/donations/storefront/donate`, {
385
+ method: "POST",
386
+ headers: { "Content-Type": "application/json" },
387
+ body: JSON.stringify({ vendorId: workspaceId, ...input })
388
+ });
389
+ const body = await res.json();
390
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
391
+ return { paymentLink: body.paymentLink, orderId: body.orderId, reference: body.paymentRef };
392
+ } catch (err) {
393
+ const msg = err instanceof Error ? err.message : "Donation failed";
394
+ setError(msg);
395
+ throw err;
396
+ } finally {
397
+ setLoading(false);
398
+ }
399
+ }, [apiBase, workspaceId]);
400
+ return { initiate, loading, error };
401
+ }
402
+
403
+ // src/hooks/useSubscribeMembership.ts
404
+ var import_react14 = require("react");
405
+ function useSubscribeMembership() {
406
+ const { workspaceId, apiBase } = useSellHapi();
407
+ const [loading, setLoading] = (0, import_react14.useState)(false);
408
+ const [error, setError] = (0, import_react14.useState)(null);
409
+ const initiate = (0, import_react14.useCallback)(async (input) => {
410
+ setLoading(true);
411
+ setError(null);
412
+ try {
413
+ const res = await fetch(`${apiBase}/api/memberships/storefront/subscribe`, {
414
+ method: "POST",
415
+ headers: { "Content-Type": "application/json" },
416
+ body: JSON.stringify({ vendorId: workspaceId, ...input })
417
+ });
418
+ const body = await res.json();
419
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
420
+ return { paymentLink: body.paymentLink, orderId: body.orderId };
421
+ } catch (err) {
422
+ const msg = err instanceof Error ? err.message : "Subscription failed";
423
+ setError(msg);
424
+ throw err;
425
+ } finally {
426
+ setLoading(false);
427
+ }
428
+ }, [apiBase, workspaceId]);
429
+ return { initiate, loading, error };
430
+ }
431
+
432
+ // src/hooks/useProductCheckout.ts
433
+ var import_react15 = require("react");
434
+ function useProductCheckout() {
435
+ const { workspaceId, apiBase } = useSellHapi();
436
+ const [loading, setLoading] = (0, import_react15.useState)(false);
437
+ const [error, setError] = (0, import_react15.useState)(null);
438
+ const initiate = (0, import_react15.useCallback)(async (input) => {
439
+ setLoading(true);
440
+ setError(null);
441
+ try {
442
+ const res = await fetch(`${apiBase}/api/storefront/${encodeURIComponent(workspaceId)}/products/checkout`, {
443
+ method: "POST",
444
+ headers: { "Content-Type": "application/json" },
445
+ body: JSON.stringify(input)
446
+ });
447
+ const body = await res.json();
448
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
449
+ return { paymentLink: body.paymentLink, orderId: body.orderId, reference: body.reference };
450
+ } catch (err) {
451
+ const msg = err instanceof Error ? err.message : "Checkout failed";
452
+ setError(msg);
453
+ throw err;
454
+ } finally {
455
+ setLoading(false);
456
+ }
457
+ }, [apiBase, workspaceId]);
458
+ return { initiate, initiateCheckout: initiate, loading, error };
459
+ }
460
+
461
+ // src/hooks/useCart.ts
462
+ var import_react16 = require("react");
463
+ var CART_EVENT = "sellhapi:cart:update";
464
+ function storageKey(workspaceId) {
465
+ return `sellhapi_cart_${workspaceId}`;
466
+ }
467
+ function readCart(workspaceId) {
468
+ if (typeof window === "undefined") return [];
469
+ try {
470
+ const raw = localStorage.getItem(storageKey(workspaceId));
471
+ if (!raw) return [];
472
+ const parsed = JSON.parse(raw);
473
+ return Array.isArray(parsed.items) ? parsed.items : [];
474
+ } catch {
475
+ return [];
476
+ }
477
+ }
478
+ function writeCart(workspaceId, items) {
479
+ if (typeof window === "undefined") return;
480
+ localStorage.setItem(storageKey(workspaceId), JSON.stringify({ items }));
481
+ window.dispatchEvent(new CustomEvent(CART_EVENT, { detail: { workspaceId } }));
482
+ }
483
+ function useCart() {
484
+ const { workspaceId } = useSellHapi();
485
+ const [items, setItems] = (0, import_react16.useState)(() => readCart(workspaceId));
486
+ (0, import_react16.useEffect)(() => {
487
+ const handler = (e) => {
488
+ const detail = e.detail;
489
+ if (detail?.workspaceId === workspaceId) {
490
+ setItems(readCart(workspaceId));
491
+ }
492
+ };
493
+ window.addEventListener(CART_EVENT, handler);
494
+ return () => window.removeEventListener(CART_EVENT, handler);
495
+ }, [workspaceId]);
496
+ const addItem = (0, import_react16.useCallback)((incoming) => {
497
+ const qty = incoming.quantity ?? 1;
498
+ const current = readCart(workspaceId);
499
+ const idx = current.findIndex(
500
+ (i) => i.productId === incoming.productId && i.variantId === incoming.variantId
501
+ );
502
+ let next;
503
+ if (idx >= 0) {
504
+ const newQty = current[idx].quantity + qty;
505
+ const maxStock = incoming.maxStock ?? current[idx].maxStock;
506
+ const capped = maxStock != null && maxStock > 0 ? Math.min(newQty, maxStock) : newQty;
507
+ next = current.map(
508
+ (item, i) => i === idx ? { ...item, quantity: capped, maxStock: maxStock ?? item.maxStock } : item
509
+ );
510
+ } else {
511
+ const maxStock = incoming.maxStock;
512
+ const cappedQty = maxStock != null && maxStock > 0 ? Math.min(qty, maxStock) : qty;
513
+ next = [...current, { ...incoming, quantity: cappedQty }];
514
+ }
515
+ writeCart(workspaceId, next);
516
+ setItems(next);
517
+ }, [workspaceId]);
518
+ const removeItem = (0, import_react16.useCallback)((productId, variantId) => {
519
+ const next = readCart(workspaceId).filter(
520
+ (i) => !(i.productId === productId && i.variantId === variantId)
521
+ );
522
+ writeCart(workspaceId, next);
523
+ setItems(next);
524
+ }, [workspaceId]);
525
+ const updateQty = (0, import_react16.useCallback)((productId, quantity, variantId) => {
526
+ if (quantity <= 0) {
527
+ removeItem(productId, variantId);
528
+ return;
529
+ }
530
+ const current = readCart(workspaceId);
531
+ const existing = current.find((i) => i.productId === productId && i.variantId === variantId);
532
+ const maxStock = existing?.maxStock;
533
+ const capped = maxStock != null && maxStock > 0 ? Math.min(quantity, maxStock) : quantity;
534
+ const next = current.map(
535
+ (i) => i.productId === productId && i.variantId === variantId ? { ...i, quantity: capped } : i
536
+ );
537
+ writeCart(workspaceId, next);
538
+ setItems(next);
539
+ }, [workspaceId, removeItem]);
540
+ const clear = (0, import_react16.useCallback)(() => {
541
+ writeCart(workspaceId, []);
542
+ setItems([]);
543
+ }, [workspaceId]);
544
+ const count = items.reduce((s, i) => s + i.quantity, 0);
545
+ const totalKobo = items.reduce((s, i) => s + i.priceKobo * i.quantity, 0);
546
+ const savingsKobo = items.reduce((s, i) => {
547
+ const orig = i.originalPriceKobo ?? i.priceKobo;
548
+ return s + Math.max(0, orig - i.priceKobo) * i.quantity;
549
+ }, 0);
550
+ const openCart = (0, import_react16.useCallback)(() => {
551
+ if (typeof window !== "undefined") {
552
+ window.dispatchEvent(new CustomEvent("sellhapi:open-cart"));
553
+ }
554
+ }, []);
555
+ return { items, count, totalKobo, savingsKobo, isEmpty: items.length === 0, addItem, removeItem, updateQty, clear, openCart };
556
+ }
557
+ function useCartCheckout() {
558
+ const { workspaceId, apiBase } = useSellHapi();
559
+ const [loading, setLoading] = (0, import_react16.useState)(false);
560
+ const [error, setError] = (0, import_react16.useState)(null);
561
+ const initiate = (0, import_react16.useCallback)(async (customer, options = {}) => {
562
+ const items = readCart(workspaceId);
563
+ if (items.length === 0) throw new Error("Cart is empty");
564
+ setLoading(true);
565
+ setError(null);
566
+ try {
567
+ const res = await fetch(
568
+ `${apiBase}/api/storefront/${encodeURIComponent(workspaceId)}/cart/checkout`,
569
+ {
570
+ method: "POST",
571
+ headers: { "Content-Type": "application/json" },
572
+ body: JSON.stringify({
573
+ items: items.map((i) => ({
574
+ productId: i.productId,
575
+ variantId: i.variantId,
576
+ quantity: i.quantity
577
+ })),
578
+ customer,
579
+ deliveryMethod: options.deliveryMethod ?? "delivery",
580
+ destinationState: options.destinationState,
581
+ shippingRateId: options.shippingRateId,
582
+ shippingFeeName: options.shippingFeeName,
583
+ shippingFeeKobo: options.shippingFeeKobo ?? 0,
584
+ customerNote: options.customerNote,
585
+ discountId: options.discountId,
586
+ discountAmountKobo: options.discountAmountKobo ?? 0
587
+ })
588
+ }
589
+ );
590
+ const body = await res.json();
591
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
592
+ return { paymentLink: body.paymentLink, orderId: body.orderId, reference: body.reference };
593
+ } catch (err) {
594
+ const msg = err instanceof Error ? err.message : "Checkout failed";
595
+ setError(msg);
596
+ throw err;
597
+ } finally {
598
+ setLoading(false);
599
+ }
600
+ }, [apiBase, workspaceId]);
601
+ return { initiate, loading, error };
602
+ }
603
+
604
+ // src/hooks/useCheckoutConfig.ts
605
+ var import_react17 = require("react");
606
+ var DEFAULT_CONFIG = {
607
+ orderNotes: false,
608
+ pickup: false,
609
+ discounts: false,
610
+ pickupConfig: null
611
+ };
612
+ function useCheckoutConfig() {
613
+ const { workspaceId, apiBase } = useSellHapi();
614
+ const [config, setConfig] = (0, import_react17.useState)(DEFAULT_CONFIG);
615
+ const [loading, setLoading] = (0, import_react17.useState)(true);
616
+ (0, import_react17.useEffect)(() => {
617
+ if (!workspaceId || !apiBase) {
618
+ setLoading(false);
619
+ return;
620
+ }
621
+ fetch(`${apiBase}/api/storefront/${encodeURIComponent(workspaceId)}/checkout-config`).then((r) => r.json()).then((data) => {
622
+ setConfig(data);
623
+ setLoading(false);
624
+ }).catch(() => setLoading(false));
625
+ }, [apiBase, workspaceId]);
626
+ return { config, loading };
627
+ }
628
+
629
+ // src/hooks/useShippingRates.ts
630
+ var import_react18 = require("react");
631
+ function useShippingRates(destinationState) {
632
+ const { workspaceId, apiBase } = useSellHapi();
633
+ const [rates, setRates] = (0, import_react18.useState)([]);
634
+ const [loading, setLoading] = (0, import_react18.useState)(false);
635
+ (0, import_react18.useEffect)(() => {
636
+ if (!workspaceId || !apiBase || destinationState === null) {
637
+ setRates([]);
638
+ return;
639
+ }
640
+ setLoading(true);
641
+ fetch(`${apiBase}/api/storefront/${encodeURIComponent(workspaceId)}/shipping-rates`).then((r) => r.json()).then((body) => {
642
+ setRates(body.data ?? []);
643
+ setLoading(false);
644
+ }).catch(() => setLoading(false));
645
+ }, [apiBase, workspaceId, destinationState]);
646
+ return { rates, loading };
647
+ }
648
+
649
+ // src/hooks/useValidateDiscount.ts
650
+ var import_react19 = require("react");
651
+ function useValidateDiscount() {
652
+ const { workspaceId, apiBase } = useSellHapi();
653
+ const [loading, setLoading] = (0, import_react19.useState)(false);
654
+ const [result, setResult] = (0, import_react19.useState)(null);
655
+ const validate = (0, import_react19.useCallback)(async (code, cartItems) => {
656
+ setLoading(true);
657
+ try {
658
+ const res = await fetch(
659
+ `${apiBase}/api/storefront/${encodeURIComponent(workspaceId)}/discounts/validate`,
660
+ {
661
+ method: "POST",
662
+ headers: { "Content-Type": "application/json" },
663
+ body: JSON.stringify({ code: code.trim().toUpperCase(), cartItems })
664
+ }
665
+ );
666
+ const data = await res.json();
667
+ setResult(data);
668
+ return data;
669
+ } catch {
670
+ const failed = { valid: false, error: "Could not validate code", code: "NETWORK_ERROR" };
671
+ setResult(failed);
672
+ return failed;
673
+ } finally {
674
+ setLoading(false);
675
+ }
676
+ }, [apiBase, workspaceId]);
677
+ const reset = (0, import_react19.useCallback)(() => setResult(null), []);
678
+ return { validate, loading, result, reset };
679
+ }
680
+
681
+ // src/hooks/useStudioEditMode.ts
682
+ var import_react20 = require("react");
683
+ var SCOPE_COLORS = {
684
+ media: "#3A01DB",
685
+ layout: "#E6891F",
686
+ navigation: "#059669",
687
+ config: "#6B7280"
688
+ };
689
+ function scopeColor(el, fallback) {
690
+ const scope = el.getAttribute("data-scope") ?? "";
691
+ return SCOPE_COLORS[scope] ?? fallback;
692
+ }
693
+ var STYLE_ID = "__sellhapi_studio_edit__";
694
+ function injectEditStyle(mode) {
695
+ let el = document.getElementById(STYLE_ID);
696
+ if (!el) {
697
+ el = document.createElement("style");
698
+ el.id = STYLE_ID;
699
+ document.head.appendChild(el);
700
+ }
701
+ if (mode === "none") {
702
+ el.textContent = "";
703
+ return;
704
+ }
705
+ if (mode === "region") {
706
+ el.textContent = `
707
+ [data-region] { position: relative; }
708
+ [data-region]:hover {
709
+ outline: 2px solid #3b82f6 !important;
710
+ outline-offset: 3px;
711
+ cursor: pointer;
712
+ }
713
+ [data-region][data-scope="media"]:hover { outline-color: #3A01DB !important; }
714
+ [data-region][data-scope="layout"]:hover { outline-color: #E6891F !important; }
715
+ [data-region][data-scope="navigation"]:hover { outline-color: #059669 !important; }
716
+ [data-region][data-scope="config"]:hover { outline-color: #6B7280 !important; }
717
+ `;
718
+ return;
719
+ }
720
+ if (mode === "component") {
721
+ el.textContent = `
722
+ [data-component]:hover, [data-studio-hover] {
723
+ outline: 2px solid #7c3aed !important;
724
+ outline-offset: 2px;
725
+ cursor: pointer;
726
+ }
727
+ [data-component][data-scope="media"]:hover { outline-color: #3A01DB !important; }
728
+ [data-component][data-scope="layout"]:hover { outline-color: #E6891F !important; }
729
+ [data-component][data-scope="navigation"]:hover { outline-color: #059669 !important; }
730
+ [data-component][data-scope="config"]:hover { outline-color: #6B7280 !important; }
731
+ `;
732
+ }
733
+ }
734
+ function closestRegion(el) {
735
+ let cur = el;
736
+ while (cur) {
737
+ if (cur.hasAttribute("data-region")) return cur;
738
+ cur = cur.parentElement;
739
+ }
740
+ return null;
741
+ }
742
+ function closestComponent(el) {
743
+ let cur = el;
744
+ while (cur) {
745
+ if (cur.hasAttribute("data-component")) return cur;
746
+ cur = cur.parentElement;
747
+ }
748
+ return null;
749
+ }
750
+ var SKIP_TAGS_SYNTH = /* @__PURE__ */ new Set(["HTML", "BODY", "HEAD", "SCRIPT", "STYLE", "NOSCRIPT", "SVG", "PATH"]);
751
+ var SKIP_IDS_SYNTH = /* @__PURE__ */ new Set(["root", "__next", "sellhapi-preview", "__sellhapi_studio_label__"]);
752
+ function closestMeaningfulBlock(el) {
753
+ let cur = el;
754
+ while (cur) {
755
+ const tag = cur.tagName;
756
+ const id = cur.id ?? "";
757
+ if (!SKIP_TAGS_SYNTH.has(tag) && !SKIP_IDS_SYNTH.has(id)) return cur;
758
+ cur = cur.parentElement;
759
+ }
760
+ return null;
761
+ }
762
+ function synthesizeCompName(el) {
763
+ const tag = el.tagName.toLowerCase();
764
+ const cls = Array.from(el.classList).find((c) => !/^[0-9]/.test(c)) ?? "";
765
+ const text = (el.textContent ?? "").trim().slice(0, 24).replace(/\s+/g, "-").replace(/[^a-z0-9-]/gi, "").toLowerCase();
766
+ return [tag, cls, text].filter(Boolean).join("-").slice(0, 48) || tag;
767
+ }
768
+ var LABEL_ID = "__sellhapi_studio_label__";
769
+ function showLabel(text, color) {
770
+ let lbl = document.getElementById(LABEL_ID);
771
+ if (!lbl) {
772
+ lbl = document.createElement("div");
773
+ lbl.id = LABEL_ID;
774
+ Object.assign(lbl.style, {
775
+ position: "fixed",
776
+ top: "10px",
777
+ left: "10px",
778
+ zIndex: "99999",
779
+ padding: "3px 10px",
780
+ borderRadius: "4px",
781
+ fontSize: "11px",
782
+ fontFamily: "monospace",
783
+ color: "#fff",
784
+ pointerEvents: "none"
785
+ });
786
+ document.body.appendChild(lbl);
787
+ }
788
+ lbl.style.background = color;
789
+ lbl.textContent = text;
790
+ lbl.style.display = "block";
791
+ }
792
+ function hideLabel() {
793
+ const lbl = document.getElementById(LABEL_ID);
794
+ if (lbl) lbl.style.display = "none";
795
+ }
796
+ function flashElement(el, color) {
797
+ const htmlEl = el;
798
+ const prevOutline = htmlEl.style.outline;
799
+ const prevOffset = htmlEl.style.outlineOffset;
800
+ htmlEl.style.outline = `2.5px solid ${color}`;
801
+ htmlEl.style.outlineOffset = "3px";
802
+ setTimeout(() => {
803
+ htmlEl.style.outline = prevOutline;
804
+ htmlEl.style.outlineOffset = prevOffset;
805
+ }, 600);
806
+ }
807
+ function useStudioEditMode() {
808
+ (0, import_react20.useEffect)(() => {
809
+ if (window === window.top) return;
810
+ let currentMode = "none";
811
+ function handleClick(e) {
812
+ if (currentMode === "none") return;
813
+ const target = e.target;
814
+ if (currentMode === "region") {
815
+ const regionEl = closestRegion(target);
816
+ if (!regionEl) return;
817
+ e.preventDefault();
818
+ e.stopPropagation();
819
+ const slug = regionEl.getAttribute("data-region") ?? "unknown";
820
+ const label = regionEl.getAttribute("data-label") ?? slug;
821
+ const scope = regionEl.getAttribute("data-scope") ?? "content";
822
+ const color = scopeColor(regionEl, "#3b82f6");
823
+ flashElement(regionEl, color);
824
+ hideLabel();
825
+ window.parent.postMessage({ type: "region:selected", slug, label, scope }, "*");
826
+ } else if (currentMode === "component") {
827
+ let compEl = closestComponent(target);
828
+ if (!compEl) {
829
+ const fallback = closestMeaningfulBlock(target);
830
+ if (!fallback) return;
831
+ if (!fallback.hasAttribute("data-component")) {
832
+ fallback.setAttribute("data-component", synthesizeCompName(fallback));
833
+ }
834
+ fallback.removeAttribute("data-studio-hover");
835
+ compEl = fallback;
836
+ }
837
+ e.preventDefault();
838
+ e.stopPropagation();
839
+ const name = compEl.getAttribute("data-component") ?? "unknown";
840
+ const label = compEl.getAttribute("data-label") ?? name;
841
+ const scope = compEl.getAttribute("data-scope") ?? "content";
842
+ const color = scopeColor(compEl, "#7c3aed");
843
+ flashElement(compEl, color);
844
+ hideLabel();
845
+ window.parent.postMessage({ type: "component:selected", name, label, scope }, "*");
846
+ }
847
+ }
848
+ function handleMouseOver(e) {
849
+ if (currentMode === "none") return;
850
+ const target = e.target;
851
+ if (currentMode === "region") {
852
+ const el = closestRegion(target);
853
+ if (el) showLabel(el.getAttribute("data-region") ?? "region", scopeColor(el, "#3b82f6"));
854
+ else hideLabel();
855
+ } else if (currentMode === "component") {
856
+ document.querySelectorAll("[data-studio-hover]").forEach((e2) => e2.removeAttribute("data-studio-hover"));
857
+ const el = closestComponent(target) ?? closestMeaningfulBlock(target);
858
+ if (el) {
859
+ if (!el.hasAttribute("data-component")) el.setAttribute("data-studio-hover", "true");
860
+ const lbl = el.getAttribute("data-component") ?? el.getAttribute("data-label") ?? synthesizeCompName(el);
861
+ showLabel(lbl, scopeColor(el, "#7c3aed"));
862
+ } else {
863
+ hideLabel();
864
+ }
865
+ }
866
+ }
867
+ function handleMouseOut() {
868
+ hideLabel();
869
+ document.querySelectorAll("[data-studio-hover]").forEach((e) => e.removeAttribute("data-studio-hover"));
870
+ }
871
+ function handleMessage(e) {
872
+ if (!e.data || typeof e.data !== "object") return;
873
+ const { type } = e.data;
874
+ if (type === "studio:set-mode") {
875
+ const { mode } = e.data;
876
+ currentMode = mode ?? "none";
877
+ injectEditStyle(currentMode);
878
+ hideLabel();
879
+ } else if (type === "studio:navigate") {
880
+ const { page } = e.data;
881
+ window.dispatchEvent(new CustomEvent("studio:navigate", { detail: { page } }));
882
+ } else if (type === "studio:highlight") {
883
+ const { selector } = e.data;
884
+ const el = selector ? document.querySelector(selector) : null;
885
+ if (el) {
886
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
887
+ flashElement(el, "#f59e0b");
888
+ }
889
+ }
890
+ }
891
+ document.addEventListener("click", handleClick, true);
892
+ document.addEventListener("mouseover", handleMouseOver, true);
893
+ document.addEventListener("mouseout", handleMouseOut, true);
894
+ window.addEventListener("message", handleMessage);
895
+ return () => {
896
+ document.removeEventListener("click", handleClick, true);
897
+ document.removeEventListener("mouseover", handleMouseOver, true);
898
+ document.removeEventListener("mouseout", handleMouseOut, true);
899
+ window.removeEventListener("message", handleMessage);
900
+ injectEditStyle("none");
901
+ hideLabel();
902
+ };
903
+ }, []);
904
+ }
905
+
906
+ // src/hooks/useContentItems.ts
907
+ var import_react21 = require("react");
908
+ function useContentItems(options) {
909
+ const { workspaceId, apiBase, vendorSlug } = useSellHapi();
910
+ const url = (0, import_react21.useMemo)(() => {
911
+ const params = new URLSearchParams({
912
+ workspaceId,
913
+ types: options.types.join(",")
914
+ });
915
+ if (options.limit) params.set("limit", String(options.limit));
916
+ if (options.tags?.length) params.set("tags", options.tags.join(","));
917
+ if (options.q) params.set("q", options.q);
918
+ if (options.ids?.length) params.set("ids", options.ids.join(","));
919
+ if (vendorSlug) params.set("projectId", vendorSlug);
920
+ return `${apiBase}/api/content/library?${params.toString()}`;
921
+ }, [apiBase, workspaceId, vendorSlug, options.types.join(","), options.limit, options.tags?.join(","), options.q, options.ids?.join(",")]);
922
+ return useFetch(url, []);
923
+ }
924
+ var useContentImages = (opts = {}) => useContentItems({ types: ["image"], ...opts });
925
+ var useContentVideos = (opts = {}) => useContentItems({ types: ["video"], ...opts });
926
+ var useContentVideoUrls = (opts = {}) => useContentItems({ types: ["video_url"], ...opts });
927
+ var useContentAudio = (opts = {}) => useContentItems({ types: ["audio"], ...opts });
928
+ var useContentDocuments = (opts = {}) => useContentItems({ types: ["document"], ...opts });
929
+ var useContentFaqs = (opts = {}) => useContentItems({ types: ["faq"], ...opts });
930
+ var useContentTestimonials = (opts = {}) => useContentItems({ types: ["testimonial"], ...opts });
931
+ var useContentTeamMembers = (opts = {}) => useContentItems({ types: ["team_member"], ...opts });
932
+ var useContentPortfolio = (opts = {}) => useContentItems({ types: ["portfolio"], ...opts });
933
+ var useContentPress = (opts = {}) => useContentItems({ types: ["press"], ...opts });
934
+ var useContentSocialLinks = (opts = {}) => useContentItems({ types: ["social_link"], ...opts });
935
+ var useContentBrandStats = (opts = {}) => useContentItems({ types: ["brand_stat"], ...opts });
936
+
937
+ // src/hooks/useContentForm.ts
938
+ var import_react22 = require("react");
939
+ function useContentForm(formId) {
940
+ const { apiBase, workspaceId } = useSellHapi();
941
+ const [form, setForm] = (0, import_react22.useState)(null);
942
+ const [loading, setLoading] = (0, import_react22.useState)(true);
943
+ const [error, setError] = (0, import_react22.useState)(null);
944
+ (0, import_react22.useEffect)(() => {
945
+ if (!formId) return;
946
+ let cancelled = false;
947
+ setLoading(true);
948
+ setError(null);
949
+ const url = workspaceId ? `${apiBase}/api/content/forms/${formId}/schema?workspaceId=${encodeURIComponent(workspaceId)}` : `${apiBase}/api/content/forms/${formId}/schema`;
950
+ fetch(url).then(async (res) => {
951
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
952
+ const json = await res.json();
953
+ if (!cancelled) {
954
+ setForm(json);
955
+ setLoading(false);
956
+ }
957
+ }).catch((err) => {
958
+ if (!cancelled) {
959
+ setError(err instanceof Error ? err.message : "Failed to load form");
960
+ setLoading(false);
961
+ }
962
+ });
963
+ return () => {
964
+ cancelled = true;
965
+ };
966
+ }, [apiBase, workspaceId, formId]);
967
+ return { form, data: form, loading, error };
968
+ }
969
+ function useSubmitForm(formId) {
970
+ const { apiBase } = useSellHapi();
971
+ const [submitting, setSubmitting] = (0, import_react22.useState)(false);
972
+ const [submitted, setSubmitted] = (0, import_react22.useState)(false);
973
+ const [error, setError] = (0, import_react22.useState)(null);
974
+ const submit = async (data) => {
975
+ if (submitting || submitted) return;
976
+ setSubmitting(true);
977
+ setError(null);
978
+ try {
979
+ const res = await fetch(`${apiBase}/api/content/forms/${formId}/submit`, {
980
+ method: "POST",
981
+ headers: { "Content-Type": "application/json" },
982
+ body: JSON.stringify({ data })
983
+ });
984
+ if (!res.ok) {
985
+ const json = await res.json().catch(() => ({}));
986
+ throw new Error(json.error ?? `HTTP ${res.status}`);
987
+ }
988
+ setSubmitted(true);
989
+ } catch (err) {
990
+ setError(err instanceof Error ? err.message : "Submission failed");
991
+ } finally {
992
+ setSubmitting(false);
993
+ }
994
+ };
995
+ return { submit, submitting, submitted, error };
996
+ }
997
+
998
+ // src/hooks/useCollection.ts
999
+ var import_react23 = require("react");
1000
+ function useCollection(slug) {
1001
+ const { apiBase, workspaceId } = useSellHapi();
1002
+ const [result, setResult] = (0, import_react23.useState)({
1003
+ id: "",
1004
+ title: "",
1005
+ slug: null,
1006
+ description: null,
1007
+ items: [],
1008
+ loading: true,
1009
+ error: null
1010
+ });
1011
+ (0, import_react23.useEffect)(() => {
1012
+ if (!slug || !workspaceId) return;
1013
+ let cancelled = false;
1014
+ setResult((prev) => ({ ...prev, loading: true, error: null }));
1015
+ const params = new URLSearchParams({ workspaceId, slug });
1016
+ fetch(`${apiBase}/api/content/collections/public?${params.toString()}`).then(async (res) => {
1017
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1018
+ const json = await res.json();
1019
+ if (!cancelled) {
1020
+ setResult({
1021
+ ...json.data,
1022
+ items: json.items ?? [],
1023
+ loading: false,
1024
+ error: null
1025
+ });
1026
+ }
1027
+ }).catch((err) => {
1028
+ if (!cancelled) {
1029
+ setResult((prev) => ({
1030
+ ...prev,
1031
+ loading: false,
1032
+ error: err instanceof Error ? err.message : "Failed to load collection"
1033
+ }));
1034
+ }
1035
+ });
1036
+ return () => {
1037
+ cancelled = true;
1038
+ };
1039
+ }, [apiBase, workspaceId, slug]);
1040
+ return result;
1041
+ }
1042
+
1043
+ // src/components/ProductGrid.tsx
1044
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1045
+ var gridStyle = {
1046
+ display: "grid",
1047
+ gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
1048
+ gap: 24
1049
+ };
1050
+ var cardStyle = {
1051
+ border: "1px solid #e5e7eb",
1052
+ borderRadius: 8,
1053
+ overflow: "hidden",
1054
+ background: "#fff"
1055
+ };
1056
+ var imgStyle = {
1057
+ width: "100%",
1058
+ height: 180,
1059
+ objectFit: "cover",
1060
+ background: "#f3f4f6",
1061
+ display: "block"
1062
+ };
1063
+ var bodyStyle = { padding: "12px 16px 16px" };
1064
+ function ProductGrid({ heading, limit, categoryId, type }) {
1065
+ const physicalResult = useProducts(type === "digital" ? { limit: 0 } : { limit, categoryId });
1066
+ const digitalResult = useDigitalProducts(type === "digital" ? { limit } : { limit: 0 });
1067
+ const result = type === "digital" ? digitalResult : physicalResult;
1068
+ const { data: products, loading, error } = result;
1069
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading products\u2026" });
1070
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load products." });
1071
+ if (!products.length) return null;
1072
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
1073
+ heading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { marginBottom: 20, fontWeight: 700, fontSize: 24 }, children: heading }),
1074
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: gridStyle, children: products.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardStyle, children: [
1075
+ p.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: p.imageUrl, alt: p.name, style: imgStyle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...imgStyle, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1076
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: bodyStyle, children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { fontWeight: 600, margin: "0 0 4px", fontSize: 15 }, children: p.name }),
1078
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#6b7280", margin: "0 0 12px", fontSize: 13 }, children: p.description ?? "" }),
1079
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { fontWeight: 700, margin: "0 0 12px", fontSize: 16 }, children: [
1080
+ "\u20A6",
1081
+ p.price.toLocaleString()
1082
+ ] }),
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1084
+ "button",
1085
+ {
1086
+ style: { width: "100%", padding: "8px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 },
1087
+ children: type === "digital" ? "Download / Buy" : "Add to Cart"
1088
+ }
1089
+ )
1090
+ ] })
1091
+ ] }, p.id)) })
1092
+ ] });
1093
+ }
1094
+
1095
+ // src/components/ProductCard.tsx
1096
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1097
+ var cardStyle2 = {
1098
+ border: "1px solid #e5e7eb",
1099
+ borderRadius: 8,
1100
+ overflow: "hidden",
1101
+ background: "#fff",
1102
+ maxWidth: 280
1103
+ };
1104
+ var imgStyle2 = {
1105
+ width: "100%",
1106
+ height: 180,
1107
+ objectFit: "cover",
1108
+ background: "#f3f4f6",
1109
+ display: "block"
1110
+ };
1111
+ function ProductCard({ product }) {
1112
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: cardStyle2, children: [
1113
+ product.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: product.imageUrl, alt: product.name, style: imgStyle2 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...imgStyle2, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: "12px 16px 16px" }, children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { fontWeight: 600, margin: "0 0 4px", fontSize: 15 }, children: product.name }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { style: { fontWeight: 700, margin: "0 0 12px" }, children: [
1117
+ "\u20A6",
1118
+ product.price.toLocaleString()
1119
+ ] }),
1120
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { style: { width: "100%", padding: "8px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: "Add to Cart" })
1121
+ ] })
1122
+ ] });
1123
+ }
1124
+
1125
+ // src/components/EventCard.tsx
1126
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1127
+ var cardStyle3 = {
1128
+ border: "1px solid #e5e7eb",
1129
+ borderRadius: 10,
1130
+ overflow: "hidden",
1131
+ background: "#fff",
1132
+ maxWidth: 340
1133
+ };
1134
+ var imgStyle3 = {
1135
+ width: "100%",
1136
+ height: 200,
1137
+ objectFit: "cover",
1138
+ background: "#f3f4f6",
1139
+ display: "block"
1140
+ };
1141
+ function EventCard({ event }) {
1142
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: cardStyle3, children: [
1143
+ event.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: event.imageUrl, alt: event.title, style: imgStyle3 }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { ...imgStyle3, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1144
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { padding: "14px 18px 18px" }, children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 16 }, children: event.title }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { color: "#6b7280", margin: "0 0 4px", fontSize: 13 }, children: new Date(event.startsAt).toLocaleDateString("en-NG", { weekday: "short", day: "numeric", month: "long", year: "numeric" }) }),
1147
+ event.location && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { color: "#6b7280", margin: "0 0 14px", fontSize: 13 }, children: [
1148
+ "\u{1F4CD} ",
1149
+ event.location
1150
+ ] }),
1151
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { style: { width: "100%", padding: "9px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: "Get Tickets" })
1152
+ ] })
1153
+ ] });
1154
+ }
1155
+
1156
+ // src/components/EventGrid.tsx
1157
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1158
+ var gridStyle2 = {
1159
+ display: "grid",
1160
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
1161
+ gap: 24
1162
+ };
1163
+ var cardStyle4 = {
1164
+ border: "1px solid #e5e7eb",
1165
+ borderRadius: 10,
1166
+ overflow: "hidden",
1167
+ background: "#fff"
1168
+ };
1169
+ var imgStyle4 = {
1170
+ width: "100%",
1171
+ height: 200,
1172
+ objectFit: "cover",
1173
+ background: "#f3f4f6",
1174
+ display: "block"
1175
+ };
1176
+ function EventGrid({ heading, subheading, limit }) {
1177
+ const { data: events, loading, error } = useEvents({ limit });
1178
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading events\u2026" });
1179
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load events." });
1180
+ if (!events.length) return null;
1181
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { children: [
1182
+ heading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { style: { fontWeight: 700, fontSize: 28, marginBottom: 8 }, children: heading }),
1183
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { color: "#6b7280", marginBottom: 24 }, children: subheading }),
1184
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: gridStyle2, children: events.map((ev) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: cardStyle4, children: [
1185
+ ev.imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: ev.imageUrl, alt: ev.title, style: imgStyle4 }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { ...imgStyle4, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1186
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { padding: "14px 18px 18px" }, children: [
1187
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 16 }, children: ev.title }),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { color: "#6b7280", margin: "0 0 4px", fontSize: 13 }, children: new Date(ev.startsAt).toLocaleDateString("en-NG", { weekday: "short", day: "numeric", month: "long", year: "numeric" }) }),
1189
+ ev.location && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { style: { color: "#6b7280", margin: "0 0 14px", fontSize: 13 }, children: [
1190
+ "\u{1F4CD} ",
1191
+ ev.location
1192
+ ] }),
1193
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { style: { width: "100%", padding: "9px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: "Get Tickets" })
1194
+ ] })
1195
+ ] }, ev.id)) })
1196
+ ] });
1197
+ }
1198
+
1199
+ // src/components/MembershipTiers.tsx
1200
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1201
+ var gridStyle3 = {
1202
+ display: "grid",
1203
+ gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
1204
+ gap: 24
1205
+ };
1206
+ var cardStyle5 = {
1207
+ border: "1px solid #e5e7eb",
1208
+ borderRadius: 12,
1209
+ padding: "28px 24px",
1210
+ background: "#fff"
1211
+ };
1212
+ function MembershipTiers({ heading, subheading }) {
1213
+ const { data: tiers, loading, error } = useMemberships();
1214
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading tiers\u2026" });
1215
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load membership tiers." });
1216
+ if (!tiers.length) return null;
1217
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { children: [
1218
+ heading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { style: { fontWeight: 700, fontSize: 28, marginBottom: 8, textAlign: "center" }, children: heading }),
1219
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { color: "#6b7280", marginBottom: 32, textAlign: "center" }, children: subheading }),
1220
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: gridStyle3, children: tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: cardStyle5, children: [
1221
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { fontWeight: 700, fontSize: 20, margin: "0 0 6px" }, children: tier.name }),
1222
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: { fontWeight: 800, fontSize: 28, margin: "0 0 4px" }, children: [
1223
+ "\u20A6",
1224
+ (tier.priceKobo / 100).toLocaleString(),
1225
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { fontSize: 14, fontWeight: 400, color: "#6b7280" }, children: [
1226
+ "/",
1227
+ tier.billingPeriod === "monthly" ? "mo" : "yr"
1228
+ ] })
1229
+ ] }),
1230
+ tier.description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { color: "#6b7280", fontSize: 13, margin: "0 0 16px" }, children: tier.description }),
1231
+ Array.isArray(tier.benefits) && tier.benefits.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: { margin: "0 0 20px", paddingLeft: 20 }, children: tier.benefits.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { style: { fontSize: 14, marginBottom: 4 }, children: b }, i)) }),
1232
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { style: { width: "100%", padding: "10px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: "Join Now" })
1233
+ ] }, tier.id)) })
1234
+ ] });
1235
+ }
1236
+
1237
+ // src/components/AppointmentBooker.tsx
1238
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1239
+ var gridStyle4 = {
1240
+ display: "grid",
1241
+ gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
1242
+ gap: 20
1243
+ };
1244
+ var cardStyle6 = {
1245
+ border: "1px solid #e5e7eb",
1246
+ borderRadius: 10,
1247
+ padding: "20px",
1248
+ background: "#fff"
1249
+ };
1250
+ function AppointmentBooker({ heading, subheading, limit }) {
1251
+ const { data: services, loading, error } = useServices({ limit });
1252
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading services\u2026" });
1253
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load services." });
1254
+ if (!services.length) return null;
1255
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { children: [
1256
+ heading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: { fontWeight: 700, fontSize: 26, marginBottom: 8 }, children: heading }),
1257
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: { color: "#6b7280", marginBottom: 28 }, children: subheading }),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: gridStyle4, children: services.map((svc) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: cardStyle6, children: [
1259
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 16 }, children: svc.name }),
1260
+ svc.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: { color: "#6b7280", fontSize: 13, margin: "0 0 10px" }, children: svc.description }),
1261
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: { fontWeight: 700, margin: "0 0 4px" }, children: [
1262
+ "\u20A6",
1263
+ (svc.priceKobo / 100).toLocaleString()
1264
+ ] }),
1265
+ svc.durationMinutes > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: { color: "#9ca3af", fontSize: 13, margin: "0 0 14px" }, children: [
1266
+ "\u23F1 ",
1267
+ svc.durationMinutes,
1268
+ " min"
1269
+ ] }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { style: { width: "100%", padding: "8px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: "Book Now" })
1271
+ ] }, svc.id)) })
1272
+ ] });
1273
+ }
1274
+
1275
+ // src/components/ArticleGrid.tsx
1276
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1277
+ var gridStyle5 = {
1278
+ display: "grid",
1279
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
1280
+ gap: 24
1281
+ };
1282
+ var cardStyle7 = {
1283
+ border: "1px solid #e5e7eb",
1284
+ borderRadius: 10,
1285
+ overflow: "hidden",
1286
+ background: "#fff"
1287
+ };
1288
+ var imgStyle5 = {
1289
+ width: "100%",
1290
+ height: 180,
1291
+ objectFit: "cover",
1292
+ background: "#f3f4f6",
1293
+ display: "block"
1294
+ };
1295
+ function ArticleGrid({ heading, limit, category, tag }) {
1296
+ const { data: articles, loading, error } = useArticles({ limit, category, tag });
1297
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading articles\u2026" });
1298
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load articles." });
1299
+ if (!articles.length) return null;
1300
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { children: [
1301
+ heading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { style: { fontWeight: 700, fontSize: 26, marginBottom: 20 }, children: heading }),
1302
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: gridStyle5, children: articles.map((article) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: cardStyle7, children: [
1303
+ article.coverUrl ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: article.coverUrl, alt: article.title, style: imgStyle5 }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { ...imgStyle5, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { padding: "14px 18px 18px" }, children: [
1305
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 15 }, children: article.title }),
1306
+ article.excerpt && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { color: "#6b7280", fontSize: 13, margin: "0 0 8px", lineHeight: 1.5 }, children: article.excerpt }),
1307
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { color: "#9ca3af", fontSize: 12 }, children: new Date(article.publishedAt).toLocaleDateString("en-NG", { day: "numeric", month: "long", year: "numeric" }) })
1308
+ ] })
1309
+ ] }, article.id)) })
1310
+ ] });
1311
+ }
1312
+
1313
+ // src/components/ArticleCard.tsx
1314
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1315
+ var cardStyle8 = {
1316
+ border: "1px solid #e5e7eb",
1317
+ borderRadius: 10,
1318
+ overflow: "hidden",
1319
+ background: "#fff",
1320
+ maxWidth: 340
1321
+ };
1322
+ var imgStyle6 = {
1323
+ width: "100%",
1324
+ height: 180,
1325
+ objectFit: "cover",
1326
+ background: "#f3f4f6",
1327
+ display: "block"
1328
+ };
1329
+ function ArticleCard({ article }) {
1330
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: cardStyle8, children: [
1331
+ article.coverUrl ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: article.coverUrl, alt: article.title, style: imgStyle6 }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { ...imgStyle6, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1332
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "14px 18px 18px" }, children: [
1333
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 15 }, children: article.title }),
1334
+ article.excerpt && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { color: "#6b7280", fontSize: 13, margin: "0 0 8px", lineHeight: 1.5 }, children: article.excerpt }),
1335
+ article.author && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { style: { color: "#9ca3af", fontSize: 12, margin: "0 0 4px" }, children: [
1336
+ "By ",
1337
+ article.author.name
1338
+ ] }),
1339
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { color: "#9ca3af", fontSize: 12 }, children: new Date(article.publishedAt).toLocaleDateString("en-NG", { day: "numeric", month: "long", year: "numeric" }) })
1340
+ ] })
1341
+ ] });
1342
+ }
1343
+
1344
+ // src/components/DonationCampaignGrid.tsx
1345
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1346
+ var gridStyle6 = {
1347
+ display: "grid",
1348
+ gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
1349
+ gap: 24
1350
+ };
1351
+ var cardStyle9 = {
1352
+ border: "1px solid #e5e7eb",
1353
+ borderRadius: 10,
1354
+ overflow: "hidden",
1355
+ background: "#fff"
1356
+ };
1357
+ var imgStyle7 = {
1358
+ width: "100%",
1359
+ height: 180,
1360
+ objectFit: "cover",
1361
+ background: "#f3f4f6",
1362
+ display: "block"
1363
+ };
1364
+ function progressPercent(raised, goal) {
1365
+ if (!goal || goal === 0) return 0;
1366
+ return Math.min(100, Math.round(raised / goal * 100));
1367
+ }
1368
+ function DonationCampaignGrid({ heading, subheading, limit }) {
1369
+ const { data: campaigns, loading, error } = useDonationCampaigns({ limit });
1370
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#9ca3af" }, children: "Loading campaigns\u2026" });
1371
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { padding: 24, color: "#ef4444" }, children: "Failed to load campaigns." });
1372
+ if (!campaigns.length) return null;
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { children: [
1374
+ heading && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: { fontWeight: 700, fontSize: 26, marginBottom: 8 }, children: heading }),
1375
+ subheading && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { color: "#6b7280", marginBottom: 28 }, children: subheading }),
1376
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: gridStyle6, children: campaigns.map((c) => {
1377
+ const pct = progressPercent(c.raisedAmountKobo, c.goalAmountKobo);
1378
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: cardStyle9, children: [
1379
+ c.coverImageUrl ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { src: c.coverImageUrl, alt: c.title, style: imgStyle7 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { ...imgStyle7, display: "flex", alignItems: "center", justifyContent: "center", color: "#d1d5db", fontSize: 12 }, children: "No image" }),
1380
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { padding: "14px 18px 18px" }, children: [
1381
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { fontWeight: 600, margin: "0 0 6px", fontSize: 16 }, children: c.title }),
1382
+ c.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { color: "#6b7280", fontSize: 13, margin: "0 0 10px" }, children: c.description }),
1383
+ c.goalAmountKobo && c.showGoalProgress && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1384
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { background: "#f3f4f6", borderRadius: 4, height: 6, marginBottom: 6 }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { background: "#22c55e", width: `${pct}%`, height: "100%", borderRadius: 4, transition: "width 0.3s" } }) }),
1385
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { style: { fontSize: 12, color: "#6b7280", margin: "0 0 12px" }, children: [
1386
+ "\u20A6",
1387
+ (c.raisedAmountKobo / 100).toLocaleString(),
1388
+ " raised of \u20A6",
1389
+ (c.goalAmountKobo / 100).toLocaleString(),
1390
+ " (",
1391
+ pct,
1392
+ "%)"
1393
+ ] })
1394
+ ] }),
1395
+ c.fixedAmountKobo != null && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { style: { fontSize: 13, fontWeight: 600, color: "#374151", margin: "0 0 10px" }, children: [
1396
+ "\u20A6",
1397
+ (c.fixedAmountKobo / 100).toLocaleString(),
1398
+ " per contribution"
1399
+ ] }),
1400
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { style: { width: "100%", padding: "9px 0", background: "#22c55e", color: "#fff", border: "none", borderRadius: 6, cursor: "pointer", fontWeight: 600 }, children: c.fixedAmountKobo != null ? `Contribute \u20A6${(c.fixedAmountKobo / 100).toLocaleString()}` : "Donate Now" })
1401
+ ] })
1402
+ ] }, c.id);
1403
+ }) })
1404
+ ] });
1405
+ }
1406
+
1407
+ // src/components/DonationForm.tsx
1408
+ var import_react24 = require("react");
1409
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1410
+ var inputStyle = {
1411
+ width: "100%",
1412
+ padding: "10px 12px",
1413
+ border: "1px solid #d1d5db",
1414
+ borderRadius: 6,
1415
+ fontSize: 14,
1416
+ outline: "none",
1417
+ boxSizing: "border-box"
1418
+ };
1419
+ function DonationForm({ campaignId, heading, buttonLabel = "Donate", primaryColor = "#111827", onSuccess }) {
1420
+ const { workspaceId, apiBase } = useSellHapi();
1421
+ const [amount, setAmount] = (0, import_react24.useState)("");
1422
+ const [name, setName] = (0, import_react24.useState)("");
1423
+ const [email, setEmail] = (0, import_react24.useState)("");
1424
+ const [phone, setPhone] = (0, import_react24.useState)("");
1425
+ const [loading, setLoading] = (0, import_react24.useState)(false);
1426
+ const [error, setError] = (0, import_react24.useState)(null);
1427
+ const [success, setSuccess] = (0, import_react24.useState)(false);
1428
+ if (success) {
1429
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { padding: 32, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { fontWeight: 700, fontSize: 18, color: primaryColor }, children: "Thank you for your donation! \u{1F64F}" }) });
1430
+ }
1431
+ async function handleSubmit(e) {
1432
+ e.preventDefault();
1433
+ const amountKobo = Math.round(parseFloat(amount) * 100);
1434
+ if (isNaN(amountKobo) || amountKobo <= 0) {
1435
+ setError("Please enter a valid amount.");
1436
+ return;
1437
+ }
1438
+ setLoading(true);
1439
+ setError(null);
1440
+ try {
1441
+ const res = await fetch(`${apiBase}/api/donations/storefront/donate`, {
1442
+ method: "POST",
1443
+ headers: { "Content-Type": "application/json" },
1444
+ body: JSON.stringify({
1445
+ vendorId: workspaceId,
1446
+ campaignId,
1447
+ amountKobo,
1448
+ customer: { name, email, phone: phone || "N/A" }
1449
+ })
1450
+ });
1451
+ const body = await res.json().catch(() => ({}));
1452
+ if (!res.ok) throw new Error(body.error ?? body.message ?? `HTTP ${res.status}`);
1453
+ onSuccess?.();
1454
+ if (body.paymentLink) {
1455
+ window.location.href = body.paymentLink;
1456
+ } else {
1457
+ setSuccess(true);
1458
+ }
1459
+ } catch (err) {
1460
+ setError(err instanceof Error ? err.message : "Donation failed. Please try again.");
1461
+ } finally {
1462
+ setLoading(false);
1463
+ }
1464
+ }
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("form", { onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: 14, maxWidth: 400 }, children: [
1466
+ heading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { style: { fontWeight: 700, margin: 0 }, children: heading }),
1467
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1468
+ "input",
1469
+ {
1470
+ type: "number",
1471
+ min: "1",
1472
+ step: "any",
1473
+ placeholder: "Amount (\u20A6)",
1474
+ value: amount,
1475
+ onChange: (e) => setAmount(e.target.value),
1476
+ style: inputStyle,
1477
+ required: true
1478
+ }
1479
+ ),
1480
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1481
+ "input",
1482
+ {
1483
+ type: "text",
1484
+ placeholder: "Your name",
1485
+ value: name,
1486
+ onChange: (e) => setName(e.target.value),
1487
+ style: inputStyle,
1488
+ required: true
1489
+ }
1490
+ ),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1492
+ "input",
1493
+ {
1494
+ type: "email",
1495
+ placeholder: "Email address",
1496
+ value: email,
1497
+ onChange: (e) => setEmail(e.target.value),
1498
+ style: inputStyle,
1499
+ required: true
1500
+ }
1501
+ ),
1502
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1503
+ "input",
1504
+ {
1505
+ type: "tel",
1506
+ placeholder: "Phone number (optional)",
1507
+ value: phone,
1508
+ onChange: (e) => setPhone(e.target.value),
1509
+ style: inputStyle
1510
+ }
1511
+ ),
1512
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { color: "#ef4444", fontSize: 13, margin: 0 }, children: error }),
1513
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1514
+ "button",
1515
+ {
1516
+ type: "submit",
1517
+ disabled: loading,
1518
+ style: { padding: "11px 0", background: primaryColor, color: "#fff", border: "none", borderRadius: 6, fontWeight: 600, cursor: loading ? "not-allowed" : "pointer", opacity: loading ? 0.7 : 1 },
1519
+ children: loading ? "Processing\u2026" : buttonLabel
1520
+ }
1521
+ )
1522
+ ] });
1523
+ }
1524
+
1525
+ // src/components/RSVPForm.tsx
1526
+ var import_react25 = require("react");
1527
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1528
+ var inputStyle2 = {
1529
+ width: "100%",
1530
+ padding: "10px 12px",
1531
+ border: "1px solid #d1d5db",
1532
+ borderRadius: 6,
1533
+ fontSize: 14,
1534
+ outline: "none",
1535
+ boxSizing: "border-box"
1536
+ };
1537
+ function RSVPForm({ eventId, heading, buttonLabel = "RSVP Now" }) {
1538
+ const { submit, loading, error, success } = useCreateRSVP();
1539
+ const [name, setName] = (0, import_react25.useState)("");
1540
+ const [email, setEmail] = (0, import_react25.useState)("");
1541
+ const [phone, setPhone] = (0, import_react25.useState)("");
1542
+ if (success) {
1543
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { padding: 32, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { fontWeight: 700, fontSize: 18 }, children: "You're registered! See you there. \u{1F389}" }) });
1544
+ }
1545
+ function handleSubmit(e) {
1546
+ e.preventDefault();
1547
+ void submit({ eventId, name, email, phone: phone || void 0 });
1548
+ }
1549
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("form", { onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: 14, maxWidth: 400 }, children: [
1550
+ heading && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { style: { fontWeight: 700, margin: 0 }, children: heading }),
1551
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1552
+ "input",
1553
+ {
1554
+ type: "text",
1555
+ placeholder: "Your name",
1556
+ value: name,
1557
+ onChange: (e) => setName(e.target.value),
1558
+ style: inputStyle2,
1559
+ required: true
1560
+ }
1561
+ ),
1562
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1563
+ "input",
1564
+ {
1565
+ type: "email",
1566
+ placeholder: "Email address",
1567
+ value: email,
1568
+ onChange: (e) => setEmail(e.target.value),
1569
+ style: inputStyle2,
1570
+ required: true
1571
+ }
1572
+ ),
1573
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1574
+ "input",
1575
+ {
1576
+ type: "tel",
1577
+ placeholder: "Phone number (optional)",
1578
+ value: phone,
1579
+ onChange: (e) => setPhone(e.target.value),
1580
+ style: inputStyle2
1581
+ }
1582
+ ),
1583
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { color: "#ef4444", fontSize: 13, margin: 0 }, children: error }),
1584
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1585
+ "button",
1586
+ {
1587
+ type: "submit",
1588
+ disabled: loading,
1589
+ style: { padding: "11px 0", background: "#111827", color: "#fff", border: "none", borderRadius: 6, fontWeight: 600, cursor: loading ? "not-allowed" : "pointer", opacity: loading ? 0.7 : 1 },
1590
+ children: loading ? "Registering\u2026" : buttonLabel
1591
+ }
1592
+ )
1593
+ ] });
1594
+ }