@burdenoff/microfe-eventfullymanaged 2026.830.1 → 2026.1030.2
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/dist/eventfullymanaged/api/eventApi.d.ts +39 -0
- package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.js +185 -157
- package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +63 -57
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeRecommendations.d.ts +13 -0
- package/dist/eventfullymanaged/pages/modules/AttendeeRecommendations.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/modules/AttendeeRecommendations.js +181 -0
- package/dist/eventfullymanaged/pages/modules/AttendeeRecommendations.js.map +1 -0
- package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +6 -4
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/utils/pageContext.d.ts +66 -0
- package/dist/utils/pageContext.d.ts.map +1 -0
- package/dist/utils/pageContext.js +133 -0
- package/dist/utils/pageContext.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,117 +1,123 @@
|
|
|
1
1
|
import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
|
|
2
2
|
import { useAttendeesQuery as t, useRegistrationsQuery as n } from "../../api/eventApi.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { AttendeeRecommendations as r } from "./AttendeeRecommendations.js";
|
|
4
|
+
import { EntityListSection as i } from "./EntityListSection.js";
|
|
5
|
+
import { useMemo as a, useRef as o } from "react";
|
|
6
|
+
import { useParams as s } from "react-router";
|
|
7
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
8
|
+
import { MessageSquare as u, Ticket as d, Users as f } from "lucide-react";
|
|
9
|
+
import { EmphasisPanel as p, GlassCard as m, GradientText as h, NextSteps as g, PagePurpose as _ } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { ContextConversationWidget as v } from "@burdenoff/fe-libs/shared";
|
|
11
|
+
import { useRegisterPageConversationContext as y } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
11
12
|
//#region src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx
|
|
12
|
-
function
|
|
13
|
-
return
|
|
13
|
+
function b({ attendeeId: e, attendeeName: t }) {
|
|
14
|
+
return y(a(() => ({
|
|
14
15
|
entityType: "EVENTFULLYMANAGED_ATTENDEE",
|
|
15
16
|
entityId: e,
|
|
16
17
|
contextTitle: `Attendee · ${t}`
|
|
17
|
-
}), [e, t])), /* @__PURE__ */
|
|
18
|
+
}), [e, t])), /* @__PURE__ */ c(v, { widgetKey: "attendee-message" });
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
-
let { id:
|
|
20
|
+
function x() {
|
|
21
|
+
let { id: v = "" } = s(), { navigate: y } = e(), x = t({
|
|
21
22
|
limit: 100,
|
|
22
23
|
offset: 0
|
|
23
|
-
}),
|
|
24
|
+
}), S = n({
|
|
24
25
|
limit: 100,
|
|
25
26
|
offset: 0
|
|
26
|
-
}),
|
|
27
|
-
return /* @__PURE__ */
|
|
27
|
+
}), C = o(null), w = a(() => (x.data?.attendees.items ?? []).find((e) => e.id === v), [v, x.data]), T = a(() => (S.data?.registrations.items ?? []).filter((e) => e.attendeeId === v), [v, S.data]);
|
|
28
|
+
return /* @__PURE__ */ l("div", {
|
|
28
29
|
className: "space-y-6 p-6",
|
|
29
30
|
children: [
|
|
30
|
-
/* @__PURE__ */
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
-
/* @__PURE__ */
|
|
31
|
+
/* @__PURE__ */ c(_, { children: "A single view of one attendee — who they are and everything they have registered for. Use this when you need to look someone up: verify their ticket, answer a support question, or understand their history with your events before reaching out." }),
|
|
32
|
+
/* @__PURE__ */ l(p, { children: [
|
|
33
|
+
/* @__PURE__ */ l("h1", {
|
|
33
34
|
className: "text-2xl font-bold text-text-primary",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
35
|
+
children: [/* @__PURE__ */ c(h, { children: "Attendee" }), " Profile"]
|
|
35
36
|
}),
|
|
36
|
-
/* @__PURE__ */
|
|
37
|
+
/* @__PURE__ */ c("p", {
|
|
37
38
|
className: "mt-1 text-sm text-text-secondary",
|
|
38
|
-
children:
|
|
39
|
+
children: x.loading ? "Loading attendee profile..." : w ? `${w.fullName ?? w.id} profile from registration and attendee APIs.` : "Attendee not found in current query scope."
|
|
39
40
|
}),
|
|
40
|
-
|
|
41
|
+
w ? /* @__PURE__ */ l("div", {
|
|
41
42
|
className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
|
|
42
43
|
children: [
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
+
/* @__PURE__ */ l(m, {
|
|
44
45
|
treatment: "metric",
|
|
45
46
|
className: "p-3",
|
|
46
|
-
children: [/* @__PURE__ */
|
|
47
|
+
children: [/* @__PURE__ */ c("p", {
|
|
47
48
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
48
49
|
children: "Name"
|
|
49
|
-
}), /* @__PURE__ */
|
|
50
|
+
}), /* @__PURE__ */ c("p", {
|
|
50
51
|
className: "mt-1 font-medium text-text-primary",
|
|
51
|
-
children:
|
|
52
|
+
children: w.fullName ?? w.id
|
|
52
53
|
})]
|
|
53
54
|
}),
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ l(m, {
|
|
55
56
|
treatment: "metric",
|
|
56
57
|
className: "p-3",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
58
|
+
children: [/* @__PURE__ */ c("p", {
|
|
58
59
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
59
60
|
children: "Email"
|
|
60
|
-
}), /* @__PURE__ */
|
|
61
|
+
}), /* @__PURE__ */ c("p", {
|
|
61
62
|
className: "mt-1 font-medium text-text-primary",
|
|
62
|
-
children:
|
|
63
|
+
children: w.email ?? "Unavailable"
|
|
63
64
|
})]
|
|
64
65
|
}),
|
|
65
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ l(m, {
|
|
66
67
|
treatment: "metric",
|
|
67
68
|
className: "p-3",
|
|
68
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ c("p", {
|
|
69
70
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
70
71
|
children: "Company"
|
|
71
|
-
}), /* @__PURE__ */
|
|
72
|
+
}), /* @__PURE__ */ c("p", {
|
|
72
73
|
className: "mt-1 font-medium text-text-primary",
|
|
73
|
-
children:
|
|
74
|
+
children: w.company ?? "Unavailable"
|
|
74
75
|
})]
|
|
75
76
|
}),
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ l(m, {
|
|
77
78
|
treatment: "metric",
|
|
78
79
|
className: "p-3",
|
|
79
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ c("p", {
|
|
80
81
|
className: "text-xs uppercase tracking-wide text-text-secondary",
|
|
81
82
|
children: "Role"
|
|
82
|
-
}), /* @__PURE__ */
|
|
83
|
+
}), /* @__PURE__ */ c("p", {
|
|
83
84
|
className: "mt-1 font-medium text-text-primary",
|
|
84
|
-
children:
|
|
85
|
+
children: w.role ?? "Unavailable"
|
|
85
86
|
})]
|
|
86
87
|
})
|
|
87
88
|
]
|
|
88
89
|
}) : null
|
|
89
90
|
] }),
|
|
90
|
-
/* @__PURE__ */
|
|
91
|
-
heading:
|
|
91
|
+
/* @__PURE__ */ c(i, {
|
|
92
|
+
heading: S.loading ? "Loading attendee registrations..." : `Registrations (${T.length})`,
|
|
92
93
|
emptyLabel: "No registrations linked to this attendee.",
|
|
93
|
-
rows:
|
|
94
|
+
rows: T.map((e) => ({
|
|
94
95
|
id: e.id,
|
|
95
96
|
title: `Registration ${e.id}`,
|
|
96
97
|
subtitle: [e.quantity ? `Qty ${e.quantity}` : void 0, e.totalAmount ? `${e.totalAmount} ${e.currency ?? ""}` : void 0].filter(Boolean).join(" • "),
|
|
97
98
|
status: e.status
|
|
98
99
|
}))
|
|
99
100
|
}),
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
w?.eventId ? /* @__PURE__ */ c(r, {
|
|
102
|
+
attendeeId: w.id,
|
|
103
|
+
eventId: w.eventId,
|
|
104
|
+
attendees: x.data?.attendees.items ?? []
|
|
105
|
+
}) : null,
|
|
106
|
+
v ? /* @__PURE__ */ l("div", {
|
|
107
|
+
ref: C,
|
|
102
108
|
className: "space-y-3",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
109
|
+
children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("h2", {
|
|
104
110
|
className: "text-lg font-semibold text-text-primary",
|
|
105
111
|
children: "Messages"
|
|
106
|
-
}), /* @__PURE__ */
|
|
112
|
+
}), /* @__PURE__ */ c("p", {
|
|
107
113
|
className: "mt-1 text-sm text-text-secondary",
|
|
108
114
|
children: "Start a direct conversation with this attendee. Threads and attachments are handled by the shared conversations service."
|
|
109
|
-
})] }), /* @__PURE__ */
|
|
110
|
-
attendeeId:
|
|
111
|
-
attendeeName:
|
|
115
|
+
})] }), /* @__PURE__ */ c(b, {
|
|
116
|
+
attendeeId: v,
|
|
117
|
+
attendeeName: w?.fullName ?? w?.id ?? v
|
|
112
118
|
})]
|
|
113
119
|
}) : null,
|
|
114
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ c(g, {
|
|
115
121
|
storageKey: "eventfullymanaged-attendee-profile",
|
|
116
122
|
title: "Act on this attendee",
|
|
117
123
|
steps: [
|
|
@@ -119,22 +125,22 @@ function b() {
|
|
|
119
125
|
id: "view-registrations",
|
|
120
126
|
label: "Review all registrations",
|
|
121
127
|
description: "See the full registration list to manage tickets and payments.",
|
|
122
|
-
icon: /* @__PURE__ */
|
|
123
|
-
onClick: () =>
|
|
128
|
+
icon: /* @__PURE__ */ c(d, { className: "h-4 w-4" }),
|
|
129
|
+
onClick: () => y("/registration")
|
|
124
130
|
},
|
|
125
131
|
{
|
|
126
132
|
id: "networking",
|
|
127
133
|
label: "Find networking matches",
|
|
128
134
|
description: "Connect this attendee with relevant people and meetings.",
|
|
129
|
-
icon: /* @__PURE__ */
|
|
130
|
-
onClick: () =>
|
|
135
|
+
icon: /* @__PURE__ */ c(f, { className: "h-4 w-4" }),
|
|
136
|
+
onClick: () => y("/networking")
|
|
131
137
|
},
|
|
132
138
|
{
|
|
133
139
|
id: "reach-out",
|
|
134
140
|
label: "Send a message",
|
|
135
141
|
description: "Open a direct conversation thread with this attendee.",
|
|
136
|
-
icon: /* @__PURE__ */
|
|
137
|
-
onClick: () =>
|
|
142
|
+
icon: /* @__PURE__ */ c(u, { className: "h-4 w-4" }),
|
|
143
|
+
onClick: () => C.current?.scrollIntoView({
|
|
138
144
|
behavior: "smooth",
|
|
139
145
|
block: "start"
|
|
140
146
|
})
|
|
@@ -145,6 +151,6 @@ function b() {
|
|
|
145
151
|
});
|
|
146
152
|
}
|
|
147
153
|
//#endregion
|
|
148
|
-
export {
|
|
154
|
+
export { x as default };
|
|
149
155
|
|
|
150
156
|
//# sourceMappingURL=AttendeeProfilePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttendeeProfilePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx"],"sourcesContent":["import { MessageSquare, Ticket, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useRef } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useAttendeesQuery, useRegistrationsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Direct message thread with a single attendee, backed by wspace-conversations-svc.\n * Kept as a dedicated component so `useRegisterPageConversationContext` scopes the\n * thread to this attendee (entityType + entityId) and unregisters on unmount.\n */\nfunction AttendeeConversation({ attendeeId, attendeeName }: { attendeeId: string; attendeeName: string }) {\n // Stable reference: `useRegisterPageConversationContext` keys its effect on this\n // object's identity, so a fresh inline object every render would loop the shared\n // PageConversationContextProvider (see NetworkingPage for the full rationale).\n const conversationContext = useMemo(\n () => ({\n entityType: 'EVENTFULLYMANAGED_ATTENDEE',\n entityId: attendeeId,\n contextTitle: `Attendee · ${attendeeName}`,\n }),\n [attendeeId, attendeeName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"attendee-message\" />;\n}\n\ntype AttendeeRow = {\n id: string;\n fullName?: string;\n email?: string;\n company?: string;\n role?: string;\n};\n\ntype RegistrationRow = {\n id: string;\n attendeeId?: string;\n status?: string;\n quantity?: number;\n currency?: string;\n totalAmount?: string;\n};\n\nexport default function AttendeeProfilePage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const attendeesQuery = useAttendeesQuery({ limit: 100, offset: 0 });\n const registrationsQuery = useRegistrationsQuery({ limit: 100, offset: 0 });\n const conversationRef = useRef<HTMLDivElement | null>(null);\n\n const attendee = useMemo(\n () => ((attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[]).find((item) => item.id === id),\n [id, attendeesQuery.data]\n );\n const registrations = useMemo(\n () =>\n ((registrationsQuery.data?.registrations.items ?? []) as RegistrationRow[]).filter(\n (registration) => registration.attendeeId === id\n ),\n [id, registrationsQuery.data]\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n A single view of one attendee — who they are and everything they have registered for. Use this when you need to\n look someone up: verify their ticket, answer a support question, or understand their history with your events\n before reaching out.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Attendee</GradientText> Profile\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {attendeesQuery.loading\n ? 'Loading attendee profile...'\n : attendee\n ? `${attendee.fullName ?? attendee.id} profile from registration and attendee APIs.`\n : 'Attendee not found in current query scope.'}\n </p>\n {attendee ? (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Name</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.fullName ?? attendee.id}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Email</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.email ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Company</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.company ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Role</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.role ?? 'Unavailable'}</p>\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n <EntityListSection\n heading={\n registrationsQuery.loading ? 'Loading attendee registrations...' : `Registrations (${registrations.length})`\n }\n emptyLabel=\"No registrations linked to this attendee.\"\n rows={registrations.map((registration) => ({\n id: registration.id,\n title: `Registration ${registration.id}`,\n subtitle: [\n registration.quantity ? `Qty ${registration.quantity}` : undefined,\n registration.totalAmount ? `${registration.totalAmount} ${registration.currency ?? ''}` : undefined,\n ]\n .filter(Boolean)\n .join(' • '),\n status: registration.status,\n }))}\n />\n\n {id ? (\n <div ref={conversationRef} className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Start a direct conversation with this attendee. Threads and attachments are handled by the shared\n conversations service.\n </p>\n </div>\n <AttendeeConversation attendeeId={id} attendeeName={attendee?.fullName ?? attendee?.id ?? id} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-attendee-profile\"\n title=\"Act on this attendee\"\n steps={[\n {\n id: 'view-registrations',\n label: 'Review all registrations',\n description: 'See the full registration list to manage tickets and payments.',\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'networking',\n label: 'Find networking matches',\n description: 'Connect this attendee with relevant people and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'reach-out',\n label: 'Send a message',\n description: 'Open a direct conversation thread with this attendee.',\n icon: <MessageSquare className=\"h-4 w-4\" />,\n onClick: () => conversationRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttendeeProfilePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeProfilePage.tsx"],"sourcesContent":["import { MessageSquare, Ticket, Users } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useRef } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useAttendeesQuery, useRegistrationsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { AttendeeRecommendations } from './AttendeeRecommendations';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Direct message thread with a single attendee, backed by wspace-conversations-svc.\n * Kept as a dedicated component so `useRegisterPageConversationContext` scopes the\n * thread to this attendee (entityType + entityId) and unregisters on unmount.\n */\nfunction AttendeeConversation({ attendeeId, attendeeName }: { attendeeId: string; attendeeName: string }) {\n // Stable reference: `useRegisterPageConversationContext` keys its effect on this\n // object's identity, so a fresh inline object every render would loop the shared\n // PageConversationContextProvider (see NetworkingPage for the full rationale).\n const conversationContext = useMemo(\n () => ({\n entityType: 'EVENTFULLYMANAGED_ATTENDEE',\n entityId: attendeeId,\n contextTitle: `Attendee · ${attendeeName}`,\n }),\n [attendeeId, attendeeName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"attendee-message\" />;\n}\n\ntype AttendeeRow = {\n id: string;\n eventId?: string;\n fullName?: string;\n email?: string;\n company?: string;\n role?: string;\n};\n\ntype RegistrationRow = {\n id: string;\n attendeeId?: string;\n status?: string;\n quantity?: number;\n currency?: string;\n totalAmount?: string;\n};\n\nexport default function AttendeeProfilePage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const attendeesQuery = useAttendeesQuery({ limit: 100, offset: 0 });\n const registrationsQuery = useRegistrationsQuery({ limit: 100, offset: 0 });\n const conversationRef = useRef<HTMLDivElement | null>(null);\n\n const attendee = useMemo(\n () => ((attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[]).find((item) => item.id === id),\n [id, attendeesQuery.data]\n );\n const registrations = useMemo(\n () =>\n ((registrationsQuery.data?.registrations.items ?? []) as RegistrationRow[]).filter(\n (registration) => registration.attendeeId === id\n ),\n [id, registrationsQuery.data]\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n A single view of one attendee — who they are and everything they have registered for. Use this when you need to\n look someone up: verify their ticket, answer a support question, or understand their history with your events\n before reaching out.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Attendee</GradientText> Profile\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {attendeesQuery.loading\n ? 'Loading attendee profile...'\n : attendee\n ? `${attendee.fullName ?? attendee.id} profile from registration and attendee APIs.`\n : 'Attendee not found in current query scope.'}\n </p>\n {attendee ? (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Name</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.fullName ?? attendee.id}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Email</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.email ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Company</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.company ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Role</p>\n <p className=\"mt-1 font-medium text-text-primary\">{attendee.role ?? 'Unavailable'}</p>\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n <EntityListSection\n heading={\n registrationsQuery.loading ? 'Loading attendee registrations...' : `Registrations (${registrations.length})`\n }\n emptyLabel=\"No registrations linked to this attendee.\"\n rows={registrations.map((registration) => ({\n id: registration.id,\n title: `Registration ${registration.id}`,\n subtitle: [\n registration.quantity ? `Qty ${registration.quantity}` : undefined,\n registration.totalAmount ? `${registration.totalAmount} ${registration.currency ?? ''}` : undefined,\n ]\n .filter(Boolean)\n .join(' • '),\n status: registration.status,\n }))}\n />\n\n {attendee?.eventId ? (\n <AttendeeRecommendations\n attendeeId={attendee.id}\n eventId={attendee.eventId}\n attendees={(attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[]}\n />\n ) : null}\n\n {id ? (\n <div ref={conversationRef} className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Start a direct conversation with this attendee. Threads and attachments are handled by the shared\n conversations service.\n </p>\n </div>\n <AttendeeConversation attendeeId={id} attendeeName={attendee?.fullName ?? attendee?.id ?? id} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-attendee-profile\"\n title=\"Act on this attendee\"\n steps={[\n {\n id: 'view-registrations',\n label: 'Review all registrations',\n description: 'See the full registration list to manage tickets and payments.',\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'networking',\n label: 'Find networking matches',\n description: 'Connect this attendee with relevant people and meetings.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'reach-out',\n label: 'Send a message',\n description: 'Open a direct conversation thread with this attendee.',\n icon: <MessageSquare className=\"h-4 w-4\" />,\n onClick: () => conversationRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type AttendeeName = {
|
|
2
|
+
id: string;
|
|
3
|
+
fullName?: string;
|
|
4
|
+
};
|
|
5
|
+
interface AttendeeRecommendationsProps {
|
|
6
|
+
attendeeId: string;
|
|
7
|
+
eventId: string;
|
|
8
|
+
/** Attendees already loaded by the page, used to put names on suggested connections. */
|
|
9
|
+
attendees: AttendeeName[];
|
|
10
|
+
}
|
|
11
|
+
export declare function AttendeeRecommendations({ attendeeId, eventId, attendees }: AttendeeRecommendationsProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=AttendeeRecommendations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttendeeRecommendations.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeRecommendations.tsx"],"names":[],"mappings":"AAmCA,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,UAAU,4BAA4B;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,wBAAgB,uBAAuB,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,4BAA4B,2CAqIvG"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { useNetworkingProfilesQuery as e, useRecommendedSessionsQuery as t, useSuggestedConnectionsQuery as n } from "../../api/eventApi.js";
|
|
2
|
+
import { useMemo as r } from "react";
|
|
3
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
4
|
+
import { CalendarClock as o, Sparkles as s, Users as c } from "lucide-react";
|
|
5
|
+
import { Badge as l, GlassCard as u, IllustratedEmptyState as d } from "@burdenoff/fe-libs/ui";
|
|
6
|
+
//#region src/eventfullymanaged/pages/modules/AttendeeRecommendations.tsx
|
|
7
|
+
var f = {
|
|
8
|
+
NO_PROFILE: "Add a networking profile with interests for this attendee to get session recommendations.",
|
|
9
|
+
NO_INTERESTS: "This attendee’s networking profile has no interests yet, so there is nothing to match sessions on.",
|
|
10
|
+
NO_UPCOMING_SESSIONS: "There are no upcoming sessions this attendee has not already attended.",
|
|
11
|
+
NO_SIGNAL: "No upcoming session matches this attendee’s interests yet."
|
|
12
|
+
};
|
|
13
|
+
function p(e, t) {
|
|
14
|
+
let n = new Date(e), r = new Date(t);
|
|
15
|
+
return Number.isNaN(n.getTime()) || Number.isNaN(r.getTime()) ? "" : `${n.toLocaleDateString(void 0, {
|
|
16
|
+
month: "short",
|
|
17
|
+
day: "numeric"
|
|
18
|
+
})}, ${n.toLocaleTimeString(void 0, {
|
|
19
|
+
hour: "2-digit",
|
|
20
|
+
minute: "2-digit"
|
|
21
|
+
})} – ${r.toLocaleTimeString(void 0, {
|
|
22
|
+
hour: "2-digit",
|
|
23
|
+
minute: "2-digit"
|
|
24
|
+
})}`;
|
|
25
|
+
}
|
|
26
|
+
function m({ attendeeId: m, eventId: h, attendees: g }) {
|
|
27
|
+
let _ = t({
|
|
28
|
+
eventId: h,
|
|
29
|
+
attendeeId: m,
|
|
30
|
+
limit: 5
|
|
31
|
+
}), v = n({
|
|
32
|
+
eventId: h,
|
|
33
|
+
attendeeId: m,
|
|
34
|
+
limit: 5
|
|
35
|
+
}), y = e({
|
|
36
|
+
eventId: h,
|
|
37
|
+
limit: 200,
|
|
38
|
+
offset: 0
|
|
39
|
+
}), b = _.data?.recommendedSessions, x = v.data?.suggestedConnections.items ?? [], S = r(() => {
|
|
40
|
+
let e = new Map(g.map((e) => [e.id, e.fullName ?? e.id])), t = /* @__PURE__ */ new Map();
|
|
41
|
+
for (let n of y.data?.networkingProfiles.items ?? []) t.set(n.id, {
|
|
42
|
+
attendeeId: n.attendeeId,
|
|
43
|
+
name: e.get(n.attendeeId) ?? "Attendee"
|
|
44
|
+
});
|
|
45
|
+
return t;
|
|
46
|
+
}, [g, y.data]), C = r(() => [...S.entries()].find(([, e]) => e.attendeeId === m)?.[0], [m, S]);
|
|
47
|
+
return /* @__PURE__ */ a("div", {
|
|
48
|
+
className: "grid grid-cols-1 gap-6 lg:grid-cols-2",
|
|
49
|
+
"data-testid": "attendee-recommendations",
|
|
50
|
+
children: [/* @__PURE__ */ a(u, {
|
|
51
|
+
treatment: "plain",
|
|
52
|
+
className: "border-border-seam p-5",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ a("div", {
|
|
55
|
+
className: "flex items-center gap-2",
|
|
56
|
+
children: [/* @__PURE__ */ i(o, {
|
|
57
|
+
className: "h-5 w-5 text-accent-primary",
|
|
58
|
+
"aria-hidden": "true"
|
|
59
|
+
}), /* @__PURE__ */ i("h2", {
|
|
60
|
+
className: "text-lg font-semibold text-text-primary",
|
|
61
|
+
children: "Recommended sessions"
|
|
62
|
+
})]
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ i("p", {
|
|
65
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
66
|
+
children: "Ranked from this attendee’s networking interests and the sessions they attended before. Each suggestion says why it was picked."
|
|
67
|
+
}),
|
|
68
|
+
/* @__PURE__ */ i("div", {
|
|
69
|
+
className: "mt-4",
|
|
70
|
+
children: _.loading ? /* @__PURE__ */ i("p", {
|
|
71
|
+
className: "text-sm text-text-secondary",
|
|
72
|
+
children: "Finding sessions for this attendee..."
|
|
73
|
+
}) : _.error ? /* @__PURE__ */ i("p", {
|
|
74
|
+
className: "text-sm text-text-secondary",
|
|
75
|
+
children: "Session recommendations are unavailable right now."
|
|
76
|
+
}) : !b || b.items.length === 0 ? /* @__PURE__ */ i(d, {
|
|
77
|
+
illustration: "empty-data",
|
|
78
|
+
title: b?.emptyReason ? f[b.emptyReason] : "No session recommendations yet.",
|
|
79
|
+
className: "py-8"
|
|
80
|
+
}) : /* @__PURE__ */ i("ul", {
|
|
81
|
+
className: "space-y-2.5",
|
|
82
|
+
"data-testid": "recommended-sessions",
|
|
83
|
+
children: b.items.map((e) => /* @__PURE__ */ a("li", {
|
|
84
|
+
className: "rounded-card border border-border-seam bg-bg-surface p-3",
|
|
85
|
+
children: [/* @__PURE__ */ a("div", {
|
|
86
|
+
className: "flex items-start justify-between gap-3",
|
|
87
|
+
children: [/* @__PURE__ */ a("div", {
|
|
88
|
+
className: "min-w-0",
|
|
89
|
+
children: [/* @__PURE__ */ i("p", {
|
|
90
|
+
className: "truncate font-medium text-text-primary",
|
|
91
|
+
children: e.session.title
|
|
92
|
+
}), /* @__PURE__ */ i("p", {
|
|
93
|
+
className: "mt-0.5 text-sm text-text-secondary",
|
|
94
|
+
children: [
|
|
95
|
+
p(e.session.startsAt, e.session.endsAt),
|
|
96
|
+
e.session.track,
|
|
97
|
+
e.seatsRemaining === null || e.seatsRemaining === void 0 ? void 0 : `${e.seatsRemaining} seats left`
|
|
98
|
+
].filter(Boolean).join(" • ")
|
|
99
|
+
})]
|
|
100
|
+
}), /* @__PURE__ */ a(l, {
|
|
101
|
+
variant: "info",
|
|
102
|
+
className: "shrink-0",
|
|
103
|
+
children: [e.score, "% fit"]
|
|
104
|
+
})]
|
|
105
|
+
}), /* @__PURE__ */ a("p", {
|
|
106
|
+
className: "mt-2 flex items-start gap-1.5 text-sm text-text-secondary",
|
|
107
|
+
children: [/* @__PURE__ */ i(s, {
|
|
108
|
+
className: "mt-0.5 h-3.5 w-3.5 shrink-0",
|
|
109
|
+
"aria-hidden": "true"
|
|
110
|
+
}), /* @__PURE__ */ i("span", { children: e.reason })]
|
|
111
|
+
})]
|
|
112
|
+
}, e.session.id))
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
}), /* @__PURE__ */ a(u, {
|
|
117
|
+
treatment: "plain",
|
|
118
|
+
className: "border-border-seam p-5",
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ a("div", {
|
|
121
|
+
className: "flex items-center gap-2",
|
|
122
|
+
children: [/* @__PURE__ */ i(c, {
|
|
123
|
+
className: "h-5 w-5 text-accent-primary",
|
|
124
|
+
"aria-hidden": "true"
|
|
125
|
+
}), /* @__PURE__ */ i("h2", {
|
|
126
|
+
className: "text-lg font-semibold text-text-primary",
|
|
127
|
+
children: "People to meet"
|
|
128
|
+
})]
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ i("p", {
|
|
131
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
132
|
+
children: "Other opted-in attendees with overlapping interests. Suggestions only: nothing is booked until a meeting is created."
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ i("div", {
|
|
135
|
+
className: "mt-4",
|
|
136
|
+
children: v.loading ? /* @__PURE__ */ i("p", {
|
|
137
|
+
className: "text-sm text-text-secondary",
|
|
138
|
+
children: "Finding people to meet..."
|
|
139
|
+
}) : v.error ? /* @__PURE__ */ i("p", {
|
|
140
|
+
className: "text-sm text-text-secondary",
|
|
141
|
+
children: "Connection suggestions are unavailable right now."
|
|
142
|
+
}) : x.length === 0 ? /* @__PURE__ */ i(d, {
|
|
143
|
+
illustration: "empty-data",
|
|
144
|
+
title: "No connection suggestions. The attendee needs a public networking profile and others with overlapping interests.",
|
|
145
|
+
className: "py-8"
|
|
146
|
+
}) : /* @__PURE__ */ i("ul", {
|
|
147
|
+
className: "space-y-2.5",
|
|
148
|
+
"data-testid": "suggested-connections",
|
|
149
|
+
children: x.map((e) => {
|
|
150
|
+
let t = e.sourceProfileId === C ? e.targetProfileId : e.sourceProfileId;
|
|
151
|
+
return /* @__PURE__ */ a("li", {
|
|
152
|
+
className: "rounded-card border border-border-seam bg-bg-surface p-3",
|
|
153
|
+
children: [/* @__PURE__ */ a("div", {
|
|
154
|
+
className: "flex items-start justify-between gap-3",
|
|
155
|
+
children: [/* @__PURE__ */ i("p", {
|
|
156
|
+
className: "min-w-0 truncate font-medium text-text-primary",
|
|
157
|
+
children: S.get(t)?.name ?? "Attendee"
|
|
158
|
+
}), /* @__PURE__ */ a(l, {
|
|
159
|
+
variant: "info",
|
|
160
|
+
className: "shrink-0",
|
|
161
|
+
children: [e.score, "% fit"]
|
|
162
|
+
})]
|
|
163
|
+
}), e.reason ? /* @__PURE__ */ a("p", {
|
|
164
|
+
className: "mt-2 flex items-start gap-1.5 text-sm text-text-secondary",
|
|
165
|
+
children: [/* @__PURE__ */ i(s, {
|
|
166
|
+
className: "mt-0.5 h-3.5 w-3.5 shrink-0",
|
|
167
|
+
"aria-hidden": "true"
|
|
168
|
+
}), /* @__PURE__ */ i("span", { children: e.reason })]
|
|
169
|
+
}) : null]
|
|
170
|
+
}, e.id);
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
})]
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { m as AttendeeRecommendations };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=AttendeeRecommendations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttendeeRecommendations.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/AttendeeRecommendations.tsx"],"sourcesContent":["import { CalendarClock, Sparkles, Users } from 'lucide-react';\nimport { Badge, GlassCard, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport {\n type SessionRecommendationEmptyReason,\n useNetworkingProfilesQuery,\n useRecommendedSessionsQuery,\n useSuggestedConnectionsQuery,\n} from '../../api/eventApi';\n\n/**\n * Attendee-facing recommendations (BOFF-3764): sessions ranked for this\n * attendee and people worth meeting, each with the reason it was suggested.\n * Both lists come from deterministic, explainable scorers in\n * wspace-eventfullymanaged-svc; nothing is booked or registered from here.\n */\n\nconst EMPTY_SESSION_MESSAGES: Record<SessionRecommendationEmptyReason, string> = {\n NO_PROFILE: 'Add a networking profile with interests for this attendee to get session recommendations.',\n NO_INTERESTS: 'This attendee’s networking profile has no interests yet, so there is nothing to match sessions on.',\n NO_UPCOMING_SESSIONS: 'There are no upcoming sessions this attendee has not already attended.',\n NO_SIGNAL: 'No upcoming session matches this attendee’s interests yet.',\n};\n\nfunction formatSessionTime(startsAt: string, endsAt: string): string {\n const start = new Date(startsAt);\n const end = new Date(endsAt);\n if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return '';\n const day = start.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n const from = start.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\n const to = end.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });\n return `${day}, ${from} – ${to}`;\n}\n\ntype AttendeeName = { id: string; fullName?: string };\n\ninterface AttendeeRecommendationsProps {\n attendeeId: string;\n eventId: string;\n /** Attendees already loaded by the page, used to put names on suggested connections. */\n attendees: AttendeeName[];\n}\n\nexport function AttendeeRecommendations({ attendeeId, eventId, attendees }: AttendeeRecommendationsProps) {\n const sessionsQuery = useRecommendedSessionsQuery({ eventId, attendeeId, limit: 5 });\n const connectionsQuery = useSuggestedConnectionsQuery({ eventId, attendeeId, limit: 5 });\n const profilesQuery = useNetworkingProfilesQuery({ eventId, limit: 200, offset: 0 });\n\n const recommendations = sessionsQuery.data?.recommendedSessions;\n const connections = connectionsQuery.data?.suggestedConnections.items ?? [];\n\n const profileToName = useMemo(() => {\n const attendeeNames = new Map(attendees.map((row) => [row.id, row.fullName ?? row.id]));\n const map = new Map<string, { attendeeId: string; name: string }>();\n for (const profile of profilesQuery.data?.networkingProfiles.items ?? []) {\n map.set(profile.id, {\n attendeeId: profile.attendeeId,\n name: attendeeNames.get(profile.attendeeId) ?? 'Attendee',\n });\n }\n return map;\n }, [attendees, profilesQuery.data]);\n const selfProfileId = useMemo(\n () => [...profileToName.entries()].find(([, value]) => value.attendeeId === attendeeId)?.[0],\n [attendeeId, profileToName]\n );\n\n return (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\" data-testid=\"attendee-recommendations\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-5 w-5 text-accent-primary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Recommended sessions</h2>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Ranked from this attendee’s networking interests and the sessions they attended before. Each suggestion says\n why it was picked.\n </p>\n <div className=\"mt-4\">\n {sessionsQuery.loading ? (\n <p className=\"text-sm text-text-secondary\">Finding sessions for this attendee...</p>\n ) : sessionsQuery.error ? (\n <p className=\"text-sm text-text-secondary\">Session recommendations are unavailable right now.</p>\n ) : !recommendations || recommendations.items.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n recommendations?.emptyReason\n ? EMPTY_SESSION_MESSAGES[recommendations.emptyReason]\n : 'No session recommendations yet.'\n }\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\" data-testid=\"recommended-sessions\">\n {recommendations.items.map((item) => (\n <li key={item.session.id} className=\"rounded-card border border-border-seam bg-bg-surface p-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{item.session.title}</p>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {[\n formatSessionTime(item.session.startsAt, item.session.endsAt),\n item.session.track,\n item.seatsRemaining === null || item.seatsRemaining === undefined\n ? undefined\n : `${item.seatsRemaining} seats left`,\n ]\n .filter(Boolean)\n .join(' • ')}\n </p>\n </div>\n <Badge variant=\"info\" className=\"shrink-0\">\n {item.score}% fit\n </Badge>\n </div>\n <p className=\"mt-2 flex items-start gap-1.5 text-sm text-text-secondary\">\n <Sparkles className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>{item.reason}</span>\n </p>\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center gap-2\">\n <Users className=\"h-5 w-5 text-accent-primary\" aria-hidden=\"true\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">People to meet</h2>\n </div>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Other opted-in attendees with overlapping interests. Suggestions only: nothing is booked until a meeting is\n created.\n </p>\n <div className=\"mt-4\">\n {connectionsQuery.loading ? (\n <p className=\"text-sm text-text-secondary\">Finding people to meet...</p>\n ) : connectionsQuery.error ? (\n <p className=\"text-sm text-text-secondary\">Connection suggestions are unavailable right now.</p>\n ) : connections.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No connection suggestions. The attendee needs a public networking profile and others with overlapping interests.\"\n className=\"py-8\"\n />\n ) : (\n <ul className=\"space-y-2.5\" data-testid=\"suggested-connections\">\n {connections.map((match) => {\n const otherProfileId =\n match.sourceProfileId === selfProfileId ? match.targetProfileId : match.sourceProfileId;\n const other = profileToName.get(otherProfileId);\n return (\n <li key={match.id} className=\"rounded-card border border-border-seam bg-bg-surface p-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"min-w-0 truncate font-medium text-text-primary\">{other?.name ?? 'Attendee'}</p>\n <Badge variant=\"info\" className=\"shrink-0\">\n {match.score}% fit\n </Badge>\n </div>\n {match.reason ? (\n <p className=\"mt-2 flex items-start gap-1.5 text-sm text-text-secondary\">\n <Sparkles className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span>{match.reason}</span>\n </p>\n ) : null}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.d.ts","sourceRoot":"","sources":["../../src/services/createSandboxAssistantTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.d.ts","sourceRoot":"","sources":["../../src/services/createSandboxAssistantTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,4BAA4B,IAAI,kBAAkB,CAQjE"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { gatherPageContext as e } from "../utils/pageContext.js";
|
|
2
|
+
import { useSandboxAssistantTransport as t } from "@burdenoff/fe-libs/shared/assistant";
|
|
2
3
|
//#region src/services/createSandboxAssistantTransport.ts
|
|
3
|
-
function
|
|
4
|
-
return
|
|
4
|
+
function n() {
|
|
5
|
+
return t({
|
|
5
6
|
product: "eventfullymanaged",
|
|
7
|
+
gatherPageContext: e,
|
|
6
8
|
enableCredits: !0
|
|
7
9
|
});
|
|
8
10
|
}
|
|
9
11
|
//#endregion
|
|
10
|
-
export {
|
|
12
|
+
export { n as useSandboxAssistantTransport };
|
|
11
13
|
|
|
12
14
|
//# sourceMappingURL=createSandboxAssistantTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * eventfullymanaged adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper. The exported name/shape are unchanged so app-shell\n * imports don't move. The public return type is a LOCALLY-declared structural\n * mirror — never re-export fe-libs types (vite-plugin-dts path rewriting).\n */\n\nimport { useSandboxAssistantTransport as useSharedTransport } from '@burdenoff/fe-libs/shared/assistant';\n\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n prewarm?: (level?: 'probe' | 'full') => void;\n suspend?: () => void;\n}\n\nexport function useSandboxAssistantTransport(): AssistantTransport {\n return useSharedTransport({\n product: 'eventfullymanaged',\n\n enableCredits: true,\n });\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * eventfullymanaged adapter for the shared fe-libs AssistantWidget transport.\n *\n * The sandbox lifecycle + poll/stream loop now lives in\n * `@burdenoff/fe-libs/shared/assistant` (one implementation for all products);\n * this is a thin wrapper. The exported name/shape are unchanged so app-shell\n * imports don't move. The public return type is a LOCALLY-declared structural\n * mirror — never re-export fe-libs types (vite-plugin-dts path rewriting).\n */\n\nimport { useSandboxAssistantTransport as useSharedTransport } from '@burdenoff/fe-libs/shared/assistant';\nimport { gatherPageContext } from '../utils/pageContext';\n\ninterface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n prewarm?: (level?: 'probe' | 'full') => void;\n suspend?: () => void;\n}\n\nexport function useSandboxAssistantTransport(): AssistantTransport {\n return useSharedTransport({\n product: 'eventfullymanaged',\n // Route-derived ids only (BOFF-3741): lets the assistant act on \"this\n // event / session / sponsor\" without a clarifying question.\n gatherPageContext,\n enableCredits: true,\n });\n}\n"],"mappings":";;;AAyBA,SAAgB,IAAmD;AACjE,QAAO,EAAmB;EACxB,SAAS;EAGT;EACA,eAAe;EAChB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page context for the EventfullyManaged AI assistant (BOFF-3741).
|
|
3
|
+
*
|
|
4
|
+
* Captures where the organizer is in the app — the URL, the page and the record
|
|
5
|
+
* on screen — so the floating assistant can act on "this event", "this session"
|
|
6
|
+
* or "here" without asking which record the user means.
|
|
7
|
+
*
|
|
8
|
+
* Data boundary: this module emits ids and route names only. It never reads the
|
|
9
|
+
* DOM, the Apollo cache or any form state, so attendee names and emails, lead
|
|
10
|
+
* contact details, speaker bios and networking-profile free text cannot reach
|
|
11
|
+
* the assistant through it. The query string and fragment are dropped from
|
|
12
|
+
* `currentUrl` as well: they can carry search text or tokens, and the sandbox's
|
|
13
|
+
* key allow-list only filters keys after the payload has left the browser.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Entity types the assistant understands for EventfullyManaged. The api-calls
|
|
17
|
+
* guide in algoshred/ai-assistant (`context/api-calls/products/eventfullymanaged.md`)
|
|
18
|
+
* documents each one with the query that reads it; keep the two in step.
|
|
19
|
+
*/
|
|
20
|
+
export declare const EVENTFULLYMANAGED_ENTITY_TYPES: readonly ["EVENTFULLYMANAGED_EVENT", "EVENTFULLYMANAGED_SESSION", "EVENTFULLYMANAGED_TICKET_TYPE", "EVENTFULLYMANAGED_VENUE", "EVENTFULLYMANAGED_SPONSOR", "EVENTFULLYMANAGED_SPEAKER", "EVENTFULLYMANAGED_ATTENDEE", "EVENTFULLYMANAGED_PROMO_CODE", "EVENTFULLYMANAGED_NETWORKING_PROFILE", "EVENTFULLYMANAGED_MEETING", "EVENTFULLYMANAGED_EXHIBITOR"];
|
|
21
|
+
export type EventfullyManagedEntityType = (typeof EVENTFULLYMANAGED_ENTITY_TYPES)[number];
|
|
22
|
+
/**
|
|
23
|
+
* A `type` alias rather than an `interface`, on purpose: TypeScript gives a type
|
|
24
|
+
* alias an implicit index signature, which keeps it assignable to the shared
|
|
25
|
+
* transport's `gatherPageContext: () => object` seam without a cast.
|
|
26
|
+
*
|
|
27
|
+
* Every key here is on the sandbox agent's allow-list
|
|
28
|
+
* (`ALLOWED_CONTEXT_KEYS` in ai-assistant `src/context-loader.ts`). In particular
|
|
29
|
+
* the URL key is `currentUrl`: an `activeUrl` key would be silently dropped.
|
|
30
|
+
*/
|
|
31
|
+
export type PageContext = {
|
|
32
|
+
currentUrl: string;
|
|
33
|
+
currentPage: string;
|
|
34
|
+
entityType?: EventfullyManagedEntityType;
|
|
35
|
+
entityId?: string;
|
|
36
|
+
activeTab?: string;
|
|
37
|
+
platform: string;
|
|
38
|
+
version: string;
|
|
39
|
+
viewportSize: string;
|
|
40
|
+
};
|
|
41
|
+
export interface ResolvedEntity {
|
|
42
|
+
entityType?: EventfullyManagedEntityType;
|
|
43
|
+
entityId?: string;
|
|
44
|
+
activeTab?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Strip the mount prefix and any trailing slash, so the table above matches
|
|
48
|
+
* the same page whether the MFE runs inside the app shell
|
|
49
|
+
* (`/eventfullymanaged/events/abc`) or standalone in dev (`/events/abc`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function toProductPath(pathname: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the record the user is looking at from the route alone.
|
|
54
|
+
*
|
|
55
|
+
* Returns an empty object on list, dashboard and create pages: there is no
|
|
56
|
+
* "this" there, and a guessed entity is worse than none because the assistant
|
|
57
|
+
* would act on it confidently.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveEventfullyManagedEntity(pathname: string): ResolvedEntity;
|
|
60
|
+
/**
|
|
61
|
+
* Up to three path segments under the product, e.g. `events/<id>/sessions`.
|
|
62
|
+
* Matches the shape every other product sends, so the agent can name the page.
|
|
63
|
+
*/
|
|
64
|
+
export declare function toCurrentPage(pathname: string): string;
|
|
65
|
+
export declare function gatherPageContext(): PageContext;
|
|
66
|
+
//# sourceMappingURL=pageContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,2VAYjC,CAAC;AAEX,MAAM,MAAM,2BAA2B,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1F;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,2BAA2B,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+FD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOtD;AAUD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAc/E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGtD;AAcD,wBAAgB,iBAAiB,IAAI,WAAW,CAY/C"}
|