@burdenoff/microfe-eventfullymanaged 2026.730.1 → 2026.801.1
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/EventfullyManagedRoutes.d.ts.map +1 -1
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js +61 -77
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts +208 -0
- package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.js +293 -136
- package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts +18 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js +110 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts +47 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js +209 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js.map +1 -0
- package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js +80 -72
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js +353 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js +336 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
- package/dist/eventfullymanaged/utils/shellBridge.d.ts +46 -0
- package/dist/eventfullymanaged/utils/shellBridge.d.ts.map +1 -0
- package/dist/eventfullymanaged/utils/shellBridge.js +19 -0
- package/dist/eventfullymanaged/utils/shellBridge.js.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
|
|
2
|
+
import { notify as t } from "../../lib/toast.js";
|
|
3
|
+
import { VIRTUAL_PARTICIPANTS_CHANGED_SUBSCRIPTION_SDL as n, VIRTUAL_SIGNAL_SUBSCRIPTION_SDL as r, useEndVirtualSessionMutation as i, useEventQuery as a, useHostVirtualSessionMutation as o, useJoinVirtualSessionMutation as s, useLeaveVirtualSessionMutation as ee, usePromoteVirtualPresenterMutation as te, useSendVirtualSignalMutation as ne, useSetVirtualMuteMutation as re, useSetVirtualVideoMutation as ie, useVirtualSessionQuery as ae } from "../../api/eventApi.js";
|
|
4
|
+
import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
|
|
5
|
+
import { useEvmSubscription as u } from "../../hooks/useEvmSubscription.js";
|
|
6
|
+
import { useVirtualCall as oe } from "../../hooks/useVirtualCall.js";
|
|
7
|
+
import { useCallback as se, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
8
|
+
import { useParams as ce } from "react-router";
|
|
9
|
+
import { Fragment as le, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
10
|
+
import { ArrowLeft as _, Mic as v, MicOff as y, PhoneOff as ue, Radio as de, ScreenShare as b, Users as x, Video as S, VideoOff as C } from "lucide-react";
|
|
11
|
+
import { Badge as w, Button as T, EmphasisPanel as fe, GlassCard as E, GradientText as pe, IllustratedEmptyState as D, PagePurpose as me, Select as he, SelectContent as ge, SelectItem as O, SelectTrigger as _e, SelectValue as ve, Skeleton as k } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/eventfullymanaged/pages/events/VirtualSessionPage.tsx
|
|
13
|
+
function A(e) {
|
|
14
|
+
return e.slice(0, 2).toUpperCase();
|
|
15
|
+
}
|
|
16
|
+
function j(e) {
|
|
17
|
+
return e === "connected" ? "bg-status-success" : e === "connecting" || e === "new" ? "bg-status-warning" : e === "failed" || e === "disconnected" || e === "closed" ? "bg-status-danger" : "bg-text-muted";
|
|
18
|
+
}
|
|
19
|
+
function M({ actorId: e, role: t, isLocal: n, stream: r, muted: i, videoEnabled: a, connectionState: o }) {
|
|
20
|
+
let s = p(null);
|
|
21
|
+
return d(() => {
|
|
22
|
+
s.current && (s.current.srcObject = r);
|
|
23
|
+
}, [r]), /* @__PURE__ */ g("div", {
|
|
24
|
+
className: "relative aspect-video overflow-hidden rounded-card border border-border-seam bg-bg-sunken",
|
|
25
|
+
children: [r && a ? /* @__PURE__ */ h("video", {
|
|
26
|
+
ref: s,
|
|
27
|
+
autoPlay: !0,
|
|
28
|
+
playsInline: !0,
|
|
29
|
+
muted: n,
|
|
30
|
+
className: "h-full w-full object-cover",
|
|
31
|
+
style: n ? { transform: "scaleX(-1)" } : void 0
|
|
32
|
+
}) : /* @__PURE__ */ h("div", {
|
|
33
|
+
className: "flex h-full w-full items-center justify-center",
|
|
34
|
+
children: /* @__PURE__ */ h("span", {
|
|
35
|
+
className: "flex h-12 w-12 items-center justify-center rounded-full bg-accent-primary/10 text-sm font-semibold text-accent-primary",
|
|
36
|
+
children: A(e)
|
|
37
|
+
})
|
|
38
|
+
}), /* @__PURE__ */ g("div", {
|
|
39
|
+
className: "absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/60 to-transparent p-2",
|
|
40
|
+
children: [/* @__PURE__ */ g("span", {
|
|
41
|
+
className: "truncate text-xs font-medium text-white",
|
|
42
|
+
children: [n ? "You" : `Participant ${e.slice(0, 6)}`, t === "HOST" ? " · Host" : t === "PRESENTER" ? " · Presenter" : ""]
|
|
43
|
+
}), /* @__PURE__ */ g("span", {
|
|
44
|
+
className: "flex items-center gap-1.5",
|
|
45
|
+
children: [i ? /* @__PURE__ */ h(y, { className: "h-3.5 w-3.5 text-white" }) : null, n ? null : /* @__PURE__ */ h("span", {
|
|
46
|
+
className: `h-2 w-2 rounded-full ${j(o)}`,
|
|
47
|
+
title: o ?? "connecting"
|
|
48
|
+
})]
|
|
49
|
+
})]
|
|
50
|
+
})]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function N() {
|
|
54
|
+
let { id: p = "" } = ce(), { navigate: A, currentUser: N } = e(), P = N?.id ?? "", { data: F, loading: ye } = a(p), I = ae({ eventId: p }), L = I.data?.virtualSession ?? null, [R, be] = m("MEETING"), [xe, { loading: z }] = o(), [Se, { loading: B }] = s(), [Ce, { loading: V }] = ee(), [we, { loading: H }] = i(), [Te] = re(), [Ee] = ie(), [De] = te(), [U] = ne(), W = f(() => L?.participants.find((e) => e.actorId === P && !e.leftAt) ?? null, [L, P]), G = f(() => (L?.participants ?? []).filter((e) => !e.leftAt), [L]), [K, q] = m(!1), [J, Y] = m(!1);
|
|
55
|
+
d(() => {
|
|
56
|
+
W && (q(W.muted), Y(!W.videoEnabled));
|
|
57
|
+
}, [W?.muted, W?.videoEnabled]);
|
|
58
|
+
let Oe = se(async (e) => {
|
|
59
|
+
L && await U({ variables: {
|
|
60
|
+
sessionId: L.id,
|
|
61
|
+
toActorId: e.toActorId,
|
|
62
|
+
signalType: e.signalType,
|
|
63
|
+
payload: e.payload
|
|
64
|
+
} });
|
|
65
|
+
}, [L, U]), X = oe({
|
|
66
|
+
sessionId: W ? L?.id : null,
|
|
67
|
+
selfActorId: P,
|
|
68
|
+
mode: L?.mode ?? "MEETING",
|
|
69
|
+
selfRole: W?.role ?? null,
|
|
70
|
+
participants: G.map((e) => ({
|
|
71
|
+
actorId: e.actorId,
|
|
72
|
+
role: e.role
|
|
73
|
+
})),
|
|
74
|
+
sendSignal: Oe,
|
|
75
|
+
muted: K,
|
|
76
|
+
videoEnabled: !J
|
|
77
|
+
});
|
|
78
|
+
u({
|
|
79
|
+
query: r,
|
|
80
|
+
variables: { sessionId: L?.id },
|
|
81
|
+
operationName: "EventfullymanagedVirtualSignal",
|
|
82
|
+
enabled: !!(W && L),
|
|
83
|
+
onData: (e) => {
|
|
84
|
+
let t = e.eventfullyManagedVirtualSignal;
|
|
85
|
+
t && X.handleIncomingSignal(t);
|
|
86
|
+
},
|
|
87
|
+
pollFn: () => {},
|
|
88
|
+
pollIntervalMs: 15e3
|
|
89
|
+
}), u({
|
|
90
|
+
query: n,
|
|
91
|
+
variables: { sessionId: L?.id },
|
|
92
|
+
operationName: "EventfullymanagedVirtualParticipantsChanged",
|
|
93
|
+
enabled: !!L,
|
|
94
|
+
onData: () => {
|
|
95
|
+
I.refetch();
|
|
96
|
+
},
|
|
97
|
+
pollFn: () => {
|
|
98
|
+
I.refetch();
|
|
99
|
+
},
|
|
100
|
+
pollIntervalMs: 5e3
|
|
101
|
+
});
|
|
102
|
+
let ke = async (e) => {
|
|
103
|
+
try {
|
|
104
|
+
let n = await xe({ variables: {
|
|
105
|
+
eventId: p,
|
|
106
|
+
mode: e
|
|
107
|
+
} });
|
|
108
|
+
if (!l(n, n.data?.hostVirtualSession?.id, "Could not start session")) return;
|
|
109
|
+
t.success("Session started", e === "WEBINAR" ? "Hosting as a webinar." : "Hosting a meeting."), await I.refetch();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
t.error("Could not start session", c(e));
|
|
112
|
+
}
|
|
113
|
+
}, Ae = async () => {
|
|
114
|
+
try {
|
|
115
|
+
let e = await Se({ variables: { eventId: p } });
|
|
116
|
+
if (!l(e, e.data?.joinVirtualSession?.id, "Could not join session")) return;
|
|
117
|
+
await I.refetch();
|
|
118
|
+
} catch (e) {
|
|
119
|
+
t.error("Could not join session", c(e));
|
|
120
|
+
}
|
|
121
|
+
}, je = async () => {
|
|
122
|
+
if (L) try {
|
|
123
|
+
await Ce({ variables: { sessionId: L.id } }), t.success("Left session", "You are no longer in the virtual session."), await I.refetch();
|
|
124
|
+
} catch (e) {
|
|
125
|
+
t.error("Could not leave session", c(e));
|
|
126
|
+
}
|
|
127
|
+
}, Me = async () => {
|
|
128
|
+
if (L) try {
|
|
129
|
+
await we({ variables: { sessionId: L.id } }), t.success("Session ended", "The virtual session has been ended for everyone."), await I.refetch();
|
|
130
|
+
} catch (e) {
|
|
131
|
+
t.error("Could not end session", c(e));
|
|
132
|
+
}
|
|
133
|
+
}, Ne = async () => {
|
|
134
|
+
if (!L) return;
|
|
135
|
+
let e = !K;
|
|
136
|
+
q(e);
|
|
137
|
+
try {
|
|
138
|
+
await Te({ variables: {
|
|
139
|
+
sessionId: L.id,
|
|
140
|
+
muted: e
|
|
141
|
+
} });
|
|
142
|
+
} catch (n) {
|
|
143
|
+
q(!e), t.error("Could not update mute state", c(n));
|
|
144
|
+
}
|
|
145
|
+
}, Z = async () => {
|
|
146
|
+
if (!L) return;
|
|
147
|
+
let e = !J;
|
|
148
|
+
Y(e);
|
|
149
|
+
try {
|
|
150
|
+
await Ee({ variables: {
|
|
151
|
+
sessionId: L.id,
|
|
152
|
+
videoEnabled: !e
|
|
153
|
+
} });
|
|
154
|
+
} catch (n) {
|
|
155
|
+
Y(!e), t.error("Could not update camera state", c(n));
|
|
156
|
+
}
|
|
157
|
+
}, Pe = async (e) => {
|
|
158
|
+
if (L) try {
|
|
159
|
+
await De({ variables: {
|
|
160
|
+
sessionId: L.id,
|
|
161
|
+
actorId: e.actorId
|
|
162
|
+
} }), t.success("Promoted to presenter", `Participant ${e.actorId.slice(0, 6)} can now broadcast.`), await I.refetch();
|
|
163
|
+
} catch (e) {
|
|
164
|
+
t.error("Could not promote participant", c(e));
|
|
165
|
+
}
|
|
166
|
+
}, Q = W?.role === "HOST", $ = z || B || V || H;
|
|
167
|
+
return ye || I.loading ? /* @__PURE__ */ g("div", {
|
|
168
|
+
className: "space-y-4 p-6",
|
|
169
|
+
children: [/* @__PURE__ */ h(k, { className: "h-8 w-64" }), /* @__PURE__ */ h(k, { className: "h-64 w-full" })]
|
|
170
|
+
}) : /* @__PURE__ */ g("div", {
|
|
171
|
+
className: "space-y-6 p-6",
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ g(T, {
|
|
174
|
+
type: "button",
|
|
175
|
+
variant: "ghost",
|
|
176
|
+
size: "sm",
|
|
177
|
+
onClick: () => A(`/events/${p}`),
|
|
178
|
+
children: [/* @__PURE__ */ h(_, { className: "h-4 w-4" }), "Back to Event"]
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ h(me, { children: "A virtual session is a real, browser-native WebRTC room for this event — no external meeting link. Hosts start a Meeting (small mesh, up to 8 people all connected directly) or a Webinar (up to 30 attendees who see and hear only the host/presenters). Signaling is relayed through EventfullyManaged; media never touches our servers." }),
|
|
181
|
+
/* @__PURE__ */ g(fe, {
|
|
182
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
183
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("h1", {
|
|
184
|
+
className: "text-2xl font-bold text-text-primary",
|
|
185
|
+
children: /* @__PURE__ */ h(pe, { children: "Virtual Session" })
|
|
186
|
+
}), /* @__PURE__ */ h("p", {
|
|
187
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
188
|
+
children: F?.event?.name ?? "Event"
|
|
189
|
+
})] }), L ? /* @__PURE__ */ g(w, {
|
|
190
|
+
variant: L.status === "ACTIVE" ? "success" : "secondary",
|
|
191
|
+
className: "uppercase",
|
|
192
|
+
children: [
|
|
193
|
+
L.mode,
|
|
194
|
+
" · ",
|
|
195
|
+
L.status
|
|
196
|
+
]
|
|
197
|
+
}) : null]
|
|
198
|
+
}),
|
|
199
|
+
!L || L.status !== "ACTIVE" ? /* @__PURE__ */ g(E, {
|
|
200
|
+
treatment: "plain",
|
|
201
|
+
className: "border-border-seam p-8",
|
|
202
|
+
children: [/* @__PURE__ */ h(D, {
|
|
203
|
+
illustration: "empty-data",
|
|
204
|
+
title: "No live virtual session",
|
|
205
|
+
description: "Start a session to host attendees over WebRTC, or join once someone else has started one.",
|
|
206
|
+
className: "py-6"
|
|
207
|
+
}), /* @__PURE__ */ g("div", {
|
|
208
|
+
className: "mt-4 flex flex-wrap items-center justify-center gap-2",
|
|
209
|
+
children: [/* @__PURE__ */ g(he, {
|
|
210
|
+
value: R,
|
|
211
|
+
onValueChange: (e) => be(e),
|
|
212
|
+
children: [/* @__PURE__ */ h(_e, {
|
|
213
|
+
"aria-label": "Session mode",
|
|
214
|
+
className: "w-48",
|
|
215
|
+
children: /* @__PURE__ */ h(ve, {})
|
|
216
|
+
}), /* @__PURE__ */ g(ge, { children: [/* @__PURE__ */ h(O, {
|
|
217
|
+
value: "MEETING",
|
|
218
|
+
children: "Meeting (up to 8)"
|
|
219
|
+
}), /* @__PURE__ */ h(O, {
|
|
220
|
+
value: "WEBINAR",
|
|
221
|
+
children: "Webinar (up to 30)"
|
|
222
|
+
})] })]
|
|
223
|
+
}), /* @__PURE__ */ g(T, {
|
|
224
|
+
type: "button",
|
|
225
|
+
onClick: () => ke(R),
|
|
226
|
+
disabled: $,
|
|
227
|
+
children: [/* @__PURE__ */ h(de, { className: "h-4 w-4" }), z ? "Starting…" : "Start session"]
|
|
228
|
+
})]
|
|
229
|
+
})]
|
|
230
|
+
}) : W ? /* @__PURE__ */ g(le, { children: [
|
|
231
|
+
X.mediaError ? /* @__PURE__ */ g(E, {
|
|
232
|
+
treatment: "plain",
|
|
233
|
+
className: "border-status-danger/40 bg-status-danger/5 p-4 text-sm text-status-danger",
|
|
234
|
+
children: [
|
|
235
|
+
"Camera/microphone unavailable: ",
|
|
236
|
+
X.mediaError,
|
|
237
|
+
". You are still connected to the session and can see other participants, but others will not see or hear you."
|
|
238
|
+
]
|
|
239
|
+
}) : null,
|
|
240
|
+
/* @__PURE__ */ g("div", {
|
|
241
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
|
|
242
|
+
children: [/* @__PURE__ */ h(M, {
|
|
243
|
+
actorId: P,
|
|
244
|
+
role: W.role,
|
|
245
|
+
isLocal: !0,
|
|
246
|
+
stream: X.localStream,
|
|
247
|
+
muted: K,
|
|
248
|
+
videoEnabled: !J
|
|
249
|
+
}), G.filter((e) => e.actorId !== P).map((e) => /* @__PURE__ */ h(M, {
|
|
250
|
+
actorId: e.actorId,
|
|
251
|
+
role: e.role,
|
|
252
|
+
isLocal: !1,
|
|
253
|
+
stream: X.remoteStreams.get(e.actorId) ?? null,
|
|
254
|
+
muted: e.muted,
|
|
255
|
+
videoEnabled: e.videoEnabled,
|
|
256
|
+
connectionState: X.connectionStates.get(e.actorId)
|
|
257
|
+
}, e.actorId))]
|
|
258
|
+
}),
|
|
259
|
+
/* @__PURE__ */ g(E, {
|
|
260
|
+
treatment: "plain",
|
|
261
|
+
className: "flex flex-wrap items-center justify-center gap-2 border-border-seam p-4",
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ g(T, {
|
|
264
|
+
type: "button",
|
|
265
|
+
variant: K ? "outline" : "secondary",
|
|
266
|
+
onClick: Ne,
|
|
267
|
+
children: [h(K ? y : v, { className: "h-4 w-4" }), K ? "Unmute" : "Mute"]
|
|
268
|
+
}),
|
|
269
|
+
/* @__PURE__ */ g(T, {
|
|
270
|
+
type: "button",
|
|
271
|
+
variant: J ? "outline" : "secondary",
|
|
272
|
+
onClick: Z,
|
|
273
|
+
children: [h(J ? C : S, { className: "h-4 w-4" }), J ? "Start Video" : "Stop Video"]
|
|
274
|
+
}),
|
|
275
|
+
/* @__PURE__ */ g(T, {
|
|
276
|
+
type: "button",
|
|
277
|
+
variant: "outline",
|
|
278
|
+
onClick: je,
|
|
279
|
+
disabled: $,
|
|
280
|
+
children: [/* @__PURE__ */ h(ue, { className: "h-4 w-4" }), V ? "Leaving…" : "Leave"]
|
|
281
|
+
}),
|
|
282
|
+
Q ? /* @__PURE__ */ g(T, {
|
|
283
|
+
type: "button",
|
|
284
|
+
variant: "destructive",
|
|
285
|
+
onClick: Me,
|
|
286
|
+
disabled: $,
|
|
287
|
+
children: [/* @__PURE__ */ h(b, { className: "h-4 w-4" }), H ? "Ending…" : "End for everyone"]
|
|
288
|
+
}) : null
|
|
289
|
+
]
|
|
290
|
+
}),
|
|
291
|
+
/* @__PURE__ */ g(E, {
|
|
292
|
+
treatment: "plain",
|
|
293
|
+
className: "border-border-seam p-4",
|
|
294
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
295
|
+
className: "flex items-center gap-2 text-sm font-semibold text-text-primary",
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ h(x, { className: "h-4 w-4 text-accent-primary" }),
|
|
298
|
+
"Participants (",
|
|
299
|
+
G.length,
|
|
300
|
+
"/",
|
|
301
|
+
L.maxParticipants,
|
|
302
|
+
")"
|
|
303
|
+
]
|
|
304
|
+
}), /* @__PURE__ */ h("div", {
|
|
305
|
+
className: "mt-3 space-y-2",
|
|
306
|
+
children: G.map((e) => /* @__PURE__ */ g("div", {
|
|
307
|
+
className: "flex items-center justify-between gap-2 text-sm",
|
|
308
|
+
children: [/* @__PURE__ */ g("span", {
|
|
309
|
+
className: "flex items-center gap-2 text-text-secondary",
|
|
310
|
+
children: [
|
|
311
|
+
/* @__PURE__ */ h("span", { className: `h-2 w-2 rounded-full ${e.actorId === P ? "bg-status-success" : j(X.connectionStates.get(e.actorId))}` }),
|
|
312
|
+
e.actorId === P ? "You" : `Participant ${e.actorId.slice(0, 6)}`,
|
|
313
|
+
/* @__PURE__ */ h(w, {
|
|
314
|
+
variant: "secondary",
|
|
315
|
+
className: "uppercase",
|
|
316
|
+
children: e.role
|
|
317
|
+
})
|
|
318
|
+
]
|
|
319
|
+
}), Q && L.mode === "WEBINAR" && e.role === "ATTENDEE" ? /* @__PURE__ */ h(T, {
|
|
320
|
+
type: "button",
|
|
321
|
+
variant: "ghost",
|
|
322
|
+
size: "sm",
|
|
323
|
+
onClick: () => Pe(e),
|
|
324
|
+
children: "Promote to presenter"
|
|
325
|
+
}) : null]
|
|
326
|
+
}, e.id))
|
|
327
|
+
})]
|
|
328
|
+
})
|
|
329
|
+
] }) : /* @__PURE__ */ g(E, {
|
|
330
|
+
treatment: "plain",
|
|
331
|
+
className: "border-border-seam p-8",
|
|
332
|
+
children: [/* @__PURE__ */ h(D, {
|
|
333
|
+
illustration: "empty-people",
|
|
334
|
+
title: "A session is live",
|
|
335
|
+
description: `${L.participantCount} of ${L.maxParticipants} seats in use. Join to turn on your camera and mic.`,
|
|
336
|
+
className: "py-6"
|
|
337
|
+
}), /* @__PURE__ */ h("div", {
|
|
338
|
+
className: "mt-4 flex justify-center",
|
|
339
|
+
children: /* @__PURE__ */ g(T, {
|
|
340
|
+
type: "button",
|
|
341
|
+
onClick: Ae,
|
|
342
|
+
disabled: $,
|
|
343
|
+
children: [/* @__PURE__ */ h(S, { className: "h-4 w-4" }), B ? "Joining…" : "Join session"]
|
|
344
|
+
})
|
|
345
|
+
})]
|
|
346
|
+
})
|
|
347
|
+
]
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
//#endregion
|
|
351
|
+
export { N as default };
|
|
352
|
+
|
|
353
|
+
//# sourceMappingURL=VirtualSessionPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VirtualSessionPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/events/VirtualSessionPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect -- mirrors server-confirmed mute/video state into local toggle state, same established pattern as the fleet's *FormPage.tsx entity-hydration effects */\n/**\n * VirtualSessionPage (BOFF-4318)\n *\n * Real join/host flow for an event's virtual/hybrid session, backed by\n * `wspace-eventfullymanaged-svc`'s bounded browser-native WebRTC core\n * (`VirtualSessionService`). MEETING mode is a full mesh (cap 8);\n * WEBINAR mode is a host/presenter-broadcast star (cap 30) — attendees\n * only peer with the host/presenters, never each other. See\n * `useVirtualCall` for the exact topology and signaling logic.\n *\n * Large-audience broadcast (hundreds+ viewers) needs a real SFU/media\n * server and is explicit deferred follow-up — not built here.\n */\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Mic, MicOff, PhoneOff, Radio, ScreenShare, Users, Video, VideoOff } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport {\n useEndVirtualSessionMutation,\n useEventQuery,\n useHostVirtualSessionMutation,\n useJoinVirtualSessionMutation,\n useLeaveVirtualSessionMutation,\n usePromoteVirtualPresenterMutation,\n useSendVirtualSignalMutation,\n useSetVirtualMuteMutation,\n useSetVirtualVideoMutation,\n useVirtualSessionQuery,\n VIRTUAL_PARTICIPANTS_CHANGED_SUBSCRIPTION_SDL,\n VIRTUAL_SIGNAL_SUBSCRIPTION_SDL,\n type VirtualParticipantGql,\n} from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { useEvmSubscription } from '../../hooks/useEvmSubscription';\nimport { type IncomingVirtualSignal, type VirtualCallRole, useVirtualCall } from '../../hooks/useVirtualCall';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nfunction initials(id: string): string {\n return id.slice(0, 2).toUpperCase();\n}\n\nfunction connectionDotClass(state: RTCPeerConnectionState | undefined): string {\n if (state === 'connected') return 'bg-status-success';\n if (state === 'connecting' || state === 'new') return 'bg-status-warning';\n if (state === 'failed' || state === 'disconnected' || state === 'closed') return 'bg-status-danger';\n return 'bg-text-muted';\n}\n\ninterface VideoTileProps {\n actorId: string;\n role: VirtualCallRole;\n isLocal: boolean;\n stream: MediaStream | null;\n /** Muted/videoEnabled reflect the persisted participant state (server-confirmed for remotes, local toggle for self). */\n muted: boolean;\n videoEnabled: boolean;\n connectionState?: RTCPeerConnectionState;\n}\n\nfunction VideoTile({ actorId, role, isLocal, stream, muted, videoEnabled, connectionState }: VideoTileProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n useEffect(() => {\n if (videoRef.current) videoRef.current.srcObject = stream;\n }, [stream]);\n\n const showVideo = Boolean(stream) && videoEnabled;\n\n return (\n <div className=\"relative aspect-video overflow-hidden rounded-card border border-border-seam bg-bg-sunken\">\n {showVideo ? (\n <video\n ref={videoRef}\n autoPlay\n playsInline\n muted={isLocal}\n className=\"h-full w-full object-cover\"\n style={isLocal ? { transform: 'scaleX(-1)' } : undefined}\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <span className=\"flex h-12 w-12 items-center justify-center rounded-full bg-accent-primary/10 text-sm font-semibold text-accent-primary\">\n {initials(actorId)}\n </span>\n </div>\n )}\n <div className=\"absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/60 to-transparent p-2\">\n <span className=\"truncate text-xs font-medium text-white\">\n {isLocal ? 'You' : `Participant ${actorId.slice(0, 6)}`}\n {role === 'HOST' ? ' · Host' : role === 'PRESENTER' ? ' · Presenter' : ''}\n </span>\n <span className=\"flex items-center gap-1.5\">\n {muted ? <MicOff className=\"h-3.5 w-3.5 text-white\" /> : null}\n {!isLocal ? (\n <span\n className={`h-2 w-2 rounded-full ${connectionDotClass(connectionState)}`}\n title={connectionState ?? 'connecting'}\n />\n ) : null}\n </span>\n </div>\n </div>\n );\n}\n\nexport default function VirtualSessionPage() {\n const { id: eventId = '' } = useParams<{ id: string }>();\n const { navigate, currentUser } = useEventfullyManaged();\n const selfActorId = currentUser?.id ?? '';\n\n const { data: eventData, loading: eventLoading } = useEventQuery(eventId);\n const sessionQuery = useVirtualSessionQuery({ eventId });\n const session = sessionQuery.data?.virtualSession ?? null;\n\n const [mode, setMode] = useState<'MEETING' | 'WEBINAR'>('MEETING');\n const [hostSession, { loading: hosting }] = useHostVirtualSessionMutation();\n const [joinSession, { loading: joining }] = useJoinVirtualSessionMutation();\n const [leaveSession, { loading: leaving }] = useLeaveVirtualSessionMutation();\n const [endSession, { loading: ending }] = useEndVirtualSessionMutation();\n const [setMuteMutation] = useSetVirtualMuteMutation();\n const [setVideoMutation] = useSetVirtualVideoMutation();\n const [promotePresenter] = usePromoteVirtualPresenterMutation();\n const [sendSignalMutation] = useSendVirtualSignalMutation();\n\n const selfParticipant = useMemo(\n () => session?.participants.find((p) => p.actorId === selfActorId && !p.leftAt) ?? null,\n [session, selfActorId]\n );\n const activeParticipants = useMemo(() => (session?.participants ?? []).filter((p) => !p.leftAt), [session]);\n\n const [localMuted, setLocalMuted] = useState(false);\n const [localVideoOff, setLocalVideoOff] = useState(false);\n useEffect(() => {\n if (selfParticipant) {\n setLocalMuted(selfParticipant.muted);\n setLocalVideoOff(!selfParticipant.videoEnabled);\n }\n }, [selfParticipant?.muted, selfParticipant?.videoEnabled]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const sendSignal = useCallback(\n async (args: { toActorId: string; signalType: 'OFFER' | 'ANSWER' | 'ICE_CANDIDATE'; payload: unknown }) => {\n if (!session) return;\n await sendSignalMutation({\n variables: {\n sessionId: session.id,\n toActorId: args.toActorId,\n signalType: args.signalType,\n payload: args.payload,\n },\n });\n },\n [session, sendSignalMutation]\n );\n\n const call = useVirtualCall({\n sessionId: selfParticipant ? session?.id : null,\n selfActorId,\n mode: session?.mode ?? 'MEETING',\n selfRole: selfParticipant?.role ?? null,\n participants: activeParticipants.map((p) => ({ actorId: p.actorId, role: p.role })),\n sendSignal,\n muted: localMuted,\n videoEnabled: !localVideoOff,\n });\n\n useEvmSubscription<{ eventfullyManagedVirtualSignal: IncomingVirtualSignal }>({\n query: VIRTUAL_SIGNAL_SUBSCRIPTION_SDL,\n variables: { sessionId: session?.id },\n operationName: 'EventfullymanagedVirtualSignal',\n enabled: Boolean(selfParticipant && session),\n onData: (data) => {\n const signal = data.eventfullyManagedVirtualSignal;\n if (signal) call.handleIncomingSignal(signal);\n },\n // No real polling substitute exists for signaling — a missed signal is\n // simply a missed negotiation step. The fallback here just retries the\n // subscription's own reconnect path via re-render, kept as a no-op.\n pollFn: () => {},\n pollIntervalMs: 15_000,\n });\n\n useEvmSubscription<{ eventfullyManagedVirtualParticipantsChanged: unknown }>({\n query: VIRTUAL_PARTICIPANTS_CHANGED_SUBSCRIPTION_SDL,\n variables: { sessionId: session?.id },\n operationName: 'EventfullymanagedVirtualParticipantsChanged',\n enabled: Boolean(session),\n onData: () => {\n void sessionQuery.refetch();\n },\n pollFn: () => {\n void sessionQuery.refetch();\n },\n pollIntervalMs: 5_000,\n });\n\n const onHost = async (chosenMode: 'MEETING' | 'WEBINAR') => {\n try {\n const response = await hostSession({ variables: { eventId, mode: chosenMode } });\n if (!mutationSucceeded(response, response.data?.hostVirtualSession?.id, 'Could not start session')) {\n return;\n }\n notify.success('Session started', chosenMode === 'WEBINAR' ? 'Hosting as a webinar.' : 'Hosting a meeting.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not start session', describeError(error));\n }\n };\n\n const onJoin = async () => {\n try {\n const response = await joinSession({ variables: { eventId } });\n if (!mutationSucceeded(response, response.data?.joinVirtualSession?.id, 'Could not join session')) {\n return;\n }\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not join session', describeError(error));\n }\n };\n\n const onLeave = async () => {\n if (!session) return;\n try {\n await leaveSession({ variables: { sessionId: session.id } });\n notify.success('Left session', 'You are no longer in the virtual session.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not leave session', describeError(error));\n }\n };\n\n const onEnd = async () => {\n if (!session) return;\n try {\n await endSession({ variables: { sessionId: session.id } });\n notify.success('Session ended', 'The virtual session has been ended for everyone.');\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not end session', describeError(error));\n }\n };\n\n const toggleMute = async () => {\n if (!session) return;\n const next = !localMuted;\n setLocalMuted(next);\n try {\n await setMuteMutation({ variables: { sessionId: session.id, muted: next } });\n } catch (error) {\n setLocalMuted(!next);\n notify.error('Could not update mute state', describeError(error));\n }\n };\n\n const toggleVideo = async () => {\n if (!session) return;\n const next = !localVideoOff;\n setLocalVideoOff(next);\n try {\n await setVideoMutation({ variables: { sessionId: session.id, videoEnabled: !next } });\n } catch (error) {\n setLocalVideoOff(!next);\n notify.error('Could not update camera state', describeError(error));\n }\n };\n\n const onPromote = async (participant: VirtualParticipantGql) => {\n if (!session) return;\n try {\n await promotePresenter({ variables: { sessionId: session.id, actorId: participant.actorId } });\n notify.success('Promoted to presenter', `Participant ${participant.actorId.slice(0, 6)} can now broadcast.`);\n await sessionQuery.refetch();\n } catch (error) {\n notify.error('Could not promote participant', describeError(error));\n }\n };\n\n const isHost = selfParticipant?.role === 'HOST';\n const busy = hosting || joining || leaving || ending;\n\n if (eventLoading || sessionQuery.loading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-8 w-64\" />\n <Skeleton className=\"h-64 w-full\" />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate(`/events/${eventId}`)}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Event\n </Button>\n\n <PagePurpose>\n A virtual session is a real, browser-native WebRTC room for this event — no external meeting link. Hosts start a\n Meeting (small mesh, up to 8 people all connected directly) or a Webinar (up to 30 attendees who see and hear\n only the host/presenters). Signaling is relayed through EventfullyManaged; media never touches our servers.\n </PagePurpose>\n\n <EmphasisPanel className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Virtual Session</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">{eventData?.event?.name ?? 'Event'}</p>\n </div>\n {session ? (\n <Badge variant={session.status === 'ACTIVE' ? 'success' : 'secondary'} className=\"uppercase\">\n {session.mode} · {session.status}\n </Badge>\n ) : null}\n </EmphasisPanel>\n\n {!session || session.status !== 'ACTIVE' ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No live virtual session\"\n description=\"Start a session to host attendees over WebRTC, or join once someone else has started one.\"\n className=\"py-6\"\n />\n <div className=\"mt-4 flex flex-wrap items-center justify-center gap-2\">\n <Select value={mode} onValueChange={(value) => setMode(value as 'MEETING' | 'WEBINAR')}>\n <SelectTrigger aria-label=\"Session mode\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"MEETING\">Meeting (up to 8)</SelectItem>\n <SelectItem value=\"WEBINAR\">Webinar (up to 30)</SelectItem>\n </SelectContent>\n </Select>\n <Button type=\"button\" onClick={() => onHost(mode)} disabled={busy}>\n <Radio className=\"h-4 w-4\" />\n {hosting ? 'Starting…' : 'Start session'}\n </Button>\n </div>\n </GlassCard>\n ) : !selfParticipant ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"A session is live\"\n description={`${session.participantCount} of ${session.maxParticipants} seats in use. Join to turn on your camera and mic.`}\n className=\"py-6\"\n />\n <div className=\"mt-4 flex justify-center\">\n <Button type=\"button\" onClick={onJoin} disabled={busy}>\n <Video className=\"h-4 w-4\" />\n {joining ? 'Joining…' : 'Join session'}\n </Button>\n </div>\n </GlassCard>\n ) : (\n <>\n {call.mediaError ? (\n <GlassCard\n treatment=\"plain\"\n className=\"border-status-danger/40 bg-status-danger/5 p-4 text-sm text-status-danger\"\n >\n Camera/microphone unavailable: {call.mediaError}. You are still connected to the session and can see other\n participants, but others will not see or hear you.\n </GlassCard>\n ) : null}\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <VideoTile\n actorId={selfActorId}\n role={selfParticipant.role}\n isLocal\n stream={call.localStream}\n muted={localMuted}\n videoEnabled={!localVideoOff}\n />\n {activeParticipants\n .filter((p) => p.actorId !== selfActorId)\n .map((participant) => (\n <VideoTile\n key={participant.actorId}\n actorId={participant.actorId}\n role={participant.role}\n isLocal={false}\n stream={call.remoteStreams.get(participant.actorId) ?? null}\n muted={participant.muted}\n videoEnabled={participant.videoEnabled}\n connectionState={call.connectionStates.get(participant.actorId)}\n />\n ))}\n </div>\n\n <GlassCard\n treatment=\"plain\"\n className=\"flex flex-wrap items-center justify-center gap-2 border-border-seam p-4\"\n >\n <Button type=\"button\" variant={localMuted ? 'outline' : 'secondary'} onClick={toggleMute}>\n {localMuted ? <MicOff className=\"h-4 w-4\" /> : <Mic className=\"h-4 w-4\" />}\n {localMuted ? 'Unmute' : 'Mute'}\n </Button>\n <Button type=\"button\" variant={localVideoOff ? 'outline' : 'secondary'} onClick={toggleVideo}>\n {localVideoOff ? <VideoOff className=\"h-4 w-4\" /> : <Video className=\"h-4 w-4\" />}\n {localVideoOff ? 'Start Video' : 'Stop Video'}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={onLeave} disabled={busy}>\n <PhoneOff className=\"h-4 w-4\" />\n {leaving ? 'Leaving…' : 'Leave'}\n </Button>\n {isHost ? (\n <Button type=\"button\" variant=\"destructive\" onClick={onEnd} disabled={busy}>\n <ScreenShare className=\"h-4 w-4\" />\n {ending ? 'Ending…' : 'End for everyone'}\n </Button>\n ) : null}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4\">\n <h2 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Users className=\"h-4 w-4 text-accent-primary\" />\n Participants ({activeParticipants.length}/{session.maxParticipants})\n </h2>\n <div className=\"mt-3 space-y-2\">\n {activeParticipants.map((participant) => (\n <div key={participant.id} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <span\n className={`h-2 w-2 rounded-full ${\n participant.actorId === selfActorId\n ? 'bg-status-success'\n : connectionDotClass(call.connectionStates.get(participant.actorId))\n }`}\n />\n {participant.actorId === selfActorId ? 'You' : `Participant ${participant.actorId.slice(0, 6)}`}\n <Badge variant=\"secondary\" className=\"uppercase\">\n {participant.role}\n </Badge>\n </span>\n {isHost && session.mode === 'WEBINAR' && participant.role === 'ATTENDEE' ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onPromote(participant)}>\n Promote to presenter\n </Button>\n ) : null}\n </div>\n ))}\n </div>\n </GlassCard>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { EntityFormShell as o } from "../../components/EntityFormShell.js";
|
|
|
5
5
|
import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
|
|
6
6
|
import { safeUrl as l } from "../../components/SafeLink.js";
|
|
7
7
|
import { useEffect as u, useMemo as d, useState as f } from "react";
|
|
8
|
-
import { useParams as p } from "react-router
|
|
8
|
+
import { useParams as p } from "react-router";
|
|
9
9
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
10
|
import { Input as g, Select as _, SelectContent as v, SelectItem as y, SelectTrigger as b, SelectValue as x } from "@burdenoff/fe-libs/ui";
|
|
11
11
|
//#region src/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExhibitorFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useCreateExhibitorMutation,\n useEventsQuery,\n useExhibitorsQuery,\n useUpdateExhibitorMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ExhibitorFormState = {\n eventId: string;\n name: string;\n boothCode: string;\n category: string;\n status: string;\n website: string;\n contactName: string;\n contactEmail: string;\n};\n\nconst initialState: ExhibitorFormState = {\n eventId: '',\n name: '',\n boothCode: '',\n category: '',\n status: 'CONFIRMED',\n website: '',\n contactName: '',\n contactEmail: '',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'INVITED', label: 'Invited' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nexport default function ExhibitorFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver exists for exhibitors, so the edit initial-state is derived\n // from the list query row.\n const { data: exhibitorsData } = useExhibitorsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (exhibitorsData?.exhibitors?.items ?? []).find((item) => item.id === id),\n [exhibitorsData, id]\n );\n\n const [createExhibitor, { loading: creating }] = useCreateExhibitorMutation();\n const [updateExhibitor, { loading: updating }] = useUpdateExhibitorMutation();\n const [form, setForm] = useState<ExhibitorFormState>(initialState);\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const website = typeof metadata.website === 'string' ? metadata.website : '';\n setForm({\n eventId: existing.eventId ?? '',\n name: existing.name ?? '',\n boothCode: existing.boothCode ?? '',\n category: existing.category ?? '',\n status: existing.status ?? 'CONFIRMED',\n website,\n contactName: existing.contactName ?? '',\n contactEmail: existing.contactEmail ?? '',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ExhibitorFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this exhibitor belongs to.');\n return;\n }\n\n // The website is free text persisted verbatim, so normalise/reject it before\n // it is stored and later rendered as a link.\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n name: form.name,\n boothCode: form.boothCode,\n category: form.category,\n status: form.status,\n contactName: form.contactName,\n contactEmail: form.contactEmail,\n metadata: { ...existingMetadata, website: normalizedWebsite ?? '' },\n };\n\n try {\n if (isEdit) {\n const response = await updateExhibitor({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateExhibitor?.id, 'Could not update exhibitor')) {\n return;\n }\n navigate('/exhibitors');\n return;\n }\n\n const response = await createExhibitor({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createExhibitor?.id, 'Could not create exhibitor')) {\n return;\n }\n navigate('/exhibitors');\n } catch (error) {\n notify.error('Could not save exhibitor', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Exhibitor' : 'New Exhibitor'}\n description=\"Manage a booth exhibitor with live backend persistence.\"\n purpose=\"Register a company staffing a booth: link it to the event, give it a booth code and category, and record who to contact. Once saved, capture the leads their booth scans from the exhibitor list.\"\n backLabel=\"Back to Exhibitors\"\n onBack={() => navigate('/exhibitors')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Exhibitor' : 'Create Exhibitor'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Name</span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booth Code</span>\n <Input value={form.boothCode} onChange={(e) => onChange('boothCode', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Category</span>\n <Input value={form.category} onChange={(e) => onChange('category', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website</span>\n <Input\n value={form.website}\n onChange={(e) => onChange('website', e.target.value)}\n placeholder=\"https://example.com\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Name</span>\n <Input value={form.contactName} onChange={(e) => onChange('contactName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Email</span>\n <Input type=\"email\" value={form.contactEmail} onChange={(e) => onChange('contactEmail', e.target.value)} />\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExhibitorFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport {\n useCreateExhibitorMutation,\n useEventsQuery,\n useExhibitorsQuery,\n useUpdateExhibitorMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ExhibitorFormState = {\n eventId: string;\n name: string;\n boothCode: string;\n category: string;\n status: string;\n website: string;\n contactName: string;\n contactEmail: string;\n};\n\nconst initialState: ExhibitorFormState = {\n eventId: '',\n name: '',\n boothCode: '',\n category: '',\n status: 'CONFIRMED',\n website: '',\n contactName: '',\n contactEmail: '',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'INVITED', label: 'Invited' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nexport default function ExhibitorFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver exists for exhibitors, so the edit initial-state is derived\n // from the list query row.\n const { data: exhibitorsData } = useExhibitorsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (exhibitorsData?.exhibitors?.items ?? []).find((item) => item.id === id),\n [exhibitorsData, id]\n );\n\n const [createExhibitor, { loading: creating }] = useCreateExhibitorMutation();\n const [updateExhibitor, { loading: updating }] = useUpdateExhibitorMutation();\n const [form, setForm] = useState<ExhibitorFormState>(initialState);\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const website = typeof metadata.website === 'string' ? metadata.website : '';\n setForm({\n eventId: existing.eventId ?? '',\n name: existing.name ?? '',\n boothCode: existing.boothCode ?? '',\n category: existing.category ?? '',\n status: existing.status ?? 'CONFIRMED',\n website,\n contactName: existing.contactName ?? '',\n contactEmail: existing.contactEmail ?? '',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ExhibitorFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this exhibitor belongs to.');\n return;\n }\n\n // The website is free text persisted verbatim, so normalise/reject it before\n // it is stored and later rendered as a link.\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n name: form.name,\n boothCode: form.boothCode,\n category: form.category,\n status: form.status,\n contactName: form.contactName,\n contactEmail: form.contactEmail,\n metadata: { ...existingMetadata, website: normalizedWebsite ?? '' },\n };\n\n try {\n if (isEdit) {\n const response = await updateExhibitor({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateExhibitor?.id, 'Could not update exhibitor')) {\n return;\n }\n navigate('/exhibitors');\n return;\n }\n\n const response = await createExhibitor({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createExhibitor?.id, 'Could not create exhibitor')) {\n return;\n }\n navigate('/exhibitors');\n } catch (error) {\n notify.error('Could not save exhibitor', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Exhibitor' : 'New Exhibitor'}\n description=\"Manage a booth exhibitor with live backend persistence.\"\n purpose=\"Register a company staffing a booth: link it to the event, give it a booth code and category, and record who to contact. Once saved, capture the leads their booth scans from the exhibitor list.\"\n backLabel=\"Back to Exhibitors\"\n onBack={() => navigate('/exhibitors')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Exhibitor' : 'Create Exhibitor'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Name</span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booth Code</span>\n <Input value={form.boothCode} onChange={(e) => onChange('boothCode', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Category</span>\n <Input value={form.category} onChange={(e) => onChange('category', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website</span>\n <Input\n value={form.website}\n onChange={(e) => onChange('website', e.target.value)}\n placeholder=\"https://example.com\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Name</span>\n <Input value={form.contactName} onChange={(e) => onChange('contactName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Email</span>\n <Input type=\"email\" value={form.contactEmail} onChange={(e) => onChange('contactEmail', e.target.value)} />\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { useEventfullyManaged as e } from "../../context/EventfullyManagedContex
|
|
|
2
2
|
import { useAttendeesQuery as t, useRegistrationsQuery as n } from "../../api/eventApi.js";
|
|
3
3
|
import { EntityListSection as r } from "./EntityListSection.js";
|
|
4
4
|
import { useMemo as i, useRef as a } from "react";
|
|
5
|
-
import { useParams as o } from "react-router
|
|
5
|
+
import { useParams as o } from "react-router";
|
|
6
6
|
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
7
7
|
import { MessageSquare as l, Ticket as u, Users as d } from "lucide-react";
|
|
8
8
|
import { EmphasisPanel as f, GlassCard as p, GradientText as m, NextSteps as h, PagePurpose as g } from "@burdenoff/fe-libs/ui";
|
|
@@ -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-dom';\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 { 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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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationsPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/modules/IntegrationsPage.tsx"],"names":[],"mappings":"AAqQA,MAAM,CAAC,OAAO,UAAU,gBAAgB,4CA8GvC"}
|