@tribe-nest/forge 3.60.0 → 3.63.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 +5 -2
- package/src/contexts/CartContext.tsx +34 -1
- package/src/contexts/PublicAuthContext.tsx +15 -3
- package/src/data/queries/_tests/metaSignals.spec.tsx +130 -0
- package/src/data/queries/useAccount.ts +33 -2
- package/src/data/queries/useAnalytics.ts +6 -6
- package/src/data/queries/useCallChat.ts +2 -2
- package/src/data/queries/useChat.ts +10 -1
- package/src/data/queries/useCheckouts.ts +2 -1
- package/src/data/queries/useCoachingAvailability.ts +10 -1
- package/src/data/queries/useCommunities.ts +2 -0
- package/src/data/queries/useCommunity.ts +362 -18
- package/src/data/queries/useCommunityAgent.ts +247 -0
- package/src/data/queries/useCommunityCalendar.ts +150 -0
- package/src/data/queries/useCommunityClassroom.ts +75 -0
- package/src/data/queries/useCommunityMembers.ts +108 -0
- package/src/data/queries/useCommunityModeration.ts +327 -0
- package/src/data/queries/useCommunityWiki.ts +137 -0
- package/src/data/queries/useCourses.ts +2 -0
- package/src/data/queries/useEmailSignIn.ts +79 -0
- package/src/data/queries/useEvents.ts +6 -1
- package/src/data/queries/useForms.ts +5 -1
- package/src/data/queries/useLeadMagnet.ts +6 -2
- package/src/data/queries/useMyBookings.ts +24 -1
- package/src/data/queries/useMyTickets.ts +24 -1
- package/src/data/queries/useNotifications.ts +6 -2
- package/src/data/queries/useOrders.ts +2 -1
- package/src/data/queries/usePWA.ts +111 -35
- package/src/data/queries/useSiteApp.ts +130 -0
- package/src/data/queries/useSubscriptions.ts +6 -1
- package/src/i18n/de.json +746 -1
- package/src/i18n/en.json +746 -1
- package/src/index.ts +20 -0
- package/src/runtime/pages.spec.ts +5 -0
- package/src/runtime/pages.ts +32 -6
- package/src/seo/_tests/seo.spec.ts +326 -0
- package/src/seo/applySeoToHead.ts +98 -0
- package/src/seo/index.ts +9 -0
- package/src/seo/resolve.ts +211 -0
- package/src/seo/seoHead.ts +61 -0
- package/src/seo/siteSeoHead.ts +97 -0
- package/src/seo/types.ts +99 -0
- package/src/server/_tests/siteBootstrap.spec.ts +65 -0
- package/src/server/index.ts +33 -4
- package/src/types/models.ts +11 -0
- package/src/ui/analytics/ForgeAnalytics.tsx +134 -34
- package/src/ui/analytics/PageMetaPixel.tsx +23 -8
- package/src/ui/analytics/_tests/ForgeAnalytics.meta.spec.tsx +129 -0
- package/src/ui/analytics/_tests/ForgeAnalytics.webVitals.spec.tsx +129 -0
- package/src/ui/analytics/_tests/metaCommerceEvents.spec.tsx +159 -0
- package/src/ui/analytics/useMetaEvents.ts +78 -0
- package/src/ui/headless/_tests/communityAgent.spec.tsx +519 -0
- package/src/ui/headless/_tests/communityCalendar.spec.tsx +195 -0
- package/src/ui/headless/_tests/communityClassroom.spec.tsx +120 -0
- package/src/ui/headless/_tests/communityHarness.tsx +10 -3
- package/src/ui/headless/_tests/communityMembers.spec.tsx +316 -0
- package/src/ui/headless/_tests/communityPoll.spec.tsx +389 -0
- package/src/ui/headless/_tests/memberHomeBar.spec.ts +84 -0
- package/src/ui/headless/_tests/siteAppBar.spec.ts +83 -0
- package/src/ui/headless/_tests/siteAppHarness.ts +19 -0
- package/src/ui/headless/_tests/siteAppHome.spec.ts +226 -0
- package/src/ui/headless/_tests/siteAppPrompts.spec.ts +185 -0
- package/src/ui/headless/_tests/useCommunityJoin.spec.tsx +228 -14
- package/src/ui/headless/_tests/useCommunityMembership.spec.tsx +29 -1
- package/src/ui/headless/_tests/useCommunityModeration.spec.tsx +420 -0
- package/src/ui/headless/_tests/useCommunityReads.spec.tsx +11 -2
- package/src/ui/headless/_tests/useCommunityWiki.spec.tsx +224 -0
- package/src/ui/headless/_tests/useSiteApp.spec.tsx +52 -0
- package/src/ui/headless/_tests/wikiMediaImage.spec.tsx +132 -0
- package/src/ui/headless/auth/useEmailCodeSignIn.ts +186 -0
- package/src/ui/headless/auth/useSignupForm.ts +20 -1
- package/src/ui/headless/chat/useChatAttachmentUpload.ts +9 -6
- package/src/ui/headless/chat/useChatRoom.ts +35 -1
- package/src/ui/headless/communities/index.ts +41 -0
- package/src/ui/headless/communities/useCommunityCalendar.ts +156 -0
- package/src/ui/headless/communities/useCommunityClassroom.ts +84 -0
- package/src/ui/headless/communities/useCommunityHome.ts +3 -0
- package/src/ui/headless/communities/useCommunityJoin.ts +73 -23
- package/src/ui/headless/communities/useCommunityMembership.ts +48 -2
- package/src/ui/headless/communities/useCommunityWiki.ts +266 -0
- package/src/ui/headless/community/communityPoll.ts +297 -0
- package/src/ui/headless/community/countries.ts +123 -0
- package/src/ui/headless/community/index.ts +81 -1
- package/src/ui/headless/community/linkPreview.ts +71 -0
- package/src/ui/headless/community/richLexical.ts +27 -2
- package/src/ui/headless/community/useCommunityAgent.ts +285 -0
- package/src/ui/headless/community/useCommunityMembers.ts +175 -0
- package/src/ui/headless/community/useCommunityModeration.ts +300 -0
- package/src/ui/headless/community/useCommunitySpaceFeed.ts +22 -4
- package/src/ui/headless/community/useCommunityThread.ts +16 -3
- package/src/ui/headless/community/useMentionComposer.ts +17 -9
- package/src/ui/headless/community/useRichMentionComposer.ts +21 -5
- package/src/ui/headless/downloads/index.ts +8 -0
- package/src/ui/headless/downloads/useDownloadPage.ts +134 -0
- package/src/ui/headless/index.ts +13 -0
- package/src/ui/headless/memberHome/index.ts +10 -0
- package/src/ui/headless/memberHome/memberHomeBar.ts +91 -0
- package/src/ui/headless/memberHome/useMemberHomeNav.ts +56 -18
- package/src/ui/headless/portal/index.ts +57 -0
- package/src/ui/headless/portal/usePortal.ts +274 -0
- package/src/ui/headless/portal/usePortalFiles.ts +68 -0
- package/src/ui/headless/portal/usePortalMessages.ts +189 -0
- package/src/ui/headless/portal/usePortalPreview.ts +142 -0
- package/src/ui/headless/siteApp/appBar.ts +102 -0
- package/src/ui/headless/siteApp/appHome.ts +344 -0
- package/src/ui/headless/siteApp/appPrompts.ts +229 -0
- package/src/ui/headless/siteApp/index.ts +58 -0
- package/src/ui/headless/siteApp/useAppHome.ts +210 -0
- package/src/ui/headless/siteApp/useAppInstallRequest.ts +16 -0
- package/src/ui/headless/work/index.ts +2 -0
- package/src/ui/headless/work/useWorkPortal.ts +37 -26
- package/src/ui/index.ts +11 -0
- package/src/ui/styled/AccountDashboard.tsx +712 -352
- package/src/ui/styled/AiAgentWidget.tsx +13 -1
- package/src/ui/styled/ChatRoom.tsx +126 -16
- package/src/ui/styled/Checkout.tsx +8 -0
- package/src/ui/styled/CheckoutConfirmation.tsx +8 -0
- package/src/ui/styled/CoachingConfirmation.tsx +8 -0
- package/src/ui/styled/CoachingDetail.tsx +2 -0
- package/src/ui/styled/CourseConfirmation.tsx +5 -0
- package/src/ui/styled/CourseDetail.tsx +2 -0
- package/src/ui/styled/DownloadPage.tsx +263 -0
- package/src/ui/styled/EventConfirmation.tsx +10 -0
- package/src/ui/styled/EventDetail.tsx +10 -0
- package/src/ui/styled/FilmLibrary.tsx +22 -9
- package/src/ui/styled/MusicLinkPage.tsx +5 -5
- package/src/ui/styled/ProductDetail.tsx +12 -0
- package/src/ui/styled/PwaRegistration.tsx +5 -1
- package/src/ui/styled/SignupForm.tsx +18 -3
- package/src/ui/styled/_tests/AccountDashboardBookingCall.spec.tsx +5 -5
- package/src/ui/styled/_tests/AccountNotifications.spec.tsx +135 -0
- package/src/ui/styled/_tests/DownloadPage.spec.tsx +68 -0
- package/src/ui/styled/app/AppBar.tsx +298 -0
- package/src/ui/styled/app/AppHome.tsx +1139 -0
- package/src/ui/styled/app/AppHomeMark.tsx +66 -0
- package/src/ui/styled/app/AppPrompts.tsx +464 -0
- package/src/ui/styled/app/_tests/AppBar.spec.tsx +96 -0
- package/src/ui/styled/app/_tests/AppHome.spec.tsx +209 -0
- package/src/ui/styled/app/_tests/AppPrompts.spec.tsx +232 -0
- package/src/ui/styled/app/index.ts +7 -0
- package/src/ui/styled/chat/ChatThreadPanel.tsx +29 -4
- package/src/ui/styled/chat/MessageBubble.tsx +43 -6
- package/src/ui/styled/communities/CommunityAbout.tsx +6 -1
- package/src/ui/styled/communities/CommunityCalendar.tsx +484 -0
- package/src/ui/styled/communities/CommunityClassroom.tsx +302 -0
- package/src/ui/styled/communities/CommunityHome.tsx +134 -11
- package/src/ui/styled/communities/CommunityJoinSheet.tsx +119 -19
- package/src/ui/styled/communities/CommunityMembershipMenu.tsx +168 -52
- package/src/ui/styled/communities/CommunityPublicWiki.tsx +75 -0
- package/src/ui/styled/communities/CommunityWiki.tsx +289 -0
- package/src/ui/styled/communities/MyMembershipsList.tsx +31 -5
- package/src/ui/styled/communities/_tests/CommunityAppHome.spec.tsx +92 -0
- package/src/ui/styled/communities/_tests/CommunityClassroomCalendar.spec.tsx +453 -0
- package/src/ui/styled/communities/_tests/CommunityMobile.spec.tsx +399 -0
- package/src/ui/styled/communities/_tests/CommunityWiki.spec.tsx +223 -0
- package/src/ui/styled/communities/index.ts +9 -0
- package/src/ui/styled/communities/shared.tsx +39 -11
- package/src/ui/styled/community/CommunityAgent.tsx +286 -0
- package/src/ui/styled/community/CommunityComposer.tsx +266 -15
- package/src/ui/styled/community/CommunityFeed.tsx +50 -1
- package/src/ui/styled/community/CommunityLinkPreview.tsx +159 -0
- package/src/ui/styled/community/CommunityMemberGlobe.tsx +171 -0
- package/src/ui/styled/community/CommunityMemberProfile.tsx +137 -4
- package/src/ui/styled/community/CommunityMembers.tsx +387 -0
- package/src/ui/styled/community/CommunityModeration.tsx +587 -0
- package/src/ui/styled/community/CommunityModerationState.tsx +127 -0
- package/src/ui/styled/community/CommunityModeratorMenu.tsx +317 -0
- package/src/ui/styled/community/CommunityNotifications.tsx +52 -6
- package/src/ui/styled/community/CommunityPollCard.tsx +250 -0
- package/src/ui/styled/community/CommunityPostCard.tsx +110 -34
- package/src/ui/styled/community/CommunityPostDetail.tsx +157 -71
- package/src/ui/styled/community/CommunityRichText.tsx +177 -32
- package/src/ui/styled/community/CommunitySpaces.tsx +49 -14
- package/src/ui/styled/community/_tests/CommunityAgent.spec.tsx +571 -0
- package/src/ui/styled/community/_tests/CommunityLinkPreview.spec.tsx +167 -0
- package/src/ui/styled/community/_tests/CommunityMembers.spec.tsx +137 -0
- package/src/ui/styled/community/_tests/CommunityModeration.spec.tsx +291 -0
- package/src/ui/styled/community/_tests/CommunityPoll.spec.tsx +386 -0
- package/src/ui/styled/community/index.ts +37 -2
- package/src/ui/styled/forge-utilities.css +234 -0
- package/src/ui/styled/members/CallRecordingActions.tsx +182 -0
- package/src/ui/styled/members/MemberCallsCalendar.tsx +13 -158
- package/src/ui/styled/members/MemberHomeLayout.tsx +295 -15
- package/src/ui/styled/members/MemberPostsFeed.tsx +3 -2
- package/src/ui/styled/members/index.ts +8 -0
- package/src/ui/styled/portal/PortalAccountSection.tsx +61 -0
- package/src/ui/styled/portal/PortalActivity.tsx +167 -0
- package/src/ui/styled/portal/PortalBilling.tsx +197 -0
- package/src/ui/styled/portal/PortalFiles.tsx +279 -0
- package/src/ui/styled/portal/PortalHome.tsx +401 -0
- package/src/ui/styled/portal/PortalMessages.tsx +300 -0
- package/src/ui/styled/portal/PortalPreviewBanner.tsx +75 -0
- package/src/ui/styled/portal/PortalProjects.tsx +407 -0
- package/src/ui/styled/portal/PortalShell.tsx +397 -0
- package/src/ui/styled/portal/_tests/PortalAppHome.spec.tsx +90 -0
- package/src/ui/styled/portal/_tests/PortalFiles.spec.tsx +159 -0
- package/src/ui/styled/portal/_tests/PortalHome.spec.tsx +148 -0
- package/src/ui/styled/portal/_tests/PortalMessages.spec.tsx +168 -0
- package/src/ui/styled/portal/_tests/PortalPreviewBanner.spec.tsx +132 -0
- package/src/ui/styled/portal/_tests/portalNav.spec.ts +123 -0
- package/src/ui/styled/portal/index.ts +13 -0
- package/src/ui/styled/portal/portalNav.ts +260 -0
- package/src/ui/styled/portal/shared.tsx +206 -0
- package/src/ui/styled/work/WorkInviteAccept.tsx +228 -168
- package/src/ui/styled/work/WorkPortalProjects.tsx +68 -120
- package/src/ui/styled/work/WorkProjectDocuments.tsx +99 -128
- package/src/ui/styled/work/WorkProjectInvoices.tsx +91 -86
- package/src/ui/styled/work/WorkProjectReport.tsx +11 -17
- package/src/ui/styled/work/WorkReportBody.tsx +223 -248
- package/src/ui/styled/work/WorkTaskAttachments.tsx +23 -29
- package/src/ui/styled/work/WorkTaskComments.tsx +75 -102
- package/src/ui/styled/work/WorkTaskDetail.tsx +48 -59
- package/src/ui/styled/work/WorkTokenReport.tsx +8 -16
- package/src/ui/styled/work/index.ts +1 -1
- package/src/utils/_tests/landing.spec.ts +39 -0
- package/src/utils/_tests/metaEvents.spec.ts +264 -0
- package/src/utils/_tests/structuredDataSeo.spec.ts +247 -0
- package/src/utils/landing.ts +36 -10
- package/src/utils/metaEvents.ts +302 -0
- package/src/utils/structuredData.ts +339 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The site app (one installable app per website): the bar model, the home
|
|
2
|
+
// sections, and the install and notification prompts.
|
|
3
|
+
export {
|
|
4
|
+
APP_BAR_HREFS,
|
|
5
|
+
buildAppBar,
|
|
6
|
+
isAppBarItemActive,
|
|
7
|
+
normaliseBarItems,
|
|
8
|
+
type AppBarItem,
|
|
9
|
+
type AppBarKey,
|
|
10
|
+
type AppBarModel,
|
|
11
|
+
} from "./appBar";
|
|
12
|
+
export {
|
|
13
|
+
APP_HOME_LIST_LIMIT,
|
|
14
|
+
APP_PORTAL_TILES,
|
|
15
|
+
attentionAction,
|
|
16
|
+
buildAppHomeSections,
|
|
17
|
+
buildPortalTiles,
|
|
18
|
+
courseProgress,
|
|
19
|
+
greetingPart,
|
|
20
|
+
isSafeLinkHref,
|
|
21
|
+
pickLearningCourse,
|
|
22
|
+
pickNextCall,
|
|
23
|
+
pickNextTicket,
|
|
24
|
+
upcomingEvents,
|
|
25
|
+
type AppAttentionAction,
|
|
26
|
+
type AppCommunityCard,
|
|
27
|
+
type AppCourseProgress,
|
|
28
|
+
type AppHomeData,
|
|
29
|
+
type AppHomeSectionView,
|
|
30
|
+
type AppLearningCourse,
|
|
31
|
+
type AppNextCall,
|
|
32
|
+
type AppNextTicket,
|
|
33
|
+
type AppPortalTile,
|
|
34
|
+
type AppPortalTileKey,
|
|
35
|
+
} from "./appHome";
|
|
36
|
+
export { useAppHome, useAppCommunityUnread, type UseAppHomeResult } from "./useAppHome";
|
|
37
|
+
export { useAppInstallRequest } from "./useAppInstallRequest";
|
|
38
|
+
export {
|
|
39
|
+
APP_INSTALL_CONTEXTS,
|
|
40
|
+
APP_INSTALL_SNOOZE_MS,
|
|
41
|
+
APP_PUSH_CATEGORIES,
|
|
42
|
+
clearPendingAppInstall,
|
|
43
|
+
decideAppInstall,
|
|
44
|
+
decideAppPush,
|
|
45
|
+
pushPreferencePatch,
|
|
46
|
+
readInstallDismissedAt,
|
|
47
|
+
readPendingAppInstall,
|
|
48
|
+
readPushAnswer,
|
|
49
|
+
rememberInstallDismissed,
|
|
50
|
+
rememberPushAnswer,
|
|
51
|
+
requestAppInstall,
|
|
52
|
+
subscribeAppInstallRequests,
|
|
53
|
+
type AppInstallContext,
|
|
54
|
+
type AppInstallMode,
|
|
55
|
+
type AppInstallRequest,
|
|
56
|
+
type AppPushAnswer,
|
|
57
|
+
type AppPushCategoryKey,
|
|
58
|
+
} from "./appPrompts";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useContext, useMemo } from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { useForge } from "../../../provider/ForgeProvider";
|
|
4
|
+
import { PublicAuthContext } from "../../../contexts/PublicAuthContext";
|
|
5
|
+
import {
|
|
6
|
+
SITE_APP_DEFAULT_SECTIONS,
|
|
7
|
+
useSiteApp,
|
|
8
|
+
type SiteAppHomeSection,
|
|
9
|
+
type SiteAppSectionKey,
|
|
10
|
+
} from "../../../data/queries/useSiteApp";
|
|
11
|
+
import { useMyCommunityMemberships, usePublicCommunities } from "../../../data/queries/useCommunities";
|
|
12
|
+
import { communityKeys } from "../../../data/queries/useCommunity";
|
|
13
|
+
import { useMyBookings } from "../../../data/queries/useMyBookings";
|
|
14
|
+
import { useMyTickets } from "../../../data/queries/useMyTickets";
|
|
15
|
+
import { useMyCourses } from "../../../data/queries/useMyCourses";
|
|
16
|
+
import { useCourseAccess } from "../../../data/queries/useCourseAccess";
|
|
17
|
+
import { useGetCourses } from "../../../data/queries/useCourses";
|
|
18
|
+
import { useGetProducts } from "../../../data/queries/useProducts";
|
|
19
|
+
import { membershipCallKeys, type MyMembershipCall } from "../../../data/queries/useMembershipCalls";
|
|
20
|
+
import type { IEvent } from "../../../types/models";
|
|
21
|
+
import { internalPath } from "../../../runtime/pages";
|
|
22
|
+
import { getMembershipAccess } from "../../../utils/membershipAccess";
|
|
23
|
+
import { usePortalSummary } from "../portal/usePortal";
|
|
24
|
+
import { communityAboutPath, communityHomePath } from "../communities/paths";
|
|
25
|
+
import {
|
|
26
|
+
buildAppHomeSections,
|
|
27
|
+
buildPortalTiles,
|
|
28
|
+
courseProgress,
|
|
29
|
+
pickLearningCourse,
|
|
30
|
+
pickNextCall,
|
|
31
|
+
pickNextTicket,
|
|
32
|
+
upcomingEvents,
|
|
33
|
+
type AppCommunityCard,
|
|
34
|
+
type AppHomeSectionView,
|
|
35
|
+
type AppLearningCourse,
|
|
36
|
+
} from "./appHome";
|
|
37
|
+
|
|
38
|
+
export interface UseAppHomeResult {
|
|
39
|
+
/** The settings or the account are still being read: draw a loading state. */
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
/** Signed out: the home is the sign-in screen. */
|
|
42
|
+
isSignedIn: boolean;
|
|
43
|
+
/** The app's name (the website's, unless the creator renamed the app). */
|
|
44
|
+
appName: string | null;
|
|
45
|
+
iconUrl: string | null;
|
|
46
|
+
/** The member's first name, for the greeting. */
|
|
47
|
+
firstName: string | null;
|
|
48
|
+
/** The sections to draw, in order. Empty while loading. */
|
|
49
|
+
sections: AppHomeSectionView[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The data behind the site app's home (`/i/app`).
|
|
54
|
+
*
|
|
55
|
+
* Reads the website's app settings, then only what the configured sections
|
|
56
|
+
* need: a section the creator hid costs no request. Signed out, nothing
|
|
57
|
+
* member-shaped is fetched at all. Built on the same public hooks the portal
|
|
58
|
+
* and the communities use, so the home never disagrees with the page a tile
|
|
59
|
+
* opens. `buildAppHomeSections` decides order, visibility and the empty-skip.
|
|
60
|
+
*/
|
|
61
|
+
export function useAppHome(opts: { now?: Date } = {}): UseAppHomeResult {
|
|
62
|
+
const { client, profileId } = useForge();
|
|
63
|
+
const auth = useContext(PublicAuthContext);
|
|
64
|
+
const user = auth?.user ?? null;
|
|
65
|
+
const signedIn = !!user;
|
|
66
|
+
const now = opts.now ?? new Date();
|
|
67
|
+
|
|
68
|
+
const app = useSiteApp();
|
|
69
|
+
// An unknown host (a preview) answers 404: the contract's defaults stand in.
|
|
70
|
+
const config: readonly SiteAppHomeSection[] | undefined =
|
|
71
|
+
app.data?.homeSections ?? (app.isError ? SITE_APP_DEFAULT_SECTIONS : undefined);
|
|
72
|
+
const wants = (key: SiteAppSectionKey) =>
|
|
73
|
+
!!config?.some((entry) => entry.type === "builtin" && entry.key === key && entry.visible !== false);
|
|
74
|
+
|
|
75
|
+
const summary = usePortalSummary();
|
|
76
|
+
const sectionsCount = summary.data?.sections;
|
|
77
|
+
|
|
78
|
+
const wantsCommunities = wants("communities") || wants("explore_communities");
|
|
79
|
+
const mine = useMyCommunityMemberships({ enabled: signedIn && wantsCommunities });
|
|
80
|
+
const everyCommunity = usePublicCommunities({ enabled: wantsCommunities });
|
|
81
|
+
|
|
82
|
+
const comingUp = signedIn && wants("coming_up");
|
|
83
|
+
const calls = useQuery<MyMembershipCall[]>({
|
|
84
|
+
// The member's whole call calendar, across communities and courses.
|
|
85
|
+
queryKey: [...membershipCallKeys.mine(profileId), "now", "open", "app-home", user?.id ?? null],
|
|
86
|
+
queryFn: async () =>
|
|
87
|
+
(await client.get("/public/scheduled-calls/mine", { params: { profileId, limit: 5 } })).data as MyMembershipCall[],
|
|
88
|
+
enabled: comingUp && !!profileId && !!client,
|
|
89
|
+
refetchInterval: 60_000,
|
|
90
|
+
});
|
|
91
|
+
const bookings = useMyBookings(comingUp && (sectionsCount?.bookings ?? 0) > 0 ? user?.id : undefined, 1, 20);
|
|
92
|
+
const tickets = useMyTickets(comingUp && (sectionsCount?.tickets ?? 0) > 0 ? user?.id : undefined, 1, 20);
|
|
93
|
+
|
|
94
|
+
const myCourses = useMyCourses({ accountId: user?.id, enabled: signedIn && wants("learning") });
|
|
95
|
+
const learningCourse = pickLearningCourse(myCourses.data);
|
|
96
|
+
const access = useCourseAccess(learningCourse?.accessId);
|
|
97
|
+
|
|
98
|
+
const wantsEvents = wants("events");
|
|
99
|
+
const events = useQuery<IEvent[]>({
|
|
100
|
+
queryKey: ["events", profileId],
|
|
101
|
+
queryFn: async () => (await client.get("/public/events", { params: { profileId } })).data as IEvent[],
|
|
102
|
+
enabled: wantsEvents && !!profileId && !!client,
|
|
103
|
+
});
|
|
104
|
+
const courses = useGetCourses(undefined, wants("courses"));
|
|
105
|
+
const products = useGetProducts(undefined, wants("shop"));
|
|
106
|
+
|
|
107
|
+
const sections = useMemo(() => {
|
|
108
|
+
if (!config) return [];
|
|
109
|
+
const byId = new Map((everyCommunity.data ?? []).map((community) => [community.id, community]));
|
|
110
|
+
const memberships = (mine.data ?? []).filter(
|
|
111
|
+
(row) => getMembershipAccess({ status: row.status, cancelAtPeriodEnd: !!row.cancelAtPeriodEnd }).hasAccess,
|
|
112
|
+
);
|
|
113
|
+
const joined = new Set(memberships.map((row) => row.community.id));
|
|
114
|
+
const communities: AppCommunityCard[] = memberships.map((row) => {
|
|
115
|
+
const full = byId.get(row.community.id);
|
|
116
|
+
return {
|
|
117
|
+
id: row.community.id,
|
|
118
|
+
name: row.community.name,
|
|
119
|
+
slug: row.community.slug,
|
|
120
|
+
logoUrl: full?.logoUrl ?? null,
|
|
121
|
+
coverUrl: full?.coverUrl ?? null,
|
|
122
|
+
tierName: row.tierName ?? row.membershipTier?.name ?? null,
|
|
123
|
+
memberCount: full?.memberCount ?? null,
|
|
124
|
+
href: communityHomePath(row.community.slug),
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
// Signed out, or a membership read that failed, lists nothing as "yours";
|
|
128
|
+
// explore then shows every community rather than none.
|
|
129
|
+
const explore: AppCommunityCard[] = (everyCommunity.data ?? [])
|
|
130
|
+
.filter((community) => !joined.has(community.id) && !(mine.data ?? []).some((row) => row.community.id === community.id))
|
|
131
|
+
.map((community) => ({
|
|
132
|
+
id: community.id,
|
|
133
|
+
name: community.name,
|
|
134
|
+
slug: community.slug,
|
|
135
|
+
logoUrl: community.logoUrl,
|
|
136
|
+
coverUrl: community.coverUrl,
|
|
137
|
+
tierName: null,
|
|
138
|
+
memberCount: community.memberCount,
|
|
139
|
+
href: communityAboutPath(community.slug),
|
|
140
|
+
joinMode: community.joinMode ?? "open",
|
|
141
|
+
}));
|
|
142
|
+
const learning: AppLearningCourse | null = learningCourse
|
|
143
|
+
? {
|
|
144
|
+
accessId: learningCourse.accessId,
|
|
145
|
+
title: learningCourse.title,
|
|
146
|
+
href: internalPath("course-access/$accessId", { accessId: learningCourse.accessId }),
|
|
147
|
+
progress: courseProgress(access.data),
|
|
148
|
+
}
|
|
149
|
+
: null;
|
|
150
|
+
return buildAppHomeSections(config, {
|
|
151
|
+
attention: summary.data?.attention ?? [],
|
|
152
|
+
portalTiles: buildPortalTiles(summary.data),
|
|
153
|
+
communities,
|
|
154
|
+
nextCall: pickNextCall(calls.data, bookings.data?.data, now),
|
|
155
|
+
nextTicket: pickNextTicket(tickets.data?.data, now),
|
|
156
|
+
learning,
|
|
157
|
+
explore,
|
|
158
|
+
events: upcomingEvents(events.data, now),
|
|
159
|
+
courses: courses.data?.data ?? [],
|
|
160
|
+
products: products.data?.data ?? [],
|
|
161
|
+
});
|
|
162
|
+
// `now` is read per render on purpose; the inputs below are what change the answer.
|
|
163
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
+
}, [
|
|
165
|
+
config,
|
|
166
|
+
everyCommunity.data,
|
|
167
|
+
mine.data,
|
|
168
|
+
learningCourse,
|
|
169
|
+
access.data,
|
|
170
|
+
summary.data,
|
|
171
|
+
calls.data,
|
|
172
|
+
bookings.data,
|
|
173
|
+
tickets.data,
|
|
174
|
+
events.data,
|
|
175
|
+
courses.data,
|
|
176
|
+
products.data,
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
const authReady = auth ? auth.isInitialized !== false : true;
|
|
180
|
+
const isLoading = !config || !authReady || (signedIn && summary.isLoading);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
isLoading,
|
|
184
|
+
isSignedIn: signedIn,
|
|
185
|
+
appName: app.data?.appName ?? null,
|
|
186
|
+
iconUrl: app.data?.iconUrl ?? null,
|
|
187
|
+
firstName: user?.firstName?.trim() || null,
|
|
188
|
+
sections: isLoading ? [] : sections,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Unread notifications in one community, for its card on the app home. The
|
|
194
|
+
* same request and cache entry as the community's own bell, so opening the
|
|
195
|
+
* community and coming back shows the same number.
|
|
196
|
+
*/
|
|
197
|
+
export function useAppCommunityUnread(communityId: string | undefined, enabled = true) {
|
|
198
|
+
const { client, profileId } = useForge();
|
|
199
|
+
return useQuery<number>({
|
|
200
|
+
queryKey: communityKeys.unread(communityId),
|
|
201
|
+
queryFn: async () => {
|
|
202
|
+
const res = await client.get("/public/community/notifications", {
|
|
203
|
+
params: { profileId, communityId, page: 1, limit: 1, unreadOnly: true },
|
|
204
|
+
});
|
|
205
|
+
return res.data.unreadCount as number;
|
|
206
|
+
},
|
|
207
|
+
enabled: enabled && !!profileId && !!communityId && !!client,
|
|
208
|
+
refetchInterval: 30_000,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { requestAppInstall, type AppInstallContext } from "./appPrompts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ask for the app install once, the first time `ready` is true (a payment
|
|
6
|
+
* confirmed, a booking confirmed). For a success that is a callback rather
|
|
7
|
+
* than a state, call `requestAppInstall` directly.
|
|
8
|
+
*/
|
|
9
|
+
export function useAppInstallRequest(ready: boolean, context: AppInstallContext): void {
|
|
10
|
+
const asked = useRef(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!ready || asked.current) return;
|
|
13
|
+
asked.current = true;
|
|
14
|
+
requestAppInstall(context);
|
|
15
|
+
}, [ready, context]);
|
|
16
|
+
}
|
|
@@ -129,25 +129,19 @@ export interface WorkInviteContext {
|
|
|
129
129
|
email: string;
|
|
130
130
|
firstName: string | null;
|
|
131
131
|
/**
|
|
132
|
-
* The invited address
|
|
133
|
-
*
|
|
134
|
-
*
|
|
132
|
+
* The invited address has no account yet. The invite never creates one: the
|
|
133
|
+
* page sends the person through the normal signup (which verifies the
|
|
134
|
+
* address with an emailed code) and back.
|
|
135
135
|
*/
|
|
136
|
+
needsAccount: boolean;
|
|
137
|
+
/** The complement of `needsAccount`: sign in, then come back. */
|
|
136
138
|
requiresExistingLogin?: boolean;
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
/** POST /public/work/invite/accept, made as the signed-in invited account. */
|
|
139
142
|
export interface WorkInviteAcceptResult {
|
|
140
|
-
|
|
141
|
-
* True when the invited address already has an account: the invite granted
|
|
142
|
-
* portal access but issued NO session, and the person signs in normally
|
|
143
|
-
* (C10). `token` is absent in that case.
|
|
144
|
-
*/
|
|
145
|
-
requiresLogin?: boolean;
|
|
146
|
-
token?: string;
|
|
147
|
-
/** Rotating refresh token for the portal session (backend §7). Persist it. */
|
|
148
|
-
refreshToken?: string;
|
|
143
|
+
accepted: true;
|
|
149
144
|
projectId: string;
|
|
150
|
-
account: { id: string; email: string; firstName: string | null; lastName: string | null };
|
|
151
145
|
}
|
|
152
146
|
|
|
153
147
|
// ── Query keys ───────────────────────────────────────────────────────────────
|
|
@@ -410,22 +404,23 @@ export function useUploadWorkDocument(projectId: string) {
|
|
|
410
404
|
});
|
|
411
405
|
}
|
|
412
406
|
|
|
413
|
-
// ── Client invite accept
|
|
407
|
+
// ── Client invite accept ─────────────────────────────────────────────────────
|
|
414
408
|
|
|
415
409
|
/**
|
|
416
410
|
* Fetch the branded context for an invite link (GET /public/work/invite).
|
|
417
|
-
* Public: no bearer token needed
|
|
418
|
-
*
|
|
411
|
+
* Public: no bearer token needed. Sends the site's `profileId`, so a token for
|
|
412
|
+
* another tenant's project is refused here. Errors when the token or project is
|
|
413
|
+
* missing, invalid, expired or already used.
|
|
419
414
|
*/
|
|
420
415
|
export function useWorkInvite(
|
|
421
416
|
token: string | undefined,
|
|
422
417
|
projectId: string | undefined,
|
|
423
418
|
): UseQueryResult<WorkInviteContext> {
|
|
424
|
-
const { client } = useForge();
|
|
419
|
+
const { client, profileId } = useForge();
|
|
425
420
|
return useQuery<WorkInviteContext>({
|
|
426
|
-
queryKey: ["work-invite", token, projectId],
|
|
421
|
+
queryKey: ["work-invite", token, projectId, profileId],
|
|
427
422
|
queryFn: async () => {
|
|
428
|
-
const res = await client.get(`/public/work/invite`, { params: { token, projectId } });
|
|
423
|
+
const res = await client.get(`/public/work/invite`, { params: { token, projectId, profileId } });
|
|
429
424
|
return res.data as WorkInviteContext;
|
|
430
425
|
},
|
|
431
426
|
enabled: !!client && !!token && !!projectId,
|
|
@@ -434,20 +429,36 @@ export function useWorkInvite(
|
|
|
434
429
|
}
|
|
435
430
|
|
|
436
431
|
/**
|
|
437
|
-
* Accept an invite (POST /public/work/invite/accept)
|
|
438
|
-
*
|
|
439
|
-
*
|
|
432
|
+
* Accept an invite (POST /public/work/invite/accept) as the SIGNED-IN account.
|
|
433
|
+
* The account's email must be the invited address; nothing is created and no
|
|
434
|
+
* password is involved.
|
|
440
435
|
*/
|
|
441
436
|
export function useAcceptWorkInvite() {
|
|
442
|
-
const { client } = useForge();
|
|
443
|
-
return useMutation<WorkInviteAcceptResult, unknown, { token: string; projectId: string
|
|
444
|
-
mutationFn: async ({ token, projectId
|
|
445
|
-
const res = await client.post(`/public/work/invite/accept`, { token, projectId,
|
|
437
|
+
const { client, profileId } = useForge();
|
|
438
|
+
return useMutation<WorkInviteAcceptResult, unknown, { token: string; projectId: string }>({
|
|
439
|
+
mutationFn: async ({ token, projectId }) => {
|
|
440
|
+
const res = await client.post(`/public/work/invite/accept`, { token, projectId, profileId });
|
|
446
441
|
return res.data as WorkInviteAcceptResult;
|
|
447
442
|
},
|
|
448
443
|
});
|
|
449
444
|
}
|
|
450
445
|
|
|
446
|
+
/** Session-storage key the signup form reads its prefilled email from. */
|
|
447
|
+
export const SIGNUP_EMAIL_PREFILL_KEY = "forge:signup-email";
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Hand an email to the next signup form in this tab, without putting it in a
|
|
451
|
+
* URL (where analytics would record it). Read once by `useSignupForm`.
|
|
452
|
+
*/
|
|
453
|
+
export function rememberSignupEmail(email: string | null | undefined) {
|
|
454
|
+
if (!email || typeof window === "undefined") return;
|
|
455
|
+
try {
|
|
456
|
+
window.sessionStorage.setItem(SIGNUP_EMAIL_PREFILL_KEY, email);
|
|
457
|
+
} catch {
|
|
458
|
+
// Storage unavailable: the person types it.
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
451
462
|
// ── Presentational helpers ───────────────────────────────────────────────────
|
|
452
463
|
|
|
453
464
|
/** Major-unit amount → display string using the project currency symbol. */
|
package/src/ui/index.ts
CHANGED
|
@@ -148,7 +148,9 @@ export { ForgotPasswordForm, type ForgotPasswordFormProps } from "./styled/Forgo
|
|
|
148
148
|
export { ResetPasswordForm, type ResetPasswordFormProps } from "./styled/ResetPasswordForm";
|
|
149
149
|
export {
|
|
150
150
|
AccountDashboard,
|
|
151
|
+
AccountSection,
|
|
151
152
|
type AccountDashboardProps,
|
|
153
|
+
type AccountSectionProps,
|
|
152
154
|
type AccountTabKey,
|
|
153
155
|
ACCOUNT_TABS,
|
|
154
156
|
} from "./styled/AccountDashboard";
|
|
@@ -161,6 +163,7 @@ export {
|
|
|
161
163
|
export { AudioPlayer, type AudioPlayerProps } from "./styled/AudioPlayer";
|
|
162
164
|
export { ForgeAnalytics, type ForgeAnalyticsProps } from "./analytics/ForgeAnalytics";
|
|
163
165
|
export { PageMetaPixel, usePageMetaPixel, type PageMetaPixelProps } from "./analytics/PageMetaPixel";
|
|
166
|
+
export { useMetaViewContent, useMetaPurchase, useMetaInitiateCheckout, type MetaViewContentInput } from "./analytics/useMetaEvents";
|
|
164
167
|
export { CookieConsent, type CookieConsentProps } from "./styled/CookieConsent";
|
|
165
168
|
export { Loading, type LoadingProps } from "./styled/Loading";
|
|
166
169
|
/** The inventory-hold countdown + its lapsed/409 state. See `useInventoryHold`. */
|
|
@@ -374,6 +377,13 @@ export * from "./styled/communities";
|
|
|
374
377
|
// Work (project management) client-portal styled blocks.
|
|
375
378
|
export * from "./styled/work";
|
|
376
379
|
|
|
380
|
+
// The client portal (`/i/account/*`): shell, home, projects, invoices, documents, activity.
|
|
381
|
+
export * from "./styled/portal";
|
|
382
|
+
|
|
383
|
+
// The site app (`/i/app`): its home, the bottom bar a custom start page can
|
|
384
|
+
// drop in (`<AppBar />`), and the install and notification prompts.
|
|
385
|
+
export * from "./styled/app";
|
|
386
|
+
|
|
377
387
|
// A community's member home (`/i/communities/<slug>/home`) styled blocks.
|
|
378
388
|
export * from "./styled/members";
|
|
379
389
|
|
|
@@ -385,3 +395,4 @@ export * from "./styled/ReviewRequestPage";
|
|
|
385
395
|
// it, at BOTH the canonical `/i/documents/$token` and the older
|
|
386
396
|
// `/documents/$token` that already-sent links point at.
|
|
387
397
|
export * from "./styled/DocumentSigningPage";
|
|
398
|
+
export * from "./styled/DownloadPage";
|