@burdenoff/microfe-eventfullymanaged 2026.1030.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/utils/pageContext.js +1 -1
- 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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"}
|
|
@@ -110,7 +110,7 @@ function c(e) {
|
|
|
110
110
|
return t.length ? ["eventfullymanaged", ...t].slice(0, 3).join("/") : "eventfullymanaged";
|
|
111
111
|
}
|
|
112
112
|
function l() {
|
|
113
|
-
return "2026.1030.
|
|
113
|
+
return "2026.1030.2";
|
|
114
114
|
}
|
|
115
115
|
function u() {
|
|
116
116
|
let { origin: e, pathname: t } = window.location, n = s(t);
|