@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
|
@@ -51,6 +51,12 @@ export function useCommunityStyles() {
|
|
|
51
51
|
* Paystack popup is open (see `DialogRoot`). The hook that owns the flow must
|
|
52
52
|
* live in the COMPONENT that renders this, not inside `children`: a Paystack
|
|
53
53
|
* stand-down remounts the content.
|
|
54
|
+
*
|
|
55
|
+
* Below `lg` it is a bottom sheet: anchored to the bottom edge, full width,
|
|
56
|
+
* top corners rounded, a grab handle, scrolling inside, and clear of the
|
|
57
|
+
* phone's home indicator. From `lg` up it is the centred dialog it always was.
|
|
58
|
+
* The layout is breakpoint classes, so there is nothing to measure and nothing
|
|
59
|
+
* that renders differently on the server.
|
|
54
60
|
*/
|
|
55
61
|
export function CommunitySheet({
|
|
56
62
|
open,
|
|
@@ -71,12 +77,20 @@ export function CommunitySheet({
|
|
|
71
77
|
return (
|
|
72
78
|
<DialogRoot open={open} onOpenChange={onOpenChange}>
|
|
73
79
|
<DialogPortal>
|
|
74
|
-
<DialogOverlay style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", zIndex:
|
|
80
|
+
<DialogOverlay style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: COMMUNITY_SHEET_Z }} />
|
|
75
81
|
<DialogContent
|
|
76
82
|
data-testid={testId}
|
|
83
|
+
data-sheet="bottom-below-lg"
|
|
77
84
|
aria-describedby={undefined}
|
|
85
|
+
className={COMMUNITY_SHEET_CLASSES}
|
|
78
86
|
style={sheetStyle(t)}
|
|
79
87
|
>
|
|
88
|
+
<div
|
|
89
|
+
aria-hidden
|
|
90
|
+
data-sheet-handle=""
|
|
91
|
+
className="fg:mx-auto fg:mb-3 fg:lg:hidden"
|
|
92
|
+
style={{ width: 40, height: 4, borderRadius: 2, background: t.border }}
|
|
93
|
+
/>
|
|
80
94
|
<div className="fg:flex fg:items-center fg:justify-between fg:gap-3" style={{ marginBottom: 12 }}>
|
|
81
95
|
<DialogTitle style={{ fontSize: 18, fontWeight: 700, margin: 0, fontFamily: t.headingFontFamily }}>
|
|
82
96
|
{title}
|
|
@@ -98,20 +112,34 @@ export function CommunitySheet({
|
|
|
98
112
|
);
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
/**
|
|
116
|
+
* The sheet's stacking order. It is modal, so it covers everything pinned to
|
|
117
|
+
* the screen: the site's chat bubble (`AiAgentWidget` defaults to 9999) and
|
|
118
|
+
* the member home's bottom bar.
|
|
119
|
+
*/
|
|
120
|
+
export const COMMUNITY_SHEET_Z = 10000;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Where the sheet sits: a bottom sheet below `lg`, the centred dialog from
|
|
124
|
+
* `lg` up. The corner radius comes from the theme through `--cs-radius`.
|
|
125
|
+
*/
|
|
126
|
+
export const COMMUNITY_SHEET_CLASSES = [
|
|
127
|
+
"fg:fixed fg:inset-x-0 fg:bottom-0 fg:w-full fg:max-h-[90dvh] fg:overflow-y-auto",
|
|
128
|
+
"fg:rounded-t-[var(--cs-radius)] fg:px-5 fg:pt-3",
|
|
129
|
+
"fg:lg:inset-x-auto fg:lg:bottom-auto fg:lg:top-1/2 fg:lg:left-1/2 fg:lg:-translate-x-1/2 fg:lg:-translate-y-1/2",
|
|
130
|
+
"fg:lg:w-[min(560px,calc(100vw-32px))] fg:lg:max-h-[88vh] fg:lg:rounded-[var(--cs-radius-lg)] fg:lg:p-6",
|
|
131
|
+
].join(" ");
|
|
132
|
+
|
|
101
133
|
function sheetStyle(t: ResolvedThemeTokens): CSSProperties {
|
|
102
134
|
return {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
maxHeight: "88vh",
|
|
109
|
-
overflowY: "auto",
|
|
135
|
+
// A bottom sheet's top corners stay visibly round on a square theme.
|
|
136
|
+
["--cs-radius" as string]: `${Math.max(t.cornerRadius, 12)}px`,
|
|
137
|
+
["--cs-radius-lg" as string]: `${t.cornerRadius}px`,
|
|
138
|
+
// Clear of the phone's home indicator; the inset is 0 everywhere else.
|
|
139
|
+
paddingBottom: "calc(24px + env(safe-area-inset-bottom))",
|
|
110
140
|
background: t.background,
|
|
111
141
|
color: t.text,
|
|
112
|
-
|
|
113
|
-
padding: 24,
|
|
114
|
-
zIndex: 51,
|
|
142
|
+
zIndex: COMMUNITY_SHEET_Z + 1,
|
|
115
143
|
fontFamily: t.fontFamily,
|
|
116
144
|
boxSizing: "border-box",
|
|
117
145
|
};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Bot, Sparkles, ThumbsDown, ThumbsUp, X } from "lucide-react";
|
|
2
|
+
import { useForgeT } from "../../../i18n";
|
|
3
|
+
import { useThemeTokens } from "../../theme/ForgeThemeProvider";
|
|
4
|
+
import {
|
|
5
|
+
linkifyText,
|
|
6
|
+
useCommunityAgentAnswerFeedback,
|
|
7
|
+
useCommunityCatchUp,
|
|
8
|
+
} from "../../headless/community/useCommunityAgent";
|
|
9
|
+
import type { CommunityAgentFeedbackEntity } from "../../../data/queries/useCommunityAgent";
|
|
10
|
+
|
|
11
|
+
/** What an agent is drawn from: its name and avatar. */
|
|
12
|
+
export interface CommunityAgentIdentity {
|
|
13
|
+
name: string;
|
|
14
|
+
avatarUrl?: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The "AI" chip. Shown beside the community AI's name everywhere it appears,
|
|
19
|
+
* so it is never mistaken for a member or for the creator.
|
|
20
|
+
*/
|
|
21
|
+
export function CommunityAgentBadge() {
|
|
22
|
+
const t = useThemeTokens();
|
|
23
|
+
const tr = useForgeT();
|
|
24
|
+
return (
|
|
25
|
+
<span
|
|
26
|
+
data-testid="community-agent-badge"
|
|
27
|
+
title={tr("forge.community_agent.badge_title")}
|
|
28
|
+
style={{
|
|
29
|
+
background: t.primary,
|
|
30
|
+
color: t.textPrimary,
|
|
31
|
+
borderRadius: 999,
|
|
32
|
+
fontSize: 10,
|
|
33
|
+
fontWeight: 700,
|
|
34
|
+
padding: "1px 6px",
|
|
35
|
+
letterSpacing: 0.4,
|
|
36
|
+
whiteSpace: "nowrap",
|
|
37
|
+
flexShrink: 0,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{tr("forge.community_agent.badge")}
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CommunityAgentAvatarProps {
|
|
46
|
+
agent: CommunityAgentIdentity;
|
|
47
|
+
size?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The agent's avatar, or a neutral AI glyph when the creator set none. */
|
|
51
|
+
export function CommunityAgentAvatar({ agent, size = 36 }: CommunityAgentAvatarProps) {
|
|
52
|
+
const t = useThemeTokens();
|
|
53
|
+
const style: React.CSSProperties = { width: size, height: size, borderRadius: "50%", flexShrink: 0, objectFit: "cover" };
|
|
54
|
+
if (agent.avatarUrl) return <img src={agent.avatarUrl} alt={agent.name} style={style} />;
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
role="img"
|
|
58
|
+
aria-label={agent.name}
|
|
59
|
+
data-testid="community-agent-glyph"
|
|
60
|
+
style={{
|
|
61
|
+
...style,
|
|
62
|
+
display: "flex",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
justifyContent: "center",
|
|
65
|
+
background: `${t.primary}22`,
|
|
66
|
+
color: t.primary,
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<Bot size={Math.max(12, Math.round(size * 0.55))} aria-hidden />
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CommunityAgentNameProps {
|
|
75
|
+
agent: CommunityAgentIdentity;
|
|
76
|
+
fontSize?: number;
|
|
77
|
+
fontWeight?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The agent's name with its "AI" badge, in place of a member's name. */
|
|
81
|
+
export function CommunityAgentName({ agent, fontSize = 14, fontWeight = 600 }: CommunityAgentNameProps) {
|
|
82
|
+
const t = useThemeTokens();
|
|
83
|
+
return (
|
|
84
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, minWidth: 0 }}>
|
|
85
|
+
<span
|
|
86
|
+
style={{
|
|
87
|
+
color: t.text,
|
|
88
|
+
fontWeight,
|
|
89
|
+
fontSize,
|
|
90
|
+
whiteSpace: "nowrap",
|
|
91
|
+
overflow: "hidden",
|
|
92
|
+
textOverflow: "ellipsis",
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{agent.name}
|
|
96
|
+
</span>
|
|
97
|
+
<CommunityAgentBadge />
|
|
98
|
+
</span>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Plain text with its http(s) addresses made clickable, line breaks kept. For
|
|
104
|
+
* the agent's answers, which end with a "Sources:" list of addresses.
|
|
105
|
+
*/
|
|
106
|
+
export function CommunityLinkifiedText({ text }: { text: string }) {
|
|
107
|
+
const t = useThemeTokens();
|
|
108
|
+
return (
|
|
109
|
+
<span style={{ whiteSpace: "pre-line", wordBreak: "break-word" }}>
|
|
110
|
+
{linkifyText(text).map((segment, i) =>
|
|
111
|
+
segment.kind === "link" ? (
|
|
112
|
+
<a
|
|
113
|
+
key={i}
|
|
114
|
+
href={segment.url}
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noopener noreferrer"
|
|
117
|
+
style={{ color: "inherit", textDecoration: "underline", textDecorationColor: t.primary }}
|
|
118
|
+
>
|
|
119
|
+
{segment.text}
|
|
120
|
+
</a>
|
|
121
|
+
) : (
|
|
122
|
+
<span key={i}>{segment.text}</span>
|
|
123
|
+
),
|
|
124
|
+
)}
|
|
125
|
+
</span>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** "<name> is thinking..." while the agent writes an answer. */
|
|
130
|
+
export function CommunityAgentThinking({ name }: { name?: string | null }) {
|
|
131
|
+
const t = useThemeTokens();
|
|
132
|
+
const tr = useForgeT();
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
role="status"
|
|
136
|
+
data-testid="community-agent-thinking"
|
|
137
|
+
style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: t.muted }}
|
|
138
|
+
>
|
|
139
|
+
<Sparkles size={13} aria-hidden style={{ color: t.primary }} />
|
|
140
|
+
{name ? tr("forge.community_agent.thinking", { agent_name: name }) : tr("forge.community_agent.thinking_unnamed")}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface CommunityAgentFeedbackProps {
|
|
146
|
+
entityType: CommunityAgentFeedbackEntity;
|
|
147
|
+
entityId: string;
|
|
148
|
+
/** The member's saved rating, from `useCommunityAgentAnswerRatings`. */
|
|
149
|
+
saved?: 1 | -1 | null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Thumbs up / down on one AI answer, for the member who asked (render it only
|
|
154
|
+
* where `useCommunityAgentAnswerRatings` says they may rate). Starts from the
|
|
155
|
+
* saved rating; optimistic; a second press on the active thumb clears it. A
|
|
156
|
+
* 404 hides it without a word.
|
|
157
|
+
*/
|
|
158
|
+
export function CommunityAgentFeedback({ entityType, entityId, saved = null }: CommunityAgentFeedbackProps) {
|
|
159
|
+
const t = useThemeTokens();
|
|
160
|
+
const tr = useForgeT();
|
|
161
|
+
const feedback = useCommunityAgentAnswerFeedback(entityType, entityId, saved);
|
|
162
|
+
if (feedback.hidden) return null;
|
|
163
|
+
|
|
164
|
+
const thumb = (value: 1 | -1, label: string, icon: React.ReactNode) => {
|
|
165
|
+
const active = feedback.value === value;
|
|
166
|
+
return (
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
onClick={() => feedback.rate(value)}
|
|
170
|
+
aria-label={label}
|
|
171
|
+
aria-pressed={active}
|
|
172
|
+
title={label}
|
|
173
|
+
style={{
|
|
174
|
+
display: "inline-flex",
|
|
175
|
+
alignItems: "center",
|
|
176
|
+
background: active ? `${t.primary}1a` : "none",
|
|
177
|
+
border: "none",
|
|
178
|
+
borderRadius: t.cornerRadius,
|
|
179
|
+
cursor: "pointer",
|
|
180
|
+
color: active ? t.primary : t.muted,
|
|
181
|
+
padding: "2px 4px",
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{icon}
|
|
185
|
+
</button>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<span data-testid="community-agent-feedback" style={{ display: "inline-flex", alignItems: "center", gap: 2 }}>
|
|
191
|
+
{thumb(1, tr("forge.community_agent.feedback_up"), <ThumbsUp size={13} aria-hidden />)}
|
|
192
|
+
{thumb(-1, tr("forge.community_agent.feedback_down"), <ThumbsDown size={13} aria-hidden />)}
|
|
193
|
+
</span>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface CommunityCatchMeUpProps {
|
|
198
|
+
postId: string;
|
|
199
|
+
agent: CommunityAgentIdentity;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* "Catch me up" on a long thread. The summary opens in a private panel, marked
|
|
204
|
+
* as written by the AI, that only this member sees; nothing is posted. A
|
|
205
|
+
* reached daily limit or an unavailable AI is said plainly.
|
|
206
|
+
*/
|
|
207
|
+
export function CommunityCatchMeUp({ postId, agent }: CommunityCatchMeUpProps) {
|
|
208
|
+
const t = useThemeTokens();
|
|
209
|
+
const tr = useForgeT();
|
|
210
|
+
const catchUp = useCommunityCatchUp(postId);
|
|
211
|
+
const border = `1px solid ${t.border}`;
|
|
212
|
+
|
|
213
|
+
const errorCopy =
|
|
214
|
+
catchUp.error === "daily_limit"
|
|
215
|
+
? tr("forge.community_agent.catch_up_daily_limit")
|
|
216
|
+
: catchUp.error === "unavailable"
|
|
217
|
+
? tr("forge.community_agent.catch_up_unavailable")
|
|
218
|
+
: catchUp.error
|
|
219
|
+
? tr("forge.community_agent.catch_up_failed")
|
|
220
|
+
: null;
|
|
221
|
+
|
|
222
|
+
if (catchUp.summary) {
|
|
223
|
+
return (
|
|
224
|
+
<section
|
|
225
|
+
data-testid="community-catch-me-up-panel"
|
|
226
|
+
aria-label={tr("forge.community_agent.catch_up_panel_label", { agent_name: catchUp.summary.agentName })}
|
|
227
|
+
style={{ border, borderRadius: t.cornerRadius, background: `${t.primary}0d`, padding: 14 }}
|
|
228
|
+
>
|
|
229
|
+
<header style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
|
|
230
|
+
<CommunityAgentAvatar agent={agent} size={24} />
|
|
231
|
+
<span style={{ flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
|
|
232
|
+
<CommunityAgentName agent={{ ...agent, name: catchUp.summary.agentName || agent.name }} fontSize={13} />
|
|
233
|
+
<span style={{ fontSize: 12, color: t.muted }}>{tr("forge.community_agent.catch_up_private")}</span>
|
|
234
|
+
</span>
|
|
235
|
+
<button
|
|
236
|
+
type="button"
|
|
237
|
+
onClick={catchUp.dismiss}
|
|
238
|
+
aria-label={tr("forge.community_agent.catch_up_dismiss")}
|
|
239
|
+
style={{ background: "none", border: "none", cursor: "pointer", color: t.muted, padding: 2 }}
|
|
240
|
+
>
|
|
241
|
+
<X size={15} aria-hidden />
|
|
242
|
+
</button>
|
|
243
|
+
</header>
|
|
244
|
+
<div style={{ color: t.text, fontSize: 14, lineHeight: 1.55 }}>
|
|
245
|
+
<CommunityLinkifiedText text={catchUp.summary.summary} />
|
|
246
|
+
</div>
|
|
247
|
+
</section>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 6, alignItems: "flex-start" }}>
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
onClick={catchUp.ask}
|
|
256
|
+
disabled={catchUp.isPending}
|
|
257
|
+
style={{
|
|
258
|
+
display: "inline-flex",
|
|
259
|
+
alignItems: "center",
|
|
260
|
+
gap: 6,
|
|
261
|
+
background: "none",
|
|
262
|
+
border,
|
|
263
|
+
borderRadius: t.cornerRadius,
|
|
264
|
+
padding: "6px 12px",
|
|
265
|
+
cursor: catchUp.isPending ? "wait" : "pointer",
|
|
266
|
+
color: t.text,
|
|
267
|
+
fontSize: 13,
|
|
268
|
+
fontWeight: 600,
|
|
269
|
+
opacity: catchUp.isPending ? 0.7 : 1,
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
<Sparkles size={14} aria-hidden style={{ color: t.primary }} />
|
|
273
|
+
{catchUp.isPending ? tr("forge.community_agent.catch_up_pending") : tr("forge.community_agent.catch_up")}
|
|
274
|
+
</button>
|
|
275
|
+
{errorCopy && (
|
|
276
|
+
<div
|
|
277
|
+
role="alert"
|
|
278
|
+
data-testid="community-catch-me-up-error"
|
|
279
|
+
style={{ fontSize: 13, color: t.text, borderLeft: `3px solid ${t.primary}`, paddingLeft: 8 }}
|
|
280
|
+
>
|
|
281
|
+
{errorCopy}
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|