@tribe-nest/forge 3.60.0 → 3.61.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 +2 -1
- 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 +17 -0
- package/src/runtime/pages.spec.ts +5 -0
- package/src/runtime/pages.ts +32 -6
- package/src/types/models.ts +11 -0
- package/src/ui/analytics/ForgeAnalytics.tsx +64 -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/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/metaEvents.spec.ts +264 -0
- package/src/utils/landing.ts +19 -6
- package/src/utils/metaEvents.ts +302 -0
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import { useState, type CSSProperties, type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
CalendarDays,
|
|
4
|
-
ChevronLeft,
|
|
5
|
-
ChevronRight,
|
|
6
|
-
Download,
|
|
7
|
-
FileText,
|
|
8
|
-
Loader2,
|
|
9
|
-
MessageSquare,
|
|
10
|
-
Mic,
|
|
11
|
-
MicOff,
|
|
12
|
-
PlayCircle,
|
|
13
|
-
Users,
|
|
14
|
-
X,
|
|
15
|
-
} from "lucide-react";
|
|
2
|
+
import { CalendarDays, ChevronLeft, ChevronRight, MessageSquare, Mic, MicOff, Users, X } from "lucide-react";
|
|
16
3
|
import { useThemeTokens } from "../../theme/ForgeThemeProvider";
|
|
17
4
|
import { useMemberCalls, type UseMemberCallsOptions } from "../../headless/memberHome/useMemberCalls";
|
|
18
|
-
import {
|
|
19
|
-
import { useForge } from "../../../provider/ForgeProvider";
|
|
20
|
-
import {
|
|
21
|
-
fetchMemberCallTranscript,
|
|
22
|
-
memberCallTranscriptKey,
|
|
23
|
-
type MemberCallTranscript,
|
|
24
|
-
type MyMembershipCall,
|
|
25
|
-
} from "../../../data/queries/useMembershipCalls";
|
|
5
|
+
import type { MyMembershipCall } from "../../../data/queries/useMembershipCalls";
|
|
26
6
|
import { CallTranscriptViewer } from "./CallTranscriptViewer";
|
|
27
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CallRecordingProcessing,
|
|
9
|
+
CallRecordingWatchLink,
|
|
10
|
+
CallTranscriptPanel,
|
|
11
|
+
callRecordingWatchable,
|
|
12
|
+
} from "./CallRecordingActions";
|
|
28
13
|
import { Loading } from "../Loading";
|
|
29
14
|
|
|
30
15
|
export interface MemberCallsCalendarProps extends UseMemberCallsOptions {
|
|
@@ -397,7 +382,7 @@ function EmptyMonth({
|
|
|
397
382
|
* call is still to come, and is shown as a clearly-labelled placeholder rather
|
|
398
383
|
* than hidden, so the shape a member will grow into is already visible.
|
|
399
384
|
*/
|
|
400
|
-
function CallDetailsModal({
|
|
385
|
+
export function CallDetailsModal({
|
|
401
386
|
call,
|
|
402
387
|
now,
|
|
403
388
|
tokens: t,
|
|
@@ -414,7 +399,7 @@ function CallDetailsModal({
|
|
|
414
399
|
}) {
|
|
415
400
|
const canJoin = call.isOpen && !!call.callPublicId;
|
|
416
401
|
const hasEnded = new Date(call.closesAt).getTime() < now.getTime();
|
|
417
|
-
const watchable = call.recording
|
|
402
|
+
const watchable = callRecordingWatchable(call.recording);
|
|
418
403
|
const processing = call.recording?.status === "processing";
|
|
419
404
|
|
|
420
405
|
const row: CSSProperties = { display: "flex", alignItems: "center", gap: 8, fontSize: 14, color: t.muted };
|
|
@@ -563,36 +548,8 @@ function CallDetailsModal({
|
|
|
563
548
|
Join call
|
|
564
549
|
</button>
|
|
565
550
|
)}
|
|
566
|
-
{watchable &&
|
|
567
|
-
|
|
568
|
-
href={call.recording!.watchUrl!}
|
|
569
|
-
target="_blank"
|
|
570
|
-
rel="noreferrer"
|
|
571
|
-
style={{
|
|
572
|
-
display: "flex",
|
|
573
|
-
alignItems: "center",
|
|
574
|
-
gap: 6,
|
|
575
|
-
padding: "10px 20px",
|
|
576
|
-
borderRadius: t.cornerRadius,
|
|
577
|
-
background: canJoin ? "transparent" : t.primary,
|
|
578
|
-
border: canJoin ? `1px solid ${t.border}` : "none",
|
|
579
|
-
color: canJoin ? t.text : t.textPrimary,
|
|
580
|
-
fontSize: 14,
|
|
581
|
-
fontWeight: 700,
|
|
582
|
-
textDecoration: "none",
|
|
583
|
-
}}
|
|
584
|
-
>
|
|
585
|
-
<PlayCircle size={16} />
|
|
586
|
-
Watch recording
|
|
587
|
-
{call.recording!.durationSeconds ? ` · ${Math.round(call.recording!.durationSeconds / 60)} min` : ""}
|
|
588
|
-
</a>
|
|
589
|
-
)}
|
|
590
|
-
{!canJoin && !watchable && processing && (
|
|
591
|
-
<span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 14, color: t.muted, fontWeight: 600 }}>
|
|
592
|
-
<Loader2 size={15} />
|
|
593
|
-
Recording is processing
|
|
594
|
-
</span>
|
|
595
|
-
)}
|
|
551
|
+
{watchable && <CallRecordingWatchLink recording={call.recording!} secondary={canJoin} />}
|
|
552
|
+
{!canJoin && !watchable && processing && <CallRecordingProcessing />}
|
|
596
553
|
{!canJoin && !watchable && !processing && (
|
|
597
554
|
<span style={{ fontSize: 14, color: t.muted, fontWeight: 600 }}>
|
|
598
555
|
{hasEnded ? "This call has ended" : opensIn(call.opensAt, now)}
|
|
@@ -601,10 +558,9 @@ function CallDetailsModal({
|
|
|
601
558
|
</div>
|
|
602
559
|
|
|
603
560
|
{call.recording?.transcript && (
|
|
604
|
-
<
|
|
561
|
+
<CallTranscriptPanel
|
|
605
562
|
recordingId={call.recording.id}
|
|
606
563
|
transcript={call.recording.transcript}
|
|
607
|
-
tokens={t}
|
|
608
564
|
onRead={() => onReadTranscript(call.recording!.id)}
|
|
609
565
|
/>
|
|
610
566
|
)}
|
|
@@ -618,104 +574,3 @@ function CallDetailsModal({
|
|
|
618
574
|
</div>
|
|
619
575
|
);
|
|
620
576
|
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* The call's summary, and the way into its transcript.
|
|
624
|
-
*
|
|
625
|
-
* The lines are NOT here: a two-hour meeting is thousands of them, which is
|
|
626
|
-
* unusable in a modal. "Read transcript" opens them beside the video, and
|
|
627
|
-
* Download reads them once without opening anything. A member sees the
|
|
628
|
-
* finished thing or that it is being prepared, never a failure: why one could
|
|
629
|
-
* not be made is the business's problem, not its audience's.
|
|
630
|
-
*/
|
|
631
|
-
function TranscriptPanel({
|
|
632
|
-
recordingId,
|
|
633
|
-
transcript,
|
|
634
|
-
tokens: t,
|
|
635
|
-
onRead,
|
|
636
|
-
}: {
|
|
637
|
-
recordingId: string;
|
|
638
|
-
transcript: MemberCallTranscript;
|
|
639
|
-
tokens: ReturnType<typeof useThemeTokens>;
|
|
640
|
-
onRead: () => void;
|
|
641
|
-
}) {
|
|
642
|
-
const { client, profileId } = useForge();
|
|
643
|
-
const queryClient = useQueryClient();
|
|
644
|
-
const [downloading, setDownloading] = useState(false);
|
|
645
|
-
const heading: CSSProperties = { display: "flex", alignItems: "center", gap: 8, fontSize: 14, fontWeight: 700 };
|
|
646
|
-
const button: CSSProperties = {
|
|
647
|
-
display: "inline-flex",
|
|
648
|
-
alignItems: "center",
|
|
649
|
-
gap: 6,
|
|
650
|
-
padding: "8px 14px",
|
|
651
|
-
borderRadius: t.cornerRadius,
|
|
652
|
-
border: `1px solid ${t.border}`,
|
|
653
|
-
background: "transparent",
|
|
654
|
-
color: t.text,
|
|
655
|
-
fontFamily: t.fontFamily,
|
|
656
|
-
fontSize: 14,
|
|
657
|
-
fontWeight: 600,
|
|
658
|
-
cursor: "pointer",
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
if (transcript.status === "processing") {
|
|
662
|
-
return (
|
|
663
|
-
<div style={{ display: "flex", flexDirection: "column", gap: 6 }} data-testid="member-call-transcript">
|
|
664
|
-
<span style={heading}>
|
|
665
|
-
<FileText size={16} />
|
|
666
|
-
Transcript
|
|
667
|
-
</span>
|
|
668
|
-
<span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 14, color: t.muted }} role="status">
|
|
669
|
-
<Loader2 size={15} />
|
|
670
|
-
The transcript is being prepared
|
|
671
|
-
</span>
|
|
672
|
-
</div>
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const download = async () => {
|
|
677
|
-
setDownloading(true);
|
|
678
|
-
try {
|
|
679
|
-
const detail = await queryClient.fetchQuery({
|
|
680
|
-
queryKey: memberCallTranscriptKey(profileId, recordingId),
|
|
681
|
-
queryFn: () => fetchMemberCallTranscript(client, profileId, recordingId),
|
|
682
|
-
});
|
|
683
|
-
downloadTranscript(detail);
|
|
684
|
-
} finally {
|
|
685
|
-
setDownloading(false);
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
|
|
689
|
-
return (
|
|
690
|
-
<div style={{ display: "flex", flexDirection: "column", gap: 10 }} data-testid="member-call-transcript">
|
|
691
|
-
<span style={heading}>
|
|
692
|
-
<FileText size={16} />
|
|
693
|
-
Summary
|
|
694
|
-
</span>
|
|
695
|
-
<p
|
|
696
|
-
style={{
|
|
697
|
-
margin: 0,
|
|
698
|
-
fontSize: 14,
|
|
699
|
-
lineHeight: 1.5,
|
|
700
|
-
display: "-webkit-box",
|
|
701
|
-
WebkitLineClamp: 4,
|
|
702
|
-
WebkitBoxOrient: "vertical",
|
|
703
|
-
overflow: "hidden",
|
|
704
|
-
color: transcript.summary ? t.text : t.muted,
|
|
705
|
-
}}
|
|
706
|
-
>
|
|
707
|
-
{transcript.summary ? transcript.summary.summary : "The transcript is ready."}
|
|
708
|
-
</p>
|
|
709
|
-
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
|
710
|
-
<button type="button" onClick={onRead} style={button}>
|
|
711
|
-
<FileText size={15} />
|
|
712
|
-
Read transcript
|
|
713
|
-
</button>
|
|
714
|
-
<button type="button" onClick={() => void download()} disabled={downloading} style={button}>
|
|
715
|
-
{downloading ? <Loader2 size={15} /> : <Download size={15} />}
|
|
716
|
-
Download
|
|
717
|
-
</button>
|
|
718
|
-
</div>
|
|
719
|
-
</div>
|
|
720
|
-
);
|
|
721
|
-
}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
BadgeCheck,
|
|
4
|
+
Bell,
|
|
5
|
+
BookOpen,
|
|
6
|
+
CalendarDays,
|
|
7
|
+
ChevronRight,
|
|
8
|
+
GraduationCap,
|
|
9
|
+
LayoutGrid,
|
|
10
|
+
Library,
|
|
11
|
+
Menu,
|
|
12
|
+
MessageCircle,
|
|
13
|
+
Newspaper,
|
|
14
|
+
ShieldCheck,
|
|
15
|
+
Trophy,
|
|
16
|
+
Users,
|
|
17
|
+
type LucideIcon,
|
|
18
|
+
} from "lucide-react";
|
|
2
19
|
import { useThemeTokens } from "../../theme/ForgeThemeProvider";
|
|
3
20
|
import { useForgeT } from "../../../i18n";
|
|
4
21
|
import {
|
|
@@ -7,6 +24,8 @@ import {
|
|
|
7
24
|
type MemberHomeNavKey,
|
|
8
25
|
type UseMemberHomeNavOptions,
|
|
9
26
|
} from "../../headless/memberHome/useMemberHomeNav";
|
|
27
|
+
import { buildMemberHomeBar, type MemberHomeMoreGroup } from "../../headless/memberHome/memberHomeBar";
|
|
28
|
+
import { CommunitySheet } from "../communities/shared";
|
|
10
29
|
|
|
11
30
|
/** Literal keys, so the translation spec can see every one of them. */
|
|
12
31
|
const TAB_LABEL_KEYS: Record<MemberHomeNavKey, string> = {
|
|
@@ -14,11 +33,48 @@ const TAB_LABEL_KEYS: Record<MemberHomeNavKey, string> = {
|
|
|
14
33
|
collections: "forge.member_home_layout.tab_collections",
|
|
15
34
|
spaces: "forge.member_home_layout.tab_spaces",
|
|
16
35
|
calendar: "forge.member_home_layout.tab_calendar",
|
|
36
|
+
classroom: "forge.member_home_layout.tab_classroom",
|
|
17
37
|
chat: "forge.member_home_layout.tab_chat",
|
|
18
38
|
leaderboard: "forge.member_home_layout.tab_leaderboard",
|
|
19
39
|
notifications: "forge.member_home_layout.tab_notifications",
|
|
40
|
+
members: "forge.member_home_layout.tab_members",
|
|
41
|
+
wiki: "forge.member_home_layout.tab_wiki",
|
|
42
|
+
moderation: "forge.member_home_layout.tab_moderation",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The bar has room for one short word under each icon, so two tabs get shorter names there. */
|
|
46
|
+
const BAR_LABEL_KEYS: Record<MemberHomeNavKey, string> = {
|
|
47
|
+
...TAB_LABEL_KEYS,
|
|
48
|
+
posts: "forge.member_home_layout.bar_feed",
|
|
49
|
+
notifications: "forge.member_home_layout.bar_alerts",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const GROUP_LABEL_KEYS: Record<MemberHomeMoreGroup, string> = {
|
|
53
|
+
community: "forge.member_home_layout.group_community",
|
|
54
|
+
you: "forge.member_home_layout.group_you",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const TAB_ICONS: Record<MemberHomeNavKey, LucideIcon> = {
|
|
58
|
+
posts: Newspaper,
|
|
59
|
+
collections: Library,
|
|
60
|
+
spaces: LayoutGrid,
|
|
61
|
+
calendar: CalendarDays,
|
|
62
|
+
classroom: GraduationCap,
|
|
63
|
+
chat: MessageCircle,
|
|
64
|
+
leaderboard: Trophy,
|
|
65
|
+
notifications: Bell,
|
|
66
|
+
members: Users,
|
|
67
|
+
wiki: BookOpen,
|
|
68
|
+
moderation: ShieldCheck,
|
|
20
69
|
};
|
|
21
70
|
|
|
71
|
+
/** Tailwind's `lg`: the side nav replaces the bottom bar from here up. */
|
|
72
|
+
const BAR_BREAKPOINT = 1024;
|
|
73
|
+
/** The bar's height above the safe area, matching `BarButton`'s min height. */
|
|
74
|
+
const BAR_HEIGHT = 56;
|
|
75
|
+
/** The chat room's message box on a phone, plus a gap: how far the site's chat bubble rises in chat. */
|
|
76
|
+
const CHAT_COMPOSER_LIFT = 88;
|
|
77
|
+
|
|
22
78
|
export interface MemberHomeLayoutProps extends UseMemberHomeNavOptions {
|
|
23
79
|
/** Current location pathname (drives the active tab when `activeKey` is not given). */
|
|
24
80
|
currentPath?: string;
|
|
@@ -27,6 +83,11 @@ export interface MemberHomeLayoutProps extends UseMemberHomeNavOptions {
|
|
|
27
83
|
* Pass your router's navigate for client-side transitions.
|
|
28
84
|
*/
|
|
29
85
|
navigate?: (href: string) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Opens the member's membership sheet. Given, the phone's "More" sheet lists
|
|
88
|
+
* "Your membership" under "You". `CommunityHome` passes it for a member.
|
|
89
|
+
*/
|
|
90
|
+
onOpenMembership?: () => void;
|
|
30
91
|
children: ReactNode;
|
|
31
92
|
className?: string;
|
|
32
93
|
style?: CSSProperties;
|
|
@@ -35,7 +96,13 @@ export interface MemberHomeLayoutProps extends UseMemberHomeNavOptions {
|
|
|
35
96
|
/**
|
|
36
97
|
* A community's member home shell (`/i/communities/<slug>/home`): persistent
|
|
37
98
|
* nav across Posts / Collections / Spaces / Calendar / Chat / Leaderboard /
|
|
38
|
-
* Notifications
|
|
99
|
+
* Notifications and the rest.
|
|
100
|
+
*
|
|
101
|
+
* On `lg+` a side nav. Below `lg` a bottom bar with four slots and "More",
|
|
102
|
+
* which opens a sheet with everything else (see `buildMemberHomeBar` for who
|
|
103
|
+
* gets a slot). The bar publishes its height as `--forge-bottom-bar-height` on
|
|
104
|
+
* the root, so the site's chat bubble sits above it rather than on "More".
|
|
105
|
+
*
|
|
39
106
|
* Tab visibility comes from `useMemberHomeNav` (conditional tabs hide until the
|
|
40
107
|
* community has content). Must sit inside `<CommunityProvider>`; `CommunityHome`
|
|
41
108
|
* wraps it with the community header. Hosts own routing: this only renders
|
|
@@ -44,6 +111,7 @@ export interface MemberHomeLayoutProps extends UseMemberHomeNavOptions {
|
|
|
44
111
|
export function MemberHomeLayout({
|
|
45
112
|
currentPath,
|
|
46
113
|
navigate,
|
|
114
|
+
onOpenMembership,
|
|
47
115
|
children,
|
|
48
116
|
className,
|
|
49
117
|
style,
|
|
@@ -51,8 +119,53 @@ export function MemberHomeLayout({
|
|
|
51
119
|
}: MemberHomeLayoutProps) {
|
|
52
120
|
const t = useThemeTokens();
|
|
53
121
|
const tr = useForgeT();
|
|
54
|
-
const { visibleItems, isReady } = useMemberHomeNav({ currentPath, ...navOptions });
|
|
122
|
+
const { visibleItems, isReady, activeKey } = useMemberHomeNav({ currentPath, ...navOptions });
|
|
55
123
|
const go = navigate ?? ((href: string) => window.location.assign(href));
|
|
124
|
+
const [moreOpen, setMoreOpen] = useState(false);
|
|
125
|
+
|
|
126
|
+
const model = buildMemberHomeBar({ visibleItems, activeKey });
|
|
127
|
+
const hasMore = model.more.length > 0 || !!onOpenMembership;
|
|
128
|
+
// One lone "Feed" button is not navigation: a visitor with nothing else to
|
|
129
|
+
// open gets no bar at all.
|
|
130
|
+
const showBar = isReady && (model.bar.length > 1 || hasMore);
|
|
131
|
+
|
|
132
|
+
// Tell anything else pinned to the bottom of the screen (the site's chat
|
|
133
|
+
// bubble, the chat room's height) how tall the bar is. Set on the root
|
|
134
|
+
// because those live outside this tree.
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (!showBar || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
137
|
+
const root = document.documentElement;
|
|
138
|
+
const narrow = window.matchMedia(`(max-width: ${BAR_BREAKPOINT - 1}px)`);
|
|
139
|
+
const apply = () =>
|
|
140
|
+
narrow.matches
|
|
141
|
+
? root.style.setProperty("--forge-bottom-bar-height", `${BAR_HEIGHT}px`)
|
|
142
|
+
: root.style.removeProperty("--forge-bottom-bar-height");
|
|
143
|
+
apply();
|
|
144
|
+
narrow.addEventListener?.("change", apply);
|
|
145
|
+
return () => {
|
|
146
|
+
narrow.removeEventListener?.("change", apply);
|
|
147
|
+
root.style.removeProperty("--forge-bottom-bar-height");
|
|
148
|
+
};
|
|
149
|
+
}, [showBar]);
|
|
150
|
+
|
|
151
|
+
// In chat the message box sits right above the bar, with Send on the right,
|
|
152
|
+
// where the site's chat bubble would land. Lift the bubble clear of it.
|
|
153
|
+
const inChat = activeKey === "chat";
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!inChat || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
156
|
+
const root = document.documentElement;
|
|
157
|
+
const narrow = window.matchMedia(`(max-width: ${BAR_BREAKPOINT - 1}px)`);
|
|
158
|
+
const apply = () =>
|
|
159
|
+
narrow.matches
|
|
160
|
+
? root.style.setProperty("--forge-floating-lift", `${CHAT_COMPOSER_LIFT}px`)
|
|
161
|
+
: root.style.removeProperty("--forge-floating-lift");
|
|
162
|
+
apply();
|
|
163
|
+
narrow.addEventListener?.("change", apply);
|
|
164
|
+
return () => {
|
|
165
|
+
narrow.removeEventListener?.("change", apply);
|
|
166
|
+
root.style.removeProperty("--forge-floating-lift");
|
|
167
|
+
};
|
|
168
|
+
}, [inChat]);
|
|
56
169
|
|
|
57
170
|
const itemStyle = (item: MemberHomeNavItem): CSSProperties => ({
|
|
58
171
|
display: "flex",
|
|
@@ -70,7 +183,7 @@ export function MemberHomeLayout({
|
|
|
70
183
|
color: item.active ? t.textPrimary : t.text,
|
|
71
184
|
});
|
|
72
185
|
|
|
73
|
-
const
|
|
186
|
+
const renderSideItem = (item: MemberHomeNavItem) => (
|
|
74
187
|
<button
|
|
75
188
|
key={item.key}
|
|
76
189
|
type="button"
|
|
@@ -82,20 +195,47 @@ export function MemberHomeLayout({
|
|
|
82
195
|
</button>
|
|
83
196
|
);
|
|
84
197
|
|
|
198
|
+
const sheetRowStyle = (active: boolean): CSSProperties => ({
|
|
199
|
+
display: "flex",
|
|
200
|
+
alignItems: "center",
|
|
201
|
+
gap: 12,
|
|
202
|
+
width: "100%",
|
|
203
|
+
padding: "12px 4px",
|
|
204
|
+
background: "transparent",
|
|
205
|
+
border: "none",
|
|
206
|
+
borderBottom: `1px solid ${t.border}`,
|
|
207
|
+
color: active ? t.primary : t.text,
|
|
208
|
+
fontFamily: t.fontFamily,
|
|
209
|
+
fontSize: 16,
|
|
210
|
+
fontWeight: active ? 700 : 500,
|
|
211
|
+
textAlign: "left",
|
|
212
|
+
cursor: "pointer",
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const sheetRow = (key: string, Icon: LucideIcon, label: string, active: boolean, onPress: () => void) => (
|
|
216
|
+
<button
|
|
217
|
+
key={key}
|
|
218
|
+
type="button"
|
|
219
|
+
data-testid={`community-more-${key}`}
|
|
220
|
+
aria-current={active ? "page" : undefined}
|
|
221
|
+
onClick={onPress}
|
|
222
|
+
style={sheetRowStyle(active)}
|
|
223
|
+
>
|
|
224
|
+
<Icon size={18} aria-hidden style={{ flexShrink: 0 }} />
|
|
225
|
+
<span style={{ flex: 1 }}>{label}</span>
|
|
226
|
+
<ChevronRight size={16} aria-hidden style={{ color: t.muted }} />
|
|
227
|
+
</button>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const groups = model.more.map(({ group, items }) => ({ group, items }));
|
|
231
|
+
// "Your membership" belongs to the person, so it opens the "You" group.
|
|
232
|
+
if (onOpenMembership && !groups.some((g) => g.group === "you")) groups.push({ group: "you", items: [] });
|
|
233
|
+
|
|
85
234
|
return (
|
|
86
235
|
<div
|
|
87
236
|
className={`fg:flex fg:w-full fg:flex-col fg:lg:flex-row fg:lg:items-start fg:lg:gap-8 ${className ?? ""}`}
|
|
88
237
|
style={{ color: t.text, fontFamily: t.fontFamily, ...style }}
|
|
89
238
|
>
|
|
90
|
-
{/* Top tab bar on small screens (horizontally scrollable, no burger). */}
|
|
91
|
-
<nav
|
|
92
|
-
aria-label={tr("forge.member_home_layout.nav_label")}
|
|
93
|
-
className="fg:flex fg:w-full fg:gap-1 fg:overflow-x-auto fg:lg:hidden"
|
|
94
|
-
style={{ borderBottom: `1px solid ${t.border}`, paddingBottom: 8, marginBottom: 16 }}
|
|
95
|
-
>
|
|
96
|
-
{isReady ? visibleItems.map(renderItem) : null}
|
|
97
|
-
</nav>
|
|
98
|
-
|
|
99
239
|
{/* Side nav, lg and up. */}
|
|
100
240
|
<nav
|
|
101
241
|
aria-label={tr("forge.member_home_layout.nav_label")}
|
|
@@ -107,10 +247,150 @@ export function MemberHomeLayout({
|
|
|
107
247
|
padding: 8,
|
|
108
248
|
}}
|
|
109
249
|
>
|
|
110
|
-
{isReady ? visibleItems.map(
|
|
250
|
+
{isReady ? visibleItems.map(renderSideItem) : null}
|
|
111
251
|
</nav>
|
|
112
252
|
|
|
113
|
-
|
|
253
|
+
{/* Below lg the bar is fixed over the bottom of the page, so the page ends above it. */}
|
|
254
|
+
<main
|
|
255
|
+
className={`fg:min-w-0 fg:w-full fg:flex-1 ${showBar ? "fg:pb-[calc(72px+env(safe-area-inset-bottom))] fg:lg:pb-0" : ""}`}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</main>
|
|
259
|
+
|
|
260
|
+
{/* Bottom bar, below lg. */}
|
|
261
|
+
{showBar ? (
|
|
262
|
+
<nav
|
|
263
|
+
aria-label={tr("forge.member_home_layout.nav_label")}
|
|
264
|
+
data-testid="community-bar"
|
|
265
|
+
className="fg:fixed fg:inset-x-0 fg:bottom-0 fg:z-40 fg:flex fg:lg:hidden"
|
|
266
|
+
style={{
|
|
267
|
+
background: t.background,
|
|
268
|
+
borderTop: `1px solid ${t.border}`,
|
|
269
|
+
paddingBottom: "env(safe-area-inset-bottom)",
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{model.bar.map((item) => {
|
|
273
|
+
const Icon = TAB_ICONS[item.key];
|
|
274
|
+
return (
|
|
275
|
+
<BarButton
|
|
276
|
+
key={item.key}
|
|
277
|
+
icon={<Icon size={20} aria-hidden />}
|
|
278
|
+
label={tr(BAR_LABEL_KEYS[item.key])}
|
|
279
|
+
active={item.active}
|
|
280
|
+
href={item.href}
|
|
281
|
+
onGo={go}
|
|
282
|
+
testId={`community-bar-${item.key}`}
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
285
|
+
})}
|
|
286
|
+
{hasMore ? (
|
|
287
|
+
<BarButton
|
|
288
|
+
icon={<Menu size={20} aria-hidden />}
|
|
289
|
+
label={tr("forge.member_home_layout.more")}
|
|
290
|
+
active={model.moreActive}
|
|
291
|
+
onPress={() => setMoreOpen(true)}
|
|
292
|
+
testId="community-bar-more"
|
|
293
|
+
/>
|
|
294
|
+
) : null}
|
|
295
|
+
</nav>
|
|
296
|
+
) : null}
|
|
297
|
+
|
|
298
|
+
<CommunitySheet
|
|
299
|
+
open={moreOpen}
|
|
300
|
+
onOpenChange={setMoreOpen}
|
|
301
|
+
testId="community-more-sheet"
|
|
302
|
+
title={tr("forge.member_home_layout.more_title")}
|
|
303
|
+
closeLabel={tr("forge.member_home_layout.aria_close")}
|
|
304
|
+
>
|
|
305
|
+
{groups.map(({ group, items }, index) => (
|
|
306
|
+
<div key={group} style={{ marginTop: index === 0 ? 0 : 22 }}>
|
|
307
|
+
<p style={{ margin: "0 0 6px", fontSize: 13, color: t.muted, fontWeight: 500 }}>
|
|
308
|
+
{tr(GROUP_LABEL_KEYS[group])}
|
|
309
|
+
</p>
|
|
310
|
+
{group === "you" && onOpenMembership
|
|
311
|
+
? sheetRow("membership", BadgeCheck, tr("forge.member_home_layout.your_membership"), false, () => {
|
|
312
|
+
setMoreOpen(false);
|
|
313
|
+
onOpenMembership();
|
|
314
|
+
})
|
|
315
|
+
: null}
|
|
316
|
+
{items.map((item) =>
|
|
317
|
+
sheetRow(item.key, TAB_ICONS[item.key], tr(TAB_LABEL_KEYS[item.key]), item.active, () => {
|
|
318
|
+
setMoreOpen(false);
|
|
319
|
+
go(item.href);
|
|
320
|
+
}),
|
|
321
|
+
)}
|
|
322
|
+
</div>
|
|
323
|
+
))}
|
|
324
|
+
</CommunitySheet>
|
|
114
325
|
</div>
|
|
115
326
|
);
|
|
116
327
|
}
|
|
328
|
+
|
|
329
|
+
function BarButton({
|
|
330
|
+
icon,
|
|
331
|
+
label,
|
|
332
|
+
active,
|
|
333
|
+
href,
|
|
334
|
+
onGo,
|
|
335
|
+
onPress,
|
|
336
|
+
testId,
|
|
337
|
+
}: {
|
|
338
|
+
icon: ReactNode;
|
|
339
|
+
label: string;
|
|
340
|
+
active: boolean;
|
|
341
|
+
href?: string;
|
|
342
|
+
onGo?: (href: string) => void;
|
|
343
|
+
onPress?: () => void;
|
|
344
|
+
testId: string;
|
|
345
|
+
}) {
|
|
346
|
+
const t = useThemeTokens();
|
|
347
|
+
const style: CSSProperties = {
|
|
348
|
+
flex: 1,
|
|
349
|
+
minWidth: 0,
|
|
350
|
+
display: "flex",
|
|
351
|
+
flexDirection: "column",
|
|
352
|
+
alignItems: "center",
|
|
353
|
+
justifyContent: "center",
|
|
354
|
+
gap: 3,
|
|
355
|
+
padding: "8px 2px 7px",
|
|
356
|
+
minHeight: BAR_HEIGHT,
|
|
357
|
+
background: "transparent",
|
|
358
|
+
border: "none",
|
|
359
|
+
color: active ? t.primary : t.muted,
|
|
360
|
+
fontFamily: t.fontFamily,
|
|
361
|
+
fontSize: 11,
|
|
362
|
+
fontWeight: active ? 700 : 500,
|
|
363
|
+
textDecoration: "none",
|
|
364
|
+
cursor: "pointer",
|
|
365
|
+
};
|
|
366
|
+
const content = (
|
|
367
|
+
<>
|
|
368
|
+
{icon}
|
|
369
|
+
<span style={{ maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
|
370
|
+
{label}
|
|
371
|
+
</span>
|
|
372
|
+
</>
|
|
373
|
+
);
|
|
374
|
+
if (href && onGo) {
|
|
375
|
+
return (
|
|
376
|
+
<a
|
|
377
|
+
href={href}
|
|
378
|
+
data-testid={testId}
|
|
379
|
+
aria-current={active ? "page" : undefined}
|
|
380
|
+
onClick={(event) => {
|
|
381
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
onGo(href);
|
|
384
|
+
}}
|
|
385
|
+
style={style}
|
|
386
|
+
>
|
|
387
|
+
{content}
|
|
388
|
+
</a>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return (
|
|
392
|
+
<button type="button" data-testid={testId} aria-pressed={active} onClick={onPress} style={style}>
|
|
393
|
+
{content}
|
|
394
|
+
</button>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
@@ -23,11 +23,12 @@ const POST_TYPES: Array<{ value: PostType; label: string }> = [
|
|
|
23
23
|
{ value: "image", label: "Image" },
|
|
24
24
|
{ value: "video", label: "Video" },
|
|
25
25
|
{ value: "audio", label: "Audio" },
|
|
26
|
-
|
|
26
|
+
// No "poll": creators no longer make poll posts (a poll lives on a community
|
|
27
|
+
// post now). Old poll rows still render through MemberPostCard.
|
|
27
28
|
];
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
* The member posts feed
|
|
31
|
+
* The member posts feed, at parity with the craft members area: gated feed
|
|
31
32
|
* (server-side `hasAccess`, locked posts render an upgrade CTA), debounced
|
|
32
33
|
* search, post-type + membership-tier filters, saved-posts view, engagement
|
|
33
34
|
* on every card, and load-more paging. Built on the headless
|
|
@@ -6,3 +6,11 @@ export { MemberPostCard, type MemberPostCardProps } from "./MemberPostCard";
|
|
|
6
6
|
export { PostCollectionsGrid, type PostCollectionsGridProps } from "./PostCollectionsGrid";
|
|
7
7
|
export { PostCollectionDetail, type PostCollectionDetailProps } from "./PostCollectionDetail";
|
|
8
8
|
export { MemberCallsCalendar, type MemberCallsCalendarProps } from "./MemberCallsCalendar";
|
|
9
|
+
export {
|
|
10
|
+
CallRecordingWatchLink,
|
|
11
|
+
CallRecordingProcessing,
|
|
12
|
+
CallTranscriptPanel,
|
|
13
|
+
callRecordingWatchable,
|
|
14
|
+
type CallRecording,
|
|
15
|
+
type CallRecordingWatchLinkProps,
|
|
16
|
+
} from "./CallRecordingActions";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useForgeT } from "../../../i18n";
|
|
2
|
+
import { AccountSection, type AccountSectionProps, type AccountTabKey } from "../AccountDashboard";
|
|
3
|
+
import { PortalHeading } from "./shared";
|
|
4
|
+
|
|
5
|
+
/** The portal sections that are the account dashboard's panels, by their dashboard key. */
|
|
6
|
+
export type PortalAccountSectionKey =
|
|
7
|
+
| "bookings"
|
|
8
|
+
| "memberships"
|
|
9
|
+
| "courses"
|
|
10
|
+
| "films"
|
|
11
|
+
| "orders"
|
|
12
|
+
| "tickets"
|
|
13
|
+
| "waitlist"
|
|
14
|
+
| "saved"
|
|
15
|
+
| "settings"
|
|
16
|
+
| "notifications";
|
|
17
|
+
|
|
18
|
+
const DASHBOARD_KEY: Record<PortalAccountSectionKey, AccountTabKey> = {
|
|
19
|
+
bookings: "bookings",
|
|
20
|
+
memberships: "membership",
|
|
21
|
+
courses: "courses",
|
|
22
|
+
films: "rentals",
|
|
23
|
+
orders: "orders",
|
|
24
|
+
tickets: "tickets",
|
|
25
|
+
waitlist: "waitlist",
|
|
26
|
+
saved: "saved",
|
|
27
|
+
settings: "account",
|
|
28
|
+
notifications: "notifications",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* One of the account dashboard's panels as a portal page, under its own title.
|
|
33
|
+
*
|
|
34
|
+
* The panels are the dashboard's own components, so a fix to one is a fix to
|
|
35
|
+
* both. Only the title is the portal's.
|
|
36
|
+
*/
|
|
37
|
+
export function PortalAccountSection({
|
|
38
|
+
section,
|
|
39
|
+
...rest
|
|
40
|
+
}: Omit<AccountSectionProps, "section"> & { section: PortalAccountSectionKey }) {
|
|
41
|
+
const tr = useForgeT();
|
|
42
|
+
// Literal keys, so the translation spec can see every one of them.
|
|
43
|
+
const title: Record<PortalAccountSectionKey, string> = {
|
|
44
|
+
bookings: tr("forge.portal_account_section.bookings"),
|
|
45
|
+
memberships: tr("forge.portal_account_section.memberships"),
|
|
46
|
+
courses: tr("forge.portal_account_section.courses"),
|
|
47
|
+
films: tr("forge.portal_account_section.films"),
|
|
48
|
+
orders: tr("forge.portal_account_section.orders"),
|
|
49
|
+
tickets: tr("forge.portal_account_section.tickets"),
|
|
50
|
+
waitlist: tr("forge.portal_account_section.waitlist"),
|
|
51
|
+
saved: tr("forge.portal_account_section.saved"),
|
|
52
|
+
settings: tr("forge.portal_account_section.settings"),
|
|
53
|
+
notifications: tr("forge.portal_account_section.notifications"),
|
|
54
|
+
};
|
|
55
|
+
return (
|
|
56
|
+
<div data-testid={`portal-section-${section}`}>
|
|
57
|
+
<PortalHeading title={title[section]} />
|
|
58
|
+
<AccountSection section={DASHBOARD_KEY[section]} {...rest} />
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|