@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.
- package/package.json +42 -0
- package/src/client/createForgeClient.ts +54 -0
- package/src/content/ForgeContentProvider.tsx +118 -0
- package/src/content/ForgeEditBridge.tsx +134 -0
- package/src/content/editFlag.ts +23 -0
- package/src/content/index.ts +28 -0
- package/src/content/resolve.ts +67 -0
- package/src/content/types.ts +97 -0
- package/src/content/useField.ts +64 -0
- package/src/contexts/AppAuthContext.tsx +185 -0
- package/src/contexts/AudioPlayerContext.tsx +389 -0
- package/src/contexts/CartContext.tsx +131 -0
- package/src/contexts/PublicAuthContext.tsx +333 -0
- package/src/contexts/useAppAdminGuard.ts +56 -0
- package/src/css.d.ts +5 -0
- package/src/data/collectionParams.ts +57 -0
- package/src/data/queries/useAccount.ts +71 -0
- package/src/data/queries/useAccountSettings.ts +82 -0
- package/src/data/queries/useAnalytics.ts +86 -0
- package/src/data/queries/useAuthActions.ts +28 -0
- package/src/data/queries/useBlog.ts +103 -0
- package/src/data/queries/useBlogComments.ts +96 -0
- package/src/data/queries/useBroadcasts.ts +31 -0
- package/src/data/queries/useCertificates.ts +58 -0
- package/src/data/queries/useChat.ts +267 -0
- package/src/data/queries/useCoachingAvailability.ts +69 -0
- package/src/data/queries/useCoachingProducts.ts +67 -0
- package/src/data/queries/useCohortPage.ts +17 -0
- package/src/data/queries/useCollections.ts +211 -0
- package/src/data/queries/useCommunity.ts +794 -0
- package/src/data/queries/useCoupons.ts +20 -0
- package/src/data/queries/useCourseAccess.ts +50 -0
- package/src/data/queries/useCourses.ts +91 -0
- package/src/data/queries/useCurrencies.ts +33 -0
- package/src/data/queries/useDocuments.ts +208 -0
- package/src/data/queries/useDonations.ts +29 -0
- package/src/data/queries/useEngagement.ts +115 -0
- package/src/data/queries/useEvents.ts +69 -0
- package/src/data/queries/useFinalize.ts +109 -0
- package/src/data/queries/useForms.ts +31 -0
- package/src/data/queries/useInvoice.ts +65 -0
- package/src/data/queries/useLeadMagnet.ts +96 -0
- package/src/data/queries/useMembership.ts +20 -0
- package/src/data/queries/useNotifications.ts +267 -0
- package/src/data/queries/useOffers.ts +56 -0
- package/src/data/queries/useOrders.ts +71 -0
- package/src/data/queries/usePWA.ts +101 -0
- package/src/data/queries/usePageActions.ts +36 -0
- package/src/data/queries/usePaymentFlow.ts +80 -0
- package/src/data/queries/usePaymentLink.ts +47 -0
- package/src/data/queries/usePodcast.ts +85 -0
- package/src/data/queries/usePostCollections.ts +48 -0
- package/src/data/queries/usePosts.ts +94 -0
- package/src/data/queries/useProducts.ts +108 -0
- package/src/data/queries/useReplay.ts +19 -0
- package/src/data/queries/useReviews.ts +122 -0
- package/src/data/queries/useShipping.ts +109 -0
- package/src/data/queries/useSubscriptions.ts +66 -0
- package/src/data/queries/useWebsite.ts +128 -0
- package/src/data/queries/useWebsiteAgent.ts +100 -0
- package/src/index.ts +98 -0
- package/src/provider/ForgeAppProvider.tsx +149 -0
- package/src/provider/ForgeProvider.tsx +91 -0
- package/src/provider/SiteConfigProvider.tsx +27 -0
- package/src/server/_tests/buildPwaHead.spec.ts +182 -0
- package/src/server/_tests/buildWebManifest.spec.ts +313 -0
- package/src/server/_tests/forgeServiceWorkerScript.spec.ts +251 -0
- package/src/server/index.ts +260 -0
- package/src/server/jobs.ts +104 -0
- package/src/server/pwa.ts +332 -0
- package/src/types/index.ts +74 -0
- package/src/types/models.ts +1313 -0
- package/src/ui/analytics/ForgeAnalytics.tsx +208 -0
- package/src/ui/analytics/PageMetaPixel.tsx +92 -0
- package/src/ui/content/Editable.tsx +90 -0
- package/src/ui/format/PriceDisplay.tsx +142 -0
- package/src/ui/format/useFormatCurrency.ts +92 -0
- package/src/ui/headless/agent/useAiAgent.ts +106 -0
- package/src/ui/headless/auth/useLoginFlow.ts +72 -0
- package/src/ui/headless/auth/useSignupForm.ts +85 -0
- package/src/ui/headless/broadcast/useBroadcastWatch.ts +59 -0
- package/src/ui/headless/chat/attachmentKind.ts +27 -0
- package/src/ui/headless/chat/useChatAttachmentUpload.ts +132 -0
- package/src/ui/headless/chat/useChatRoom.ts +223 -0
- package/src/ui/headless/chat/useMessageThread.ts +60 -0
- package/src/ui/headless/checkout/useCheckout.ts +438 -0
- package/src/ui/headless/coaching/useCoachingBooking.ts +223 -0
- package/src/ui/headless/community/index.ts +20 -0
- package/src/ui/headless/community/lexical.ts +131 -0
- package/src/ui/headless/community/replyTree.ts +51 -0
- package/src/ui/headless/community/richLexical.ts +314 -0
- package/src/ui/headless/community/useCommunityAttachmentUpload.ts +137 -0
- package/src/ui/headless/community/useCommunitySpaceFeed.ts +122 -0
- package/src/ui/headless/community/useCommunityThread.ts +95 -0
- package/src/ui/headless/community/useMentionComposer.ts +105 -0
- package/src/ui/headless/community/useRichMentionComposer.ts +193 -0
- package/src/ui/headless/consent/useCookieConsent.ts +81 -0
- package/src/ui/headless/course/useCourseCheckout.ts +99 -0
- package/src/ui/headless/dialog.tsx +14 -0
- package/src/ui/headless/document/index.ts +7 -0
- package/src/ui/headless/document/useContractSignature.ts +68 -0
- package/src/ui/headless/document/useDocumentSigningFlow.ts +92 -0
- package/src/ui/headless/document/useOtpCodeInput.ts +86 -0
- package/src/ui/headless/document/useSignaturePad.ts +180 -0
- package/src/ui/headless/donation/Donation.tsx +115 -0
- package/src/ui/headless/donation/DonationContext.tsx +223 -0
- package/src/ui/headless/donation/fees.ts +15 -0
- package/src/ui/headless/donation/index.ts +10 -0
- package/src/ui/headless/engagement/usePostEngagement.ts +119 -0
- package/src/ui/headless/event/useEventCheckout.ts +141 -0
- package/src/ui/headless/forms/useContactForm.ts +58 -0
- package/src/ui/headless/forms/useEmailListForm.ts +75 -0
- package/src/ui/headless/forms/useSectionedForm.ts +256 -0
- package/src/ui/headless/index.ts +60 -0
- package/src/ui/headless/invoice/useInvoicePayment.ts +48 -0
- package/src/ui/headless/memberHome/index.ts +7 -0
- package/src/ui/headless/memberHome/useMemberHomeNav.ts +82 -0
- package/src/ui/headless/membership/MembershipGate.tsx +39 -0
- package/src/ui/headless/membership/useMembershipCheckout.ts +121 -0
- package/src/ui/headless/offer/Offer.tsx +88 -0
- package/src/ui/headless/offer/OfferContext.tsx +162 -0
- package/src/ui/headless/offer/index.ts +8 -0
- package/src/ui/headless/paymentLink/usePaymentLinkPayment.ts +46 -0
- package/src/ui/headless/podcast/usePodcastPlayer.ts +96 -0
- package/src/ui/headless/posts/index.ts +13 -0
- package/src/ui/headless/posts/upsell.ts +41 -0
- package/src/ui/headless/posts/useMemberPostsFeed.ts +110 -0
- package/src/ui/headless/posts/usePostCollectionDetail.ts +56 -0
- package/src/ui/headless/posts/usePostCollectionsList.ts +90 -0
- package/src/ui/headless/posts/usePostItem.ts +81 -0
- package/src/ui/headless/reviews/index.ts +6 -0
- package/src/ui/headless/reviews/useHelpfulVote.ts +45 -0
- package/src/ui/headless/reviews/useReviewForm.ts +108 -0
- package/src/ui/headless/reviews/useReviewsList.ts +79 -0
- package/src/ui/headless/work/index.ts +25 -0
- package/src/ui/headless/work/useWorkPortal.ts +349 -0
- package/src/ui/index.ts +144 -0
- package/src/ui/payment/ForgePaymentProvider.tsx +45 -0
- package/src/ui/payment/ForgeStripePayment.tsx +171 -0
- package/src/ui/shell/TribeNestApp.tsx +121 -0
- package/src/ui/shell/shellGating.spec.ts +28 -0
- package/src/ui/shell/shellGating.ts +11 -0
- package/src/ui/styled/AccountDashboard.tsx +575 -0
- package/src/ui/styled/AiAgentWidget.tsx +152 -0
- package/src/ui/styled/AudioPlayer.tsx +123 -0
- package/src/ui/styled/BlogCategory.tsx +79 -0
- package/src/ui/styled/BlogComments.tsx +236 -0
- package/src/ui/styled/BlogList.tsx +101 -0
- package/src/ui/styled/BlogPost.tsx +160 -0
- package/src/ui/styled/BlogPostFooter.tsx +33 -0
- package/src/ui/styled/BotProtection.tsx +101 -0
- package/src/ui/styled/Button.tsx +88 -0
- package/src/ui/styled/Cart.tsx +276 -0
- package/src/ui/styled/ChatRoom.tsx +252 -0
- package/src/ui/styled/Checkout.tsx +988 -0
- package/src/ui/styled/CheckoutConfirmation.tsx +452 -0
- package/src/ui/styled/CoachingBooking.tsx +605 -0
- package/src/ui/styled/CoachingConfirmation.tsx +151 -0
- package/src/ui/styled/CoachingDetail.tsx +99 -0
- package/src/ui/styled/CohortPage.tsx +82 -0
- package/src/ui/styled/ConfirmSubscription.tsx +134 -0
- package/src/ui/styled/Confirmation.tsx +171 -0
- package/src/ui/styled/ContactForm.tsx +81 -0
- package/src/ui/styled/CookieConsent.tsx +239 -0
- package/src/ui/styled/CourseAccess.tsx +169 -0
- package/src/ui/styled/CourseCheckout.tsx +425 -0
- package/src/ui/styled/CourseConfirmation.tsx +191 -0
- package/src/ui/styled/CourseDetail.tsx +140 -0
- package/src/ui/styled/CoursesGrid.tsx +85 -0
- package/src/ui/styled/CurrencySwitcher.tsx +51 -0
- package/src/ui/styled/DonationButton.tsx +247 -0
- package/src/ui/styled/DonationPage.tsx +29 -0
- package/src/ui/styled/EmailListForm.tsx +88 -0
- package/src/ui/styled/EventConfirmation.tsx +228 -0
- package/src/ui/styled/EventCountdown.tsx +98 -0
- package/src/ui/styled/EventDetail.tsx +141 -0
- package/src/ui/styled/EventTickets.tsx +548 -0
- package/src/ui/styled/EventsList.tsx +83 -0
- package/src/ui/styled/ForgotPasswordForm.tsx +105 -0
- package/src/ui/styled/FormRenderer.tsx +249 -0
- package/src/ui/styled/InstallBanner.tsx +114 -0
- package/src/ui/styled/InvoiceConfirmation.tsx +50 -0
- package/src/ui/styled/InvoicePayment.tsx +141 -0
- package/src/ui/styled/LeadMagnet.tsx +93 -0
- package/src/ui/styled/Loading.tsx +56 -0
- package/src/ui/styled/LoginForm.tsx +134 -0
- package/src/ui/styled/Markdown.tsx +103 -0
- package/src/ui/styled/MembershipCheckout.tsx +197 -0
- package/src/ui/styled/MembershipTiers.tsx +115 -0
- package/src/ui/styled/OfferButton.tsx +181 -0
- package/src/ui/styled/PageActions.tsx +124 -0
- package/src/ui/styled/PaymentLinkConfirmation.tsx +50 -0
- package/src/ui/styled/PaymentLinkPayment.tsx +85 -0
- package/src/ui/styled/Paywall.tsx +54 -0
- package/src/ui/styled/PlayButton.tsx +48 -0
- package/src/ui/styled/PodcastEpisode.tsx +137 -0
- package/src/ui/styled/PodcastList.tsx +58 -0
- package/src/ui/styled/PodcastShow.tsx +164 -0
- package/src/ui/styled/PostsFeed.tsx +63 -0
- package/src/ui/styled/ProductDetail.tsx +616 -0
- package/src/ui/styled/ProductGrid.tsx +82 -0
- package/src/ui/styled/PushOptIn.tsx +65 -0
- package/src/ui/styled/PwaRegistration.tsx +58 -0
- package/src/ui/styled/ReactionBar.tsx +93 -0
- package/src/ui/styled/ReplayList.tsx +134 -0
- package/src/ui/styled/ResetPasswordForm.tsx +127 -0
- package/src/ui/styled/ReviewForm.tsx +292 -0
- package/src/ui/styled/ReviewsSection.tsx +260 -0
- package/src/ui/styled/SectionedFormRenderer.tsx +146 -0
- package/src/ui/styled/SignupForm.tsx +109 -0
- package/src/ui/styled/UserMenu.tsx +166 -0
- package/src/ui/styled/chat/ChatAttachments.tsx +172 -0
- package/src/ui/styled/chat/ChatComposer.tsx +153 -0
- package/src/ui/styled/chat/ChatThreadPanel.tsx +94 -0
- package/src/ui/styled/chat/MessageBubble.tsx +66 -0
- package/src/ui/styled/chat/NewDmDialog.tsx +78 -0
- package/src/ui/styled/community/CommunityComposer.tsx +358 -0
- package/src/ui/styled/community/CommunityFeed.tsx +483 -0
- package/src/ui/styled/community/CommunityLeaderboard.tsx +109 -0
- package/src/ui/styled/community/CommunityMediaGallery.tsx +116 -0
- package/src/ui/styled/community/CommunityMemberProfile.tsx +285 -0
- package/src/ui/styled/community/CommunityNotifications.tsx +222 -0
- package/src/ui/styled/community/CommunityPostCard.tsx +165 -0
- package/src/ui/styled/community/CommunityPostDetail.tsx +337 -0
- package/src/ui/styled/community/CommunityReactionBar.tsx +107 -0
- package/src/ui/styled/community/CommunityRichText.tsx +157 -0
- package/src/ui/styled/community/CommunitySpaces.tsx +174 -0
- package/src/ui/styled/community/MemberAvatar.tsx +74 -0
- package/src/ui/styled/community/index.ts +23 -0
- package/src/ui/styled/community/util.ts +15 -0
- package/src/ui/styled/members/MemberHomeLayout.tsx +98 -0
- package/src/ui/styled/members/MemberPostCard.tsx +392 -0
- package/src/ui/styled/members/MemberPostsFeed.tsx +219 -0
- package/src/ui/styled/members/PostCollectionDetail.tsx +227 -0
- package/src/ui/styled/members/PostCollectionsGrid.tsx +287 -0
- package/src/ui/styled/members/index.ts +7 -0
- package/src/ui/styled/rich-text.css +150 -0
- package/src/ui/styled/work/WorkInviteAccept.tsx +222 -0
- package/src/ui/styled/work/WorkPortalProjects.tsx +163 -0
- package/src/ui/styled/work/WorkProjectInvoices.tsx +113 -0
- package/src/ui/styled/work/WorkProjectReport.tsx +66 -0
- package/src/ui/styled/work/WorkReportBody.tsx +327 -0
- package/src/ui/styled/work/WorkTaskAttachments.tsx +67 -0
- package/src/ui/styled/work/WorkTaskComments.tsx +151 -0
- package/src/ui/styled/work/WorkTaskDetail.tsx +92 -0
- package/src/ui/styled/work/WorkTokenReport.tsx +49 -0
- package/src/ui/styled/work/index.ts +10 -0
- package/src/ui/theme/ForgeThemeProvider.tsx +121 -0
- package/src/ui/theme/contrast.ts +22 -0
- package/src/utils/analyticsBus.ts +31 -0
- package/src/utils/attribution.ts +68 -0
- package/src/utils/cookieConsent.ts +83 -0
- package/src/utils/landing.ts +153 -0
- package/src/utils/metaPixel.ts +122 -0
- package/src/utils/structuredData.ts +126 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMutation,
|
|
3
|
+
useQuery,
|
|
4
|
+
useQueryClient,
|
|
5
|
+
type UseQueryResult,
|
|
6
|
+
} from "@tanstack/react-query";
|
|
7
|
+
import { useForge } from "../../../provider/ForgeProvider";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Headless data layer for the read-only "Work" (project management) CLIENT
|
|
11
|
+
* PORTAL. Speaks the public, profile-scoped `/public/work/*` API through the
|
|
12
|
+
* Forge Axios client (`useForge().client`). For the two authed reads the bearer
|
|
13
|
+
* token IS the client identity (an external contact linked to a platform
|
|
14
|
+
* account, enforced server-side); the tokenized report needs no login at all.
|
|
15
|
+
*
|
|
16
|
+
* These are Tier-1 primitives: they return clean React Query results
|
|
17
|
+
* ({ data, isLoading, error, … }) and render nothing, so a creator can build a
|
|
18
|
+
* fully custom portal UI on top of them. The styled `WorkPortalProjects` /
|
|
19
|
+
* `WorkProjectReport` / `WorkTokenReport` blocks are built on exactly these.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// ── Types (mirror WorkPublicService responses) ───────────────────────────────
|
|
23
|
+
|
|
24
|
+
export interface WorkProjectSummary {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
status: string;
|
|
28
|
+
currency: string;
|
|
29
|
+
dueDate: string | null;
|
|
30
|
+
startDate: string | null;
|
|
31
|
+
taskCount: number;
|
|
32
|
+
doneCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WorkMilestone {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
dueDate: string | null;
|
|
39
|
+
status: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WorkTask {
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
done: boolean;
|
|
46
|
+
dueDate: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WorkTimeReportRow {
|
|
50
|
+
taskId: string | null;
|
|
51
|
+
taskTitle: string;
|
|
52
|
+
hours: number;
|
|
53
|
+
amount: number;
|
|
54
|
+
amountCents: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WorkTimeReport {
|
|
58
|
+
currency: string;
|
|
59
|
+
byTask: WorkTimeReportRow[];
|
|
60
|
+
totalHours: number;
|
|
61
|
+
totalAmount: number;
|
|
62
|
+
totalAmountCents: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface WorkProjectReportData {
|
|
66
|
+
project: {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
status: string;
|
|
70
|
+
currency: string;
|
|
71
|
+
dueDate: string | null;
|
|
72
|
+
startDate: string | null;
|
|
73
|
+
};
|
|
74
|
+
milestones: WorkMilestone[];
|
|
75
|
+
tasks: WorkTask[];
|
|
76
|
+
timeReport: WorkTimeReport;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface WorkTaskComment {
|
|
80
|
+
id: string;
|
|
81
|
+
content: string;
|
|
82
|
+
accountId: string;
|
|
83
|
+
fullName: string;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface WorkTaskAttachment {
|
|
88
|
+
id: string;
|
|
89
|
+
mediaId: string;
|
|
90
|
+
name: string | null;
|
|
91
|
+
filename: string | null;
|
|
92
|
+
url: string | null;
|
|
93
|
+
type: string | null;
|
|
94
|
+
size: number | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface WorkProjectInvoice {
|
|
98
|
+
id: string;
|
|
99
|
+
invoiceNumber: string;
|
|
100
|
+
status: string;
|
|
101
|
+
total: number;
|
|
102
|
+
currency: string;
|
|
103
|
+
dueDate: string | null;
|
|
104
|
+
paidAt: string | null;
|
|
105
|
+
createdAt: string;
|
|
106
|
+
/** Still owing → the portal shows a Pay button. */
|
|
107
|
+
payable: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface WorkInviteContext {
|
|
111
|
+
projectId: string;
|
|
112
|
+
projectName: string;
|
|
113
|
+
profileName: string;
|
|
114
|
+
email: string;
|
|
115
|
+
firstName: string | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface WorkInviteAcceptResult {
|
|
119
|
+
token: string;
|
|
120
|
+
projectId: string;
|
|
121
|
+
account: { id: string; email: string; firstName: string | null; lastName: string | null };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Query keys ───────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
const keys = {
|
|
127
|
+
projects: (profileId?: string) => ["work-portal-projects", profileId] as const,
|
|
128
|
+
report: (profileId: string | undefined, projectId: string) =>
|
|
129
|
+
["work-portal-report", profileId, projectId] as const,
|
|
130
|
+
tokenReport: (token: string) => ["work-portal-token-report", token] as const,
|
|
131
|
+
taskComments: (profileId: string | undefined, projectId: string, taskId: string) =>
|
|
132
|
+
["work-portal-task-comments", profileId, projectId, taskId] as const,
|
|
133
|
+
taskAttachments: (profileId: string | undefined, projectId: string, taskId: string) =>
|
|
134
|
+
["work-portal-task-attachments", profileId, projectId, taskId] as const,
|
|
135
|
+
projectInvoices: (profileId: string | undefined, projectId: string) =>
|
|
136
|
+
["work-portal-project-invoices", profileId, projectId] as const,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ── Authed client's projects ─────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/** The authenticated client's (non-archived) client projects with a task/done
|
|
142
|
+
* summary — GET /public/work/projects. The token is the identity. */
|
|
143
|
+
export function useWorkPortalProjects(): UseQueryResult<WorkProjectSummary[]> {
|
|
144
|
+
const { client, profileId } = useForge();
|
|
145
|
+
return useQuery<WorkProjectSummary[]>({
|
|
146
|
+
queryKey: keys.projects(profileId),
|
|
147
|
+
queryFn: async () => {
|
|
148
|
+
const res = await client.get("/public/work/projects", { params: { profileId } });
|
|
149
|
+
return res.data as WorkProjectSummary[];
|
|
150
|
+
},
|
|
151
|
+
enabled: !!profileId && !!client,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Authed client's single-project report ────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/** The authenticated client's overview + billable-only time report for one of
|
|
158
|
+
* their projects — GET /public/work/projects/:id/report. */
|
|
159
|
+
export function useWorkProjectReport(projectId: string): UseQueryResult<WorkProjectReportData> {
|
|
160
|
+
const { client, profileId } = useForge();
|
|
161
|
+
return useQuery<WorkProjectReportData>({
|
|
162
|
+
queryKey: keys.report(profileId, projectId),
|
|
163
|
+
queryFn: async () => {
|
|
164
|
+
const res = await client.get(`/public/work/projects/${projectId}/report`, {
|
|
165
|
+
params: { profileId },
|
|
166
|
+
});
|
|
167
|
+
return res.data as WorkProjectReportData;
|
|
168
|
+
},
|
|
169
|
+
enabled: !!profileId && !!client && !!projectId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Public tokenized report (no login) ───────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
/** Login-free tokenized share of a project's time report — GET
|
|
176
|
+
* /public/work/time-report/:token. The signed token IS the authorization. */
|
|
177
|
+
export function useWorkTokenReport(token: string): UseQueryResult<WorkProjectReportData> {
|
|
178
|
+
const { client } = useForge();
|
|
179
|
+
return useQuery<WorkProjectReportData>({
|
|
180
|
+
queryKey: keys.tokenReport(token),
|
|
181
|
+
queryFn: async () => {
|
|
182
|
+
const res = await client.get(`/public/work/time-report/${token}`);
|
|
183
|
+
return res.data as WorkProjectReportData;
|
|
184
|
+
},
|
|
185
|
+
enabled: !!client && !!token,
|
|
186
|
+
retry: false,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Authed client's task comments (read + post) ──────────────────────────────
|
|
191
|
+
|
|
192
|
+
export interface UseWorkTaskCommentsResult {
|
|
193
|
+
comments: WorkTaskComment[];
|
|
194
|
+
isLoading: boolean;
|
|
195
|
+
error: unknown;
|
|
196
|
+
/** Post a new comment; resolves once the thread has been refreshed. */
|
|
197
|
+
addComment: (content: string) => Promise<void>;
|
|
198
|
+
isAdding: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The authenticated client's comment thread for a single task on one of their
|
|
203
|
+
* projects — GET/POST /public/work/projects/:projectId/tasks/:taskId/comments.
|
|
204
|
+
* Newest first. The bearer token IS the client identity (server-enforced).
|
|
205
|
+
* Posting invalidates the thread so the new comment appears. Tier-1 primitive:
|
|
206
|
+
* returns clean data + a bound mutation, renders nothing.
|
|
207
|
+
*/
|
|
208
|
+
export function useWorkTaskComments(projectId: string, taskId: string): UseWorkTaskCommentsResult {
|
|
209
|
+
const { client, profileId } = useForge();
|
|
210
|
+
const queryClient = useQueryClient();
|
|
211
|
+
|
|
212
|
+
const query = useQuery<WorkTaskComment[]>({
|
|
213
|
+
queryKey: keys.taskComments(profileId, projectId, taskId),
|
|
214
|
+
queryFn: async () => {
|
|
215
|
+
const res = await client.get(
|
|
216
|
+
`/public/work/projects/${projectId}/tasks/${taskId}/comments`,
|
|
217
|
+
{ params: { profileId } },
|
|
218
|
+
);
|
|
219
|
+
return res.data as WorkTaskComment[];
|
|
220
|
+
},
|
|
221
|
+
enabled: !!profileId && !!client && !!projectId && !!taskId,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const mutation = useMutation<WorkTaskComment, unknown, string>({
|
|
225
|
+
mutationFn: async (content: string) => {
|
|
226
|
+
const res = await client.post(
|
|
227
|
+
`/public/work/projects/${projectId}/tasks/${taskId}/comments`,
|
|
228
|
+
{ content },
|
|
229
|
+
{ params: { profileId } },
|
|
230
|
+
);
|
|
231
|
+
return res.data as WorkTaskComment;
|
|
232
|
+
},
|
|
233
|
+
onSuccess: async () => {
|
|
234
|
+
await queryClient.invalidateQueries({
|
|
235
|
+
queryKey: keys.taskComments(profileId, projectId, taskId),
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
comments: query.data ?? [],
|
|
242
|
+
isLoading: query.isLoading,
|
|
243
|
+
error: query.error,
|
|
244
|
+
addComment: async (content: string) => {
|
|
245
|
+
await mutation.mutateAsync(content);
|
|
246
|
+
},
|
|
247
|
+
isAdding: mutation.isPending,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ── Authed client's task attachments (read-only) ─────────────────────────────
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Read-only attachments the team has added to a task on one of the client's
|
|
255
|
+
* projects — GET /public/work/projects/:projectId/tasks/:taskId/attachments.
|
|
256
|
+
* Tier-1 primitive: returns clean data, renders nothing.
|
|
257
|
+
*/
|
|
258
|
+
export function useWorkTaskAttachments(
|
|
259
|
+
projectId: string,
|
|
260
|
+
taskId: string,
|
|
261
|
+
): UseQueryResult<WorkTaskAttachment[]> {
|
|
262
|
+
const { client, profileId } = useForge();
|
|
263
|
+
return useQuery<WorkTaskAttachment[]>({
|
|
264
|
+
queryKey: keys.taskAttachments(profileId, projectId, taskId),
|
|
265
|
+
queryFn: async () => {
|
|
266
|
+
const res = await client.get(
|
|
267
|
+
`/public/work/projects/${projectId}/tasks/${taskId}/attachments`,
|
|
268
|
+
{ params: { profileId } },
|
|
269
|
+
);
|
|
270
|
+
return res.data as WorkTaskAttachment[];
|
|
271
|
+
},
|
|
272
|
+
enabled: !!profileId && !!client && !!projectId && !!taskId,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Authed client's invoices for a project ───────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The client's (non-draft) invoices for one of their projects —
|
|
280
|
+
* GET /public/work/projects/:id/invoices. Each carries a `payable` flag the
|
|
281
|
+
* portal turns into a Pay button (links to /i/invoice/:id). Tier-1 primitive.
|
|
282
|
+
*/
|
|
283
|
+
export function useWorkProjectInvoices(projectId: string): UseQueryResult<WorkProjectInvoice[]> {
|
|
284
|
+
const { client, profileId } = useForge();
|
|
285
|
+
return useQuery<WorkProjectInvoice[]>({
|
|
286
|
+
queryKey: keys.projectInvoices(profileId, projectId),
|
|
287
|
+
queryFn: async () => {
|
|
288
|
+
const res = await client.get(`/public/work/projects/${projectId}/invoices`, { params: { profileId } });
|
|
289
|
+
return res.data as WorkProjectInvoice[];
|
|
290
|
+
},
|
|
291
|
+
enabled: !!profileId && !!client && !!projectId,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── Client invite accept (public, no auth) ───────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Fetch the branded context for an invite link (GET /public/work/invite).
|
|
299
|
+
* Public — no bearer token needed; the link's token IS the credential.
|
|
300
|
+
* `null`/errored when the token or project is missing/invalid/expired.
|
|
301
|
+
*/
|
|
302
|
+
export function useWorkInvite(
|
|
303
|
+
token: string | undefined,
|
|
304
|
+
projectId: string | undefined,
|
|
305
|
+
): UseQueryResult<WorkInviteContext> {
|
|
306
|
+
const { client } = useForge();
|
|
307
|
+
return useQuery<WorkInviteContext>({
|
|
308
|
+
queryKey: ["work-invite", token, projectId],
|
|
309
|
+
queryFn: async () => {
|
|
310
|
+
const res = await client.get(`/public/work/invite`, { params: { token, projectId } });
|
|
311
|
+
return res.data as WorkInviteContext;
|
|
312
|
+
},
|
|
313
|
+
enabled: !!client && !!token && !!projectId,
|
|
314
|
+
retry: false,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Accept an invite (POST /public/work/invite/accept): set a password and
|
|
320
|
+
* receive a fresh portal session token. The caller persists the token +
|
|
321
|
+
* navigates. Public — no bearer token needed.
|
|
322
|
+
*/
|
|
323
|
+
export function useAcceptWorkInvite() {
|
|
324
|
+
const { client } = useForge();
|
|
325
|
+
return useMutation<WorkInviteAcceptResult, unknown, { token: string; projectId: string; password: string }>({
|
|
326
|
+
mutationFn: async ({ token, projectId, password }) => {
|
|
327
|
+
const res = await client.post(`/public/work/invite/accept`, { token, projectId, password });
|
|
328
|
+
return res.data as WorkInviteAcceptResult;
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Presentational helpers ───────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
/** Major-unit amount → display string using the project currency symbol. */
|
|
336
|
+
export function formatWorkMoney(amount: number, currency = "USD"): string {
|
|
337
|
+
const symbols: Record<string, string> = { USD: "$", EUR: "€", GBP: "£", NGN: "₦", CAD: "$", AUD: "$" };
|
|
338
|
+
const symbol = symbols[currency?.toUpperCase()] ?? `${currency} `;
|
|
339
|
+
const n = Number.isFinite(amount) ? amount : 0;
|
|
340
|
+
return `${symbol}${n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** ISO date → short human date, or "—" when absent. */
|
|
344
|
+
export function formatWorkDate(value: string | null | undefined): string {
|
|
345
|
+
if (!value) return "—";
|
|
346
|
+
const d = new Date(value);
|
|
347
|
+
if (Number.isNaN(d.getTime())) return "—";
|
|
348
|
+
return d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
|
349
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Forge UI — Tier-2 styled blocks (theme-aware, opinionated) + the theme
|
|
2
|
+
// contract. Built on the Tier-1 headless primitives in `forge/ui/headless`.
|
|
3
|
+
export {
|
|
4
|
+
ForgeThemeProvider,
|
|
5
|
+
useForgeTheme,
|
|
6
|
+
useThemeTokens,
|
|
7
|
+
resolveThemeTokens,
|
|
8
|
+
defaultForgeTheme,
|
|
9
|
+
type ForgeTheme,
|
|
10
|
+
type ResolvedThemeTokens,
|
|
11
|
+
} from "./theme/ForgeThemeProvider";
|
|
12
|
+
export { readableTextOn } from "./theme/contrast";
|
|
13
|
+
export {
|
|
14
|
+
Button,
|
|
15
|
+
buttonStyle,
|
|
16
|
+
type ButtonProps,
|
|
17
|
+
type ButtonVariant,
|
|
18
|
+
type ButtonSize,
|
|
19
|
+
} from "./styled/Button";
|
|
20
|
+
export {
|
|
21
|
+
ForgePaymentProvider,
|
|
22
|
+
usePaymentRenderer,
|
|
23
|
+
type PaymentRenderer,
|
|
24
|
+
type PaymentRenderProps,
|
|
25
|
+
} from "./payment/ForgePaymentProvider";
|
|
26
|
+
|
|
27
|
+
// Re-export the headless layer so consumers can reach both tiers from `forge/ui`.
|
|
28
|
+
export * from "./headless";
|
|
29
|
+
|
|
30
|
+
// Currency formatting primitive (presentational helper).
|
|
31
|
+
export { useFormatCurrency, useAmountFormatter, formatCurrency, getCurrencySymbol } from "./format/useFormatCurrency";
|
|
32
|
+
|
|
33
|
+
// Content layer (Component 7) — editable field wrappers.
|
|
34
|
+
export {
|
|
35
|
+
EditableText,
|
|
36
|
+
EditableRichText,
|
|
37
|
+
EditableImage,
|
|
38
|
+
EditableRegion,
|
|
39
|
+
type EditableTextProps,
|
|
40
|
+
type EditableRichTextProps,
|
|
41
|
+
type EditableImageProps,
|
|
42
|
+
} from "./content/Editable";
|
|
43
|
+
|
|
44
|
+
// Styled blocks
|
|
45
|
+
export { DonationButton, type DonationButtonProps } from "./styled/DonationButton";
|
|
46
|
+
export { OfferButton, type OfferButtonProps } from "./styled/OfferButton";
|
|
47
|
+
export { Checkout, type CheckoutProps } from "./styled/Checkout";
|
|
48
|
+
export { MembershipCheckout, type MembershipCheckoutProps } from "./styled/MembershipCheckout";
|
|
49
|
+
export { ForgeStripePayment } from "./payment/ForgeStripePayment";
|
|
50
|
+
export { Cart, type CartProps } from "./styled/Cart";
|
|
51
|
+
export { CurrencySwitcher, type CurrencySwitcherProps } from "./styled/CurrencySwitcher";
|
|
52
|
+
export { UserMenu, type UserMenuProps } from "./styled/UserMenu";
|
|
53
|
+
export { EmailListForm, type EmailListFormProps } from "./styled/EmailListForm";
|
|
54
|
+
export { BlogComments, type BlogCommentsProps } from "./styled/BlogComments";
|
|
55
|
+
export { PageActions, type PageActionsProps } from "./styled/PageActions";
|
|
56
|
+
export { BlogPostFooter, type BlogPostFooterProps } from "./styled/BlogPostFooter";
|
|
57
|
+
export { BotProtection, EMPTY_BOT_FIELDS, type BotFields } from "./styled/BotProtection";
|
|
58
|
+
export { ContactForm, type ContactFormProps } from "./styled/ContactForm";
|
|
59
|
+
export { Paywall, type PaywallProps } from "./styled/Paywall";
|
|
60
|
+
export { ReactionBar, type ReactionBarProps } from "./styled/ReactionBar";
|
|
61
|
+
export { ProductGrid, type ProductGridProps } from "./styled/ProductGrid";
|
|
62
|
+
export { ProductDetail, type ProductDetailProps } from "./styled/ProductDetail";
|
|
63
|
+
export { MembershipTiers, type MembershipTiersProps } from "./styled/MembershipTiers";
|
|
64
|
+
export { EventsList, type EventsListProps } from "./styled/EventsList";
|
|
65
|
+
export { EventTickets, type EventTicketsProps } from "./styled/EventTickets";
|
|
66
|
+
export { EventCountdown, type EventCountdownProps } from "./styled/EventCountdown";
|
|
67
|
+
export { EventDetail, type EventDetailProps } from "./styled/EventDetail";
|
|
68
|
+
export { CoachingBooking, type CoachingBookingProps } from "./styled/CoachingBooking";
|
|
69
|
+
export { CoachingDetail, type CoachingDetailProps } from "./styled/CoachingDetail";
|
|
70
|
+
export { CourseCheckout, type CourseCheckoutProps } from "./styled/CourseCheckout";
|
|
71
|
+
export { CourseDetail, type CourseDetailProps } from "./styled/CourseDetail";
|
|
72
|
+
export { PostsFeed, type PostsFeedProps } from "./styled/PostsFeed";
|
|
73
|
+
export { CoursesGrid, type CoursesGridProps } from "./styled/CoursesGrid";
|
|
74
|
+
export { LoginForm, type LoginFormProps } from "./styled/LoginForm";
|
|
75
|
+
export { SignupForm, type SignupFormProps } from "./styled/SignupForm";
|
|
76
|
+
export { ForgotPasswordForm, type ForgotPasswordFormProps } from "./styled/ForgotPasswordForm";
|
|
77
|
+
export { ResetPasswordForm, type ResetPasswordFormProps } from "./styled/ResetPasswordForm";
|
|
78
|
+
export { AccountDashboard, type AccountDashboardProps, type AccountTabKey, ACCOUNT_TABS } from "./styled/AccountDashboard";
|
|
79
|
+
export { AudioPlayer, type AudioPlayerProps } from "./styled/AudioPlayer";
|
|
80
|
+
export { ForgeAnalytics, type ForgeAnalyticsProps } from "./analytics/ForgeAnalytics";
|
|
81
|
+
export { PageMetaPixel, usePageMetaPixel, type PageMetaPixelProps } from "./analytics/PageMetaPixel";
|
|
82
|
+
export { CookieConsent, type CookieConsentProps } from "./styled/CookieConsent";
|
|
83
|
+
export { Loading, type LoadingProps } from "./styled/Loading";
|
|
84
|
+
export {
|
|
85
|
+
ConfirmationStage,
|
|
86
|
+
ConfirmationCard,
|
|
87
|
+
ConfirmationRow,
|
|
88
|
+
CheckSeal,
|
|
89
|
+
WarnSeal,
|
|
90
|
+
Perforation,
|
|
91
|
+
alpha,
|
|
92
|
+
} from "./styled/Confirmation";
|
|
93
|
+
export { PlayButton, type PlayButtonProps } from "./styled/PlayButton";
|
|
94
|
+
export { CoachingConfirmation, type CoachingConfirmationProps } from "./styled/CoachingConfirmation";
|
|
95
|
+
export { EventConfirmation, type EventConfirmationProps } from "./styled/EventConfirmation";
|
|
96
|
+
export { CourseConfirmation, type CourseConfirmationProps } from "./styled/CourseConfirmation";
|
|
97
|
+
export { CheckoutConfirmation, type CheckoutConfirmationProps } from "./styled/CheckoutConfirmation";
|
|
98
|
+
export { InvoiceConfirmation, type InvoiceConfirmationProps } from "./styled/InvoiceConfirmation";
|
|
99
|
+
export { PaymentLinkConfirmation, type PaymentLinkConfirmationProps } from "./styled/PaymentLinkConfirmation";
|
|
100
|
+
export { FormRenderer, FieldInput, type FormRendererProps } from "./styled/FormRenderer";
|
|
101
|
+
export { SectionedFormRenderer, type SectionedFormRendererProps } from "./styled/SectionedFormRenderer";
|
|
102
|
+
export { ChatRoom, type ChatRoomProps } from "./styled/ChatRoom";
|
|
103
|
+
export { ChatAttachments } from "./styled/chat/ChatAttachments";
|
|
104
|
+
export { ChatComposer, type ChatComposerProps } from "./styled/chat/ChatComposer";
|
|
105
|
+
export { ChatThreadPanel, type ChatThreadPanelProps } from "./styled/chat/ChatThreadPanel";
|
|
106
|
+
export { MessageBubble, type MessageBubbleProps } from "./styled/chat/MessageBubble";
|
|
107
|
+
export { NewDmDialog, type NewDmDialogProps } from "./styled/chat/NewDmDialog";
|
|
108
|
+
export { AiAgentWidget, type AiAgentWidgetProps } from "./styled/AiAgentWidget";
|
|
109
|
+
export { Markdown } from "./styled/Markdown";
|
|
110
|
+
export { useAiAgent, type AiAgentMessage } from "./headless/agent/useAiAgent";
|
|
111
|
+
export { InvoicePayment, type InvoicePaymentProps } from "./styled/InvoicePayment";
|
|
112
|
+
export { PaymentLinkPayment, type PaymentLinkPaymentProps } from "./styled/PaymentLinkPayment";
|
|
113
|
+
export { BlogList, type BlogListProps } from "./styled/BlogList";
|
|
114
|
+
export { BlogPost, type BlogPostProps } from "./styled/BlogPost";
|
|
115
|
+
export { BlogCategory, type BlogCategoryProps } from "./styled/BlogCategory";
|
|
116
|
+
export { PodcastList, type PodcastListProps } from "./styled/PodcastList";
|
|
117
|
+
export { PodcastShow, type PodcastShowProps } from "./styled/PodcastShow";
|
|
118
|
+
export { PodcastEpisode, type PodcastEpisodeProps } from "./styled/PodcastEpisode";
|
|
119
|
+
export { ReplayList, type ReplayListProps } from "./styled/ReplayList";
|
|
120
|
+
export { ConfirmSubscription, type ConfirmSubscriptionProps } from "./styled/ConfirmSubscription";
|
|
121
|
+
export { LeadMagnet, type LeadMagnetProps } from "./styled/LeadMagnet";
|
|
122
|
+
export { CohortPage, type CohortPageProps } from "./styled/CohortPage";
|
|
123
|
+
export { CourseAccess, type CourseAccessProps } from "./styled/CourseAccess";
|
|
124
|
+
export { DonationPage, type DonationPageProps } from "./styled/DonationPage";
|
|
125
|
+
export { ReviewsSection, StarRow, type ReviewsSectionProps } from "./styled/ReviewsSection";
|
|
126
|
+
export { ReviewForm, type ReviewFormProps } from "./styled/ReviewForm";
|
|
127
|
+
export { PriceDisplay, priceTaxCaption, summarizeTaxQuote, type PriceDisplayProps } from "./format/PriceDisplay";
|
|
128
|
+
export { usePricesIncludeTax } from "../data/queries/useWebsite";
|
|
129
|
+
|
|
130
|
+
// PWA (installable code sites) — SW registration + install/push UX (Tier 2).
|
|
131
|
+
export { PwaRegistration, type PwaRegistrationProps } from "./styled/PwaRegistration";
|
|
132
|
+
export { InstallBanner, type InstallBannerProps } from "./styled/InstallBanner";
|
|
133
|
+
// The single root component — providers + audio + analytics + PWA + consent in one.
|
|
134
|
+
export { TribeNestApp, type TribeNestAppProps } from "./shell/TribeNestApp";
|
|
135
|
+
export { PushOptIn, type PushOptInProps } from "./styled/PushOptIn";
|
|
136
|
+
|
|
137
|
+
// Community (forum) styled blocks.
|
|
138
|
+
export * from "./styled/community";
|
|
139
|
+
|
|
140
|
+
// Work (project management) client-portal styled blocks.
|
|
141
|
+
export * from "./styled/work";
|
|
142
|
+
|
|
143
|
+
// Unified member home (`/i/members`) styled blocks.
|
|
144
|
+
export * from "./styled/members";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
2
|
+
import { ForgeStripePayment } from "./ForgeStripePayment";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What a payment block hands its renderer. Forge ships a built-in Stripe
|
|
6
|
+
* renderer (`ForgeStripePayment`, wired to the tenant's runtime `useSiteConfig`)
|
|
7
|
+
* and uses it by default; an app can still register its own UI (a different
|
|
8
|
+
* provider, custom styling) via `<ForgePaymentProvider>`.
|
|
9
|
+
*/
|
|
10
|
+
export interface PaymentRenderProps {
|
|
11
|
+
clientSecret?: string;
|
|
12
|
+
returnUrl: string;
|
|
13
|
+
amount: number;
|
|
14
|
+
currency?: string;
|
|
15
|
+
/** "redirect" (default) or "manual" (confirm in place, drive result via callbacks). */
|
|
16
|
+
mode?: "redirect" | "manual";
|
|
17
|
+
onSucceeded?: () => void;
|
|
18
|
+
onFailed?: (message?: string) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PaymentRenderer = (props: PaymentRenderProps) => ReactNode;
|
|
22
|
+
|
|
23
|
+
const PaymentRendererContext = createContext<PaymentRenderer | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* OPTIONAL override — register a custom payment UI so every payment block
|
|
27
|
+
* (Donation, Offer, Checkout, …) uses it instead of Forge's built-in Stripe
|
|
28
|
+
* renderer. Most sites don't need this: leave it out and payment works out of
|
|
29
|
+
* the box via `ForgeStripePayment`.
|
|
30
|
+
*/
|
|
31
|
+
export function ForgePaymentProvider({ render, children }: { render: PaymentRenderer; children: ReactNode }) {
|
|
32
|
+
return <PaymentRendererContext.Provider value={render}>{children}</PaymentRendererContext.Provider>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Forge's built-in default: the self-contained Stripe renderer. */
|
|
36
|
+
const builtInStripeRenderer: PaymentRenderer = (props) => <ForgeStripePayment {...props} />;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The payment renderer used by the payment blocks: the app's registered one if
|
|
40
|
+
* present, otherwise Forge's built-in Stripe renderer. Never null — payment
|
|
41
|
+
* always has a UI without any app wiring.
|
|
42
|
+
*/
|
|
43
|
+
export function usePaymentRenderer(): PaymentRenderer {
|
|
44
|
+
return useContext(PaymentRendererContext) ?? builtInStripeRenderer;
|
|
45
|
+
}
|