@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,122 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { useForge } from "../../provider/ForgeProvider";
3
+ import type {
4
+ IPublicReviewsPage,
5
+ IReviewRequestContext,
6
+ ISubmittedReview,
7
+ ReviewEntityType,
8
+ ReviewSortOption,
9
+ } from "../../types/models";
10
+
11
+ export interface GetPublicReviewsParams {
12
+ entityType: ReviewEntityType;
13
+ entityId: string;
14
+ page?: number;
15
+ limit?: number;
16
+ sort?: ReviewSortOption;
17
+ }
18
+
19
+ /** Published reviews (+ aggregate) for an entity — GET /public/reviews. */
20
+ export function usePublicReviews(params: GetPublicReviewsParams, enabled = true) {
21
+ const { client, profileId } = useForge();
22
+
23
+ return useQuery<IPublicReviewsPage>({
24
+ queryKey: [
25
+ "public-reviews",
26
+ profileId,
27
+ params.entityType,
28
+ params.entityId,
29
+ params.page ?? 1,
30
+ params.limit ?? 10,
31
+ params.sort ?? "recent",
32
+ ],
33
+ queryFn: async () => {
34
+ const res = await client.get("/public/reviews", {
35
+ params: {
36
+ profileId,
37
+ entityType: params.entityType,
38
+ entityId: params.entityId,
39
+ page: params.page ?? 1,
40
+ limit: params.limit ?? 10,
41
+ sort: params.sort ?? "recent",
42
+ },
43
+ });
44
+ return res.data;
45
+ },
46
+ enabled: !!profileId && !!client && !!params.entityId && enabled,
47
+ placeholderData: (prev) => prev,
48
+ });
49
+ }
50
+
51
+ /** Review-form context behind an emailed token link — GET /public/reviews/request/:token. */
52
+ export function useReviewRequest(token?: string) {
53
+ const { client } = useForge();
54
+
55
+ return useQuery<IReviewRequestContext>({
56
+ queryKey: ["review-request", token],
57
+ queryFn: async () => {
58
+ const res = await client.get(`/public/reviews/request/${token}`);
59
+ return res.data;
60
+ },
61
+ enabled: !!client && !!token,
62
+ retry: false,
63
+ });
64
+ }
65
+
66
+ export interface SubmitReviewInput {
67
+ /** Token from the emailed review request (proves the purchase). */
68
+ token?: string;
69
+ /** Fallback path (no token): entity + email/authed account. */
70
+ profileId?: string;
71
+ entityType?: ReviewEntityType;
72
+ entityId?: string;
73
+ email?: string;
74
+ rating: number;
75
+ title?: string | null;
76
+ body?: string | null;
77
+ reviewerName?: string | null;
78
+ }
79
+
80
+ /** Submit (or edit within the window) a review — POST /public/reviews. */
81
+ export function useSubmitReview() {
82
+ const { client } = useForge();
83
+ const queryClient = useQueryClient();
84
+
85
+ return useMutation<ISubmittedReview, unknown, SubmitReviewInput>({
86
+ mutationFn: async (input) => {
87
+ const res = await client.post("/public/reviews", input);
88
+ return res.data;
89
+ },
90
+ onSuccess: (review, variables) => {
91
+ queryClient.invalidateQueries({ queryKey: ["public-reviews"] });
92
+ if (variables.token) {
93
+ queryClient.invalidateQueries({ queryKey: ["review-request", variables.token] });
94
+ }
95
+ return review;
96
+ },
97
+ });
98
+ }
99
+
100
+ /** Mark a review helpful (logged-in members) — POST /public/reviews/:id/helpful. */
101
+ export function useAddHelpfulVote() {
102
+ const { client } = useForge();
103
+
104
+ return useMutation<{ helpfulCount: number }, unknown, { reviewId: string }>({
105
+ mutationFn: async ({ reviewId }) => {
106
+ const res = await client.post(`/public/reviews/${reviewId}/helpful`);
107
+ return res.data;
108
+ },
109
+ });
110
+ }
111
+
112
+ /** Withdraw a helpful vote — DELETE /public/reviews/:id/helpful. */
113
+ export function useRemoveHelpfulVote() {
114
+ const { client } = useForge();
115
+
116
+ return useMutation<{ helpfulCount: number }, unknown, { reviewId: string }>({
117
+ mutationFn: async ({ reviewId }) => {
118
+ const res = await client.delete(`/public/reviews/${reviewId}/helpful`);
119
+ return res.data;
120
+ },
121
+ });
122
+ }
@@ -0,0 +1,109 @@
1
+ import { useForge } from "../../provider/ForgeProvider";
2
+ import { useMutation, useQuery } from "@tanstack/react-query";
3
+
4
+ export type ShippingCountry = {
5
+ name: string;
6
+ code: string;
7
+ region: string;
8
+ states?: { name: string; code: string }[];
9
+ };
10
+
11
+ export type ShippingRate = {
12
+ rateId: string;
13
+ carrier: string;
14
+ service: string;
15
+ amount: number;
16
+ currency: string;
17
+ estimatedDays: number | null;
18
+ shipmentId: string;
19
+ };
20
+
21
+ /**
22
+ * A shippable group in the checkout. Physical items are grouped by how they
23
+ * ship: `external` (one combined cost), per-product `flat`, and one `dynamic`
24
+ * carrier-rated shipment (all dynamic items packed into the seller's boxes → one
25
+ * carrier choice). `flat`/`external` are auto-selected (single rate); `dynamic`
26
+ * (`selectable`) is the buyer's carrier choice.
27
+ */
28
+ export type ShippingGroup = {
29
+ id: string;
30
+ type: "flat" | "dynamic" | "external";
31
+ title: string;
32
+ productIds: string[];
33
+ selectable: boolean;
34
+ rates: ShippingRate[];
35
+ };
36
+
37
+ /** Countries the profile ships physical products to. */
38
+ export function useShippingCountries() {
39
+ const { client } = useForge();
40
+
41
+ return useQuery<ShippingCountry[]>({
42
+ queryKey: ["shipping-countries"],
43
+ queryFn: async () => {
44
+ const res = await client.get("/public/products/shipping-countries");
45
+ return res.data;
46
+ },
47
+ enabled: !!client,
48
+ });
49
+ }
50
+
51
+ export type ShippingRateCartItem = {
52
+ productId: string;
53
+ productVariantId: string;
54
+ quantity: number;
55
+ };
56
+
57
+ /** Compute shipping rates for an address + cart. */
58
+ export function useShippingRates() {
59
+ const { client, profileId } = useForge();
60
+
61
+ return useMutation<
62
+ { groups: ShippingGroup[] },
63
+ unknown,
64
+ {
65
+ shippingAddress: { address1: string; city: string; stateCode: string; countryCode: string; zip: string; phone?: string };
66
+ cartItems: ShippingRateCartItem[];
67
+ }
68
+ >({
69
+ mutationFn: async ({ shippingAddress, cartItems }) => {
70
+ const res = await client.post("/public/orders/shipping-rates", {
71
+ profileId,
72
+ shippingAddress,
73
+ cartItems,
74
+ });
75
+ return res.data;
76
+ },
77
+ });
78
+ }
79
+
80
+ export type ApplyCouponResult = {
81
+ coupon: { code: string; discountType: string | null; discountValue: number | null } | null;
82
+ discountAmount: number;
83
+ chargedAmount: number;
84
+ chargedCurrency: string;
85
+ paymentSecret?: string;
86
+ paymentId?: string;
87
+ /** Re-quoted sales tax for the re-issued payment intent (display only). */
88
+ taxQuote?: import("../../types/models").PublicTaxQuote;
89
+ };
90
+
91
+ /**
92
+ * Apply (or, with no couponCode, clear) a coupon on an order. Re-issues the
93
+ * payment intent, so the result carries the updated paymentSecret/paymentId.
94
+ */
95
+ export function useApplyCoupon() {
96
+ const { client, profileId } = useForge();
97
+
98
+ return useMutation<ApplyCouponResult, unknown, { orderId: string; couponCode?: string; returnUrl: string }>({
99
+ mutationFn: async ({ orderId, couponCode, returnUrl }) => {
100
+ const res = await client.post("/public/orders/apply-coupon", {
101
+ orderId,
102
+ profileId,
103
+ couponCode,
104
+ returnUrl,
105
+ });
106
+ return res.data;
107
+ },
108
+ });
109
+ }
@@ -0,0 +1,66 @@
1
+ import { useForge } from "../../provider/ForgeProvider";
2
+ import { useMutation } from "@tanstack/react-query";
3
+
4
+ export type CreateSubscriptionInput = {
5
+ amount?: number;
6
+ billingCycle: "month" | "year";
7
+ membershipTierId: string;
8
+ returnUrl: string;
9
+ attributionRefId?: string;
10
+ };
11
+
12
+ /** Create a paid membership subscription (returns the payment client secret). */
13
+ export function useCreateSubscription() {
14
+ const { client, profileId } = useForge();
15
+
16
+ return useMutation<{ subscriptionId: string; clientSecret: string }, unknown, CreateSubscriptionInput>({
17
+ mutationFn: async (body) => {
18
+ const res = await client.post("/public/payments/subscriptions", { ...body, profileId });
19
+ return res.data;
20
+ },
21
+ });
22
+ }
23
+
24
+ /** Activate a free membership tier. */
25
+ export function useCreateFreeSubscription() {
26
+ const { client, profileId } = useForge();
27
+
28
+ return useMutation<unknown, unknown, { membershipTierId: string; attributionRefId?: string }>({
29
+ mutationFn: async (body) => {
30
+ const res = await client.post("/public/payments/subscriptions/free", { ...body, profileId });
31
+ return res.data;
32
+ },
33
+ });
34
+ }
35
+
36
+ /** Cancel the member's active paid subscription. */
37
+ export function useCancelMembership() {
38
+ const { client, profileId } = useForge();
39
+
40
+ return useMutation<unknown, unknown, { membershipId: string }>({
41
+ mutationFn: async ({ membershipId }) => {
42
+ const res = await client.post("/public/payments/subscriptions/cancel", {
43
+ membershipId,
44
+ profileId,
45
+ });
46
+ return res.data;
47
+ },
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Reconcile the most recent subscription after a checkout return.
53
+ * Returns the latest subscription status (e.g. "active").
54
+ */
55
+ export function useConfirmLatestSubscription() {
56
+ const { client, profileId } = useForge();
57
+
58
+ return useMutation<{ status: string }, unknown, void>({
59
+ mutationFn: async () => {
60
+ const res = await client.post("/public/payments/subscriptions/confirm-latest", {
61
+ profileId,
62
+ });
63
+ return res.data;
64
+ },
65
+ });
66
+ }
@@ -0,0 +1,128 @@
1
+ import type { WebPage } from "../../types";
2
+ import { useForge } from "../../provider/ForgeProvider";
3
+ import { useInitialSiteConfig } from "../../provider/SiteConfigProvider";
4
+ import { keepPreviousData, useMutation, useQuery } from "@tanstack/react-query";
5
+
6
+ export interface SiteConfig {
7
+ /** The tenant's settlement currency (e.g. "USD") — the currency API amounts
8
+ * are in and payments are charged in. */
9
+ currency: string;
10
+ /** Sales-tax display mode: true = listing prices already contain tax
11
+ * ("incl. VAT" style, EU/UK), false/absent = tax added at checkout (US).
12
+ * Display-only — checkout totals come from the authoritative taxQuote. */
13
+ pricesIncludeTax?: boolean;
14
+ metaPixelId: string | null;
15
+ stripeAccountId: string | null;
16
+ paystackAccountId: string | null;
17
+ paymentProviderName: string | null;
18
+ paymentProviderPublicKey: string | null;
19
+ /** The single PLATFORM Stripe publishable key. Stripe is Connect, so forge
20
+ * initialises `loadStripe(stripePublishableKey, { stripeAccount: stripeAccountId })`. */
21
+ stripePublishableKey: string | null;
22
+ /** Whether Altcha proof-of-work bot protection is active. Self-hosted, so it
23
+ * works on every tenant subdomain + custom domain with no per-domain config. */
24
+ altchaEnabled?: boolean;
25
+ /** The creator's site display name — the manifest `name` fallback when the
26
+ * PWA identity has no explicit name. */
27
+ siteName?: string | null;
28
+ /** The profile-level PWA identity (icons/name/screenshots) used to build the
29
+ * installable manifest + head tags. `null` when the creator has no pwaConfig
30
+ * (minimal name-only manifest, not installable). Screenshots are optional. */
31
+ pwa?: PwaConfig | null;
32
+ }
33
+
34
+ /** The PWA identity block on `site-config` (mirrors `pwaConfig` on the profile). */
35
+ export interface PwaConfig {
36
+ name: string;
37
+ shortName: string;
38
+ description: string;
39
+ icon96: string;
40
+ icon192: string;
41
+ icon512: string;
42
+ screenshotWide1280X720?: string;
43
+ screenshotNarrow750X1334?: string;
44
+ }
45
+
46
+ /**
47
+ * The tenant's mutable runtime config (settlement currency, Meta Pixel id,
48
+ * payment provider). Fetched at runtime — NOT baked into the build — so a
49
+ * creator can change these in settings and the live site reflects them without
50
+ * a rebuild/redeploy.
51
+ *
52
+ * If a `<SiteConfigProvider>` seeded an SSR-fetched value (see `fetchSiteConfig`
53
+ * in `@tribe-nest/forge/server`), it's used as `initialData` so the config is
54
+ * present on the first render — no client-fetch flash, no hydration mismatch.
55
+ * An explicit `initialData` override still wins.
56
+ */
57
+ export function useSiteConfig(opts?: { initialData?: SiteConfig }) {
58
+ const { client, profileId } = useForge();
59
+ const ssrInitial = useInitialSiteConfig();
60
+
61
+ return useQuery<SiteConfig>({
62
+ queryKey: ["site-config", profileId],
63
+ queryFn: async () => {
64
+ const res = await client.get("/public/websites/site-config", { params: { profileId } });
65
+ return res.data;
66
+ },
67
+ enabled: !!client && !!profileId,
68
+ initialData: opts?.initialData ?? ssrInitial,
69
+ staleTime: 1000 * 60 * 5, // 5 min — settings change rarely; refetch keeps it fresh
70
+ });
71
+ }
72
+
73
+ /**
74
+ * The store's tax display mode from site config: true = prices shown to the
75
+ * buyer already include tax (inclusive/EU display), false = tax is added at
76
+ * checkout (exclusive/US display). Defaults to false until config loads.
77
+ * Feed this into `<PriceDisplay pricesIncludeTax={...}>`.
78
+ */
79
+ export function usePricesIncludeTax(): boolean {
80
+ const { data } = useSiteConfig();
81
+ return data?.pricesIncludeTax ?? false;
82
+ }
83
+
84
+ // Tax-aware price rendering primitive, re-exported here (a file the root barrel
85
+ // already star-exports) so `@tribe-nest/frontend-shared` and app code can import
86
+ // it from "@tribe-nest/forge". Its home is `ui/format/PriceDisplay` — the
87
+ // `forge/ui` barrel should export it from there as well.
88
+ export {
89
+ PriceDisplay,
90
+ priceTaxCaption,
91
+ summarizeTaxQuote,
92
+ type PriceDisplayProps,
93
+ } from "../../ui/format/PriceDisplay";
94
+
95
+ /** Submit the website contact form. */
96
+ export function useSubmitContactForm() {
97
+ const { client, profileId } = useForge();
98
+
99
+ return useMutation<unknown, unknown, { name: string; email: string; message: string; joinEmailList?: boolean }>({
100
+ mutationFn: async (body) => {
101
+ const res = await client.post("/public/websites/contact", { ...body, profileId });
102
+ return res.data;
103
+ },
104
+ });
105
+ }
106
+
107
+ /**
108
+ * The rendered web page for a pathname — either the published site (by
109
+ * subdomain) or a preview (by token). Keeps the previous page during navigation
110
+ * to avoid a white flash.
111
+ */
112
+ export function useGetWebPage(pathname: string, opts?: { subdomain?: string; previewToken?: string | null }) {
113
+ const { client } = useForge();
114
+ const { subdomain, previewToken } = opts ?? {};
115
+
116
+ return useQuery<WebPage>({
117
+ queryKey: ["web-page", subdomain, pathname, previewToken],
118
+ queryFn: async () => {
119
+ const url = previewToken
120
+ ? `/public/websites/preview?token=${previewToken}&pathname=${pathname}`
121
+ : `/public/websites?subdomain=${subdomain}&pathname=${pathname}`;
122
+ const res = await client.get(url);
123
+ return res.data;
124
+ },
125
+ enabled: !!client && (!!previewToken || !!subdomain),
126
+ placeholderData: keepPreviousData,
127
+ });
128
+ }
@@ -0,0 +1,100 @@
1
+ import { useForge } from "../../provider/ForgeProvider";
2
+ import { useQuery } from "@tanstack/react-query";
3
+
4
+ export interface WebsiteAgentConfig {
5
+ enabled: boolean;
6
+ displayName?: string;
7
+ greeting?: string | null;
8
+ allowAnonymous?: boolean;
9
+ allowOrderLookup?: boolean;
10
+ allowLeadCapture?: boolean;
11
+ }
12
+
13
+ /** Widget bootstrap config for the profile. When `enabled` is false the widget
14
+ * should render nothing. */
15
+ export function useWebsiteAgentConfig() {
16
+ const { client, profileId } = useForge();
17
+ return useQuery<WebsiteAgentConfig>({
18
+ queryKey: ["website-agent-config", profileId],
19
+ queryFn: async () => {
20
+ const res = await client.get(`/public/agent/config`, { params: { profileId } });
21
+ return res.data;
22
+ },
23
+ enabled: !!profileId && !!client,
24
+ staleTime: 5 * 60 * 1000,
25
+ });
26
+ }
27
+
28
+ export type AgentStreamEvent =
29
+ | { type: "text_delta"; delta: string }
30
+ | { type: "tool_use_start"; name: string }
31
+ | { type: "tool_use_result"; name: string }
32
+ | { type: "turn_complete" }
33
+ | { type: "error"; message: string };
34
+
35
+ /**
36
+ * Streams a chat turn over SSE using fetch (axios can't stream). Calls
37
+ * `onEvent` for each parsed event. Returns when the stream ends.
38
+ */
39
+ export async function streamAgentMessage(params: {
40
+ apiUrl: string;
41
+ token?: string | null;
42
+ profileId: string;
43
+ visitorId: string;
44
+ conversationId: string;
45
+ message: string;
46
+ onEvent: (ev: AgentStreamEvent) => void;
47
+ signal?: AbortSignal;
48
+ }): Promise<void> {
49
+ const res = await fetch(`${params.apiUrl}/public/agent/conversations/${params.conversationId}/messages`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ ...(params.token ? { authorization: `Bearer ${params.token}` } : {}),
54
+ },
55
+ body: JSON.stringify({ profileId: params.profileId, visitorId: params.visitorId, message: params.message }),
56
+ signal: params.signal,
57
+ });
58
+
59
+ if (!res.body) throw new Error("No response stream");
60
+ const reader = res.body.getReader();
61
+ const decoder = new TextDecoder();
62
+ let buffer = "";
63
+
64
+ for (;;) {
65
+ const { done, value } = await reader.read();
66
+ if (done) break;
67
+ buffer += decoder.decode(value, { stream: true });
68
+ const frames = buffer.split("\n\n");
69
+ buffer = frames.pop() ?? "";
70
+ for (const frame of frames) {
71
+ const line = frame.split("\n").find((l) => l.startsWith("data:"));
72
+ if (!line) continue;
73
+ const payload = line.slice(5).trim();
74
+ if (!payload) continue;
75
+ try {
76
+ params.onEvent(JSON.parse(payload) as AgentStreamEvent);
77
+ } catch {
78
+ /* ignore heartbeats / malformed frames */
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ /** Creates a visitor conversation, returning its id (or null when disabled). */
85
+ export async function createAgentConversation(params: {
86
+ apiUrl: string;
87
+ token?: string | null;
88
+ profileId: string;
89
+ visitorId: string;
90
+ }): Promise<{ id: string | null; requiresLogin?: boolean }> {
91
+ const res = await fetch(`${params.apiUrl}/public/agent/conversations`, {
92
+ method: "POST",
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ ...(params.token ? { authorization: `Bearer ${params.token}` } : {}),
96
+ },
97
+ body: JSON.stringify({ profileId: params.profileId, visitorId: params.visitorId }),
98
+ });
99
+ return res.json();
100
+ }
package/src/index.ts ADDED
@@ -0,0 +1,98 @@
1
+ // Forge — the headless React SDK for TribeNest creator sites.
2
+ //
3
+ // Phase 1 (the spine): client factory, root provider, core types.
4
+ // Subsequent phases add providers (auth/cart/audio), data hooks, flow hooks,
5
+ // headless blocks, and SEO — see docs/code-websites-initiative.md.
6
+
7
+ export { createForgeClient, getForgeWebSocketUrl } from "./client/createForgeClient";
8
+ export type { ForgeClientOptions } from "./client/createForgeClient";
9
+
10
+ // All-in-one root provider (query + content + auth + cart + audio + site-config
11
+ // + theme). Most sites import just this.
12
+ export { ForgeProvider, type ForgeProviderProps } from "./provider/ForgeAppProvider";
13
+ // Low-level pieces for hand-composing the tree.
14
+ export { ForgeClientProvider, useForge, type ForgeClientProviderProps, type ForgeContextValue } from "./provider/ForgeProvider";
15
+ export { SiteConfigProvider, useInitialSiteConfig } from "./provider/SiteConfigProvider";
16
+
17
+ // All domain types + enums/consts (models) and WebPage.
18
+ export * from "./types";
19
+
20
+ // Providers (Phase 2 — moving from frontend-shared into Forge)
21
+ export { AudioPlayerProvider, useAudioPlayer } from "./contexts/AudioPlayerContext";
22
+ export type { AudioTrack } from "./contexts/AudioPlayerContext";
23
+ export { CartProvider, useCart } from "./contexts/CartContext";
24
+ export type { CartItem } from "./contexts/CartContext";
25
+ export {
26
+ ACCESS_TOKEN_KEY,
27
+ PUBLIC_ACCESS_TOKEN_KEY,
28
+ usePublicAuth,
29
+ PublicAuthProvider,
30
+ PublicAuthContext,
31
+ } from "./contexts/PublicAuthContext";
32
+ export type { AuthActions } from "./contexts/PublicAuthContext";
33
+ // App-user auth (mini-apps) + the /admin guard.
34
+ export {
35
+ useAppAuth,
36
+ AppAuthProvider,
37
+ APP_ACCESS_TOKEN_KEY,
38
+ type AppAuthUser,
39
+ type AppSignupInput,
40
+ type AppSignupResult,
41
+ } from "./contexts/AppAuthContext";
42
+ export { useAppAdminGuard, type AppAdminGuardResult } from "./contexts/useAppAdminGuard";
43
+
44
+ // Data hooks (React Query) — Phase 3
45
+ export * from "./data/queries/useProducts";
46
+ export * from "./data/queries/useMembership";
47
+ export * from "./data/queries/useCourses";
48
+ export * from "./data/queries/useCoachingProducts";
49
+ export * from "./data/queries/usePosts";
50
+ export * from "./data/queries/usePostCollections";
51
+ export * from "./data/queries/useBlog";
52
+ export * from "./data/queries/usePodcast";
53
+ export * from "./data/queries/useCollections";
54
+ export * from "./data/queries/useEvents";
55
+ export * from "./data/queries/useInvoice";
56
+ export * from "./data/queries/usePaymentLink";
57
+ export * from "./data/queries/useLeadMagnet";
58
+ export * from "./data/queries/useBlogComments";
59
+ export * from "./data/queries/usePageActions";
60
+ export * from "./data/queries/useForms";
61
+ export * from "./data/queries/useReplay";
62
+ export * from "./data/queries/useCoachingAvailability";
63
+ export * from "./data/queries/useCohortPage";
64
+ export * from "./data/queries/useCourseAccess";
65
+ export * from "./data/queries/useBroadcasts";
66
+ export * from "./data/queries/useAccount";
67
+ export * from "./data/queries/useFinalize";
68
+ export * from "./data/queries/useSubscriptions";
69
+ export * from "./data/queries/useEngagement";
70
+ export * from "./data/queries/usePaymentFlow";
71
+ export * from "./data/queries/useOrders";
72
+ export * from "./data/queries/useAuthActions";
73
+ export * from "./data/queries/useCoupons";
74
+ export * from "./data/queries/useAccountSettings";
75
+ export * from "./data/queries/useDonations";
76
+ export * from "./data/queries/useOffers";
77
+ export * from "./data/queries/useShipping";
78
+ export * from "./data/queries/useWebsite";
79
+ export * from "./data/queries/useWebsiteAgent";
80
+ export * from "./data/queries/useCurrencies";
81
+ export * from "./data/queries/useChat";
82
+ export * from "./data/queries/useAnalytics";
83
+ export * from "./data/queries/useCommunity";
84
+ export * from "./data/queries/useReviews";
85
+ export * from "./data/queries/useDocuments";
86
+ export * from "./data/queries/useNotifications";
87
+ export * from "./data/queries/usePWA";
88
+ export * from "./data/queries/useCertificates";
89
+
90
+ // Content layer (Phase E) — typed/editable/localizable site content
91
+ export * from "./content";
92
+
93
+ // Utils
94
+ export * from "./utils/attribution";
95
+ export * from "./utils/landing";
96
+ export * from "./utils/metaPixel";
97
+ export * from "./utils/cookieConsent";
98
+ export * from "./utils/structuredData";