@burdenoff/microfe-eventfullymanaged 2026.703.2 → 2026.704.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.
Files changed (74) hide show
  1. package/dist/eventfullymanaged/EventfullyManagedRoutes.js +154 -66
  2. package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
  3. package/dist/eventfullymanaged/api/eventApi.js +546 -67
  4. package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
  5. package/dist/eventfullymanaged/components/ActionMenu.js +113 -0
  6. package/dist/eventfullymanaged/components/ActionMenu.js.map +1 -0
  7. package/dist/eventfullymanaged/components/EntityFormShell.js +51 -0
  8. package/dist/eventfullymanaged/components/EntityFormShell.js.map +1 -0
  9. package/dist/eventfullymanaged/components/EntityTagsField.js +146 -0
  10. package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -0
  11. package/dist/eventfullymanaged/components/Pagination.js +84 -0
  12. package/dist/eventfullymanaged/components/Pagination.js.map +1 -0
  13. package/dist/eventfullymanaged/components/SafeLink.js +32 -0
  14. package/dist/eventfullymanaged/components/SafeLink.js.map +1 -0
  15. package/dist/eventfullymanaged/components/index.js +7 -0
  16. package/dist/eventfullymanaged/pages/events/EventDetailPage.js +458 -0
  17. package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -0
  18. package/dist/eventfullymanaged/pages/events/EventFormPage.js +267 -0
  19. package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -0
  20. package/dist/eventfullymanaged/pages/events/SessionFormPage.js +308 -0
  21. package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -0
  22. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +214 -0
  23. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -0
  24. package/dist/eventfullymanaged/pages/events/eventFormUtils.js +128 -0
  25. package/dist/eventfullymanaged/pages/events/eventFormUtils.js.map +1 -0
  26. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +233 -0
  27. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -0
  28. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js +165 -0
  29. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js.map +1 -0
  30. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js +141 -0
  31. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js.map +1 -0
  32. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js +178 -45
  33. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
  34. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js +240 -70
  35. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js.map +1 -1
  36. package/dist/eventfullymanaged/pages/modules/EventsPage.js +133 -97
  37. package/dist/eventfullymanaged/pages/modules/EventsPage.js.map +1 -1
  38. package/dist/eventfullymanaged/pages/modules/KioskPage.js +746 -36
  39. package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
  40. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +210 -69
  41. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
  42. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +289 -77
  43. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
  44. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js +95 -0
  45. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -0
  46. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +77 -56
  47. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
  48. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +70 -36
  49. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
  50. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +343 -0
  51. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -0
  52. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +236 -0
  53. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -0
  54. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +181 -0
  55. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -0
  56. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +262 -0
  57. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -0
  58. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +414 -0
  59. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -0
  60. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +224 -0
  61. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -0
  62. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js +96 -0
  63. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js.map +1 -0
  64. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +149 -140
  65. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
  66. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js +97 -67
  67. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js.map +1 -1
  68. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +93 -72
  69. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
  70. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +91 -89
  71. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
  72. package/dist/eventfullymanaged/pages/venues/VenueListPage.js +83 -53
  73. package/dist/eventfullymanaged/pages/venues/VenueListPage.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
