@tribe-nest/forge 1.13.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 (255) hide show
  1. package/package.json +42 -0
  2. package/src/client/createForgeClient.ts +54 -0
  3. package/src/content/ForgeContentProvider.tsx +118 -0
  4. package/src/content/ForgeEditBridge.tsx +134 -0
  5. package/src/content/editFlag.ts +23 -0
  6. package/src/content/index.ts +28 -0
  7. package/src/content/resolve.ts +67 -0
  8. package/src/content/types.ts +97 -0
  9. package/src/content/useField.ts +64 -0
  10. package/src/contexts/AppAuthContext.tsx +185 -0
  11. package/src/contexts/AudioPlayerContext.tsx +389 -0
  12. package/src/contexts/CartContext.tsx +131 -0
  13. package/src/contexts/PublicAuthContext.tsx +333 -0
  14. package/src/contexts/useAppAdminGuard.ts +56 -0
  15. package/src/css.d.ts +5 -0
  16. package/src/data/collectionParams.ts +57 -0
  17. package/src/data/queries/useAccount.ts +71 -0
  18. package/src/data/queries/useAccountSettings.ts +82 -0
  19. package/src/data/queries/useAnalytics.ts +86 -0
  20. package/src/data/queries/useAuthActions.ts +28 -0
  21. package/src/data/queries/useBlog.ts +103 -0
  22. package/src/data/queries/useBlogComments.ts +96 -0
  23. package/src/data/queries/useBroadcasts.ts +31 -0
  24. package/src/data/queries/useCertificates.ts +58 -0
  25. package/src/data/queries/useChat.ts +267 -0
  26. package/src/data/queries/useCoachingAvailability.ts +69 -0
  27. package/src/data/queries/useCoachingProducts.ts +67 -0
  28. package/src/data/queries/useCohortPage.ts +17 -0
  29. package/src/data/queries/useCollections.ts +211 -0
  30. package/src/data/queries/useCommunity.ts +794 -0
  31. package/src/data/queries/useCoupons.ts +20 -0
  32. package/src/data/queries/useCourseAccess.ts +50 -0
  33. package/src/data/queries/useCourses.ts +91 -0
  34. package/src/data/queries/useCurrencies.ts +33 -0
  35. package/src/data/queries/useDocuments.ts +208 -0
  36. package/src/data/queries/useDonations.ts +29 -0
  37. package/src/data/queries/useEngagement.ts +115 -0
  38. package/src/data/queries/useEvents.ts +69 -0
  39. package/src/data/queries/useFinalize.ts +109 -0
  40. package/src/data/queries/useForms.ts +31 -0
  41. package/src/data/queries/useInvoice.ts +65 -0
  42. package/src/data/queries/useLeadMagnet.ts +96 -0
  43. package/src/data/queries/useMembership.ts +20 -0
  44. package/src/data/queries/useNotifications.ts +267 -0
  45. package/src/data/queries/useOffers.ts +56 -0
  46. package/src/data/queries/useOrders.ts +71 -0
  47. package/src/data/queries/usePWA.ts +101 -0
  48. package/src/data/queries/usePageActions.ts +36 -0
  49. package/src/data/queries/usePaymentFlow.ts +80 -0
  50. package/src/data/queries/usePaymentLink.ts +47 -0
  51. package/src/data/queries/usePodcast.ts +85 -0
  52. package/src/data/queries/usePostCollections.ts +48 -0
  53. package/src/data/queries/usePosts.ts +94 -0
  54. package/src/data/queries/useProducts.ts +108 -0
  55. package/src/data/queries/useReplay.ts +19 -0
  56. package/src/data/queries/useReviews.ts +122 -0
  57. package/src/data/queries/useShipping.ts +109 -0
  58. package/src/data/queries/useSubscriptions.ts +66 -0
  59. package/src/data/queries/useWebsite.ts +128 -0
  60. package/src/data/queries/useWebsiteAgent.ts +100 -0
  61. package/src/index.ts +98 -0
  62. package/src/provider/ForgeAppProvider.tsx +149 -0
  63. package/src/provider/ForgeProvider.tsx +91 -0
  64. package/src/provider/SiteConfigProvider.tsx +27 -0
  65. package/src/server/_tests/buildPwaHead.spec.ts +182 -0
  66. package/src/server/_tests/buildWebManifest.spec.ts +313 -0
  67. package/src/server/_tests/forgeServiceWorkerScript.spec.ts +251 -0
  68. package/src/server/index.ts +260 -0
  69. package/src/server/jobs.ts +104 -0
  70. package/src/server/pwa.ts +332 -0
  71. package/src/types/index.ts +74 -0
  72. package/src/types/models.ts +1313 -0
  73. package/src/ui/analytics/ForgeAnalytics.tsx +208 -0
  74. package/src/ui/analytics/PageMetaPixel.tsx +92 -0
  75. package/src/ui/content/Editable.tsx +90 -0
  76. package/src/ui/format/PriceDisplay.tsx +142 -0
  77. package/src/ui/format/useFormatCurrency.ts +92 -0
  78. package/src/ui/headless/agent/useAiAgent.ts +106 -0
  79. package/src/ui/headless/auth/useLoginFlow.ts +72 -0
  80. package/src/ui/headless/auth/useSignupForm.ts +85 -0
  81. package/src/ui/headless/broadcast/useBroadcastWatch.ts +59 -0
  82. package/src/ui/headless/chat/attachmentKind.ts +27 -0
  83. package/src/ui/headless/chat/useChatAttachmentUpload.ts +132 -0
  84. package/src/ui/headless/chat/useChatRoom.ts +223 -0
  85. package/src/ui/headless/chat/useMessageThread.ts +60 -0
  86. package/src/ui/headless/checkout/useCheckout.ts +438 -0
  87. package/src/ui/headless/coaching/useCoachingBooking.ts +223 -0
  88. package/src/ui/headless/community/index.ts +20 -0
  89. package/src/ui/headless/community/lexical.ts +131 -0
  90. package/src/ui/headless/community/replyTree.ts +51 -0
  91. package/src/ui/headless/community/richLexical.ts +314 -0
  92. package/src/ui/headless/community/useCommunityAttachmentUpload.ts +137 -0
  93. package/src/ui/headless/community/useCommunitySpaceFeed.ts +122 -0
  94. package/src/ui/headless/community/useCommunityThread.ts +95 -0
  95. package/src/ui/headless/community/useMentionComposer.ts +105 -0
  96. package/src/ui/headless/community/useRichMentionComposer.ts +193 -0
  97. package/src/ui/headless/consent/useCookieConsent.ts +81 -0
  98. package/src/ui/headless/course/useCourseCheckout.ts +99 -0
  99. package/src/ui/headless/dialog.tsx +14 -0
  100. package/src/ui/headless/document/index.ts +7 -0
  101. package/src/ui/headless/document/useContractSignature.ts +68 -0
  102. package/src/ui/headless/document/useDocumentSigningFlow.ts +92 -0
  103. package/src/ui/headless/document/useOtpCodeInput.ts +86 -0
  104. package/src/ui/headless/document/useSignaturePad.ts +180 -0
  105. package/src/ui/headless/donation/Donation.tsx +115 -0
  106. package/src/ui/headless/donation/DonationContext.tsx +223 -0
  107. package/src/ui/headless/donation/fees.ts +15 -0
  108. package/src/ui/headless/donation/index.ts +10 -0
  109. package/src/ui/headless/engagement/usePostEngagement.ts +119 -0
  110. package/src/ui/headless/event/useEventCheckout.ts +141 -0
  111. package/src/ui/headless/forms/useContactForm.ts +58 -0
  112. package/src/ui/headless/forms/useEmailListForm.ts +75 -0
  113. package/src/ui/headless/forms/useSectionedForm.ts +256 -0
  114. package/src/ui/headless/index.ts +60 -0
  115. package/src/ui/headless/invoice/useInvoicePayment.ts +48 -0
  116. package/src/ui/headless/memberHome/index.ts +7 -0
  117. package/src/ui/headless/memberHome/useMemberHomeNav.ts +82 -0
  118. package/src/ui/headless/membership/MembershipGate.tsx +39 -0
  119. package/src/ui/headless/membership/useMembershipCheckout.ts +121 -0
  120. package/src/ui/headless/offer/Offer.tsx +88 -0
  121. package/src/ui/headless/offer/OfferContext.tsx +162 -0
  122. package/src/ui/headless/offer/index.ts +8 -0
  123. package/src/ui/headless/paymentLink/usePaymentLinkPayment.ts +46 -0
  124. package/src/ui/headless/podcast/usePodcastPlayer.ts +96 -0
  125. package/src/ui/headless/posts/index.ts +13 -0
  126. package/src/ui/headless/posts/upsell.ts +41 -0
  127. package/src/ui/headless/posts/useMemberPostsFeed.ts +110 -0
  128. package/src/ui/headless/posts/usePostCollectionDetail.ts +56 -0
  129. package/src/ui/headless/posts/usePostCollectionsList.ts +90 -0
  130. package/src/ui/headless/posts/usePostItem.ts +81 -0
  131. package/src/ui/headless/reviews/index.ts +6 -0
  132. package/src/ui/headless/reviews/useHelpfulVote.ts +45 -0
  133. package/src/ui/headless/reviews/useReviewForm.ts +108 -0
  134. package/src/ui/headless/reviews/useReviewsList.ts +79 -0
  135. package/src/ui/headless/work/index.ts +25 -0
  136. package/src/ui/headless/work/useWorkPortal.ts +349 -0
  137. package/src/ui/index.ts +144 -0
  138. package/src/ui/payment/ForgePaymentProvider.tsx +45 -0
  139. package/src/ui/payment/ForgeStripePayment.tsx +171 -0
  140. package/src/ui/shell/TribeNestApp.tsx +121 -0
  141. package/src/ui/shell/shellGating.spec.ts +28 -0
  142. package/src/ui/shell/shellGating.ts +11 -0
  143. package/src/ui/styled/AccountDashboard.tsx +575 -0
  144. package/src/ui/styled/AiAgentWidget.tsx +152 -0
  145. package/src/ui/styled/AudioPlayer.tsx +123 -0
  146. package/src/ui/styled/BlogCategory.tsx +79 -0
  147. package/src/ui/styled/BlogComments.tsx +236 -0
  148. package/src/ui/styled/BlogList.tsx +101 -0
  149. package/src/ui/styled/BlogPost.tsx +160 -0
  150. package/src/ui/styled/BlogPostFooter.tsx +33 -0
  151. package/src/ui/styled/BotProtection.tsx +101 -0
  152. package/src/ui/styled/Button.tsx +88 -0
  153. package/src/ui/styled/Cart.tsx +276 -0
  154. package/src/ui/styled/ChatRoom.tsx +252 -0
  155. package/src/ui/styled/Checkout.tsx +988 -0
  156. package/src/ui/styled/CheckoutConfirmation.tsx +452 -0
  157. package/src/ui/styled/CoachingBooking.tsx +605 -0
  158. package/src/ui/styled/CoachingConfirmation.tsx +151 -0
  159. package/src/ui/styled/CoachingDetail.tsx +99 -0
  160. package/src/ui/styled/CohortPage.tsx +82 -0
  161. package/src/ui/styled/ConfirmSubscription.tsx +134 -0
  162. package/src/ui/styled/Confirmation.tsx +171 -0
  163. package/src/ui/styled/ContactForm.tsx +81 -0
  164. package/src/ui/styled/CookieConsent.tsx +239 -0
  165. package/src/ui/styled/CourseAccess.tsx +169 -0
  166. package/src/ui/styled/CourseCheckout.tsx +425 -0
  167. package/src/ui/styled/CourseConfirmation.tsx +191 -0
  168. package/src/ui/styled/CourseDetail.tsx +140 -0
  169. package/src/ui/styled/CoursesGrid.tsx +85 -0
  170. package/src/ui/styled/CurrencySwitcher.tsx +51 -0
  171. package/src/ui/styled/DonationButton.tsx +247 -0
  172. package/src/ui/styled/DonationPage.tsx +29 -0
  173. package/src/ui/styled/EmailListForm.tsx +88 -0
  174. package/src/ui/styled/EventConfirmation.tsx +228 -0
  175. package/src/ui/styled/EventCountdown.tsx +98 -0
  176. package/src/ui/styled/EventDetail.tsx +141 -0
  177. package/src/ui/styled/EventTickets.tsx +548 -0
  178. package/src/ui/styled/EventsList.tsx +83 -0
  179. package/src/ui/styled/ForgotPasswordForm.tsx +105 -0
  180. package/src/ui/styled/FormRenderer.tsx +249 -0
  181. package/src/ui/styled/InstallBanner.tsx +114 -0
  182. package/src/ui/styled/InvoiceConfirmation.tsx +50 -0
  183. package/src/ui/styled/InvoicePayment.tsx +141 -0
  184. package/src/ui/styled/LeadMagnet.tsx +93 -0
  185. package/src/ui/styled/Loading.tsx +56 -0
  186. package/src/ui/styled/LoginForm.tsx +134 -0
  187. package/src/ui/styled/Markdown.tsx +103 -0
  188. package/src/ui/styled/MembershipCheckout.tsx +197 -0
  189. package/src/ui/styled/MembershipTiers.tsx +115 -0
  190. package/src/ui/styled/OfferButton.tsx +181 -0
  191. package/src/ui/styled/PageActions.tsx +124 -0
  192. package/src/ui/styled/PaymentLinkConfirmation.tsx +50 -0
  193. package/src/ui/styled/PaymentLinkPayment.tsx +85 -0
  194. package/src/ui/styled/Paywall.tsx +54 -0
  195. package/src/ui/styled/PlayButton.tsx +48 -0
  196. package/src/ui/styled/PodcastEpisode.tsx +137 -0
  197. package/src/ui/styled/PodcastList.tsx +58 -0
  198. package/src/ui/styled/PodcastShow.tsx +164 -0
  199. package/src/ui/styled/PostsFeed.tsx +63 -0
  200. package/src/ui/styled/ProductDetail.tsx +616 -0
  201. package/src/ui/styled/ProductGrid.tsx +82 -0
  202. package/src/ui/styled/PushOptIn.tsx +65 -0
  203. package/src/ui/styled/PwaRegistration.tsx +58 -0
  204. package/src/ui/styled/ReactionBar.tsx +93 -0
  205. package/src/ui/styled/ReplayList.tsx +134 -0
  206. package/src/ui/styled/ResetPasswordForm.tsx +127 -0
  207. package/src/ui/styled/ReviewForm.tsx +292 -0
  208. package/src/ui/styled/ReviewsSection.tsx +260 -0
  209. package/src/ui/styled/SectionedFormRenderer.tsx +146 -0
  210. package/src/ui/styled/SignupForm.tsx +109 -0
  211. package/src/ui/styled/UserMenu.tsx +166 -0
  212. package/src/ui/styled/chat/ChatAttachments.tsx +172 -0
  213. package/src/ui/styled/chat/ChatComposer.tsx +153 -0
  214. package/src/ui/styled/chat/ChatThreadPanel.tsx +94 -0
  215. package/src/ui/styled/chat/MessageBubble.tsx +66 -0
  216. package/src/ui/styled/chat/NewDmDialog.tsx +78 -0
  217. package/src/ui/styled/community/CommunityComposer.tsx +358 -0
  218. package/src/ui/styled/community/CommunityFeed.tsx +483 -0
  219. package/src/ui/styled/community/CommunityLeaderboard.tsx +109 -0
  220. package/src/ui/styled/community/CommunityMediaGallery.tsx +116 -0
  221. package/src/ui/styled/community/CommunityMemberProfile.tsx +285 -0
  222. package/src/ui/styled/community/CommunityNotifications.tsx +222 -0
  223. package/src/ui/styled/community/CommunityPostCard.tsx +165 -0
  224. package/src/ui/styled/community/CommunityPostDetail.tsx +337 -0
  225. package/src/ui/styled/community/CommunityReactionBar.tsx +107 -0
  226. package/src/ui/styled/community/CommunityRichText.tsx +157 -0
  227. package/src/ui/styled/community/CommunitySpaces.tsx +174 -0
  228. package/src/ui/styled/community/MemberAvatar.tsx +74 -0
  229. package/src/ui/styled/community/index.ts +23 -0
  230. package/src/ui/styled/community/util.ts +15 -0
  231. package/src/ui/styled/members/MemberHomeLayout.tsx +98 -0
  232. package/src/ui/styled/members/MemberPostCard.tsx +392 -0
  233. package/src/ui/styled/members/MemberPostsFeed.tsx +219 -0
  234. package/src/ui/styled/members/PostCollectionDetail.tsx +227 -0
  235. package/src/ui/styled/members/PostCollectionsGrid.tsx +287 -0
  236. package/src/ui/styled/members/index.ts +7 -0
  237. package/src/ui/styled/rich-text.css +150 -0
  238. package/src/ui/styled/work/WorkInviteAccept.tsx +222 -0
  239. package/src/ui/styled/work/WorkPortalProjects.tsx +163 -0
  240. package/src/ui/styled/work/WorkProjectInvoices.tsx +113 -0
  241. package/src/ui/styled/work/WorkProjectReport.tsx +66 -0
  242. package/src/ui/styled/work/WorkReportBody.tsx +327 -0
  243. package/src/ui/styled/work/WorkTaskAttachments.tsx +67 -0
  244. package/src/ui/styled/work/WorkTaskComments.tsx +151 -0
  245. package/src/ui/styled/work/WorkTaskDetail.tsx +92 -0
  246. package/src/ui/styled/work/WorkTokenReport.tsx +49 -0
  247. package/src/ui/styled/work/index.ts +10 -0
  248. package/src/ui/theme/ForgeThemeProvider.tsx +121 -0
  249. package/src/ui/theme/contrast.ts +22 -0
  250. package/src/utils/analyticsBus.ts +31 -0
  251. package/src/utils/attribution.ts +68 -0
  252. package/src/utils/cookieConsent.ts +83 -0
  253. package/src/utils/landing.ts +153 -0
  254. package/src/utils/metaPixel.ts +122 -0
  255. package/src/utils/structuredData.ts +126 -0
