@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,20 @@
|
|
|
1
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
2
|
+
import { useMutation } from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
export type ValidatedCoupon = {
|
|
5
|
+
code: string;
|
|
6
|
+
membershipTierId?: string;
|
|
7
|
+
membershipEndDate?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** Validate a coupon code (e.g. a membership coupon) for the profile. */
|
|
11
|
+
export function useValidateCoupon() {
|
|
12
|
+
const { client, profileId } = useForge();
|
|
13
|
+
|
|
14
|
+
return useMutation<{ coupon: ValidatedCoupon }, unknown, { code: string; type: string }>({
|
|
15
|
+
mutationFn: async ({ code, type }) => {
|
|
16
|
+
const res = await client.post("/public/coupons/validate", { profileId, code, type });
|
|
17
|
+
return res.data;
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CourseAccessData } from "../../types/models";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
/** A purchased course-access record (course content + progress) by access id. */
|
|
6
|
+
export function useCourseAccess(accessId?: string) {
|
|
7
|
+
const { client, profileId } = useForge();
|
|
8
|
+
|
|
9
|
+
return useQuery<CourseAccessData>({
|
|
10
|
+
queryKey: ["course-access", accessId],
|
|
11
|
+
queryFn: async () => {
|
|
12
|
+
const res = await client.get(`/public/courses/access/${accessId}`, {
|
|
13
|
+
params: { profileId },
|
|
14
|
+
});
|
|
15
|
+
return res.data;
|
|
16
|
+
},
|
|
17
|
+
enabled: !!client && !!accessId,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Save lesson video progress / completion for a course-access record. */
|
|
22
|
+
export function useUpdateCourseProgress(accessId?: string) {
|
|
23
|
+
const { client } = useForge();
|
|
24
|
+
|
|
25
|
+
return useMutation<unknown, unknown, { lessonId: string; videoProgress: number; isCompleted?: boolean }>({
|
|
26
|
+
mutationFn: async (data) => {
|
|
27
|
+
const res = await client.post("/public/courses/progress", { accessId, ...data });
|
|
28
|
+
return res.data;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Claim course access from an emailed link — sets the member's password and
|
|
35
|
+
* returns a session token + the access id.
|
|
36
|
+
*/
|
|
37
|
+
export function useClaimCourseAccess() {
|
|
38
|
+
const { client } = useForge();
|
|
39
|
+
|
|
40
|
+
return useMutation<
|
|
41
|
+
{ token?: string; accessId?: string },
|
|
42
|
+
unknown,
|
|
43
|
+
{ token: string; password: string; firstName: string; lastName: string }
|
|
44
|
+
>({
|
|
45
|
+
mutationFn: async (body) => {
|
|
46
|
+
const res = await client.post("/public/courses/access/claim", body);
|
|
47
|
+
return res.data;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { PaginatedData, PublicCourse } from "../../types/models";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
export type CreateCourseBookingInput = {
|
|
6
|
+
email: string;
|
|
7
|
+
firstName: string;
|
|
8
|
+
lastName: string;
|
|
9
|
+
questionnaire?: unknown;
|
|
10
|
+
attributionRefId?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Create a course booking (buyer details → bookingId; free courses skip payment). */
|
|
14
|
+
export function useCreateCourseBooking(courseId?: string) {
|
|
15
|
+
const { client, profileId } = useForge();
|
|
16
|
+
|
|
17
|
+
return useMutation<{ bookingId: string; isFree?: boolean }, unknown, CreateCourseBookingInput>({
|
|
18
|
+
mutationFn: async (body) => {
|
|
19
|
+
const res = await client.post(`/public/courses/${courseId}/bookings`, {
|
|
20
|
+
courseId,
|
|
21
|
+
profileId,
|
|
22
|
+
...body,
|
|
23
|
+
});
|
|
24
|
+
return res.data;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GetCoursesParams {
|
|
30
|
+
page?: number;
|
|
31
|
+
query?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useGetCourses(params?: GetCoursesParams, enabled = true) {
|
|
35
|
+
const { client, profileId } = useForge();
|
|
36
|
+
|
|
37
|
+
return useQuery<PaginatedData<PublicCourse>>({
|
|
38
|
+
queryKey: ["courses", profileId, params],
|
|
39
|
+
queryFn: async () => {
|
|
40
|
+
const res = await client.get("/public/courses", {
|
|
41
|
+
params: {
|
|
42
|
+
profileId: profileId,
|
|
43
|
+
page: params?.page || 1,
|
|
44
|
+
limit: 10,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return res.data;
|
|
48
|
+
},
|
|
49
|
+
enabled: !!profileId && !!client && enabled,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useGetCourse(courseId?: string, options?: { initialData?: PublicCourse }) {
|
|
54
|
+
const { client, profileId } = useForge();
|
|
55
|
+
|
|
56
|
+
return useQuery<PublicCourse>({
|
|
57
|
+
queryKey: ["course", profileId, { courseId }],
|
|
58
|
+
queryFn: async () => {
|
|
59
|
+
const res = await client.get(`/public/courses/${courseId}`, {
|
|
60
|
+
params: {
|
|
61
|
+
profileId: profileId,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return res.data;
|
|
65
|
+
},
|
|
66
|
+
enabled: !!profileId && !!client && !!courseId,
|
|
67
|
+
initialData: options?.initialData,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useGetCoursesByIds(courseIds: string[]) {
|
|
72
|
+
const { client, profileId } = useForge();
|
|
73
|
+
const idsKey = courseIds.join(",");
|
|
74
|
+
|
|
75
|
+
return useQuery<PublicCourse[]>({
|
|
76
|
+
queryKey: ["courses", "batch", profileId, idsKey],
|
|
77
|
+
queryFn: async () => {
|
|
78
|
+
const res = await client.get("/public/courses/batch", {
|
|
79
|
+
params: {
|
|
80
|
+
profileId: profileId,
|
|
81
|
+
ids: idsKey,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const data = res.data as PublicCourse[];
|
|
85
|
+
return courseIds
|
|
86
|
+
.map((id) => data.find((c) => c.id === id))
|
|
87
|
+
.filter((c): c is PublicCourse => c != null);
|
|
88
|
+
},
|
|
89
|
+
enabled: !!profileId && !!client && courseIds.length > 0,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import type { Currency } from "../../types/models";
|
|
3
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
4
|
+
|
|
5
|
+
export interface CurrenciesResponse {
|
|
6
|
+
supportedCurrencies: Currency[];
|
|
7
|
+
userCurrency: Currency;
|
|
8
|
+
exchangeRates: Record<Currency, Record<Currency, number>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Supported currencies + exchange rates for the tenant (GET /public/currencies).
|
|
13
|
+
*
|
|
14
|
+
* `PublicAuthProvider` already fetches this under the same query key, so when a
|
|
15
|
+
* site renders inside the auth provider this hook just reads the shared cache.
|
|
16
|
+
* It is exported standalone so a creator rebuilding a price/currency UI can use
|
|
17
|
+
* it (and `useFormatCurrency`) without depending on the auth context.
|
|
18
|
+
*/
|
|
19
|
+
export function useCurrencies() {
|
|
20
|
+
const { client, profileId } = useForge();
|
|
21
|
+
|
|
22
|
+
return useQuery<CurrenciesResponse>({
|
|
23
|
+
// profileId scopes the cache — supportedCurrencies is narrowed to the tenant's
|
|
24
|
+
// connected account, so tenants must not share the same cache entry.
|
|
25
|
+
queryKey: ["public-currencies", profileId],
|
|
26
|
+
queryFn: async () => {
|
|
27
|
+
const res = await client.get("/public/currencies", { params: { profileId } });
|
|
28
|
+
return res.data;
|
|
29
|
+
},
|
|
30
|
+
enabled: !!client,
|
|
31
|
+
staleTime: 1000 * 60 * 60 * 2, // 2 hours
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
|
|
4
|
+
// ── Types (mirror apps/backend/src/routes/public/documents + services) ───────
|
|
5
|
+
|
|
6
|
+
export type PublicDocumentKind = "proposal" | "contract";
|
|
7
|
+
export type PublicDocumentStatus =
|
|
8
|
+
| "draft"
|
|
9
|
+
| "sent"
|
|
10
|
+
| "viewed"
|
|
11
|
+
| "accepted"
|
|
12
|
+
| "signed"
|
|
13
|
+
| "declined"
|
|
14
|
+
| "expired"
|
|
15
|
+
| "converted";
|
|
16
|
+
|
|
17
|
+
export interface PublicDocumentPricingOption {
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
amountCents: number;
|
|
21
|
+
currency: string;
|
|
22
|
+
isRecurring: boolean;
|
|
23
|
+
selected: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PublicDocumentRecipient {
|
|
27
|
+
id: string;
|
|
28
|
+
email: string;
|
|
29
|
+
name: string | null;
|
|
30
|
+
role: "signer" | "viewer";
|
|
31
|
+
status: string; // pending | viewed | signed | accepted | declined
|
|
32
|
+
requireOtp: boolean;
|
|
33
|
+
emailVerified: boolean;
|
|
34
|
+
signedName: string | null;
|
|
35
|
+
signedAt: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PublicDocument {
|
|
39
|
+
id: string;
|
|
40
|
+
profileId: string;
|
|
41
|
+
kind: PublicDocumentKind;
|
|
42
|
+
title: string;
|
|
43
|
+
status: PublicDocumentStatus;
|
|
44
|
+
bodyType: "rich" | "pdf";
|
|
45
|
+
/** Lexical JSON (object or serialized string) when bodyType=rich. */
|
|
46
|
+
body: unknown;
|
|
47
|
+
sourcePdfUrl: string | null;
|
|
48
|
+
signedPdfUrl: string | null;
|
|
49
|
+
amountCents: number | null;
|
|
50
|
+
currency: string | null;
|
|
51
|
+
expiresAt: string | null;
|
|
52
|
+
sentAt: string | null;
|
|
53
|
+
completedAt: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PublicDocumentResponse {
|
|
57
|
+
document: PublicDocument;
|
|
58
|
+
recipient: PublicDocumentRecipient;
|
|
59
|
+
pricingOptions: PublicDocumentPricingOption[];
|
|
60
|
+
profile: { name: string; subdomain: string } | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SignDocumentResult {
|
|
64
|
+
success: boolean;
|
|
65
|
+
documentStatus?: PublicDocumentStatus;
|
|
66
|
+
allSigned?: boolean;
|
|
67
|
+
alreadySigned?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const publicDocumentQueryKey = (token: string) => ["public-document", token];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Token-scoped public proposal/contract read (GET /public/documents/:token —
|
|
74
|
+
* the emailed magic link IS the identity, no login). Every GET records a view
|
|
75
|
+
* in the audit trail server-side, so this query is deliberately non-chatty:
|
|
76
|
+
* no window-focus refetch, no retries, infinite staleTime — refresh happens
|
|
77
|
+
* explicitly via invalidation after a sign/accept/decline mutation.
|
|
78
|
+
*/
|
|
79
|
+
export function usePublicDocument(token?: string) {
|
|
80
|
+
const { client } = useForge();
|
|
81
|
+
|
|
82
|
+
return useQuery<PublicDocumentResponse>({
|
|
83
|
+
queryKey: publicDocumentQueryKey(token ?? ""),
|
|
84
|
+
queryFn: async () => {
|
|
85
|
+
const res = await client.get(`/public/documents/${token}`);
|
|
86
|
+
return res.data as PublicDocumentResponse;
|
|
87
|
+
},
|
|
88
|
+
enabled: !!token && !!client,
|
|
89
|
+
retry: false,
|
|
90
|
+
staleTime: Infinity,
|
|
91
|
+
refetchOnWindowFocus: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Email the recipient a one-time code — POST /public/documents/:token/verify-email. */
|
|
96
|
+
export function useRequestDocumentOtp(token: string) {
|
|
97
|
+
const { client } = useForge();
|
|
98
|
+
return useMutation({
|
|
99
|
+
mutationFn: async () => {
|
|
100
|
+
const res = await client.post(`/public/documents/${token}/verify-email`, {});
|
|
101
|
+
return res.data as { success: boolean };
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Confirm the emailed 6-digit code — POST /public/documents/:token/confirm-otp. */
|
|
107
|
+
export function useConfirmDocumentOtp(token: string) {
|
|
108
|
+
const { client } = useForge();
|
|
109
|
+
const queryClient = useQueryClient();
|
|
110
|
+
return useMutation({
|
|
111
|
+
mutationFn: async (code: string) => {
|
|
112
|
+
const res = await client.post(`/public/documents/${token}/confirm-otp`, { code });
|
|
113
|
+
return res.data as { success: boolean };
|
|
114
|
+
},
|
|
115
|
+
onSuccess: () => {
|
|
116
|
+
// Flip recipient.emailVerified locally instead of re-fetching (a re-fetch
|
|
117
|
+
// would write another `viewed` audit event).
|
|
118
|
+
queryClient.setQueryData<PublicDocumentResponse>(publicDocumentQueryKey(token), (prev) =>
|
|
119
|
+
prev ? { ...prev, recipient: { ...prev.recipient, emailVerified: true } } : prev,
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Sign a contract (optional drawn PNG) — POST /public/documents/:token/sign.
|
|
126
|
+
* The signed legal name is derived server-side from the invited recipient
|
|
127
|
+
* (admin-entered, read-only to the signer), so it is never sent from here. */
|
|
128
|
+
export function useSignDocument(token: string) {
|
|
129
|
+
const { client } = useForge();
|
|
130
|
+
const queryClient = useQueryClient();
|
|
131
|
+
return useMutation({
|
|
132
|
+
mutationFn: async (input: { signatureImage?: string } = {}) => {
|
|
133
|
+
const res = await client.post(`/public/documents/${token}/sign`, {
|
|
134
|
+
...(input.signatureImage ? { signatureImage: input.signatureImage } : {}),
|
|
135
|
+
consent: true,
|
|
136
|
+
});
|
|
137
|
+
return res.data as SignDocumentResult;
|
|
138
|
+
},
|
|
139
|
+
onSuccess: () => {
|
|
140
|
+
// Re-fetch to pick up signedPdfUrl + final statuses for the confirmation view.
|
|
141
|
+
queryClient.invalidateQueries({ queryKey: publicDocumentQueryKey(token) });
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Accept a proposal (optionally picking a pricing option) — POST /public/documents/:token/accept. */
|
|
147
|
+
export function useAcceptDocument(token: string) {
|
|
148
|
+
const { client } = useForge();
|
|
149
|
+
const queryClient = useQueryClient();
|
|
150
|
+
return useMutation({
|
|
151
|
+
mutationFn: async (input: { pricingOptionId?: string }) => {
|
|
152
|
+
const res = await client.post(`/public/documents/${token}/accept`, input);
|
|
153
|
+
return res.data as SignDocumentResult;
|
|
154
|
+
},
|
|
155
|
+
onSuccess: () => {
|
|
156
|
+
queryClient.invalidateQueries({ queryKey: publicDocumentQueryKey(token) });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Decline a proposal/contract with an optional reason — POST /public/documents/:token/decline. */
|
|
162
|
+
export function useDeclineDocument(token: string) {
|
|
163
|
+
const { client } = useForge();
|
|
164
|
+
const queryClient = useQueryClient();
|
|
165
|
+
return useMutation({
|
|
166
|
+
mutationFn: async (input: { reason?: string }) => {
|
|
167
|
+
const res = await client.post(`/public/documents/${token}/decline`, {
|
|
168
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
169
|
+
});
|
|
170
|
+
return res.data as SignDocumentResult;
|
|
171
|
+
},
|
|
172
|
+
onSuccess: () => {
|
|
173
|
+
queryClient.invalidateQueries({ queryKey: publicDocumentQueryKey(token) });
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Downloads the signed PDF (blob) and triggers a browser save. */
|
|
179
|
+
export function useDownloadSignedDocument(token: string, title?: string) {
|
|
180
|
+
const { client } = useForge();
|
|
181
|
+
return useMutation({
|
|
182
|
+
mutationFn: async () => {
|
|
183
|
+
const res = await client.get(`/public/documents/${token}/download`, {
|
|
184
|
+
responseType: "blob",
|
|
185
|
+
});
|
|
186
|
+
return res.data as Blob;
|
|
187
|
+
},
|
|
188
|
+
onSuccess: (blob) => {
|
|
189
|
+
const filename = `${(title || "document")
|
|
190
|
+
.toLowerCase()
|
|
191
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
192
|
+
.replace(/^-+|-+$/g, "")
|
|
193
|
+
.slice(0, 60)}-signed.pdf`;
|
|
194
|
+
const url = URL.createObjectURL(blob);
|
|
195
|
+
const a = document.createElement("a");
|
|
196
|
+
a.href = url;
|
|
197
|
+
a.download = filename;
|
|
198
|
+
a.click();
|
|
199
|
+
URL.revokeObjectURL(url);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Best-effort message extraction from an Axios error. */
|
|
205
|
+
export function getDocumentApiErrorMessage(error: unknown, fallback: string): string {
|
|
206
|
+
const message = (error as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
207
|
+
return message || fallback;
|
|
208
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PublicDonation } from "../../types/models";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
/** A single donation configuration by id. */
|
|
6
|
+
export function useGetDonation(donationId?: string | null, enabled = true) {
|
|
7
|
+
const { client, profileId } = useForge();
|
|
8
|
+
|
|
9
|
+
return useQuery<PublicDonation>({
|
|
10
|
+
queryKey: ["donation", donationId],
|
|
11
|
+
queryFn: async () => {
|
|
12
|
+
const res = await client.get(`/public/donations/${donationId}`);
|
|
13
|
+
return res.data;
|
|
14
|
+
},
|
|
15
|
+
enabled: !!client && !!donationId && !!profileId && enabled,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Track a donation-button view/click (fire-and-forget). */
|
|
20
|
+
export function useTrackDonationView() {
|
|
21
|
+
const { client } = useForge();
|
|
22
|
+
|
|
23
|
+
return useMutation<unknown, unknown, { donationId: string }>({
|
|
24
|
+
mutationFn: async ({ donationId }) => {
|
|
25
|
+
const res = await client.post(`/public/donations/${donationId}/track`);
|
|
26
|
+
return res.data;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { IPublicComment, EngagementEntityType } from "../../types/models";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
// Likes / saves / comments are entity-scoped engagement endpoints (a post today,
|
|
6
|
+
// other entity types later), so the hooks take { entityId, entityType }.
|
|
7
|
+
|
|
8
|
+
/** Whether the current member has liked an entity. */
|
|
9
|
+
export function useLikeStatus(entityId?: string, entityType: EngagementEntityType = "post", enabled = true) {
|
|
10
|
+
const { client } = useForge();
|
|
11
|
+
|
|
12
|
+
return useQuery<boolean>({
|
|
13
|
+
queryKey: ["like-status", entityType, entityId],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
const res = await client.get("/public/likes/status", {
|
|
16
|
+
params: { entityId, entityType },
|
|
17
|
+
});
|
|
18
|
+
return res.data.liked;
|
|
19
|
+
},
|
|
20
|
+
enabled: !!client && !!entityId && enabled,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Whether the current member has saved an entity. */
|
|
25
|
+
export function useSaveStatus(entityId?: string, entityType: EngagementEntityType = "post", enabled = true) {
|
|
26
|
+
const { client } = useForge();
|
|
27
|
+
|
|
28
|
+
return useQuery<boolean>({
|
|
29
|
+
queryKey: ["save-status", entityType, entityId],
|
|
30
|
+
queryFn: async () => {
|
|
31
|
+
const res = await client.get("/public/saves/status", {
|
|
32
|
+
params: { entityId, entityType },
|
|
33
|
+
});
|
|
34
|
+
return res.data.saved;
|
|
35
|
+
},
|
|
36
|
+
enabled: !!client && !!entityId && enabled,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Like + comment counts for an entity. */
|
|
41
|
+
export function useEngagementCounts(entityId?: string, entityType: EngagementEntityType = "post", enabled = true) {
|
|
42
|
+
const { client } = useForge();
|
|
43
|
+
|
|
44
|
+
return useQuery<{ likeCount: number; commentCount: number }>({
|
|
45
|
+
queryKey: ["engagement-counts", entityType, entityId],
|
|
46
|
+
queryFn: async () => {
|
|
47
|
+
const [likeRes, commentRes] = await Promise.all([
|
|
48
|
+
client.get("/public/likes/count", { params: { entityId, entityType } }),
|
|
49
|
+
client.get("/public/comments/count", { params: { entityId, entityType } }),
|
|
50
|
+
]);
|
|
51
|
+
return { likeCount: likeRes.data.count, commentCount: commentRes.data.count };
|
|
52
|
+
},
|
|
53
|
+
enabled: !!client && !!entityId && enabled,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Comments on an entity. */
|
|
58
|
+
export function useComments(entityId?: string, entityType: EngagementEntityType = "post", enabled = true) {
|
|
59
|
+
const { client } = useForge();
|
|
60
|
+
|
|
61
|
+
return useQuery<IPublicComment[]>({
|
|
62
|
+
queryKey: ["comments", entityType, entityId],
|
|
63
|
+
queryFn: async () => {
|
|
64
|
+
const res = await client.get("/public/comments", {
|
|
65
|
+
params: { entityId, entityType },
|
|
66
|
+
});
|
|
67
|
+
return res.data.comments;
|
|
68
|
+
},
|
|
69
|
+
enabled: !!client && !!entityId && enabled,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Like (`liked: true`) or unlike (`liked: false`) an entity. */
|
|
74
|
+
export function useToggleLike() {
|
|
75
|
+
const { client } = useForge();
|
|
76
|
+
|
|
77
|
+
return useMutation<unknown, unknown, { entityId: string; entityType?: EngagementEntityType; liked: boolean }>({
|
|
78
|
+
mutationFn: async ({ entityId, entityType = "post", liked }) => {
|
|
79
|
+
if (liked) {
|
|
80
|
+
const res = await client.post("/public/likes", { entityId, entityType });
|
|
81
|
+
return res.data;
|
|
82
|
+
}
|
|
83
|
+
const res = await client.delete("/public/likes", { data: { entityId, entityType } });
|
|
84
|
+
return res.data;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Save (`saved: true`) or unsave (`saved: false`) an entity. */
|
|
90
|
+
export function useToggleSave() {
|
|
91
|
+
const { client } = useForge();
|
|
92
|
+
|
|
93
|
+
return useMutation<unknown, unknown, { entityId: string; entityType?: EngagementEntityType; saved: boolean }>({
|
|
94
|
+
mutationFn: async ({ entityId, entityType = "post", saved }) => {
|
|
95
|
+
if (saved) {
|
|
96
|
+
const res = await client.post("/public/saves", { entityId, entityType });
|
|
97
|
+
return res.data;
|
|
98
|
+
}
|
|
99
|
+
const res = await client.delete("/public/saves", { data: { entityId, entityType } });
|
|
100
|
+
return res.data;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Add a comment to an entity. */
|
|
106
|
+
export function useAddComment() {
|
|
107
|
+
const { client } = useForge();
|
|
108
|
+
|
|
109
|
+
return useMutation<unknown, unknown, { entityId: string; entityType?: EngagementEntityType; content: string }>({
|
|
110
|
+
mutationFn: async ({ entityId, entityType = "post", content }) => {
|
|
111
|
+
const res = await client.post("/public/comments", { entityId, entityType, content });
|
|
112
|
+
return res.data;
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { IEvent } from "../../types/models";
|
|
2
|
+
import { useForge } from "../../provider/ForgeProvider";
|
|
3
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
/** All published events for the profile. */
|
|
6
|
+
export function useEvents() {
|
|
7
|
+
const { client, profileId } = useForge();
|
|
8
|
+
|
|
9
|
+
return useQuery<IEvent[]>({
|
|
10
|
+
queryKey: ["events", profileId],
|
|
11
|
+
queryFn: async () => {
|
|
12
|
+
const res = await client.get(`/public/events`, {
|
|
13
|
+
params: { profileId },
|
|
14
|
+
});
|
|
15
|
+
return res.data;
|
|
16
|
+
},
|
|
17
|
+
enabled: !!profileId && !!client,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A single event by id or slug.
|
|
23
|
+
*
|
|
24
|
+
* Pass `initialData` (e.g. an event fetched server-side in a route loader) to
|
|
25
|
+
* seed the query — the hook then returns data on first render with no loading
|
|
26
|
+
* state, so the page can be server-rendered for SEO with no client spinner.
|
|
27
|
+
*/
|
|
28
|
+
export function useEvent(id?: string, options?: { initialData?: IEvent }) {
|
|
29
|
+
const { client, profileId } = useForge();
|
|
30
|
+
|
|
31
|
+
return useQuery<IEvent>({
|
|
32
|
+
queryKey: ["event", id, profileId],
|
|
33
|
+
queryFn: async () => {
|
|
34
|
+
const res = await client.get(`/public/events/${id}`, {
|
|
35
|
+
params: { profileId },
|
|
36
|
+
});
|
|
37
|
+
return res.data;
|
|
38
|
+
},
|
|
39
|
+
enabled: !!id && !!profileId && !!client,
|
|
40
|
+
initialData: options?.initialData,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type CreateEventOrderInput = {
|
|
45
|
+
items: Record<string, number>;
|
|
46
|
+
email: string;
|
|
47
|
+
firstName?: string;
|
|
48
|
+
lastName?: string;
|
|
49
|
+
questionnaire?: unknown;
|
|
50
|
+
attributionRefId?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type CreateEventOrderResult = {
|
|
54
|
+
orderId: string;
|
|
55
|
+
isFreeCheckout?: boolean;
|
|
56
|
+
totalAmount?: number;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Create an event ticket order (the step before start-payment). */
|
|
60
|
+
export function useCreateEventOrder(eventId?: string) {
|
|
61
|
+
const { client, profileId } = useForge();
|
|
62
|
+
|
|
63
|
+
return useMutation<CreateEventOrderResult, unknown, CreateEventOrderInput>({
|
|
64
|
+
mutationFn: async (body) => {
|
|
65
|
+
const res = await client.post(`/public/events/${eventId}/orders`, { ...body, profileId });
|
|
66
|
+
return res.data;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|