2
+ import { gql as t } from "../../../node_modules/graphql-tag/lib/index.js";
3
+ import { useQuery as n } from "../../../node_modules/@apollo/client/react/hooks/useQuery.js";
4
+ import { notify as r } from "../../lib/toast.js";
5
+ import { useCreateSpeakerMutation as i, useUpdateSpeakerMutation as a } from "../../api/eventApi.js";
6
+ import { EntityFormShell as o } from "../../components/EntityFormShell.js";
7
+ import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
8
+ import { EntityTagsField as l } from "../../components/EntityTagsField.js";
9
+ import { safeUrl as u } from "../../components/SafeLink.js";
10
+ import { ImageUploadField as d } from "../../components/ImageUploadField.js";
11
+ import { useEffect as f, useMemo as p, useState as m } from "react";
12
+ import { useParams as h } from "react-router-dom";
13
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
14
+ import { Input as v, Textarea as y } from "@burdenoff/fe-libs/ui";
15
+ //#region src/eventfullymanaged/pages/speakers/SpeakerFormPage.tsx
16
+ var b = t`
17
+ query EventfullymanagedSpeakerFormSpeakers($limit: Int, $offset: Int) {
18
+ speakers: eventfullyManagedSpeakers(limit: $limit, offset: $offset) {
19
+ total
20
+ items {
21
+ id
22
+ fullName
23
+ title
24
+ organization
25
+ bio
26
+ avatarUrl
27
+ email
28
+ phone
29
+ socialLinks
30
+ }
31
+ }
32
+ }
33
+ `, x = {
34
+ fullName: "",
35
+ title: "",
36
+ organization: "",
37
+ email: "",
38
+ phone: "",
39
+ bio: "",
40
+ avatarUrl: "",
41
+ website: ""
42
+ };
43
+ function S(e) {
44
+ return e && typeof e.website == "string" ? e.website : "";
45
+ }
46
+ function C() {
47
+ let { id: t = "" } = h(), C = p(() => !!t, [t]), { navigate: w } = e(), { data: T } = n(b, {
48
+ variables: {
49
+ limit: 100,
50
+ offset: 0
51
+ },
52
+ skip: !t
53
+ }), [E, { loading: D }] = i(), [O, { loading: k }] = a(), [A, j] = m(x), M = p(() => (T?.speakers?.items ?? []).find((e) => e.id === t), [T, t]);
54
+ f(() => {
55
+ !C || !M || j({
56
+ fullName: M.fullName ?? "",
57
+ title: M.title ?? "",
58
+ organization: M.organization ?? "",
59
+ email: M.email ?? "",
60
+ phone: M.phone ?? "",
61
+ bio: M.bio ?? "",
62
+ avatarUrl: M.avatarUrl ?? "",
63
+ website: S(M.socialLinks)
64
+ });
65
+ }, [M, C]);
66
+ let N = (e, t) => {
67
+ j((n) => ({
68
+ ...n,
69
+ [e]: t
70
+ }));
71
+ };
72
+ return /* @__PURE__ */ _(o, {
73
+ title: C ? "Edit Speaker" : "Add Speaker",
74
+ description: "A person who presents sessions at your events.",
75
+ purpose: "A speaker profile is what you attach to sessions and show to attendees. Capture who they are, where they work, and a short bio or talk abstract so the agenda tells a compelling story. The headshot appears anywhere the speaker is listed.",
76
+ backLabel: "Back to Speakers",
77
+ onBack: () => w("/speakers"),
78
+ onSubmit: async (e) => {
79
+ if (e.preventDefault(), !A.fullName.trim()) {
80
+ r.error("Name required", "Give the speaker a name before saving.");
81
+ return;
82
+ }
83
+ let n = u(A.website);
84
+ if (A.website.trim() && !n) {
85
+ r.error("Invalid website", "Enter a valid http(s) URL, or leave it blank.");
86
+ return;
87
+ }
88
+ let i = {
89
+ fullName: A.fullName.trim(),
90
+ title: A.title || null,
91
+ organization: A.organization || null,
92
+ email: A.email || null,
93
+ phone: A.phone || null,
94
+ bio: A.bio || null,
95
+ avatarUrl: A.avatarUrl || null,
96
+ socialLinks: n ? { website: n } : {}
97
+ };
98
+ try {
99
+ if (C) {
100
+ let e = await O({ variables: {
101
+ id: t,
102
+ input: i
103
+ } });
104
+ if (!c(e, e.data?.updateSpeaker?.id, "Could not update speaker")) return;
105
+ r.success("Speaker updated"), w("/speakers");
106
+ return;
107
+ }
108
+ let e = await E({ variables: { input: i } });
109
+ if (!c(e, e.data?.createSpeaker?.id, "Could not create speaker")) return;
110
+ r.success("Speaker created"), w("/speakers");
111
+ } catch (e) {
112
+ r.error("Could not save speaker", s(e));
113
+ }
114
+ },
115
+ submitLabel: "Save Speaker",
116
+ saving: D || k,
117
+ children: [
118
+ /* @__PURE__ */ _("div", {
119
+ className: "grid grid-cols-1 gap-4 md:grid-cols-2",
120
+ children: [/* @__PURE__ */ _("label", {
121
+ className: "space-y-1 text-sm",
122
+ children: [/* @__PURE__ */ g("span", {
123
+ className: "font-medium text-text-primary",
124
+ children: "Full name"
125
+ }), /* @__PURE__ */ g(v, {
126
+ value: A.fullName,
127
+ onChange: (e) => N("fullName", e.target.value),
128
+ required: !0
129
+ })]
130
+ }), /* @__PURE__ */ _("label", {
131
+ className: "space-y-1 text-sm",
132
+ children: [/* @__PURE__ */ g("span", {
133
+ className: "font-medium text-text-primary",
134
+ children: "Title"
135
+ }), /* @__PURE__ */ g(v, {
136
+ value: A.title,
137
+ onChange: (e) => N("title", e.target.value),
138
+ placeholder: "Head of Product"
139
+ })]
140
+ })]
141
+ }),
142
+ /* @__PURE__ */ _("div", {
143
+ className: "grid grid-cols-1 gap-4 md:grid-cols-3",
144
+ children: [
145
+ /* @__PURE__ */ _("label", {
146
+ className: "space-y-1 text-sm",
147
+ children: [/* @__PURE__ */ g("span", {
148
+ className: "font-medium text-text-primary",
149
+ children: "Company"
150
+ }), /* @__PURE__ */ g(v, {
151
+ value: A.organization,
152
+ onChange: (e) => N("organization", e.target.value)
153
+ })]
154
+ }),
155
+ /* @__PURE__ */ _("label", {
156
+ className: "space-y-1 text-sm",
157
+ children: [/* @__PURE__ */ g("span", {
158
+ className: "font-medium text-text-primary",
159
+ children: "Email"
160
+ }), /* @__PURE__ */ g(v, {
161
+ type: "email",
162
+ value: A.email,
163
+ onChange: (e) => N("email", e.target.value)
164
+ })]
165
+ }),
166
+ /* @__PURE__ */ _("label", {
167
+ className: "space-y-1 text-sm",
168
+ children: [/* @__PURE__ */ g("span", {
169
+ className: "font-medium text-text-primary",
170
+ children: "Phone"
171
+ }), /* @__PURE__ */ g(v, {
172
+ value: A.phone,
173
+ onChange: (e) => N("phone", e.target.value)
174
+ })]
175
+ })
176
+ ]
177
+ }),
178
+ /* @__PURE__ */ _("div", {
179
+ className: "space-y-1 text-sm",
180
+ children: [/* @__PURE__ */ g("span", {
181
+ className: "font-medium text-text-primary",
182
+ children: "Headshot"
183
+ }), /* @__PURE__ */ g(d, {
184
+ label: "Upload headshot",
185
+ folderPath: "/EventfullyManaged/Speakers",
186
+ value: A.avatarUrl || null,
187
+ onUploaded: (e) => N("avatarUrl", e),
188
+ hint: "Stored securely in Files. Shown wherever the speaker is listed."
189
+ })]
190
+ }),
191
+ /* @__PURE__ */ _("label", {
192
+ className: "block space-y-1 text-sm",
193
+ children: [/* @__PURE__ */ g("span", {
194
+ className: "font-medium text-text-primary",
195
+ children: "Bio & talk abstract"
196
+ }), /* @__PURE__ */ g(y, {
197
+ value: A.bio,
198
+ onChange: (e) => N("bio", e.target.value),
199
+ rows: 5,
200
+ placeholder: "Short professional bio and a summary of the talk they will give."
201
+ })]
202
+ }),
203
+ /* @__PURE__ */ _("label", {
204
+ className: "block space-y-1 text-sm",
205
+ children: [/* @__PURE__ */ g("span", {
206
+ className: "font-medium text-text-primary",
207
+ children: "Website / profile URL"
208
+ }), /* @__PURE__ */ g(v, {
209
+ value: A.website,
210
+ onChange: (e) => N("website", e.target.value),
211
+ placeholder: "https://…"
212
+ })]
213
+ }),
214
+ C ? /* @__PURE__ */ g(l, {
215
+ entityType: "speaker",
216
+ entityId: t
217
+ }) : null
218
+ ]
219
+ });
220
+ }
221
+ //#endregion
222
+ export { C as default };
223
+
224
+ //# sourceMappingURL=SpeakerFormPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeakerFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/speakers/SpeakerFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCreateSpeakerMutation, useUpdateSpeakerMutation } from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n// The backend `updateSpeaker` resolver is a FULL replace and there is no by-id\n// speaker resolver, so this fuller list query — selecting every editable field,\n// not just the columns the foundation SPEAKERS_QUERY lists — is the edit-hydration\n// source. The form finds its row by id client-side. Without the full selection an\n// edit would blank bio / avatar / phone / socialLinks.\nconst FULL_SPEAKERS_QUERY = gql`\n query EventfullymanagedSpeakerFormSpeakers($limit: Int, $offset: Int) {\n speakers: eventfullyManagedSpeakers(limit: $limit, offset: $offset) {\n total\n items {\n id\n fullName\n title\n organization\n bio\n avatarUrl\n email\n phone\n socialLinks\n }\n }\n }\n`;\n\ntype SpeakerRecord = {\n id: string;\n fullName: string;\n title?: string | null;\n organization?: string | null;\n bio?: string | null;\n avatarUrl?: string | null;\n email?: string | null;\n phone?: string | null;\n socialLinks?: Record<string, unknown> | null;\n};\n\ntype FullSpeakersResponse = { speakers: { total: number; items: SpeakerRecord[] } };\n\ntype SpeakerFormState = {\n fullName: string;\n title: string;\n organization: string;\n email: string;\n phone: string;\n bio: string;\n avatarUrl: string;\n website: string;\n};\n\nconst initialState: SpeakerFormState = {\n fullName: '',\n title: '',\n organization: '',\n email: '',\n phone: '',\n bio: '',\n avatarUrl: '',\n website: '',\n};\n\nfunction readWebsite(socialLinks: Record<string, unknown> | null | undefined): string {\n if (socialLinks && typeof socialLinks.website === 'string') {\n return socialLinks.website;\n }\n return '';\n}\n\nexport default function SpeakerFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data } = useQuery<FullSpeakersResponse, { limit?: number; offset?: number }>(FULL_SPEAKERS_QUERY, {\n variables: { limit: 100, offset: 0 },\n skip: !id,\n });\n const [createSpeaker, { loading: creating }] = useCreateSpeakerMutation();\n const [updateSpeaker, { loading: updating }] = useUpdateSpeakerMutation();\n const [form, setForm] = useState<SpeakerFormState>(initialState);\n\n const existingSpeaker = useMemo(\n () => (data?.speakers?.items ?? []).find((item) => item.id === id),\n [data, id]\n );\n\n useEffect(() => {\n if (!isEdit || !existingSpeaker) {\n return;\n }\n setForm({\n fullName: existingSpeaker.fullName ?? '',\n title: existingSpeaker.title ?? '',\n organization: existingSpeaker.organization ?? '',\n email: existingSpeaker.email ?? '',\n phone: existingSpeaker.phone ?? '',\n bio: existingSpeaker.bio ?? '',\n avatarUrl: existingSpeaker.avatarUrl ?? '',\n website: readWebsite(existingSpeaker.socialLinks),\n });\n }, [existingSpeaker, isEdit]);\n\n const onChange = (field: keyof SpeakerFormState, 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.fullName.trim()) {\n notify.error('Name required', 'Give the speaker a name before saving.');\n return;\n }\n\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 input = {\n fullName: form.fullName.trim(),\n title: form.title || null,\n organization: form.organization || null,\n email: form.email || null,\n phone: form.phone || null,\n bio: form.bio || null,\n avatarUrl: form.avatarUrl || null,\n socialLinks: normalizedWebsite ? { website: normalizedWebsite } : {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSpeaker({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSpeaker?.id, 'Could not update speaker')) {\n return;\n }\n notify.success('Speaker updated');\n navigate('/speakers');\n return;\n }\n const response = await createSpeaker({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSpeaker?.id, 'Could not create speaker')) {\n return;\n }\n notify.success('Speaker created');\n navigate('/speakers');\n } catch (error) {\n notify.error('Could not save speaker', describeError(error));\n }\n };\n\n const saving = creating || updating;\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Speaker' : 'Add Speaker'}\n description=\"A person who presents sessions at your events.\"\n purpose=\"A speaker profile is what you attach to sessions and show to attendees. Capture who they are, where they work, and a short bio or talk abstract so the agenda tells a compelling story. The headshot appears anywhere the speaker is listed.\"\n backLabel=\"Back to Speakers\"\n onBack={() => navigate('/speakers')}\n onSubmit={onSubmit}\n submitLabel=\"Save Speaker\"\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\">Full name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input\n value={form.title}\n onChange={(e) => onChange('title', e.target.value)}\n placeholder=\"Head of Product\"\n />\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\">Company</span>\n <Input value={form.organization} onChange={(e) => onChange('organization', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Headshot</span>\n <ImageUploadField\n label=\"Upload headshot\"\n folderPath=\"/EventfullyManaged/Speakers\"\n value={form.avatarUrl || null}\n onUploaded={(url) => onChange('avatarUrl', url)}\n hint=\"Stored securely in Files. Shown wherever the speaker is listed.\"\n />\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Bio &amp; talk abstract</span>\n <Textarea\n value={form.bio}\n onChange={(e) => onChange('bio', e.target.value)}\n rows={5}\n placeholder=\"Short professional bio and a summary of the talk they will give.\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website / profile URL</span>\n <Input\n value={form.website}\n onChange={(e) => onChange('website', e.target.value)}\n placeholder=\"https://…\"\n />\n </label>\n\n {isEdit ? <EntityTagsField entityType=\"speaker\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
2
+ import { useSpeakersQuery as t } from "../../api/eventApi.js";
3
+ import { ActionMenu as n } from "../../components/ActionMenu.js";
4
+ import { Pagination as r, usePagination as i } from "../../components/Pagination.js";
5
+ import { useMemo as a } from "react";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ import { Mic2 as c, Plus as l } from "lucide-react";
8
+ import { Button as u, GlassCard as d, IllustratedEmptyState as f, PagePurpose as p } from "@burdenoff/fe-libs/ui";
9
+ //#region src/eventfullymanaged/pages/speakers/SpeakersPage.tsx
10
+ function m() {
11
+ let { navigate: m } = e(), h = i(), { data: g, loading: _ } = t({
12
+ limit: h.limit,
13
+ offset: h.offset
14
+ }), v = g?.speakers?.total ?? 0, y = a(() => g?.speakers?.items ?? [], [g]);
15
+ return /* @__PURE__ */ s("div", {
16
+ className: "space-y-6 p-6",
17
+ children: [
18
+ /* @__PURE__ */ o(p, { children: "Speakers are the people who present your sessions. Keep a roster here — their name, role, company and bio — so that when you build the agenda you can assign a speaker to each session in a couple of clicks. A good speaker profile also gives attendees a reason to attend." }),
19
+ /* @__PURE__ */ s("div", {
20
+ className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
21
+ children: [/* @__PURE__ */ s("div", { children: [/* @__PURE__ */ o("h1", {
22
+ className: "text-2xl font-bold text-text-primary",
23
+ children: "Speakers"
24
+ }), /* @__PURE__ */ o("p", {
25
+ className: "mt-1 text-sm text-text-secondary",
26
+ children: "Manage your speaker roster with live API data."
27
+ })] }), /* @__PURE__ */ s(u, {
28
+ type: "button",
29
+ onClick: () => m("/speakers/create"),
30
+ children: [/* @__PURE__ */ o(l, { className: "h-4 w-4" }), "Add Speaker"]
31
+ })]
32
+ }),
33
+ _ ? /* @__PURE__ */ o(d, {
34
+ treatment: "plain",
35
+ className: "border-border-seam p-8 text-center text-sm text-text-secondary",
36
+ children: "Loading speakers…"
37
+ }) : y.length === 0 ? /* @__PURE__ */ o(d, {
38
+ treatment: "plain",
39
+ className: "border-border-seam",
40
+ children: /* @__PURE__ */ o(f, {
41
+ illustration: "empty-people",
42
+ title: "No speakers yet.",
43
+ description: "Add your first speaker to start building the agenda.",
44
+ action: /* @__PURE__ */ s(u, {
45
+ type: "button",
46
+ onClick: () => m("/speakers/create"),
47
+ children: [/* @__PURE__ */ o(l, { className: "h-4 w-4" }), "Add Speaker"]
48
+ })
49
+ })
50
+ }) : /* @__PURE__ */ s("div", {
51
+ className: "space-y-3",
52
+ children: [y.map((e) => /* @__PURE__ */ o(d, {
53
+ treatment: "plain",
54
+ className: "border-border-seam p-4",
55
+ children: /* @__PURE__ */ s("div", {
56
+ className: "flex items-center justify-between gap-3",
57
+ children: [/* @__PURE__ */ s("div", {
58
+ className: "flex min-w-0 items-center gap-3",
59
+ children: [/* @__PURE__ */ o("span", {
60
+ className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary",
61
+ children: /* @__PURE__ */ o(c, { className: "h-5 w-5" })
62
+ }), /* @__PURE__ */ s("div", {
63
+ className: "min-w-0",
64
+ children: [/* @__PURE__ */ o("button", {
65
+ type: "button",
66
+ onClick: () => m(`/speakers/${e.id}/edit`),
67
+ className: "rounded-button text-left font-semibold text-text-primary transition-colors hover:text-text-link focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus",
68
+ children: e.fullName
69
+ }), /* @__PURE__ */ o("p", {
70
+ className: "mt-0.5 truncate text-sm text-text-secondary",
71
+ children: [e.title, e.organization].filter(Boolean).join(" • ") || "Speaker"
72
+ })]
73
+ })]
74
+ }), /* @__PURE__ */ o(n, {
75
+ ariaLabel: `Speaker actions for ${e.fullName}`,
76
+ onEdit: () => m(`/speakers/${e.id}/edit`)
77
+ })]
78
+ })
79
+ }, e.id)), /* @__PURE__ */ o(r, {
80
+ offset: h.offset,
81
+ limit: h.limit,
82
+ loadedCount: y.length,
83
+ total: v,
84
+ onPrev: h.prevPage,
85
+ onNext: h.nextPage,
86
+ itemLabel: "speakers",
87
+ loading: _
88
+ })]
89
+ })
90
+ ]
91
+ });
92
+ }
93
+ //#endregion
94
+ export { m as default };
95
+
96
+ //# sourceMappingURL=SpeakersPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeakersPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/speakers/SpeakersPage.tsx"],"sourcesContent":["import { Mic2, Plus } from 'lucide-react';\nimport { Button, GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport { useSpeakersQuery } from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\n\ntype SpeakerRow = {\n id: string;\n fullName: string;\n title?: string | null;\n organization?: string | null;\n email?: string | null;\n};\n\nexport default function SpeakersPage() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination();\n const { data, loading } = useSpeakersQuery({ limit: pagination.limit, offset: pagination.offset });\n\n const total = data?.speakers?.total ?? 0;\n const speakers = useMemo(() => (data?.speakers?.items ?? []) as unknown as SpeakerRow[], [data]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Speakers are the people who present your sessions. Keep a roster here — their name, role, company and bio — so\n that when you build the agenda you can assign a speaker to each session in a couple of clicks. A good speaker\n profile also gives attendees a reason to attend.\n </PagePurpose>\n\n <div 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\">Speakers</h1>\n <p className=\"mt-1 text-sm text-text-secondary\">Manage your speaker roster with live API data.</p>\n </div>\n <Button type=\"button\" onClick={() => navigate('/speakers/create')}>\n <Plus className=\"h-4 w-4\" />\n Add Speaker\n </Button>\n </div>\n\n {loading ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-8 text-center text-sm text-text-secondary\">\n Loading speakers…\n </GlassCard>\n ) : speakers.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No speakers yet.\"\n description=\"Add your first speaker to start building the agenda.\"\n action={\n <Button type=\"button\" onClick={() => navigate('/speakers/create')}>\n <Plus className=\"h-4 w-4\" />\n Add Speaker\n </Button>\n }\n />\n </GlassCard>\n ) : (\n <div className=\"space-y-3\">\n {speakers.map((speaker) => (\n <GlassCard key={speaker.id} treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-text-secondary\">\n <Mic2 className=\"h-5 w-5\" />\n </span>\n <div className=\"min-w-0\">\n <button\n type=\"button\"\n onClick={() => navigate(`/speakers/${speaker.id}/edit`)}\n className=\"rounded-button text-left font-semibold text-text-primary transition-colors hover:text-text-link focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n {speaker.fullName}\n </button>\n <p className=\"mt-0.5 truncate text-sm text-text-secondary\">\n {[speaker.title, speaker.organization].filter(Boolean).join(' • ') || 'Speaker'}\n </p>\n </div>\n </div>\n <ActionMenu\n ariaLabel={`Speaker actions for ${speaker.fullName}`}\n onEdit={() => navigate(`/speakers/${speaker.id}/edit`)}\n />\n </div>\n </GlassCard>\n ))}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={speakers.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"speakers\"\n loading={loading}\n />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,SAAwB,IAAe;CACrC,IAAM,EAAE,gBAAa,GAAsB,EACrC,IAAa,GAAe,EAC5B,EAAE,SAAM,eAAY,EAAiB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EAE5F,IAAQ,GAAM,UAAU,SAAS,GACjC,IAAW,QAAe,GAAM,UAAU,SAAS,EAAE,EAA8B,CAAC,EAAK,CAAC;AAEhG,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,iRAIC,CAAA;GAEd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAAuC;KAAa,CAAA,EAClE,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAAkD,CAAA,CAC9F,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAS,eAAe,EAAS,mBAAmB;eAAjE,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,cAErB;OACL;;GAEL,IACC,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAiE;IAE5F,CAAA,GACV,EAAS,WAAW,IACtB,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;KACZ,QACE,kBAAC,GAAD;MAAQ,MAAK;MAAS,eAAe,EAAS,mBAAmB;gBAAjE,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,cAErB;;KAEX,CAAA;IACQ,CAAA,GAEZ,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAS,KAAK,MACb,kBAAC,GAAD;KAA4B,WAAU;KAAQ,WAAU;eACtD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;QACvB,CAAA,EACP,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAS,aAAa,EAAQ,GAAG,OAAO;SACvD,WAAU;mBAET,EAAQ;SACF,CAAA,EACT,kBAAC,KAAD;SAAG,WAAU;mBACV,CAAC,EAAQ,OAAO,EAAQ,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,IAAI;SACpE,CAAA,CACA;UACF;UACN,kBAAC,GAAD;OACE,WAAW,uBAAuB,EAAQ;OAC1C,cAAc,EAAS,aAAa,EAAQ,GAAG,OAAO;OACtD,CAAA,CACE;;KACI,EAxBI,EAAQ,GAwBZ,CACZ,EAEF,kBAAC,GAAD;KACE,QAAQ,EAAW;KACnB,OAAO,EAAW;KAClB,aAAa,EAAS;KACf;KACP,QAAQ,EAAW;KACnB,QAAQ,EAAW;KACnB,WAAU;KACD;KACT,CAAA,CACE;;GAEJ"}