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