@@ -0,0 +1,1313 @@
1
+ // Core domain/data model types — owned by Forge (the base SDK layer).
2
+ // `frontend-shared` re-exports these so its existing imports keep working,
3
+ // which is what flips the dependency to frontend-shared → forge.
4
+
5
+ export type ProfileAddress = {
6
+ street: string;
7
+ city: string;
8
+ state: string;
9
+ zipCode?: string;
10
+ country: string;
11
+ };
12
+
13
+ export type PWAConfig = {
14
+ name: string;
15
+ shortName: string;
16
+ description: string;
17
+ icon192: string;
18
+ icon512: string;
19
+ icon96: string;
20
+ screenshotWide1280X720: string;
21
+ screenshotNarrow750X1334: string;
22
+ };
23
+
24
+ export enum PaymentProviderName {
25
+ Stripe = "stripe",
26
+ Paystack = "paystack",
27
+ }
28
+
29
+ // ---- Sales tax (financial back-office) ----------------------------------------
30
+
31
+ /** One jurisdiction line of a checkout tax quote. `amountCents` is MINOR units
32
+ * (matches the backend `TaxBreakdownItem` passed through unchanged). */
33
+ export type TaxBreakdownEntry = {
34
+ jurisdiction: string;
35
+ /** Decimal rate, e.g. 0.19 for 19%. */
36
+ rate: number;
37
+ amountCents: number;
38
+ };
39
+
40
+ /**
41
+ * The authoritative checkout tax quote every pillar start-payment response now
42
+ * returns (`taxQuote`). All amounts are MAJOR units in `currency` — the charged
43
+ * currency. `pricesIncludeTax` selects inclusive (EU-style: the total already
44
+ * contains `tax`) vs exclusive (US-style: `tax` was added on top) display.
45
+ * The charged amount already reflects this quote — display only, never used
46
+ * for charge math on the client.
47
+ */
48
+ export type PublicTaxQuote = {
49
+ subtotal: number;
50
+ tax: number;
51
+ total: number;
52
+ currency: string;
53
+ pricesIncludeTax: boolean;
54
+ taxBreakdown: TaxBreakdownEntry[];
55
+ };
56
+
57
+ /**
58
+ * Superset response of the `/public/<domain>/.../start-payment` endpoint family.
59
+ * Each domain (orders, events, coaching, courses, invoices, payment-links,
60
+ * donations) returns the same core — paymentSecret + paymentId + a provider —
61
+ * plus its own optional extras. The provider field is named inconsistently
62
+ * across domains (`paymentProvider` vs `paymentProviderName`); usePaymentFlow
63
+ * normalizes it to `provider`.
64
+ */
65
+ export type PaymentStartResponse = {
66
+ paymentSecret: string;
67
+ paymentId: string;
68
+ paymentProvider?: PaymentProviderName;
69
+ paymentProviderName?: PaymentProviderName;
70
+ amount?: number;
71
+ currency?: string;
72
+ // Domain-specific extras (present only for the relevant flow):
73
+ orderId?: string;
74
+ bookingId?: string;
75
+ donationPaymentId?: string;
76
+ totalAmount?: number;
77
+ subTotal?: number;
78
+ feeAmount?: number;
79
+ isFreeCheckout?: boolean;
80
+ shippingCosts?: { deliveryGroupId: string; amount: number; currency: string }[];
81
+ /** Authoritative sales-tax quote for this charge (all wired pillars). */
82
+ taxQuote?: PublicTaxQuote;
83
+ };
84
+
85
+ /** Normalized result surfaced by usePaymentFlow. */
86
+ export type PaymentFlowResult = PaymentStartResponse & {
87
+ /** Provider resolved from paymentProvider | paymentProviderName. */
88
+ provider?: PaymentProviderName;
89
+ };
90
+
91
+ export enum ProductDeliveryType {
92
+ Digital = "digital",
93
+ Physical = "physical",
94
+ Service = "service",
95
+ }
96
+
97
+ /**
98
+ * The artist/tenant profile. Consolidates the two `Profile` declarations that
99
+ * were interface-merged in the old `frontend-shared/src/types.ts` (the base
100
+ * fields + `avatar`).
101
+ */
102
+ export interface Profile {
103
+ id: string;
104
+ name: string;
105
+ paymentProviderName: PaymentProviderName;
106
+ paymentProviderPublicKey: string;
107
+ pwaConfig: PWAConfig;
108
+ address: ProfileAddress;
109
+ stripeAccountId: string;
110
+ paystackAccountId: string;
111
+ currency?: string;
112
+ subdomain: string;
113
+ avatar?: string;
114
+ isDemo?: boolean;
115
+ /** Store display mode from sales-tax settings: true = listing prices already
116
+ * contain tax ("incl. VAT" style). Display-only; carried on the public
117
+ * website payload so blocks can render the right form before checkout. */
118
+ pricesIncludeTax?: boolean;
119
+ }
120
+
121
+ export type ApiError = {
122
+ response: {
123
+ data: {
124
+ message: string;
125
+ };
126
+ };
127
+ };
128
+
129
+ // ---- Currency ----------------------------------------------------------------
130
+
131
+ export enum Currency {
132
+ USD = "USD",
133
+ EUR = "EUR",
134
+ GBP = "GBP",
135
+ CAD = "CAD",
136
+ AUD = "AUD",
137
+ NZD = "NZD",
138
+ CHF = "CHF",
139
+ JPY = "JPY",
140
+ NGN = "NGN",
141
+ GHS = "GHS",
142
+ ZAR = "ZAR",
143
+ KES = "KES",
144
+ }
145
+
146
+ export const SUPPORTED_CURRENCIES = [
147
+ Currency.USD,
148
+ Currency.EUR,
149
+ Currency.GBP,
150
+ Currency.CAD,
151
+ Currency.AUD,
152
+ Currency.NZD,
153
+ Currency.CHF,
154
+ Currency.JPY,
155
+ Currency.NGN,
156
+ Currency.GHS,
157
+ Currency.ZAR,
158
+ Currency.KES,
159
+ ];
160
+
161
+ export const currencySymbol: Record<Currency, string> = {
162
+ [Currency.USD]: "$",
163
+ [Currency.EUR]: "€",
164
+ [Currency.GBP]: "£",
165
+ [Currency.CAD]: "CA$",
166
+ [Currency.AUD]: "AU$",
167
+ [Currency.NZD]: "NZ$",
168
+ [Currency.CHF]: "₣",
169
+ [Currency.JPY]: "¥",
170
+ [Currency.NGN]: "₦",
171
+ [Currency.GHS]: "GH₵",
172
+ [Currency.ZAR]: "R",
173
+ [Currency.KES]: "KES",
174
+ };
175
+
176
+ // ---- Membership --------------------------------------------------------------
177
+
178
+ export type MembershipBenefit = {
179
+ id: string;
180
+ title: string;
181
+ };
182
+
183
+ export type MembershipTier = {
184
+ id: string;
185
+ name: string;
186
+ description: string;
187
+ priceMonthly?: number;
188
+ priceYearly?: number;
189
+ payWhatYouWant: boolean;
190
+ payWhatYouWantMinimum?: number;
191
+ payWhatYouWantMaximum?: number;
192
+ payWhatYouWantYearlyMinimum?: number;
193
+ payWhatYouWantYearlyMaximum?: number;
194
+ benefits: MembershipBenefit[];
195
+ smartLinkPath?: string;
196
+ smartLinkId?: string;
197
+ welcomeMessageSubject?: string | null;
198
+ welcomeMessageContent?: string | null;
199
+ cancellationMessageSubject?: string | null;
200
+ cancellationMessageContent?: string | null;
201
+ };
202
+
203
+ export interface Membership {
204
+ id: string;
205
+ membershipTierId: string;
206
+ profilePaymentSubscriptionId?: string;
207
+ paymentProviderSubscriptionId?: string;
208
+ endDate: string;
209
+ status: string;
210
+ membershipTier: MembershipTier;
211
+ startDate: string;
212
+ subscriptionAmount: number;
213
+ subscriptionCurrency: string;
214
+ billingCycle: string;
215
+ }
216
+
217
+ // ---- Public auth -------------------------------------------------------------
218
+
219
+ export interface PublicAuthUser {
220
+ email: string;
221
+ firstName: string;
222
+ lastName: string;
223
+ kind: string;
224
+ status: string;
225
+ createdAt: string;
226
+ updatedAt: string;
227
+ id: string;
228
+ membership?: Membership;
229
+ }
230
+
231
+ export type UpdateLocalUserPayload = Pick<PublicAuthUser, "firstName">;
232
+
233
+ // Auth input shapes. The zod schemas + react-hook-form resolvers stay in
234
+ // frontend-shared/src/schema/auth (the form layer); Forge only needs the shapes.
235
+ export type LoginInput = {
236
+ email: string;
237
+ password: string;
238
+ };
239
+
240
+ export type PublicCreateAccountInput = {
241
+ email: string;
242
+ password: string;
243
+ firstName: string;
244
+ lastName: string;
245
+ profileId: string;
246
+ membershipTierId?: string;
247
+ couponCode?: string;
248
+ acceptedTerms?: boolean;
249
+ acceptedPrivacy?: boolean;
250
+ };
251
+
252
+ export interface PublicAuthState {
253
+ isAuthenticated: boolean;
254
+ isInitialized: boolean;
255
+ user: PublicAuthUser | null;
256
+ isLoading: boolean;
257
+ errorMessage: string | null;
258
+ initialize: () => Promise<boolean>;
259
+ userSelectedCurrency: Currency | null;
260
+ setUserSelectedCurrency: (currency: Currency) => void;
261
+ currencies: {
262
+ supportedCurrencies: Currency[];
263
+ userCurrency: Currency;
264
+ exchangeRates: Record<Currency, Record<Currency, number>>;
265
+ } | null;
266
+ }
267
+
268
+ export interface PublicAuthContextType extends PublicAuthState {
269
+ login: (data: LoginInput & { profileId: string }) => Promise<{ smartLinkPath: string | undefined }>;
270
+ register: (data: PublicCreateAccountInput) => Promise<{ smartLinkPath: string | undefined }>;
271
+ validateLoginCredentials: (data: LoginInput & { profileId: string }) => Promise<string>;
272
+ verifyLoginCode: (email: string, code: string, profileId: string) => Promise<{ smartLinkPath: string | undefined }>;
273
+ resendLoginCode: (email: string) => Promise<void>;
274
+ logout: (persist?: boolean) => Promise<void>;
275
+ updateLocalUser: (data: UpdateLocalUserPayload) => void;
276
+ clearErrorMessage: () => void;
277
+ refetchUser: () => Promise<void>;
278
+ }
279
+
280
+ export type ActionMap<M extends { [index: string]: unknown }> = {
281
+ [Key in keyof M]: M[Key] extends undefined
282
+ ? {
283
+ type: Key;
284
+ }
285
+ : {
286
+ type: Key;
287
+ payload: M[Key];
288
+ };
289
+ };
290
+
291
+ // ---- Public content / commerce data model -----------------------------------
292
+
293
+ export type PaginatedData<T> = {
294
+ data: T[];
295
+ total: number;
296
+ hasNextPage: boolean;
297
+ page: number;
298
+ nextPage: number | null;
299
+ pageSize: number;
300
+ };
301
+
302
+ // --- CMS collections (custom content types + entries) ---
303
+
304
+ export type CollectionFieldType =
305
+ | "text"
306
+ | "richtext"
307
+ | "number"
308
+ | "boolean"
309
+ | "date"
310
+ | "select"
311
+ | "multiselect"
312
+ | "tags"
313
+ | "media"
314
+ | "geo"
315
+ | "reference"
316
+ | "group"
317
+ | "list";
318
+
319
+ export type CollectionField = {
320
+ id: string;
321
+ collectionId: string;
322
+ key: string;
323
+ label: string;
324
+ type: CollectionFieldType;
325
+ section: string | null;
326
+ description: string | null;
327
+ placeholder: string | null;
328
+ options: Record<string, unknown> | null;
329
+ validation: Record<string, unknown> | null;
330
+ isTitle: boolean;
331
+ isSearchable: boolean;
332
+ isFilterable: boolean;
333
+ isSortable: boolean;
334
+ // Privacy tier. The public API only ever returns `public` fields — `private`
335
+ // fields are stripped server-side — so consumers won't normally see this set
336
+ // to `private`; it's here for completeness/admin surfaces.
337
+ visibility?: "public" | "private";
338
+ order: number;
339
+ };
340
+
341
+ /** A collection definition + its ordered field schema (from the `…/schema` endpoint). */
342
+ export type Collection = {
343
+ id: string;
344
+ profileId: string;
345
+ name: string;
346
+ slug: string;
347
+ singular: string | null;
348
+ plural: string | null;
349
+ kind: "entity" | "taxonomy";
350
+ icon: string | null;
351
+ settings: Record<string, unknown>;
352
+ // `private` collections 404 on the public API, so a public consumer only ever
353
+ // receives `public` ones; present for completeness.
354
+ visibility?: "public" | "private";
355
+ publishedAt: string | null;
356
+ fields: CollectionField[];
357
+ };
358
+
359
+ /** A single row in a collection. `data` is keyed by the collection's field keys. */
360
+ export type CollectionEntry = {
361
+ id: string;
362
+ collectionId: string;
363
+ profileId: string;
364
+ slug: string;
365
+ status: "draft" | "published";
366
+ data: Record<string, unknown>;
367
+ publishedAt: string | null;
368
+ /** Metres from the `near` point — present only on distance/geo queries. */
369
+ distance?: number;
370
+ /** Expanded reference fields (present only when `expand` was requested). */
371
+ expanded?: Record<string, unknown>;
372
+ /** Reverse-relation rows (present only when `reverse` was requested). */
373
+ related?: Record<string, unknown[]>;
374
+ };
375
+
376
+ export type CollectionFacet = { value: string; count: number };
377
+
378
+ /** Result of a collection entry search — a page of entries plus facet counts. */
379
+ export type CollectionSearchResult = PaginatedData<CollectionEntry> & {
380
+ facets: Record<string, CollectionFacet[]>;
381
+ /** Stable cursor for the next page (present only for cursor pagination). */
382
+ nextCursor?: string | null;
383
+ };
384
+
385
+ // A leaf { field, op, value } or an and/or group — the structured filter tree.
386
+ export type CollectionFilterOp =
387
+ | "eq" | "ne" | "gt" | "gte" | "lt" | "lte"
388
+ | "in" | "between" | "contains" | "startsWith" | "arrayContains" | "isNull";
389
+ export type CollectionFilterNode =
390
+ | { and: CollectionFilterNode[] }
391
+ | { or: CollectionFilterNode[] }
392
+ | { field: string; op: CollectionFilterOp; value?: unknown };
393
+ export type CollectionSortKey = "relevance" | "distance" | { field: string; dir?: "asc" | "desc" };
394
+
395
+ /**
396
+ * Query params for the simple `useCollectionEntries` reads (the frozen GET
397
+ * /entries contract — unchanged, lenient: filters it can't apply are dropped).
398
+ * For the richer engine (reference expansion, `select`, cursor, `mine` scope,
399
+ * strict validation) use `CollectionQuery` + `useCollectionQuery`.
400
+ */
401
+ export type CollectionQueryParams = {
402
+ page?: number;
403
+ limit?: number;
404
+ q?: string; // full-text
405
+ filter?: Record<string, string | string[]>; // field key -> value(s)
406
+ range?: Record<string, { gte?: number; lte?: number }>;
407
+ near?: { lat: number; lng: number; radius?: number }; // radius in metres
408
+ sort?: string; // "field" | "-field" | "distance" | "relevance"
409
+ facets?: string[]; // field keys to return counts for
410
+ };
411
+
412
+ // Reverse relation — rows in another collection that point back at each result.
413
+ export type CollectionReverseRelation = { collection: string; field: string; as: string; limit?: number };
414
+
415
+ /**
416
+ * The structured query DSL for `useCollectionQuery` / the POST /query endpoint.
417
+ * STRICT: an undeclared field or a wrong-kind operator is a 400 (unlike the
418
+ * lenient GET path), so it surfaces authoring mistakes.
419
+ */
420
+ export type CollectionQuery = {
421
+ where?: CollectionFilterNode;
422
+ search?: string; // full-text
423
+ near?: { lat: number; lng: number };
424
+ radius?: number; // metres
425
+ sort?: CollectionSortKey | CollectionSortKey[];
426
+ select?: string[]; // project a subset of data keys
427
+ expand?: string[]; // reference fields to inline (1 level)
428
+ reverse?: CollectionReverseRelation[]; // rows pointing back at each result
429
+ facets?: string[];
430
+ page?: number;
431
+ limit?: number;
432
+ cursor?: string; // stable keyset pagination
433
+ /** `mine` returns the logged-in member's own rows (drafts included). */
434
+ scope?: "public" | "mine";
435
+ };
436
+
437
+ /** Aggregation over a collection (group-by + metrics) — POST /aggregate. */
438
+ export type CollectionAggregate = {
439
+ groupBy: string;
440
+ metrics?: Array<{ op: "count" | "sum" | "avg" | "min" | "max"; field?: string; as: string }>;
441
+ having?: Array<{ metric: string; op: "gt" | "gte" | "lt" | "lte" | "eq" | "ne"; value: number }>;
442
+ orderBy?: { metric: string; dir?: "asc" | "desc" };
443
+ limit?: number;
444
+ };
445
+ export type CollectionAggregateBucket = { value: string; count: number } & Record<string, number | string>;
446
+
447
+ export type MediaType = "image" | "video" | "audio" | "document";
448
+
449
+ export type IMedia = {
450
+ id: string;
451
+ url: string;
452
+ size: number;
453
+ filename: string;
454
+ type: MediaType;
455
+ previewUrl?: string | null;
456
+ previewStatus?: string | null;
457
+ };
458
+
459
+ export enum ProductCategory {
460
+ Music = "Music",
461
+ Merch = "Merch",
462
+ Digital = "Digital",
463
+ Service = "Service",
464
+ Coaching = "Coaching",
465
+ Course = "Course",
466
+ }
467
+
468
+ export type PostType = "image" | "video" | "audio" | "poll";
469
+
470
+ export type IPublicComment = {
471
+ id: string;
472
+ content: string;
473
+ createdAt: string;
474
+ fullName: string;
475
+ accountId: string;
476
+ };
477
+
478
+ /** Entity an engagement action (like/save/comment) targets. */
479
+ export type EngagementEntityType = "post" | "product" | string;
480
+
481
+ export type IPublicPost = {
482
+ id: string;
483
+ caption: string;
484
+ media?: IMedia[];
485
+ likes: number;
486
+ comments: number;
487
+ createdAt: string;
488
+ hasAccess: boolean;
489
+ type: PostType;
490
+ profileId: string;
491
+ membershipTiers: string[];
492
+ };
493
+
494
+ export type IPublicPostCollection = {
495
+ id: string;
496
+ /** Per-profile slug for deep-linking (nullable when the title wasn't slugifiable). */
497
+ slug: string | null;
498
+ hasAccess: boolean;
499
+ membershipTiers: string[];
500
+ title: string;
501
+ description: string | null;
502
+ createdAt: string;
503
+ updatedAt: string;
504
+ profileId: string;
505
+ media: IMedia[];
506
+ posts: IPublicPost[];
507
+ };
508
+
509
+ export type IPublicProductTrack = {
510
+ id: string;
511
+ title: string;
512
+ description: string;
513
+ media: IMedia[];
514
+ isFeatured: boolean;
515
+ hasExplicitContent: boolean;
516
+ artist: string;
517
+ credits: string;
518
+ isrcCode: string;
519
+ };
520
+
521
+ export type IPublicProductVariant = {
522
+ id: string;
523
+ deliveryType: ProductDeliveryType;
524
+ title: string;
525
+ description: string;
526
+ price: number;
527
+ media: IMedia[];
528
+ tracks: IPublicProductTrack[];
529
+ isDefault: boolean;
530
+ payWhatYouWant?: boolean;
531
+ payWhatYouWantMaximum?: number;
532
+ upcCode: string;
533
+ color: string;
534
+ size: string;
535
+ availabilityStatus: "active" | "temporarily_out_of_stock";
536
+ };
537
+
538
+ // ---- Reviews -------------------------------------------------------------------
539
+
540
+ /** Reviewable entity types (v1 — events deferred). */
541
+ export type ReviewEntityType = "product" | "course" | "coaching_product";
542
+
543
+ export type ReviewSortOption = "recent" | "rating" | "helpful";
544
+
545
+ /** Denormalised per-entity rating aggregate, attached to public detail responses. */
546
+ export type IReviewAggregate = {
547
+ avgRating: number;
548
+ reviewCount: number;
549
+ distribution: { "1": number; "2": number; "3": number; "4": number; "5": number };
550
+ };
551
+
552
+ export type IPublicReviewReply = {
553
+ body: string;
554
+ createdAt: string;
555
+ };
556
+
557
+ /** A published review as returned by GET /public/reviews. */
558
+ export type IPublicReview = {
559
+ id: string;
560
+ rating: number;
561
+ title: string | null;
562
+ body: string | null;
563
+ reviewerName: string | null;
564
+ isVerified: boolean;
565
+ helpfulCount: number;
566
+ publishedAt: string | null;
567
+ createdAt: string;
568
+ updatedAt: string;
569
+ /** Creator response (one per review), null when none. */
570
+ reply: IPublicReviewReply | null;
571
+ };
572
+
573
+ export type IPublicReviewsPage = {
574
+ data: IPublicReview[];
575
+ total: number;
576
+ page: number;
577
+ pageSize: number;
578
+ hasNextPage: boolean;
579
+ nextPage: number | null;
580
+ aggregate: IReviewAggregate | null;
581
+ };
582
+
583
+ /** Context for the public review form behind an emailed token link. */
584
+ export type IReviewRequestContext = {
585
+ valid: boolean;
586
+ status: "pending" | "sent" | "reviewed" | "expired";
587
+ entityType: ReviewEntityType;
588
+ entityId: string;
589
+ entityTitle: string | null;
590
+ profile: { id: string; name: string; subdomain: string } | null;
591
+ defaultReviewerName: string;
592
+ existingReview: {
593
+ rating: number;
594
+ title: string | null;
595
+ body: string | null;
596
+ reviewerName: string | null;
597
+ status: string;
598
+ createdAt: string;
599
+ canEdit: boolean;
600
+ } | null;
601
+ };
602
+
603
+ /** The submitter's own review as returned by POST /public/reviews. */
604
+ export type ISubmittedReview = {
605
+ id: string;
606
+ entityType: ReviewEntityType;
607
+ entityId: string;
608
+ rating: number;
609
+ title: string | null;
610
+ body: string | null;
611
+ reviewerName: string | null;
612
+ isVerified: boolean;
613
+ status: string;
614
+ publishedAt: string | null;
615
+ createdAt: string;
616
+ /** "held_for_review" when the spam guard held the review pending moderation. */
617
+ heldReason: string | null;
618
+ };
619
+
620
+ export type IPublicProduct = {
621
+ id: string;
622
+ slug?: string;
623
+ title: string;
624
+ description: string;
625
+ category: ProductCategory;
626
+ media: IMedia[];
627
+ variants: IPublicProductVariant[];
628
+ artist: string;
629
+ credits: string;
630
+ publishedAt: string;
631
+ /** Rating aggregate — present on the detail endpoint, null until first published review. */
632
+ reviewAggregate?: IReviewAggregate | null;
633
+ shippingType?: "flat" | "dynamic";
634
+ shippingCost?: number;
635
+ weight?: number;
636
+ weightUnit?: string;
637
+ length?: number;
638
+ width?: number;
639
+ height?: number;
640
+ dimensionUnit?: string;
641
+ shippingCountries?: string[] | null;
642
+ };
643
+
644
+ export type QuestionnaireQuestion = {
645
+ id: string;
646
+ question: string;
647
+ type: "text" | "textarea" | "select" | string;
648
+ options?: string[];
649
+ optional?: boolean;
650
+ };
651
+
652
+ export type CoachingProduct = {
653
+ id: string;
654
+ title: string;
655
+ description: string;
656
+ price: string;
657
+ durationMinutes: number;
658
+ daysInFuture: number;
659
+ timezone: string;
660
+ bufferMinutes: number;
661
+ archivedAt: string | null;
662
+ availabilityDays: {
663
+ dayOfWeek: number;
664
+ fromTime: string;
665
+ toTime: string;
666
+ }[];
667
+ blockedDates: string[];
668
+ media: IMedia[];
669
+ slug: string;
670
+ questionnaire?: QuestionnaireQuestion[];
671
+ /** Rating aggregate — present on the detail endpoint, null until first published review. */
672
+ reviewAggregate?: IReviewAggregate | null;
673
+ };
674
+
675
+ export type PublicCourseLesson = {
676
+ id: string;
677
+ title: string;
678
+ description?: string | null;
679
+ order?: number;
680
+ media?: IMedia[];
681
+ };
682
+
683
+ export type PublicCourseModule = {
684
+ id: string;
685
+ title: string;
686
+ description?: string | null;
687
+ order?: number;
688
+ lessons?: PublicCourseLesson[];
689
+ };
690
+
691
+ export type PublicCourse = {
692
+ id: string;
693
+ title: string;
694
+ description: string;
695
+ price: number | string;
696
+ // Optional display-only "compare-at" price (higher than `price`). When
697
+ // present the storefront strikes it through next to `price`. Charging uses
698
+ // `price`.
699
+ compareAtPrice?: number | string | null;
700
+ publishedAt: string | null;
701
+ slug: string;
702
+ media: IMedia[];
703
+ questionnaire?: QuestionnaireQuestion[];
704
+ /** Curriculum — present on the course-detail endpoint. */
705
+ modules?: PublicCourseModule[];
706
+ /** Rating aggregate — present on the detail endpoint, null until first published review. */
707
+ reviewAggregate?: IReviewAggregate | null;
708
+ };
709
+
710
+ // ---- Blog --------------------------------------------------------------------
711
+
712
+ export type BlogPost = {
713
+ id: string;
714
+ title: string;
715
+ content: string;
716
+ publishedAt: string | null;
717
+ isFeatured: boolean;
718
+ slug: string;
719
+ media: { url: string; type: string; size: number; id: string; filename: string; durationSec?: number | null }[];
720
+ };
721
+
722
+ export type BlogCategory = {
723
+ id: string;
724
+ slug: string;
725
+ title: string;
726
+ description: string | null;
727
+ defaultLayout: "grid" | "list" | "episodes";
728
+ coverImage?: { url: string } | null;
729
+ itunesCategory: string | null;
730
+ author?: string | null;
731
+ };
732
+
733
+ /** A podcast show — a podcast-type blog category, trimmed for public display. */
734
+ export type PodcastShow = {
735
+ id: string;
736
+ slug: string;
737
+ title: string;
738
+ description: string | null;
739
+ coverImage?: { url: string } | null;
740
+ author: string | null;
741
+ itunesCategory: string | null;
742
+ episodeCount: number;
743
+ };
744
+
745
+ // ---- Events ------------------------------------------------------------------
746
+
747
+ export interface IEvent {
748
+ id: string;
749
+ profileId: string;
750
+ dateTime: string;
751
+ timezone?: string;
752
+ type: "physical" | "virtual" | "hybrid";
753
+ ticketSaleMessage?: string;
754
+ questionnaire?: {
755
+ id: string;
756
+ question: string;
757
+ type: string;
758
+ options?: string[];
759
+ optional?: boolean;
760
+ }[];
761
+ address?: {
762
+ name: string;
763
+ street: string;
764
+ city: string;
765
+ state?: string;
766
+ country: string;
767
+ zipCode?: string;
768
+ };
769
+ title: string;
770
+ description?: string;
771
+ actionText: string;
772
+ actionLink: string;
773
+ archivedAt?: string;
774
+ createdAt: string;
775
+ updatedAt: string;
776
+ tickets: ITicket[];
777
+ media: IMedia[];
778
+ slug: string;
779
+ }
780
+
781
+ export type ITicket = {
782
+ id: string;
783
+ title: string;
784
+ description: string;
785
+ price: number;
786
+ // Optional display-only "compare-at" price (higher than `price`). When
787
+ // present the storefront strikes it through next to `price`. Charging uses
788
+ // `price`.
789
+ compareAtPrice?: number | string | null;
790
+ quantity: number;
791
+ order: number;
792
+ sold: number;
793
+ maxPerPerson: number;
794
+ expiresAt?: string;
795
+ archivedAt?: string;
796
+ };
797
+
798
+ // ---- Invoices ----------------------------------------------------------------
799
+
800
+ export type InvoiceLineItem = {
801
+ id: string;
802
+ description: string;
803
+ quantity: string;
804
+ unitPrice: string;
805
+ amount: string;
806
+ sortOrder: number;
807
+ };
808
+
809
+ export type InvoiceData = {
810
+ id: string;
811
+ profileId: string;
812
+ invoiceNumber: string;
813
+ status: string;
814
+ dueDate: string | null;
815
+ issueDate: string;
816
+ subtotal: string;
817
+ taxRate: string;
818
+ taxAmount: string;
819
+ total: string;
820
+ currency: string;
821
+ notes: string | null;
822
+ clientName: string;
823
+ clientEmail: string;
824
+ clientCompany: string | null;
825
+ clientAddress: string | null;
826
+ paidAt: string | null;
827
+ lineItems: InvoiceLineItem[];
828
+ profileName: string;
829
+ profileSubdomain: string;
830
+ businessName: string | null;
831
+ businessAddress: string | null;
832
+ businessEmail: string | null;
833
+ businessPhone: string | null;
834
+ };
835
+
836
+ // ---- Cohort page -------------------------------------------------------------
837
+ // The cohort/course student page payload. The `layout` is a serialized Craft.js
838
+ // node tree — plain JSON data (NOT a Craft runtime dependency), so the base SDK
839
+ // owns the shape; the renderer that consumes it lives in frontend-shared.
840
+
841
+ export type CohortPageData = {
842
+ cohort: {
843
+ id: string;
844
+ title: string;
845
+ description: string | null;
846
+ startDate: string | null;
847
+ endDate: string | null;
848
+ timezone: string;
849
+ publishedAt: string | null;
850
+ };
851
+ course: {
852
+ id: string;
853
+ title: string;
854
+ description: string;
855
+ media?: Array<{ url: string; type: string }>;
856
+ };
857
+ modules: Array<{
858
+ id: string;
859
+ title: string;
860
+ description: string | null;
861
+ order: number;
862
+ releaseOffsetDays: number | null;
863
+ releaseAt: string | null;
864
+ lessons?: Array<{
865
+ id: string;
866
+ title: string;
867
+ order: number;
868
+ durationSeconds?: number | null;
869
+ }>;
870
+ }>;
871
+ announcements: Array<{
872
+ id: string;
873
+ title: string;
874
+ bodyHtml: string;
875
+ pinnedAt: string | null;
876
+ publishedAt: string | null;
877
+ createdAt: string;
878
+ }>;
879
+ sessions: Array<{
880
+ id: string;
881
+ title: string;
882
+ description: string | null;
883
+ scheduledAt: string;
884
+ durationMinutes: number;
885
+ status: string;
886
+ }>;
887
+ pricingPlans: Array<{
888
+ id: string;
889
+ title: string;
890
+ currency: string;
891
+ totalAmount: string | number;
892
+ installmentCount: number;
893
+ intervalDays: number;
894
+ isDefault: boolean;
895
+ }>;
896
+ progress?: {
897
+ completedLessonCount: number;
898
+ totalLessonCount: number;
899
+ };
900
+ instructor?: {
901
+ name: string;
902
+ bio: string | null;
903
+ avatarUrl?: string | null;
904
+ };
905
+ };
906
+
907
+ /** A serialized Craft.js node (plain JSON — one entry in a LayoutJson tree). */
908
+ export type LayoutNode = {
909
+ type: { resolvedName: string };
910
+ props?: Record<string, unknown>;
911
+ nodes?: string[];
912
+ parent?: string | null;
913
+ isCanvas?: boolean;
914
+ displayName?: string;
915
+ custom?: Record<string, unknown>;
916
+ hidden?: boolean;
917
+ linkedNodes?: Record<string, string>;
918
+ };
919
+
920
+ export type LayoutJson = Record<string, LayoutNode>;
921
+
922
+ /** The full public cohort page payload returned by `/public/cohorts/:id/page`. */
923
+ export type CohortPagePayload = {
924
+ data: CohortPageData | null;
925
+ layout: { craftJson: LayoutJson; isPublished: boolean } | null;
926
+ };
927
+
928
+ // ---- Orders ------------------------------------------------------------------
929
+
930
+ export enum OrderStatus {
931
+ InitiatedPayment = "initiated_payment",
932
+ PaymentFailed = "payment_failed",
933
+ Failed = "failed",
934
+ Paid = "paid",
935
+ Processing = "processing",
936
+ LabelPurchased = "label_purchased",
937
+ Shipped = "shipped",
938
+ Delivered = "delivered",
939
+ Cancelled = "cancelled",
940
+ Processed = "processed",
941
+ }
942
+
943
+ export type IPublicOrderItem = {
944
+ productId: string;
945
+ productVariantId: string;
946
+ title: string;
947
+ price: number;
948
+ coverImage?: string;
949
+ isGift: boolean;
950
+ recipientName?: string;
951
+ recipientEmail?: string;
952
+ canIncreaseQuantity: boolean;
953
+ quantity: number;
954
+ recipientMessage?: string;
955
+ payWhatYouWant: boolean;
956
+ color: string;
957
+ size: string;
958
+ };
959
+
960
+ export type IPublicOrder = {
961
+ id: string;
962
+ deliveryGroups: {
963
+ id: string;
964
+ deliveryType: ProductDeliveryType;
965
+ recipientName: string;
966
+ recipientEmail: string;
967
+ recipientMessage: string;
968
+ items: IPublicOrderItem[];
969
+ status: OrderStatus;
970
+ subTotal: number;
971
+ shippingCost: number;
972
+ shippingCostCurrency?: string;
973
+ productStoreId?: string;
974
+ address1?: string;
975
+ city?: string;
976
+ stateCode?: string;
977
+ countryCode?: string;
978
+ zip?: string;
979
+ phone?: string;
980
+ trackingNumber?: string;
981
+ carrier?: string;
982
+ service?: string;
983
+ shippingLabel?: {
984
+ id: string;
985
+ trackingNumber: string;
986
+ labelUrl: string;
987
+ carrier: string;
988
+ service: string;
989
+ rateCost: number;
990
+ status: string;
991
+ } | null;
992
+ }[];
993
+ totalAmount: number;
994
+ totalAmountInChargedCurrency?: number | null;
995
+ chargedCurrency?: string | null;
996
+ discountAmount?: number | null;
997
+ /** Order-level tax snapshot in the charged currency (major units); null pre-tax orders. */
998
+ taxAmount?: number | string | null;
999
+ taxInclusive?: boolean;
1000
+ shippingCosts?: { deliveryGroupId: string; amount: number; currency: string }[];
1001
+ currency?: string;
1002
+ status: OrderStatus;
1003
+ createdAt: string;
1004
+ updatedAt: string;
1005
+ customerName: string;
1006
+ customerEmail: string;
1007
+ };
1008
+
1009
+ export type ITicketOrderItem = {
1010
+ id: string;
1011
+ eventTicketId: string;
1012
+ quantity: number;
1013
+ price: number;
1014
+ title: string;
1015
+ };
1016
+
1017
+ export type ITicketOrder = {
1018
+ id: string;
1019
+ items: ITicketOrderItem[];
1020
+ firstName: string;
1021
+ lastName: string;
1022
+ email: string;
1023
+ totalAmount: number;
1024
+ totalAmountInChargedCurrency?: number | string | null;
1025
+ chargedCurrency?: string | null;
1026
+ /** Order-level tax snapshot in the charged currency (major units); null pre-tax orders. */
1027
+ taxAmount?: number | string | null;
1028
+ taxInclusive?: boolean;
1029
+ currency?: string;
1030
+ status: OrderStatus;
1031
+ customerName: string;
1032
+ customerEmail: string;
1033
+ createdAt: string;
1034
+ updatedAt: string;
1035
+ eventTitle: string;
1036
+ questionnaire: {
1037
+ id: string;
1038
+ question: string;
1039
+ type: string;
1040
+ options?: string[];
1041
+ answer?: string;
1042
+ optional?: boolean;
1043
+ }[];
1044
+ };
1045
+
1046
+ /** Status of a coaching/course booking through its payment lifecycle. */
1047
+ export type BookingStatus = "reserved" | "confirmed" | "canceled" | "payment_failed";
1048
+
1049
+ export type CoachingBooking = {
1050
+ id: string;
1051
+ status: BookingStatus;
1052
+ firstName: string;
1053
+ lastName: string;
1054
+ email: string;
1055
+ totalAmount: number;
1056
+ totalAmountInChargedCurrency?: number | null;
1057
+ chargedCurrency?: string | null;
1058
+ /** Booking-level tax snapshot in the charged currency (major units); null pre-tax bookings. */
1059
+ taxAmount?: number | string | null;
1060
+ taxInclusive?: boolean;
1061
+ /** Session slot + product details (present from the finalize endpoint). */
1062
+ sessionStartTime?: string | null;
1063
+ sessionEndTime?: string | null;
1064
+ productTitle?: string | null;
1065
+ durationMinutes?: number | null;
1066
+ timezone?: string | null;
1067
+ };
1068
+
1069
+ export type CourseBooking = {
1070
+ id: string;
1071
+ status: BookingStatus;
1072
+ firstName: string;
1073
+ lastName: string;
1074
+ email: string;
1075
+ totalAmount: number;
1076
+ totalAmountInChargedCurrency?: number | null;
1077
+ chargedCurrency?: string | null;
1078
+ /** Booking-level tax snapshot in the charged currency (major units); null pre-tax bookings. */
1079
+ taxAmount?: number | string | null;
1080
+ taxInclusive?: boolean;
1081
+ };
1082
+
1083
+ // ---- Course access (player) --------------------------------------------------
1084
+
1085
+ export type CourseAccessRecord = {
1086
+ id: string;
1087
+ courseId: string;
1088
+ email: string;
1089
+ firstName: string | null;
1090
+ lastName: string | null;
1091
+ currentLessonId: string | null;
1092
+ };
1093
+
1094
+ export type CourseLessonProgress = {
1095
+ id: string;
1096
+ lessonId: string;
1097
+ videoProgress: number;
1098
+ isCompleted: boolean;
1099
+ };
1100
+
1101
+ export type CourseLesson = {
1102
+ id: string;
1103
+ title: string;
1104
+ content: string;
1105
+ order: number;
1106
+ media: Array<{
1107
+ url: string;
1108
+ type: string;
1109
+ size: number;
1110
+ id: string;
1111
+ filename: string;
1112
+ }>;
1113
+ progress: CourseLessonProgress | null;
1114
+ };
1115
+
1116
+ export type CourseModule = {
1117
+ id: string;
1118
+ title: string;
1119
+ description: string | null;
1120
+ order: number;
1121
+ lessons: CourseLesson[];
1122
+ };
1123
+
1124
+ export type CourseAccessCourse = {
1125
+ id: string;
1126
+ title: string;
1127
+ description: string;
1128
+ modules: CourseModule[];
1129
+ };
1130
+
1131
+ export type CourseAccessData = {
1132
+ access: CourseAccessRecord;
1133
+ course: CourseAccessCourse;
1134
+ progress: CourseLessonProgress[];
1135
+ };
1136
+
1137
+ // ---- Live broadcasts (streaming) ---------------------------------------------
1138
+
1139
+ export type ILiveEvent = {
1140
+ id: string;
1141
+ title: string;
1142
+ description: string;
1143
+ isPaid: boolean;
1144
+ };
1145
+
1146
+ export type ILiveBroadcast = {
1147
+ id: string;
1148
+ realtimeConfig?: {
1149
+ sessionId: string;
1150
+ tracks: {
1151
+ mid: string;
1152
+ trackName: string;
1153
+ }[];
1154
+ };
1155
+ events: {
1156
+ eventId: string;
1157
+ eventTitle: string;
1158
+ eventTickets: ITicket[];
1159
+ }[];
1160
+ title: string;
1161
+ createdAt: string;
1162
+ updatedAt: string;
1163
+ profileId: string;
1164
+ streamTemplateId: string;
1165
+ startedAt?: string;
1166
+ endedAt?: string;
1167
+ egressId: string;
1168
+ event: ILiveEvent;
1169
+ startDate?: string;
1170
+ generatedThumbnailUrl?: string;
1171
+ liveUrl?: string;
1172
+ vodUrl?: string;
1173
+ thumbnailUrl?: string;
1174
+ };
1175
+
1176
+ export type IBroadcastPass = {
1177
+ sessionId: string;
1178
+ name: string;
1179
+ email: string;
1180
+ };
1181
+
1182
+ // ---- Coaching availability / booking -----------------------------------------
1183
+
1184
+ export type BookingSlot = {
1185
+ id: string;
1186
+ startTime: string;
1187
+ endTime: string;
1188
+ coachingAvailabilityDayId: string;
1189
+ };
1190
+
1191
+ // ---- Public forms ------------------------------------------------------------
1192
+
1193
+ export type FormField = {
1194
+ id: string;
1195
+ type: string;
1196
+ label: string;
1197
+ description: string | null;
1198
+ placeholder: string | null;
1199
+ isRequired: boolean;
1200
+ options: Array<{ id: string; label: string }> | null;
1201
+ validation: { min?: number; max?: number; maxLength?: number } | null;
1202
+ order: number;
1203
+ /** Which section this field belongs to (null on legacy flat forms). */
1204
+ sectionId?: string | null;
1205
+ };
1206
+
1207
+ /** Edge scope: field→field jump, section→section page flow, or in-page show/hide. */
1208
+ export type FormEdgeScope = "field" | "section" | "visibility";
1209
+
1210
+ export type FormFieldEdge = {
1211
+ id: string;
1212
+ scope?: FormEdgeScope;
1213
+ sourceFieldId: string | null;
1214
+ targetFieldId: string | null;
1215
+ sourceSectionId?: string | null;
1216
+ targetSectionId?: string | null;
1217
+ /** For section edges: jump to a section, or end the form. */
1218
+ targetKind?: "section" | "submit" | null;
1219
+ condition: { operator: string; value: string | string[] | null } | null;
1220
+ label: string | null;
1221
+ sortOrder: number;
1222
+ };
1223
+
1224
+ export type FormSection = {
1225
+ id: string;
1226
+ title: string | null;
1227
+ description: string | null;
1228
+ order: number;
1229
+ /** The choice question this section branches on (null = linear / next). */
1230
+ branchFieldId: string | null;
1231
+ };
1232
+
1233
+ export type FormData = {
1234
+ id: string;
1235
+ title: string;
1236
+ description: string | null;
1237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1238
+ settings: any;
1239
+ /** Ordered sections (empty/absent on legacy flat forms). */
1240
+ sections?: FormSection[];
1241
+ fields: FormField[];
1242
+ edges: FormFieldEdge[];
1243
+ };
1244
+
1245
+ export type FormAnswerEntry = {
1246
+ fieldId: string;
1247
+ value: string | null;
1248
+ };
1249
+
1250
+ // ---- Replay broadcasts -------------------------------------------------------
1251
+
1252
+ export type ReplayBroadcast = {
1253
+ id: string;
1254
+ title: string;
1255
+ startedAt: string;
1256
+ endedAt: string | null;
1257
+ vodUrl: string | null;
1258
+ thumbnailUrl: string | null;
1259
+ generatedThumbnailUrl: string | null;
1260
+ };
1261
+
1262
+ export type ReplayData = {
1263
+ eventPass: {
1264
+ id: string;
1265
+ ownerName: string;
1266
+ ownerEmail: string;
1267
+ eventId: string;
1268
+ eventTicketId: string;
1269
+ };
1270
+ broadcasts: ReplayBroadcast[];
1271
+ };
1272
+
1273
+ // ---- Donations & Offers ------------------------------------------------------
1274
+
1275
+ export type PublicDonation = {
1276
+ id: string;
1277
+ profileId: string;
1278
+ title: string;
1279
+ description: string | null;
1280
+ minimumAmount: string | null;
1281
+ defaultAmounts: number[];
1282
+ donateButtonText: string;
1283
+ successMessage: string | null;
1284
+ coverFeesEnabled: boolean;
1285
+ isActive: boolean;
1286
+ };
1287
+
1288
+ export type PublicOffer = {
1289
+ id: string;
1290
+ name: string;
1291
+ offerType: string;
1292
+ itemId: string;
1293
+ ticketTypeId: string | null;
1294
+ description: string | null;
1295
+ buttonText: string | null;
1296
+ };
1297
+
1298
+ // ---- Payment links -----------------------------------------------------------
1299
+
1300
+ export type PaymentLinkData = {
1301
+ id: string;
1302
+ profileId: string;
1303
+ recipientEmail: string;
1304
+ recipientName: string;
1305
+ amount: string;
1306
+ currency: string;
1307
+ message: string | null;
1308
+ status: string;
1309
+ paidAt: string | null;
1310
+ createdAt: string;
1311
+ profileName: string;
1312
+ profileSubdomain: string;
1313
+ };