@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,336 @@
|
|
|
1
|
+
import { notify as e } from "../../lib/toast.js";
|
|
2
|
+
import { useCreateIntegrationConnectorMutation as t, useDeleteIntegrationConnectorMutation as n, useEventsQuery as r, useIntegrationConnectorsQuery as i, useIntegrationSyncRunsQuery as a, useSyncAttendeesToConnectorMutation as o } from "../../api/eventApi.js";
|
|
3
|
+
import { ActionMenu as s } from "../../components/ActionMenu.js";
|
|
4
|
+
import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
|
|
5
|
+
import { useState as u } from "react";
|
|
6
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
|
+
import { CheckCircle2 as p, Plug as m, Plus as h, RefreshCw as g, XCircle as _ } from "lucide-react";
|
|
8
|
+
import { Badge as v, Button as y, EmphasisPanel as b, GlassCard as x, GradientText as S, IllustratedEmptyState as C, Input as w, Label as T, PagePurpose as E, ResponsiveDialog as D, ResponsiveDialogContent as O, ResponsiveDialogDescription as k, ResponsiveDialogFooter as A, ResponsiveDialogHeader as j, ResponsiveDialogTitle as M, Select as N, SelectContent as P, SelectItem as F, SelectTrigger as I, SelectValue as L } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/eventfullymanaged/pages/modules/IntegrationsPage.tsx
|
|
10
|
+
var R = [{
|
|
11
|
+
value: "HUBSPOT",
|
|
12
|
+
label: "HubSpot"
|
|
13
|
+
}];
|
|
14
|
+
function z(e) {
|
|
15
|
+
return e === "CONNECTED" ? {
|
|
16
|
+
variant: "success",
|
|
17
|
+
icon: p
|
|
18
|
+
} : e === "ERROR" ? {
|
|
19
|
+
variant: "warning",
|
|
20
|
+
icon: _
|
|
21
|
+
} : {
|
|
22
|
+
variant: "secondary",
|
|
23
|
+
icon: m
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function B({ open: n, onOpenChange: r }) {
|
|
27
|
+
let [i, a] = u(""), [o, s] = u("HUBSPOT"), [p, m] = u(""), [h, { loading: g }] = t();
|
|
28
|
+
return /* @__PURE__ */ d(D, {
|
|
29
|
+
open: n,
|
|
30
|
+
onOpenChange: r,
|
|
31
|
+
children: /* @__PURE__ */ f(O, {
|
|
32
|
+
className: "max-w-lg",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ f(j, { children: [/* @__PURE__ */ d(M, { children: "New integration connector" }), /* @__PURE__ */ d(k, { children: "Connects to your CRM/marketing-automation account with a private-app access token. The token is encrypted at rest and never shown again after saving." })] }),
|
|
35
|
+
/* @__PURE__ */ f("form", {
|
|
36
|
+
id: "connector-form",
|
|
37
|
+
onSubmit: async (t) => {
|
|
38
|
+
if (t.preventDefault(), !i.trim() || !p.trim()) {
|
|
39
|
+
e.error("Missing details", "Give the connector a name and paste the private-app access token.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
let t = await h({ variables: { input: {
|
|
44
|
+
provider: o,
|
|
45
|
+
name: i.trim(),
|
|
46
|
+
accessToken: p.trim()
|
|
47
|
+
} } });
|
|
48
|
+
if (!l(t, t.data?.createIntegrationConnector?.id, "Could not create connector")) return;
|
|
49
|
+
let n = t.data?.createIntegrationConnector;
|
|
50
|
+
n?.status === "CONNECTED" ? e.success("Connector connected", `${n.name} verified live against ${o}.`) : e.info("Connector created", `${n?.name ?? "Connector"} was saved but could not verify — check the token.`), a(""), m(""), r(!1);
|
|
51
|
+
} catch (t) {
|
|
52
|
+
e.error("Could not create connector", c(t));
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
className: "space-y-4",
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ f("label", {
|
|
58
|
+
className: "space-y-1 text-sm",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ d("span", {
|
|
61
|
+
className: "font-medium text-text-primary",
|
|
62
|
+
children: "Provider"
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ f(N, {
|
|
65
|
+
value: o,
|
|
66
|
+
onValueChange: s,
|
|
67
|
+
children: [/* @__PURE__ */ d(I, {
|
|
68
|
+
"aria-label": "Provider",
|
|
69
|
+
children: /* @__PURE__ */ d(L, {})
|
|
70
|
+
}), /* @__PURE__ */ d(P, { children: R.map((e) => /* @__PURE__ */ d(F, {
|
|
71
|
+
value: e.value,
|
|
72
|
+
children: e.label
|
|
73
|
+
}, e.value)) })]
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ d("p", {
|
|
76
|
+
className: "text-xs text-text-secondary",
|
|
77
|
+
children: "HubSpot is the only live connector today — more providers can be added without a schema change."
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ f("label", {
|
|
82
|
+
className: "space-y-1 text-sm",
|
|
83
|
+
children: [/* @__PURE__ */ d("span", {
|
|
84
|
+
className: "font-medium text-text-primary",
|
|
85
|
+
children: "Connector name"
|
|
86
|
+
}), /* @__PURE__ */ d(w, {
|
|
87
|
+
value: i,
|
|
88
|
+
onChange: (e) => a(e.target.value),
|
|
89
|
+
placeholder: "Marketing HubSpot",
|
|
90
|
+
required: !0
|
|
91
|
+
})]
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ f("label", {
|
|
94
|
+
className: "space-y-1 text-sm",
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ d("span", {
|
|
97
|
+
className: "font-medium text-text-primary",
|
|
98
|
+
children: "Private-app access token"
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ d(w, {
|
|
101
|
+
type: "password",
|
|
102
|
+
value: p,
|
|
103
|
+
onChange: (e) => m(e.target.value),
|
|
104
|
+
placeholder: "pat-na1-…",
|
|
105
|
+
autoComplete: "off",
|
|
106
|
+
required: !0
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ f("p", {
|
|
109
|
+
className: "text-xs text-text-secondary",
|
|
110
|
+
children: [
|
|
111
|
+
"From your HubSpot developer sandbox: Settings → Integrations → Private Apps. Needs the",
|
|
112
|
+
" ",
|
|
113
|
+
/* @__PURE__ */ d("code", { children: "crm.objects.contacts.read/write" }),
|
|
114
|
+
" scopes."
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
})
|
|
119
|
+
]
|
|
120
|
+
}),
|
|
121
|
+
/* @__PURE__ */ f(A, { children: [/* @__PURE__ */ d(y, {
|
|
122
|
+
type: "button",
|
|
123
|
+
variant: "outline",
|
|
124
|
+
onClick: () => r(!1),
|
|
125
|
+
disabled: g,
|
|
126
|
+
children: "Cancel"
|
|
127
|
+
}), /* @__PURE__ */ d(y, {
|
|
128
|
+
type: "submit",
|
|
129
|
+
form: "connector-form",
|
|
130
|
+
disabled: g,
|
|
131
|
+
children: g ? "Connecting…" : "Create & test connection"
|
|
132
|
+
})] })
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function V({ connector: t, onDelete: n, onSync: r, eventOptions: i }) {
|
|
138
|
+
let [o, c] = u(""), [l, p] = u(!1), { variant: m, icon: h } = z(t.status), _ = a({
|
|
139
|
+
connectorId: t.id,
|
|
140
|
+
limit: 5,
|
|
141
|
+
offset: 0
|
|
142
|
+
}), b = _.data?.integrationSyncRuns ?? [];
|
|
143
|
+
return /* @__PURE__ */ f(x, {
|
|
144
|
+
treatment: "plain",
|
|
145
|
+
className: "border-border-seam p-4",
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ f("div", {
|
|
148
|
+
className: "flex items-start justify-between gap-2",
|
|
149
|
+
children: [/* @__PURE__ */ f("div", {
|
|
150
|
+
className: "min-w-0",
|
|
151
|
+
children: [
|
|
152
|
+
/* @__PURE__ */ f("div", {
|
|
153
|
+
className: "flex items-center gap-2",
|
|
154
|
+
children: [/* @__PURE__ */ d("p", {
|
|
155
|
+
className: "truncate font-medium text-text-primary",
|
|
156
|
+
children: t.name
|
|
157
|
+
}), /* @__PURE__ */ d(v, {
|
|
158
|
+
variant: "secondary",
|
|
159
|
+
className: "uppercase",
|
|
160
|
+
children: t.provider
|
|
161
|
+
})]
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ f("p", {
|
|
164
|
+
className: "mt-1 flex items-center gap-1.5 text-xs text-text-secondary",
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ d(h, { className: "h-3.5 w-3.5" }),
|
|
167
|
+
/* @__PURE__ */ d(v, {
|
|
168
|
+
variant: m,
|
|
169
|
+
className: "uppercase",
|
|
170
|
+
children: t.status
|
|
171
|
+
}),
|
|
172
|
+
t.externalAccountName ? /* @__PURE__ */ f("span", { children: ["· ", t.externalAccountName] }) : null
|
|
173
|
+
]
|
|
174
|
+
}),
|
|
175
|
+
t.lastError ? /* @__PURE__ */ d("p", {
|
|
176
|
+
className: "mt-1 text-xs text-status-danger",
|
|
177
|
+
children: t.lastError
|
|
178
|
+
}) : null
|
|
179
|
+
]
|
|
180
|
+
}), /* @__PURE__ */ d(s, {
|
|
181
|
+
ariaLabel: `${t.name} connector actions`,
|
|
182
|
+
entityName: t.name,
|
|
183
|
+
onDelete: n
|
|
184
|
+
})]
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ f("div", {
|
|
187
|
+
className: "mt-3 flex flex-wrap items-center gap-2",
|
|
188
|
+
children: [/* @__PURE__ */ f(N, {
|
|
189
|
+
value: o,
|
|
190
|
+
onValueChange: c,
|
|
191
|
+
children: [/* @__PURE__ */ d(I, {
|
|
192
|
+
"aria-label": "Event to sync",
|
|
193
|
+
className: "w-56",
|
|
194
|
+
children: /* @__PURE__ */ d(L, { placeholder: "Select an event" })
|
|
195
|
+
}), /* @__PURE__ */ d(P, { children: i.map((e) => /* @__PURE__ */ d(F, {
|
|
196
|
+
value: e.id,
|
|
197
|
+
children: e.name
|
|
198
|
+
}, e.id)) })]
|
|
199
|
+
}), /* @__PURE__ */ f(y, {
|
|
200
|
+
type: "button",
|
|
201
|
+
size: "sm",
|
|
202
|
+
onClick: async () => {
|
|
203
|
+
if (!o) {
|
|
204
|
+
e.error("Pick an event", "Choose which event to sync attendees from.");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
p(!0);
|
|
208
|
+
try {
|
|
209
|
+
await r(o), await _.refetch();
|
|
210
|
+
} finally {
|
|
211
|
+
p(!1);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
disabled: l || t.status !== "CONNECTED",
|
|
215
|
+
children: [/* @__PURE__ */ d(g, { className: "h-4 w-4" }), l ? "Syncing…" : "Sync attendees"]
|
|
216
|
+
})]
|
|
217
|
+
}),
|
|
218
|
+
b.length > 0 ? /* @__PURE__ */ d("div", {
|
|
219
|
+
className: "mt-3 space-y-1.5 border-t border-border-seam pt-3",
|
|
220
|
+
children: b.map((e) => /* @__PURE__ */ f("div", {
|
|
221
|
+
className: "flex items-center justify-between text-xs text-text-secondary",
|
|
222
|
+
children: [/* @__PURE__ */ d("span", { children: new Date(e.startedAt).toLocaleString() }), /* @__PURE__ */ f("span", {
|
|
223
|
+
className: "flex items-center gap-2",
|
|
224
|
+
children: [/* @__PURE__ */ d(v, {
|
|
225
|
+
variant: e.status === "SUCCESS" ? "success" : e.status === "FAILED" ? "warning" : "info",
|
|
226
|
+
className: "uppercase",
|
|
227
|
+
children: e.status
|
|
228
|
+
}), /* @__PURE__ */ f("span", { children: [
|
|
229
|
+
e.recordsSynced,
|
|
230
|
+
" synced",
|
|
231
|
+
e.recordsFailed > 0 ? `, ${e.recordsFailed} failed` : ""
|
|
232
|
+
] })]
|
|
233
|
+
})]
|
|
234
|
+
}, e.id))
|
|
235
|
+
}) : null
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function H() {
|
|
240
|
+
let [t, a] = u(!1), s = i({
|
|
241
|
+
limit: 50,
|
|
242
|
+
offset: 0
|
|
243
|
+
}), { data: p } = r({
|
|
244
|
+
limit: 100,
|
|
245
|
+
offset: 0
|
|
246
|
+
}), [m] = n(), [g] = o(), _ = s.data?.integrationConnectors ?? [], v = p?.events?.items ?? [], w = async (t) => {
|
|
247
|
+
try {
|
|
248
|
+
let n = await m({ variables: { id: t.id } });
|
|
249
|
+
if (!l(n, n.data?.deleteIntegrationConnector ? t.id : void 0, "Could not remove connector")) return;
|
|
250
|
+
e.success("Connector removed", `${t.name} was disconnected.`);
|
|
251
|
+
} catch (t) {
|
|
252
|
+
e.error("Could not remove connector", c(t));
|
|
253
|
+
}
|
|
254
|
+
}, D = async (t, n) => {
|
|
255
|
+
try {
|
|
256
|
+
let r = await g({ variables: {
|
|
257
|
+
connectorId: t.id,
|
|
258
|
+
eventId: n
|
|
259
|
+
} }), i = r.data?.syncAttendeesToConnector;
|
|
260
|
+
if (!i) {
|
|
261
|
+
e.error("Sync failed", r.error?.message ?? "Please try again.");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
i.status === "SUCCESS" ? e.success("Sync complete", `${i.recordsSynced} attendee${i.recordsSynced === 1 ? "" : "s"} synced to ${t.name}.`) : e.error("Sync failed", i.errorMessage ?? "The provider rejected the sync.");
|
|
265
|
+
} catch (t) {
|
|
266
|
+
e.error("Could not sync attendees", c(t));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
return /* @__PURE__ */ f("div", {
|
|
270
|
+
className: "space-y-6 p-6",
|
|
271
|
+
children: [
|
|
272
|
+
/* @__PURE__ */ d(E, { children: "Integration connectors sync this workspace's real attendee and lead data to your CRM or marketing-automation platform. Register a connector with a private-app access token, verify the live connection, then sync any event's attendees as CRM contacts on demand." }),
|
|
273
|
+
/* @__PURE__ */ f(b, {
|
|
274
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
275
|
+
children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("h1", {
|
|
276
|
+
className: "text-2xl font-bold text-text-primary",
|
|
277
|
+
children: /* @__PURE__ */ d(S, { children: "Integrations" })
|
|
278
|
+
}), /* @__PURE__ */ d("p", {
|
|
279
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
280
|
+
children: "CRM and marketing-automation connectors for this workspace."
|
|
281
|
+
})] }), /* @__PURE__ */ f(y, {
|
|
282
|
+
type: "button",
|
|
283
|
+
onClick: () => a(!0),
|
|
284
|
+
children: [/* @__PURE__ */ d(h, { className: "h-4 w-4" }), "New connector"]
|
|
285
|
+
})]
|
|
286
|
+
}),
|
|
287
|
+
s.loading ? null : _.length === 0 ? /* @__PURE__ */ d(x, {
|
|
288
|
+
treatment: "plain",
|
|
289
|
+
className: "border-border-seam p-8",
|
|
290
|
+
children: /* @__PURE__ */ d(C, {
|
|
291
|
+
illustration: "empty-data",
|
|
292
|
+
title: "No connectors yet",
|
|
293
|
+
description: "Connect a CRM or marketing-automation account to sync attendees automatically.",
|
|
294
|
+
className: "py-6"
|
|
295
|
+
})
|
|
296
|
+
}) : /* @__PURE__ */ d("div", {
|
|
297
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
298
|
+
children: _.map((e) => /* @__PURE__ */ d(V, {
|
|
299
|
+
connector: e,
|
|
300
|
+
eventOptions: v,
|
|
301
|
+
onDelete: () => w(e),
|
|
302
|
+
onSync: (t) => D(e, t)
|
|
303
|
+
}, e.id))
|
|
304
|
+
}),
|
|
305
|
+
/* @__PURE__ */ d(B, {
|
|
306
|
+
open: t,
|
|
307
|
+
onOpenChange: a
|
|
308
|
+
}),
|
|
309
|
+
/* @__PURE__ */ f(x, {
|
|
310
|
+
treatment: "plain",
|
|
311
|
+
className: "border-border-seam p-4 text-xs text-text-secondary",
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ d(T, {
|
|
314
|
+
className: "text-text-primary",
|
|
315
|
+
children: "Connector framework"
|
|
316
|
+
}),
|
|
317
|
+
/* @__PURE__ */ d("p", {
|
|
318
|
+
className: "mt-1",
|
|
319
|
+
children: "Only HubSpot has a real working client today. Adding a second provider (Salesforce, Mailchimp, …) is a backend-only change — no changes needed here once it ships."
|
|
320
|
+
}),
|
|
321
|
+
/* @__PURE__ */ f("p", {
|
|
322
|
+
className: "mt-2",
|
|
323
|
+
children: [/* @__PURE__ */ d("span", {
|
|
324
|
+
className: "font-medium text-text-primary",
|
|
325
|
+
children: "Not built yet:"
|
|
326
|
+
}), " outbound webhooks and calendar sync are still roadmap items, not shipped — this page only covers CRM/marketing-automation connectors."]
|
|
327
|
+
})
|
|
328
|
+
]
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
export { H as default };
|
|
335
|
+
|
|
336
|
+
//# sourceMappingURL=IntegrationsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegrationsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/IntegrationsPage.tsx"],"sourcesContent":["/**\n * IntegrationsPage (BOFF-4324)\n *\n * Real CRM/marketing-automation integration catalog: register a connector\n * (currently HubSpot — the only provider with a real working client, see\n * `IntegrationService` in `wspace-eventfullymanaged-svc`), see its live\n * connection status, and sync an event's attendees + leads to it as CRM\n * contacts. Sync runs are recorded and shown with real record counts.\n *\n * Replaces the prior `RoadmapPanel` placeholder for this route now that the\n * connector framework is real and shipped.\n */\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n Label,\n PagePurpose,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Plug, Plus, RefreshCw, XCircle } from 'lucide-react';\nimport { useState } from 'react';\n\nimport {\n useCreateIntegrationConnectorMutation,\n useDeleteIntegrationConnectorMutation,\n useEventsQuery,\n useIntegrationConnectorsQuery,\n useIntegrationSyncRunsQuery,\n useSyncAttendeesToConnectorMutation,\n type IntegrationConnectorGql,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst PROVIDERS = [{ value: 'HUBSPOT', label: 'HubSpot' }];\n\nfunction statusMeta(status: IntegrationConnectorGql['status']): {\n variant: 'success' | 'warning' | 'secondary' | 'info';\n icon: typeof CheckCircle2;\n} {\n if (status === 'CONNECTED') return { variant: 'success', icon: CheckCircle2 };\n if (status === 'ERROR') return { variant: 'warning', icon: XCircle };\n return { variant: 'secondary', icon: Plug };\n}\n\ninterface CreateConnectorDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nfunction CreateConnectorDialog({ open, onOpenChange }: CreateConnectorDialogProps) {\n const [name, setName] = useState('');\n const [provider, setProvider] = useState('HUBSPOT');\n const [accessToken, setAccessToken] = useState('');\n const [createConnector, { loading: saving }] = useCreateIntegrationConnectorMutation();\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!name.trim() || !accessToken.trim()) {\n notify.error('Missing details', 'Give the connector a name and paste the private-app access token.');\n return;\n }\n try {\n const response = await createConnector({\n variables: { input: { provider, name: name.trim(), accessToken: accessToken.trim() } },\n });\n if (!mutationSucceeded(response, response.data?.createIntegrationConnector?.id, 'Could not create connector')) {\n return;\n }\n const created = response.data?.createIntegrationConnector;\n if (created?.status === 'CONNECTED') {\n notify.success('Connector connected', `${created.name} verified live against ${provider}.`);\n } else {\n notify.info(\n 'Connector created',\n `${created?.name ?? 'Connector'} was saved but could not verify — check the token.`\n );\n }\n setName('');\n setAccessToken('');\n onOpenChange(false);\n } catch (error) {\n notify.error('Could not create connector', describeError(error));\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New integration connector</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Connects to your CRM/marketing-automation account with a private-app access token. The token is encrypted at\n rest and never shown again after saving.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form id=\"connector-form\" onSubmit={onSubmit} className=\"space-y-4\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Provider</span>\n <Select value={provider} onValueChange={setProvider}>\n <SelectTrigger aria-label=\"Provider\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {PROVIDERS.map((p) => (\n <SelectItem key={p.value} value={p.value}>\n {p.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-secondary\">\n HubSpot is the only live connector today — more providers can be added without a schema change.\n </p>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Connector name</span>\n <Input value={name} onChange={(e) => setName(e.target.value)} placeholder=\"Marketing HubSpot\" required />\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Private-app access token</span>\n <Input\n type=\"password\"\n value={accessToken}\n onChange={(e) => setAccessToken(e.target.value)}\n placeholder=\"pat-na1-…\"\n autoComplete=\"off\"\n required\n />\n <p className=\"text-xs text-text-secondary\">\n From your HubSpot developer sandbox: Settings → Integrations → Private Apps. Needs the{' '}\n <code>crm.objects.contacts.read/write</code> scopes.\n </p>\n </label>\n </form>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" form=\"connector-form\" disabled={saving}>\n {saving ? 'Connecting…' : 'Create & test connection'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\ninterface ConnectorCardProps {\n connector: IntegrationConnectorGql;\n onDelete: () => void;\n onSync: (eventId: string) => Promise<void>;\n eventOptions: Array<{ id: string; name: string }>;\n}\n\nfunction ConnectorCard({ connector, onDelete, onSync, eventOptions }: ConnectorCardProps) {\n const [syncEventId, setSyncEventId] = useState('');\n const [syncing, setSyncing] = useState(false);\n const { variant, icon: StatusIcon } = statusMeta(connector.status);\n const runsQuery = useIntegrationSyncRunsQuery({ connectorId: connector.id, limit: 5, offset: 0 });\n const runs = runsQuery.data?.integrationSyncRuns ?? [];\n\n const onSyncClick = async () => {\n if (!syncEventId) {\n notify.error('Pick an event', 'Choose which event to sync attendees from.');\n return;\n }\n setSyncing(true);\n try {\n await onSync(syncEventId);\n await runsQuery.refetch();\n } finally {\n setSyncing(false);\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className=\"truncate font-medium text-text-primary\">{connector.name}</p>\n <Badge variant=\"secondary\" className=\"uppercase\">\n {connector.provider}\n </Badge>\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-text-secondary\">\n <StatusIcon className=\"h-3.5 w-3.5\" />\n <Badge variant={variant} className=\"uppercase\">\n {connector.status}\n </Badge>\n {connector.externalAccountName ? <span>· {connector.externalAccountName}</span> : null}\n </p>\n {connector.lastError ? <p className=\"mt-1 text-xs text-status-danger\">{connector.lastError}</p> : null}\n </div>\n <ActionMenu ariaLabel={`${connector.name} connector actions`} entityName={connector.name} onDelete={onDelete} />\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Select value={syncEventId} onValueChange={setSyncEventId}>\n <SelectTrigger aria-label=\"Event to sync\" className=\"w-56\">\n <SelectValue placeholder=\"Select an event\" />\n </SelectTrigger>\n <SelectContent>\n {eventOptions.map((event) => (\n <SelectItem key={event.id} value={event.id}>\n {event.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Button type=\"button\" size=\"sm\" onClick={onSyncClick} disabled={syncing || connector.status !== 'CONNECTED'}>\n <RefreshCw className=\"h-4 w-4\" />\n {syncing ? 'Syncing…' : 'Sync attendees'}\n </Button>\n </div>\n\n {runs.length > 0 ? (\n <div className=\"mt-3 space-y-1.5 border-t border-border-seam pt-3\">\n {runs.map((run) => (\n <div key={run.id} className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>{new Date(run.startedAt).toLocaleString()}</span>\n <span className=\"flex items-center gap-2\">\n <Badge\n variant={run.status === 'SUCCESS' ? 'success' : run.status === 'FAILED' ? 'warning' : 'info'}\n className=\"uppercase\"\n >\n {run.status}\n </Badge>\n <span>\n {run.recordsSynced} synced{run.recordsFailed > 0 ? `, ${run.recordsFailed} failed` : ''}\n </span>\n </span>\n </div>\n ))}\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport default function IntegrationsPage() {\n const [createOpen, setCreateOpen] = useState(false);\n const connectorsQuery = useIntegrationConnectorsQuery({ limit: 50, offset: 0 });\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n const [deleteConnector] = useDeleteIntegrationConnectorMutation();\n const [syncAttendees] = useSyncAttendeesToConnectorMutation();\n\n const connectors = connectorsQuery.data?.integrationConnectors ?? [];\n const eventOptions = eventsData?.events?.items ?? [];\n\n const onDelete = async (connector: IntegrationConnectorGql) => {\n try {\n const response = await deleteConnector({ variables: { id: connector.id } });\n if (\n !mutationSucceeded(\n response,\n response.data?.deleteIntegrationConnector ? connector.id : undefined,\n 'Could not remove connector'\n )\n ) {\n return;\n }\n notify.success('Connector removed', `${connector.name} was disconnected.`);\n } catch (error) {\n notify.error('Could not remove connector', describeError(error));\n }\n };\n\n const onSync = async (connector: IntegrationConnectorGql, eventId: string) => {\n try {\n const response = await syncAttendees({ variables: { connectorId: connector.id, eventId } });\n const run = response.data?.syncAttendeesToConnector;\n if (!run) {\n notify.error('Sync failed', response.error?.message ?? 'Please try again.');\n return;\n }\n if (run.status === 'SUCCESS') {\n notify.success(\n 'Sync complete',\n `${run.recordsSynced} attendee${run.recordsSynced === 1 ? '' : 's'} synced to ${connector.name}.`\n );\n } else {\n notify.error('Sync failed', run.errorMessage ?? 'The provider rejected the sync.');\n }\n } catch (error) {\n notify.error('Could not sync attendees', describeError(error));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Integration connectors sync this workspace's real attendee and lead data to your CRM or\n marketing-automation platform. Register a connector with a private-app access token, verify the live connection,\n then sync any event's attendees as CRM contacts on demand.\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>Integrations</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n CRM and marketing-automation connectors for this workspace.\n </p>\n </div>\n <Button type=\"button\" onClick={() => setCreateOpen(true)}>\n <Plus className=\"h-4 w-4\" />\n New connector\n </Button>\n </EmphasisPanel>\n\n {connectorsQuery.loading ? null : connectors.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No connectors yet\"\n description=\"Connect a CRM or marketing-automation account to sync attendees automatically.\"\n className=\"py-6\"\n />\n </GlassCard>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {connectors.map((connector) => (\n <ConnectorCard\n key={connector.id}\n connector={connector}\n eventOptions={eventOptions}\n onDelete={() => onDelete(connector)}\n onSync={(eventId) => onSync(connector, eventId)}\n />\n ))}\n </div>\n )}\n\n <CreateConnectorDialog open={createOpen} onOpenChange={setCreateOpen} />\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4 text-xs text-text-secondary\">\n <Label className=\"text-text-primary\">Connector framework</Label>\n <p className=\"mt-1\">\n Only HubSpot has a real working client today. Adding a second provider (Salesforce, Mailchimp, …) is a\n backend-only change — no changes needed here once it ships.\n </p>\n <p className=\"mt-2\">\n <span className=\"font-medium text-text-primary\">Not built yet:</span> outbound webhooks and calendar sync are\n still roadmap items, not shipped — this page only covers CRM/marketing-automation connectors.\n </p>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { EntityTagsField as r } from "../../components/EntityTagsField.js";
|
|
|
4
4
|
import { SafeLink as i } from "../../components/SafeLink.js";
|
|
5
5
|
import { EntityListSection as a } from "./EntityListSection.js";
|
|
6
6
|
import { useMemo as o } from "react";
|
|
7
|
-
import { useParams as s } from "react-router
|
|
7
|
+
import { useParams as s } from "react-router";
|
|
8
8
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
9
9
|
import { Building2 as u, Coins as d, Magnet as f, Megaphone as p, Pencil as m, TrendingUp as h } from "lucide-react";
|
|
10
10
|
import { Badge as g, EmphasisPanel as _, GlassCard as v, GradientText as y, NextSteps as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Building2, Coins, Magnet, Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCommunicationCampaignsQuery, useSponsorQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { SafeLink } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Sponsor ↔ organizer message thread, backed by wspace-conversations-svc and\n * scoped to this sponsor so the conversation follows the partnership.\n */\nfunction SponsorConversation({ sponsorId, sponsorName }: { sponsorId: string; sponsorName: 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_SPONSOR',\n entityId: sponsorId,\n contextTitle: `Sponsor · ${sponsorName}`,\n }),\n [sponsorId, sponsorName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"sponsor-message\" />;\n}\n\ntype CampaignRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nconst compactNumber = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });\n\nexport default function SponsorDetailsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const sponsorQuery = useSponsorQuery(id);\n const campaignsQuery = useCommunicationCampaignsQuery({ limit: 100, offset: 0 });\n\n const sponsor = sponsorQuery.data?.sponsor ?? null;\n const campaigns = useMemo(\n () => (campaignsQuery.data?.communicationCampaigns.items ?? []) as CampaignRow[],\n [campaignsQuery.data]\n );\n\n const metrics = (sponsor?.metrics ?? {}) as { value?: number; leads?: number; leadsTarget?: number };\n const value = Number(metrics.value ?? 0);\n const leads = Number(metrics.leads ?? 0);\n const target = Number(metrics.leadsTarget ?? 0);\n const leadsPct = target > 0 ? Math.min(100, Math.round((leads / target) * 100)) : 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Everything about one sponsor in one place — their tier, contract value, leads, contacts, and the campaigns they\n appear in. Use this to prep for a sponsor conversation, confirm details, or check how their branded campaigns\n are performing.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-card border border-border-subtle bg-bg-sunken\">\n {sponsor?.logoUrl ? (\n <img src={sponsor.logoUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <Building2 className=\"h-6 w-6 text-text-muted\" />\n )}\n </span>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{sponsor?.name ?? 'Sponsor'}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsorQuery.loading\n ? 'Loading sponsor details...'\n : sponsor\n ? 'Live sponsor profile and partnership metrics.'\n : 'Sponsor not found in current query scope.'}\n </p>\n </div>\n </div>\n {sponsor ? (\n <Badge variant=\"secondary\" className=\"shrink-0 self-start uppercase\">\n {sponsor.level}\n </Badge>\n ) : null}\n </div>\n\n {sponsor ? (\n <div className=\"mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Contract value</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">${compactNumber.format(value)}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Magnet className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Leads</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">{leads.toLocaleString()}</p>\n {target > 0 ? (\n <div className=\"mt-2\">\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full\"\n style={{\n width: `${leadsPct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {leadsPct}% of {target.toLocaleString()} target\n </p>\n </div>\n ) : null}\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">Primary contact</p>\n <p className=\"mt-1.5 truncate font-medium text-text-primary\">{sponsor.contactName ?? 'Unassigned'}</p>\n <p className=\"truncate text-xs text-text-secondary\">{sponsor.contactEmail ?? 'No email'}</p>\n {sponsor.website ? (\n <SafeLink\n href={sponsor.website}\n className=\"mt-1 block truncate text-xs text-text-link hover:underline\"\n fallbackClassName=\"mt-1 block truncate text-xs text-text-secondary\"\n >\n {sponsor.website}\n </SafeLink>\n ) : null}\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n {sponsor ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this sponsor so you can segment your portfolio — for example by industry, tier or renewal status.\n </p>\n <EntityTagsField\n entityType=\"EVENTFULLYMANAGED_SPONSOR\"\n entityId={sponsor.id}\n label=\"Sponsor tags\"\n className=\"mt-3\"\n />\n </GlassCard>\n ) : null}\n\n <EntityListSection\n heading={campaignsQuery.loading ? 'Loading campaigns...' : `Campaign Activity (${campaigns.length})`}\n emptyLabel=\"No communication campaigns available for sponsor tracking.\"\n rows={campaigns.map((campaign) => ({\n id: campaign.id,\n title: campaign.title ?? campaign.name ?? `Campaign ${campaign.id}`,\n status: campaign.status,\n }))}\n />\n\n {sponsor ? (\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Sponsor messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Coordinate with this sponsor in a dedicated thread, backed by the conversations service.\n </p>\n </div>\n <SponsorConversation sponsorId={sponsor.id} sponsorName={sponsor.name} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-sponsor-details\"\n title=\"Grow this partnership\"\n steps={[\n {\n id: 'edit-sponsor',\n label: 'Update sponsor details',\n description: 'Keep tier, contacts and contract value current.',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/edit` : '/sponsors'),\n },\n {\n id: 'track-leads',\n label: 'Track sponsor leads',\n description: 'See the leads this sponsor is generating at the event.',\n icon: <TrendingUp className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/leads` : '/sponsors'),\n },\n {\n id: 'run-campaign',\n label: 'Run a co-branded campaign',\n description: 'Promote this sponsor through a communication campaign.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Building2, Coins, Magnet, Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useCommunicationCampaignsQuery, useSponsorQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { SafeLink } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Sponsor ↔ organizer message thread, backed by wspace-conversations-svc and\n * scoped to this sponsor so the conversation follows the partnership.\n */\nfunction SponsorConversation({ sponsorId, sponsorName }: { sponsorId: string; sponsorName: 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_SPONSOR',\n entityId: sponsorId,\n contextTitle: `Sponsor · ${sponsorName}`,\n }),\n [sponsorId, sponsorName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"sponsor-message\" />;\n}\n\ntype CampaignRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nconst compactNumber = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });\n\nexport default function SponsorDetailsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const sponsorQuery = useSponsorQuery(id);\n const campaignsQuery = useCommunicationCampaignsQuery({ limit: 100, offset: 0 });\n\n const sponsor = sponsorQuery.data?.sponsor ?? null;\n const campaigns = useMemo(\n () => (campaignsQuery.data?.communicationCampaigns.items ?? []) as CampaignRow[],\n [campaignsQuery.data]\n );\n\n const metrics = (sponsor?.metrics ?? {}) as { value?: number; leads?: number; leadsTarget?: number };\n const value = Number(metrics.value ?? 0);\n const leads = Number(metrics.leads ?? 0);\n const target = Number(metrics.leadsTarget ?? 0);\n const leadsPct = target > 0 ? Math.min(100, Math.round((leads / target) * 100)) : 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Everything about one sponsor in one place — their tier, contract value, leads, contacts, and the campaigns they\n appear in. Use this to prep for a sponsor conversation, confirm details, or check how their branded campaigns\n are performing.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-card border border-border-subtle bg-bg-sunken\">\n {sponsor?.logoUrl ? (\n <img src={sponsor.logoUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <Building2 className=\"h-6 w-6 text-text-muted\" />\n )}\n </span>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{sponsor?.name ?? 'Sponsor'}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsorQuery.loading\n ? 'Loading sponsor details...'\n : sponsor\n ? 'Live sponsor profile and partnership metrics.'\n : 'Sponsor not found in current query scope.'}\n </p>\n </div>\n </div>\n {sponsor ? (\n <Badge variant=\"secondary\" className=\"shrink-0 self-start uppercase\">\n {sponsor.level}\n </Badge>\n ) : null}\n </div>\n\n {sponsor ? (\n <div className=\"mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Contract value</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">${compactNumber.format(value)}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Magnet className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Leads</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">{leads.toLocaleString()}</p>\n {target > 0 ? (\n <div className=\"mt-2\">\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full\"\n style={{\n width: `${leadsPct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {leadsPct}% of {target.toLocaleString()} target\n </p>\n </div>\n ) : null}\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">Primary contact</p>\n <p className=\"mt-1.5 truncate font-medium text-text-primary\">{sponsor.contactName ?? 'Unassigned'}</p>\n <p className=\"truncate text-xs text-text-secondary\">{sponsor.contactEmail ?? 'No email'}</p>\n {sponsor.website ? (\n <SafeLink\n href={sponsor.website}\n className=\"mt-1 block truncate text-xs text-text-link hover:underline\"\n fallbackClassName=\"mt-1 block truncate text-xs text-text-secondary\"\n >\n {sponsor.website}\n </SafeLink>\n ) : null}\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n {sponsor ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this sponsor so you can segment your portfolio — for example by industry, tier or renewal status.\n </p>\n <EntityTagsField\n entityType=\"EVENTFULLYMANAGED_SPONSOR\"\n entityId={sponsor.id}\n label=\"Sponsor tags\"\n className=\"mt-3\"\n />\n </GlassCard>\n ) : null}\n\n <EntityListSection\n heading={campaignsQuery.loading ? 'Loading campaigns...' : `Campaign Activity (${campaigns.length})`}\n emptyLabel=\"No communication campaigns available for sponsor tracking.\"\n rows={campaigns.map((campaign) => ({\n id: campaign.id,\n title: campaign.title ?? campaign.name ?? `Campaign ${campaign.id}`,\n status: campaign.status,\n }))}\n />\n\n {sponsor ? (\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Sponsor messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Coordinate with this sponsor in a dedicated thread, backed by the conversations service.\n </p>\n </div>\n <SponsorConversation sponsorId={sponsor.id} sponsorName={sponsor.name} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-sponsor-details\"\n title=\"Grow this partnership\"\n steps={[\n {\n id: 'edit-sponsor',\n label: 'Update sponsor details',\n description: 'Keep tier, contacts and contract value current.',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/edit` : '/sponsors'),\n },\n {\n id: 'track-leads',\n label: 'Track sponsor leads',\n description: 'See the leads this sponsor is generating at the event.',\n icon: <TrendingUp className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/leads` : '/sponsors'),\n },\n {\n id: 'run-campaign',\n label: 'Run a co-branded campaign',\n description: 'Promote this sponsor through a communication campaign.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { useLeadsQuery as t, useMeetingsQuery as n, useNetworkingMatchesQuery as
|
|
|
3
3
|
import { LeadCaptureDialog as a } from "../leads/LeadCaptureDialog.js";
|
|
4
4
|
import { EntityListSection as o } from "./EntityListSection.js";
|
|
5
5
|
import { useMemo as s, useState as c } from "react";
|
|
6
|
-
import { useParams as l } from "react-router
|
|
6
|
+
import { useParams as l } from "react-router";
|
|
7
7
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
8
8
|
import { Megaphone as f, User as p, UserPlus as m, Users as h } from "lucide-react";
|
|
9
9
|
import { Button as g, EmphasisPanel as _, GradientText as v, NextSteps as y, PagePurpose as b } from "@burdenoff/fe-libs/ui";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SponsorLeadsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorLeadsPage.tsx"],"sourcesContent":["import { Megaphone, User, UserPlus, Users } from 'lucide-react';\nimport { Button, EmphasisPanel, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router
|
|
1
|
+
{"version":3,"file":"SponsorLeadsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorLeadsPage.tsx"],"sourcesContent":["import { Megaphone, User, UserPlus, Users } from 'lucide-react';\nimport { Button, EmphasisPanel, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useLeadsQuery, useMeetingsQuery, useNetworkingMatchesQuery, useSponsorsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { LeadCaptureDialog } from '../leads/LeadCaptureDialog';\nimport { EntityListSection } from './EntityListSection';\n\ntype SponsorRow = {\n id: string;\n name: string;\n level: string;\n eventId?: string | null;\n};\n\ntype SimpleRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\ntype LeadRow = {\n id: string;\n sponsorId?: string | null;\n attendeeName: string;\n attendeeEmail: string;\n status: string;\n score?: number | null;\n};\n\nexport default function SponsorLeadsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const [leadOpen, setLeadOpen] = useState(false);\n\n const sponsorsQuery = useSponsorsQuery({ limit: 100, offset: 0 });\n const matchesQuery = useNetworkingMatchesQuery({ limit: 100, offset: 0 });\n const meetingsQuery = useMeetingsQuery({ limit: 100, offset: 0 });\n\n const sponsor = useMemo(\n () => ((sponsorsQuery.data?.sponsors.items ?? []) as SponsorRow[]).find((item) => item.id === id),\n [id, sponsorsQuery.data]\n );\n const eventId = sponsor?.eventId ?? '';\n\n const leadsQuery = useLeadsQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n const capturedLeads = useMemo(\n () => ((leadsQuery.data?.leads.items ?? []) as LeadRow[]).filter((lead) => lead.sponsorId === id),\n [id, leadsQuery.data]\n );\n\n const matches = useMemo(() => (matchesQuery.data?.networkingMatches.items ?? []) as SimpleRow[], [matchesQuery.data]);\n const meetings = useMemo(() => (meetingsQuery.data?.meetings.items ?? []) as SimpleRow[], [meetingsQuery.data]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n The return on a sponsorship is the people it puts in front of the sponsor. This page pulls together the leads\n captured for this sponsor, the potential leads from networking matches, and the booked sponsor meetings — so you\n can show them exactly who they met and why their investment paid off.\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>Sponsor</GradientText> Leads\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsor\n ? `${sponsor.name} lead funnel sourced from captured leads, networking and meetings.`\n : 'Lead funnel sourced from captured leads, networking and meeting streams.'}\n </p>\n </div>\n <Button type=\"button\" onClick={() => setLeadOpen(true)} disabled={!sponsor}>\n <UserPlus className=\"h-4 w-4\" />\n Add lead\n </Button>\n </EmphasisPanel>\n\n <EntityListSection\n heading={leadsQuery.loading ? 'Loading captured leads...' : `Captured Leads (${capturedLeads.length})`}\n emptyLabel=\"No leads captured for this sponsor yet.\"\n emptyIllustration=\"empty-people\"\n rows={capturedLeads.map((lead) => ({\n id: lead.id,\n title: lead.attendeeName,\n subtitle: [lead.attendeeEmail, lead.score != null ? `Score ${lead.score}` : undefined]\n .filter(Boolean)\n .join(' • '),\n status: lead.status,\n }))}\n />\n\n <EntityListSection\n heading={matchesQuery.loading ? 'Loading networking matches...' : `Potential Leads (${matches.length})`}\n emptyLabel=\"No networking leads found.\"\n emptyIllustration=\"empty-people\"\n rows={matches.map((match) => ({\n id: match.id,\n title: match.title ?? match.name ?? `Lead ${match.id}`,\n status: match.status,\n }))}\n />\n\n <EntityListSection\n heading={meetingsQuery.loading ? 'Loading meetings...' : `Sponsor Meetings (${meetings.length})`}\n emptyLabel=\"No sponsor meetings recorded.\"\n rows={meetings.map((meeting) => ({\n id: meeting.id,\n title: meeting.title ?? meeting.name ?? `Meeting ${meeting.id}`,\n status: meeting.status,\n }))}\n />\n\n <NextSteps\n storageKey=\"eventfullymanaged-sponsor-leads\"\n title=\"Turn leads into wins\"\n steps={[\n {\n id: 'sponsor-profile',\n label: 'Back to sponsor profile',\n description: 'Review this sponsor’s tier, contacts and contract value.',\n icon: <User className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}` : '/sponsors'),\n },\n {\n id: 'boost-networking',\n label: 'Boost networking',\n description: 'Improve matchmaking so sponsors meet more of the right people.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'follow-up',\n label: 'Follow up with attendees',\n description: 'Send a campaign to nurture the leads after the event.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n\n {sponsor ? (\n <LeadCaptureDialog\n open={leadOpen}\n onOpenChange={setLeadOpen}\n eventId={eventId}\n sponsorId={sponsor.id}\n contextLabel={sponsor.name}\n onCreated={() => {\n void leadsQuery.refetch();\n }}\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { useEventfullyManaged as e } from "../../context/EventfullyManagedContex
|
|
|
2
2
|
import { useVenueQuery as t } from "../../api/eventApi.js";
|
|
3
3
|
import { EntityListSection as n } from "./EntityListSection.js";
|
|
4
4
|
import { useMemo as r } from "react";
|
|
5
|
-
import { useParams as i } from "react-router
|
|
5
|
+
import { useParams as i } from "react-router";
|
|
6
6
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
7
|
import { Building2 as s, Map as c, SquareStack as l } from "lucide-react";
|
|
8
8
|
import { EmphasisPanel as u, GlassCard as d, GradientText as f, NextSteps as p, PagePurpose as m } from "@burdenoff/fe-libs/ui";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VenueResourcesPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/VenueResourcesPage.tsx"],"sourcesContent":["import { Building2, Map, SquareStack } from 'lucide-react';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router
|
|
1
|
+
{"version":3,"file":"VenueResourcesPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/VenueResourcesPage.tsx"],"sourcesContent":["import { Building2, Map, SquareStack } from 'lucide-react';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useVenueQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\ntype FloorPlanRow = {\n id: string;\n name: string;\n imageUrl?: string | null;\n};\n\nexport default function VenueResourcesPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const venueQuery = useVenueQuery(id);\n const venue = venueQuery.data?.venue;\n const floorPlans = useMemo(() => (venueQuery.data?.floorPlans.items ?? []) as FloorPlanRow[], [venueQuery.data]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n A capacity-and-layout view of a single venue. It rolls up the practical resources you need to run an event in\n this space — how many people fit, what amenities are on offer, and which floor plans are mapped. Use it when\n deciding whether a venue can host your event and what still needs setting up.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Venue</GradientText> Resources\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {venueQuery.loading\n ? 'Loading venue resources...'\n : `Resource view for ${venue?.name ?? 'selected venue'} from live backend data.`}\n </p>\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Status</p>\n <p className=\"mt-1 font-medium text-text-primary\">{venue?.status ?? 'unknown'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Capacity</p>\n <p className=\"mt-1 font-medium tabular-nums text-text-primary\">{venue?.capacity?.toLocaleString() ?? 0}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Amenities</p>\n <p className=\"mt-1 font-medium tabular-nums text-text-primary\">{venue?.amenities.length ?? 0}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Floor Plans</p>\n <p className=\"mt-1 font-medium tabular-nums text-text-primary\">{floorPlans.length}</p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n <EntityListSection\n heading={venueQuery.loading ? 'Loading floor plans...' : `Floor Plans (${floorPlans.length})`}\n emptyLabel=\"No floor plans configured for this venue.\"\n emptyIllustration=\"empty-files\"\n rows={floorPlans.map((floorPlan) => ({\n id: floorPlan.id,\n title: floorPlan.name,\n subtitle: floorPlan.imageUrl ?? undefined,\n }))}\n />\n\n <EntityListSection\n heading=\"Amenities\"\n emptyLabel=\"No amenities listed.\"\n rows={(venue?.amenities ?? []).map((amenity) => ({\n id: amenity,\n title: amenity,\n }))}\n />\n\n <NextSteps\n storageKey=\"eventfullymanaged-venue-resources\"\n title=\"Get this venue event-ready\"\n steps={[\n {\n id: 'venue-detail',\n label: 'Open venue details',\n description: 'Edit capacity, address, status and amenities.',\n icon: <Building2 className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/venues/${id}` : '/venues'),\n },\n {\n id: 'manage-floor-plans',\n label: 'Manage floor plans',\n description: 'Add or update layouts so attendees can find their way.',\n icon: <SquareStack className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/venues/${id}/floor-plans` : '/venues'),\n },\n {\n id: 'all-venues',\n label: 'Compare with other venues',\n description: 'Browse the full venue list to pick the best fit.',\n icon: <Map className=\"h-4 w-4\" />,\n onClick: () => navigate('/venues'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAeA,SAAwB,IAAqB;CAC3C,IAAM,EAAE,QAAK,OAAO,GAAW,EACzB,EAAE,gBAAa,GAAsB,EACrC,IAAa,EAAc,EAAG,EAC9B,IAAQ,EAAW,MAAM,OACzB,IAAa,QAAe,EAAW,MAAM,WAAW,SAAS,EAAE,EAAqB,CAAC,EAAW,KAAK,CAAC;AAEhH,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,4SAIC,CAAA;GAEd,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACE,kBAAC,GAAD,EAAA,UAAc,SAAoB,CAAA,EAAA,aAC/B;;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAW,UACR,+BACA,qBAAqB,GAAO,QAAQ,iBAAiB;KACvD,CAAA;IACJ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAW,WAAU;OAAQ,MAAA;OAAK,WAAU;iBAA5C,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAsD;QAAU,CAAA,EAC7E,kBAAC,KAAD;QAAG,WAAU;kBAAsC,GAAO,UAAU;QAAc,CAAA,CACxE;;MACZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAsD;QAAY,CAAA,EAC/E,kBAAC,KAAD;QAAG,WAAU;kBAAmD,GAAO,UAAU,gBAAgB,IAAI;QAAM,CAAA,CACjG;;MACZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAsD;QAAa,CAAA,EAChF,kBAAC,KAAD;QAAG,WAAU;kBAAmD,GAAO,UAAU,UAAU;QAAM,CAAA,CACvF;;MACZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAsD;QAAe,CAAA,EAClF,kBAAC,KAAD;QAAG,WAAU;kBAAmD,EAAW;QAAW,CAAA,CAC5E;;MACR;;IACQ,EAAA,CAAA;GAEhB,kBAAC,GAAD;IACE,SAAS,EAAW,UAAU,2BAA2B,gBAAgB,EAAW,OAAO;IAC3F,YAAW;IACX,mBAAkB;IAClB,MAAM,EAAW,KAAK,OAAe;KACnC,IAAI,EAAU;KACd,OAAO,EAAU;KACjB,UAAU,EAAU,YAAY,KAAA;KACjC,EAAE;IACH,CAAA;GAEF,kBAAC,GAAD;IACE,SAAQ;IACR,YAAW;IACX,OAAO,GAAO,aAAa,EAAE,EAAE,KAAK,OAAa;KAC/C,IAAI;KACJ,OAAO;KACR,EAAE;IACH,CAAA;GAEF,kBAAC,GAAD;IACE,YAAW;IACX,OAAM;IACN,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,IAAK,WAAW,MAAO,UAAU;MAC1D;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,eAAe,EAAS,IAAK,WAAW,EAAG,gBAAgB,UAAU;MACtE;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,eAAe,EAAS,UAAU;MACnC;KACF;IACD,CAAA;GACE"}
|
|
@@ -4,7 +4,7 @@ import { useAttendeesQuery as n, useCreateMeetingMutation as r, useEventsQuery a
|
|
|
4
4
|
import { EntityFormShell as c } from "../../components/EntityFormShell.js";
|
|
5
5
|
import { describeError as l, mutationSucceeded as u } from "../../lib/mutationResult.js";
|
|
6
6
|
import { useEffect as d, useMemo as f, useState as p } from "react";
|
|
7
|
-
import { useParams as m } from "react-router
|
|
7
|
+
import { useParams as m } from "react-router";
|
|
8
8
|
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
9
9
|
import { Input as _, Select as v, SelectContent as y, SelectItem as b, SelectTrigger as x, SelectValue as S, Textarea as C } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/eventfullymanaged/pages/networking/MeetingFormPage.tsx
|