@burdenoff/microfe-eventfullymanaged 2026.703.2 → 2026.704.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/eventfullymanaged/EventfullyManagedRoutes.js +151 -67
  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 +295 -76
  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 +415 -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
@@ -1,95 +1,314 @@
1
1
  import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
2
- import { useAttendeesQuery as t, usePromoCodesQuery as n, useRegistrationsQuery as r } from "../../api/eventApi.js";
3
- import { EntityListSection as i } from "./EntityListSection.js";
4
- import { useMemo as a } from "react";
5
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
- import { Megaphone as c, Tag as l, Users as u } from "lucide-react";
7
- import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
- import { EmphasisPanel as f, GradientText as p, NextSteps as m, PagePurpose as h } from "@burdenoff/fe-libs/ui";
2
+ import { notify as t } from "../../lib/toast.js";
3
+ import { useAttendeesQuery as n, useDeletePromoCodeMutation as r, usePromoCodesQuery as i, useRegistrationsQuery as a, useUpdateRegistrationMutation as o } from "../../api/eventApi.js";
4
+ import { ActionMenu as s } from "../../components/ActionMenu.js";
5
+ import { Pagination as c, usePagination as l } from "../../components/Pagination.js";
6
+ import { mutationSucceeded as u } from "../../lib/mutationResult.js";
7
+ import { useMemo as d } from "react";
8
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
9
+ import { Megaphone as m, Tag as h, TicketPlus as g, UserPlus as _, Users as v } from "lucide-react";
10
+ import { Badge as y, Button as b, EmphasisPanel as x, GlassCard as S, GradientText as C, IllustratedEmptyState as w, NextSteps as T, PagePurpose as E } from "@burdenoff/fe-libs/ui";
9
11
  //#region src/eventfullymanaged/pages/modules/RegistrationPage.tsx
10
- function g() {
11
- let { t: g } = d(), _ = (e, t) => {
12
- let n = g(e);
13
- return n === e ? t : n;
14
- }, { navigate: v } = e(), y = r({
15
- limit: 100,
16
- offset: 0
17
- }), b = t({
18
- limit: 100,
19
- offset: 0
20
- }), x = n({
21
- limit: 100,
12
+ var D = [
13
+ "PENDING",
14
+ "CONFIRMED",
15
+ "WAITLISTED",
16
+ "CANCELLED",
17
+ "CHECKED_IN"
18
+ ];
19
+ function O(e) {
20
+ switch ((e ?? "").toUpperCase()) {
21
+ case "CONFIRMED":
22
+ case "CHECKED_IN": return "default";
23
+ case "CANCELLED": return "destructive";
24
+ case "WAITLISTED": return "outline";
25
+ default: return "secondary";
26
+ }
27
+ }
28
+ function k() {
29
+ let e = l({ pageSize: 10 }), { data: r, loading: i } = a({
30
+ limit: e.limit,
31
+ offset: e.offset
32
+ }), [m] = o(), { data: h } = n({
33
+ limit: 200,
22
34
  offset: 0
23
- }), S = a(() => y.data?.registrations.items ?? [], [y.data]), C = a(() => b.data?.attendees.items ?? [], [b.data]), w = a(() => x.data?.promoCodes.items ?? [], [x.data]);
24
- return /* @__PURE__ */ s("div", {
25
- className: "space-y-6 p-6",
35
+ }), g = d(() => {
36
+ let e = /* @__PURE__ */ new Map();
37
+ for (let t of h?.attendees.items ?? []) t.id && t.fullName && e.set(t.id, t.fullName);
38
+ return e;
39
+ }, [h]), _ = (e) => e ? g.get(e) ?? `Attendee ${e.slice(0, 8)}…` : null, v = d(() => r?.registrations.items ?? [], [r]), b = r?.registrations.total ?? 0, x = async (e, n) => {
40
+ let r = await m({ variables: {
41
+ id: e.id,
42
+ input: {
43
+ eventId: e.eventId,
44
+ attendeeId: e.attendeeId ?? null,
45
+ ticketTypeId: e.ticketTypeId ?? null,
46
+ quantity: e.quantity,
47
+ currency: e.currency,
48
+ totalAmount: e.totalAmount ?? null,
49
+ status: n
50
+ }
51
+ } });
52
+ u(r, r.data?.updateRegistration?.id, "Could not update registration") && t.success("Registration updated", `Status set to ${n}`);
53
+ };
54
+ return /* @__PURE__ */ p(S, {
55
+ treatment: "plain",
56
+ className: "border-border-seam p-5",
26
57
  children: [
27
- /* @__PURE__ */ o(h, { children: _("eventfullymanaged.registration.purpose", "This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people behind them, and promo codes let you offer discounts. Use this page to monitor sign-up volume, look up an attendee, and check that your discount codes are working.") }),
28
- /* @__PURE__ */ s(f, { children: [/* @__PURE__ */ o("h1", {
29
- className: "text-2xl font-bold text-text-primary",
30
- children: /* @__PURE__ */ o(p, { children: _("eventfullymanaged.registration.title", "Registration") })
31
- }), /* @__PURE__ */ o("p", {
32
- className: "mt-1 text-sm text-text-secondary",
33
- children: _("eventfullymanaged.registration.description", "Track registrations and attendee profiles via backend GraphQL APIs.")
34
- })] }),
35
- /* @__PURE__ */ o(i, {
36
- heading: y.loading ? _("eventfullymanaged.registration.registrations.loading", "Loading registrations...") : `${_("eventfullymanaged.registration.registrations.heading", "Registrations")} (${S.length})`,
37
- emptyLabel: _("eventfullymanaged.registration.registrations.empty", "No registrations found."),
38
- rows: S.map((e) => ({
39
- id: e.id,
40
- title: e.attendeeId ? `${_("eventfullymanaged.registration.registrations.attendeePrefix", "Attendee")} ${e.attendeeId}` : `${_("eventfullymanaged.registration.registrations.itemPrefix", "Registration")} ${e.id}`,
41
- subtitle: [e.quantity ? `${_("eventfullymanaged.registration.registrations.quantityPrefix", "Qty")} ${e.quantity}` : void 0, e.totalAmount ? `${e.totalAmount} ${e.currency ?? ""}` : void 0].filter(Boolean).join(" • "),
42
- status: e.status
43
- }))
58
+ /* @__PURE__ */ f("h2", {
59
+ className: "text-lg font-semibold text-text-primary",
60
+ children: i ? "Loading registrations…" : `Registrations (${b})`
61
+ }),
62
+ v.length === 0 && !i ? /* @__PURE__ */ f(w, {
63
+ illustration: "empty-data",
64
+ title: "No registrations yet.",
65
+ description: "Register your first attendee to start tracking sign-ups.",
66
+ className: "mt-4"
67
+ }) : /* @__PURE__ */ f("ul", {
68
+ className: "mt-4 space-y-2",
69
+ children: v.map((e) => {
70
+ let t = D.filter((t) => t !== (e.status ?? "").toUpperCase()).map((t, n) => ({
71
+ key: t,
72
+ label: `Set ${t.replace("_", " ").toLowerCase()}`,
73
+ onSelect: () => void x(e, t),
74
+ separatorBefore: n === 0
75
+ }));
76
+ return /* @__PURE__ */ p("li", {
77
+ className: "flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2",
78
+ children: [/* @__PURE__ */ p("div", {
79
+ className: "min-w-0",
80
+ children: [/* @__PURE__ */ f("p", {
81
+ className: "truncate text-sm font-medium text-text-primary",
82
+ children: _(e.attendeeId) ?? `Registration ${e.id}`
83
+ }), /* @__PURE__ */ f("p", {
84
+ className: "truncate text-xs text-text-secondary",
85
+ children: [`Qty ${e.quantity}`, e.totalAmount ? `${e.totalAmount} ${e.currency}` : void 0].filter(Boolean).join(" • ")
86
+ })]
87
+ }), /* @__PURE__ */ p("div", {
88
+ className: "flex shrink-0 items-center gap-2",
89
+ children: [/* @__PURE__ */ f(y, {
90
+ variant: O(e.status),
91
+ children: e.status ?? "UNKNOWN"
92
+ }), /* @__PURE__ */ f(s, {
93
+ ariaLabel: "Registration actions",
94
+ extraItems: t
95
+ })]
96
+ })]
97
+ }, e.id);
98
+ })
44
99
  }),
45
- /* @__PURE__ */ o(i, {
46
- heading: b.loading ? _("eventfullymanaged.registration.attendees.loading", "Loading attendees...") : `${_("eventfullymanaged.registration.attendees.heading", "Attendees")} (${C.length})`,
47
- emptyLabel: _("eventfullymanaged.registration.attendees.empty", "No attendees found."),
48
- emptyIllustration: "empty-people",
49
- rows: C.map((e) => ({
50
- id: e.id,
51
- title: e.fullName ?? e.id,
52
- subtitle: [
53
- e.email,
54
- e.company,
55
- e.role
56
- ].filter(Boolean).join(" • ")
57
- }))
100
+ /* @__PURE__ */ f(c, {
101
+ offset: e.offset,
102
+ limit: e.limit,
103
+ loadedCount: v.length,
104
+ total: b,
105
+ onPrev: e.prevPage,
106
+ onNext: e.nextPage,
107
+ itemLabel: "registrations",
108
+ loading: i
109
+ })
110
+ ]
111
+ });
112
+ }
113
+ function A() {
114
+ let { navigate: t } = e(), r = l({ pageSize: 10 }), { data: i, loading: a } = n({
115
+ limit: r.limit,
116
+ offset: r.offset
117
+ }), o = d(() => i?.attendees.items ?? [], [i]), u = i?.attendees.total ?? 0;
118
+ return /* @__PURE__ */ p(S, {
119
+ treatment: "plain",
120
+ className: "border-border-seam p-5",
121
+ children: [
122
+ /* @__PURE__ */ p("div", {
123
+ className: "flex items-center justify-between gap-3",
124
+ children: [/* @__PURE__ */ f("h2", {
125
+ className: "text-lg font-semibold text-text-primary",
126
+ children: a ? "Loading attendees…" : `Attendees (${u})`
127
+ }), /* @__PURE__ */ p(b, {
128
+ type: "button",
129
+ size: "sm",
130
+ variant: "outline",
131
+ onClick: () => t("/registration/attendees/create"),
132
+ children: [/* @__PURE__ */ f(_, { className: "h-4 w-4" }), "Add attendee"]
133
+ })]
58
134
  }),
59
- /* @__PURE__ */ o(i, {
60
- heading: x.loading ? _("eventfullymanaged.registration.promoCodes.loading", "Loading promo codes...") : `${_("eventfullymanaged.registration.promoCodes.heading", "Promo Codes")} (${w.length})`,
61
- emptyLabel: _("eventfullymanaged.registration.promoCodes.empty", "No promo codes configured."),
62
- rows: w.map((e) => ({
63
- id: e.id,
64
- title: e.code,
65
- subtitle: `${e.discountType} ${e.discountValue}`,
66
- status: e.isActive ? "active" : "inactive"
67
- }))
135
+ o.length === 0 && !a ? /* @__PURE__ */ f(w, {
136
+ illustration: "empty-people",
137
+ title: "No attendees yet.",
138
+ description: "Add attendees or register one to build your guest list.",
139
+ className: "mt-4"
140
+ }) : /* @__PURE__ */ f("ul", {
141
+ className: "mt-4 space-y-2",
142
+ children: o.map((e) => /* @__PURE__ */ p("li", {
143
+ className: "flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2",
144
+ children: [/* @__PURE__ */ p("div", {
145
+ className: "min-w-0",
146
+ children: [/* @__PURE__ */ f("p", {
147
+ className: "truncate text-sm font-medium text-text-primary",
148
+ children: e.fullName
149
+ }), /* @__PURE__ */ f("p", {
150
+ className: "truncate text-xs text-text-secondary",
151
+ children: [
152
+ e.email,
153
+ e.company,
154
+ e.role
155
+ ].filter(Boolean).join(" • ")
156
+ })]
157
+ }), /* @__PURE__ */ f(s, {
158
+ ariaLabel: `Actions for ${e.fullName}`,
159
+ onView: () => t(`/attendees/${e.id}/profile`),
160
+ onEdit: () => t(`/registration/attendees/${e.id}/edit`)
161
+ })]
162
+ }, e.id))
68
163
  }),
69
- /* @__PURE__ */ o(m, {
164
+ /* @__PURE__ */ f(c, {
165
+ offset: r.offset,
166
+ limit: r.limit,
167
+ loadedCount: o.length,
168
+ total: u,
169
+ onPrev: r.prevPage,
170
+ onNext: r.nextPage,
171
+ itemLabel: "attendees",
172
+ loading: a
173
+ })
174
+ ]
175
+ });
176
+ }
177
+ function j() {
178
+ let { navigate: n } = e(), a = l({ pageSize: 10 }), { data: o, loading: u } = i({
179
+ limit: a.limit,
180
+ offset: a.offset
181
+ }), [m] = r(), h = d(() => o?.promoCodes.items ?? [], [o]), _ = o?.promoCodes.total ?? 0, v = async (e) => {
182
+ let n = await m({ variables: { id: e.id } });
183
+ if (n.error) throw t.error("Could not delete promo code", n.error.message), n.error;
184
+ t.success("Promo code deleted", e.code);
185
+ };
186
+ return /* @__PURE__ */ p(S, {
187
+ treatment: "plain",
188
+ className: "border-border-seam p-5",
189
+ children: [
190
+ /* @__PURE__ */ p("div", {
191
+ className: "flex items-center justify-between gap-3",
192
+ children: [/* @__PURE__ */ f("h2", {
193
+ className: "text-lg font-semibold text-text-primary",
194
+ children: u ? "Loading promo codes…" : `Promo Codes (${_})`
195
+ }), /* @__PURE__ */ p(b, {
196
+ type: "button",
197
+ size: "sm",
198
+ variant: "outline",
199
+ onClick: () => n("/registration/promo-codes/create"),
200
+ children: [/* @__PURE__ */ f(g, { className: "h-4 w-4" }), "New promo code"]
201
+ })]
202
+ }),
203
+ h.length === 0 && !u ? /* @__PURE__ */ f(w, {
204
+ illustration: "empty-data",
205
+ title: "No promo codes yet.",
206
+ description: "Create a code to offer discounts and drive registrations.",
207
+ className: "mt-4"
208
+ }) : /* @__PURE__ */ f("ul", {
209
+ className: "mt-4 space-y-2",
210
+ children: h.map((e) => /* @__PURE__ */ p("li", {
211
+ className: "flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2",
212
+ children: [/* @__PURE__ */ p("div", {
213
+ className: "min-w-0",
214
+ children: [/* @__PURE__ */ f("p", {
215
+ className: "truncate text-sm font-medium text-text-primary",
216
+ children: e.code
217
+ }), /* @__PURE__ */ p("p", {
218
+ className: "truncate text-xs text-text-secondary",
219
+ children: [e.discountType === "PERCENT" ? `${e.discountValue}% off` : `${e.discountValue} off`, e.maxUses == null ? ` • ${e.usedCount} used` : ` • ${e.usedCount}/${e.maxUses} used`]
220
+ })]
221
+ }), /* @__PURE__ */ p("div", {
222
+ className: "flex shrink-0 items-center gap-2",
223
+ children: [/* @__PURE__ */ f(y, {
224
+ variant: e.isActive ? "default" : "secondary",
225
+ children: e.isActive ? "Active" : "Inactive"
226
+ }), /* @__PURE__ */ f(s, {
227
+ ariaLabel: `Actions for ${e.code}`,
228
+ onEdit: () => n(`/registration/promo-codes/${e.id}/edit`),
229
+ onDelete: () => v(e),
230
+ entityName: e.code
231
+ })]
232
+ })]
233
+ }, e.id))
234
+ }),
235
+ /* @__PURE__ */ f(c, {
236
+ offset: a.offset,
237
+ limit: a.limit,
238
+ loadedCount: h.length,
239
+ total: _,
240
+ onPrev: a.prevPage,
241
+ onNext: a.nextPage,
242
+ itemLabel: "promo codes",
243
+ loading: u
244
+ })
245
+ ]
246
+ });
247
+ }
248
+ function M() {
249
+ let { navigate: t } = e();
250
+ return /* @__PURE__ */ p("div", {
251
+ className: "space-y-6 p-6",
252
+ children: [
253
+ /* @__PURE__ */ f(E, { children: "This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a registration's status, and manage discount codes — all backed by live GraphQL APIs." }),
254
+ /* @__PURE__ */ f(x, { children: /* @__PURE__ */ p("div", {
255
+ className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
256
+ children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
257
+ className: "text-2xl font-bold text-text-primary",
258
+ children: /* @__PURE__ */ f(C, { children: "Registration" })
259
+ }), /* @__PURE__ */ f("p", {
260
+ className: "mt-1 text-sm text-text-secondary",
261
+ children: "Track registrations, attendees, and promo codes with backend persistence."
262
+ })] }), /* @__PURE__ */ p("div", {
263
+ className: "flex flex-wrap gap-2",
264
+ children: [
265
+ /* @__PURE__ */ p(b, {
266
+ type: "button",
267
+ onClick: () => t("/registration/register"),
268
+ children: [/* @__PURE__ */ f(g, { className: "h-4 w-4" }), "Register attendee"]
269
+ }),
270
+ /* @__PURE__ */ p(b, {
271
+ type: "button",
272
+ variant: "outline",
273
+ onClick: () => t("/registration/attendees/create"),
274
+ children: [/* @__PURE__ */ f(_, { className: "h-4 w-4" }), "Add attendee"]
275
+ }),
276
+ /* @__PURE__ */ p(b, {
277
+ type: "button",
278
+ variant: "outline",
279
+ onClick: () => t("/registration/promo-codes/create"),
280
+ children: [/* @__PURE__ */ f(h, { className: "h-4 w-4" }), "New promo code"]
281
+ })
282
+ ]
283
+ })]
284
+ }) }),
285
+ /* @__PURE__ */ f(k, {}),
286
+ /* @__PURE__ */ f(A, {}),
287
+ /* @__PURE__ */ f(j, {}),
288
+ /* @__PURE__ */ f(T, {
70
289
  storageKey: "eventfullymanaged-registration",
71
- title: _("eventfullymanaged.registration.nextSteps.title", "Grow your registrations"),
290
+ title: "Grow your registrations",
72
291
  steps: [
73
292
  {
74
293
  id: "view-networking",
75
- label: _("eventfullymanaged.registration.nextSteps.networking", "Set up attendee networking"),
76
- description: _("eventfullymanaged.registration.nextSteps.networkingDesc", "Turn registered attendees into connections with matchmaking."),
77
- icon: /* @__PURE__ */ o(u, { className: "h-4 w-4" }),
78
- onClick: () => v("/networking")
294
+ label: "Set up attendee networking",
295
+ description: "Turn registered attendees into connections with matchmaking.",
296
+ icon: /* @__PURE__ */ f(v, { className: "h-4 w-4" }),
297
+ onClick: () => t("/networking")
79
298
  },
80
299
  {
81
300
  id: "promote",
82
- label: _("eventfullymanaged.registration.nextSteps.promote", "Promote with a campaign"),
83
- description: _("eventfullymanaged.registration.nextSteps.promoteDesc", "Send reminders and offers to drive more sign-ups."),
84
- icon: /* @__PURE__ */ o(c, { className: "h-4 w-4" }),
85
- onClick: () => v("/communication")
301
+ label: "Promote with a campaign",
302
+ description: "Send reminders and offers to drive more sign-ups.",
303
+ icon: /* @__PURE__ */ f(m, { className: "h-4 w-4" }),
304
+ onClick: () => t("/communication")
86
305
  },
87
306
  {
88
307
  id: "analyze",
89
- label: _("eventfullymanaged.registration.nextSteps.analyze", "Snapshot your numbers"),
90
- description: _("eventfullymanaged.registration.nextSteps.analyzeDesc", "Capture an analytics snapshot to track registration trends."),
91
- icon: /* @__PURE__ */ o(l, { className: "h-4 w-4" }),
92
- onClick: () => v("/analytics")
308
+ label: "Snapshot your numbers",
309
+ description: "Capture an analytics snapshot to track registration trends.",
310
+ icon: /* @__PURE__ */ f(h, { className: "h-4 w-4" }),
311
+ onClick: () => t("/analytics")
93
312
  }
94
313
  ]
95
314
  })
@@ -97,6 +316,6 @@ function g() {
97
316
  });
98
317
  }
99
318
  //#endregion
100
- export { g as default };
319
+ export { M as default };
101
320
 
102
321
  //# sourceMappingURL=RegistrationPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Megaphone, Tag, Users } from 'lucide-react';\nimport { EmphasisPanel, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport { useAttendeesQuery, usePromoCodesQuery, useRegistrationsQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\ntype RegistrationRow = {\n id: string;\n attendeeId?: string;\n status?: string;\n quantity?: number;\n currency?: string;\n totalAmount?: string;\n};\n\ntype AttendeeRow = {\n id: string;\n fullName?: string;\n email?: string;\n company?: string;\n role?: string;\n};\n\nexport default function RegistrationPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { navigate } = useEventfullyManaged();\n const registrationsQuery = useRegistrationsQuery({ limit: 100, offset: 0 });\n const attendeesQuery = useAttendeesQuery({ limit: 100, offset: 0 });\n const promoCodesQuery = usePromoCodesQuery({ limit: 100, offset: 0 });\n\n const registrations = useMemo(\n () => (registrationsQuery.data?.registrations.items ?? []) as RegistrationRow[],\n [registrationsQuery.data]\n );\n const attendees = useMemo(() => (attendeesQuery.data?.attendees.items ?? []) as AttendeeRow[], [attendeesQuery.data]);\n const promoCodes = useMemo(() => promoCodesQuery.data?.promoCodes.items ?? [], [promoCodesQuery.data]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n {t(\n 'eventfullymanaged.registration.purpose',\n 'This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people behind them, and promo codes let you offer discounts. Use this page to monitor sign-up volume, look up an attendee, and check that your discount codes are working.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{t('eventfullymanaged.registration.title', 'Registration')}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {t(\n 'eventfullymanaged.registration.description',\n 'Track registrations and attendee profiles via backend GraphQL APIs.'\n )}\n </p>\n </EmphasisPanel>\n\n <EntityListSection\n heading={\n registrationsQuery.loading\n ? t('eventfullymanaged.registration.registrations.loading', 'Loading registrations...')\n : `${t('eventfullymanaged.registration.registrations.heading', 'Registrations')} (${registrations.length})`\n }\n emptyLabel={t('eventfullymanaged.registration.registrations.empty', 'No registrations found.')}\n rows={registrations.map((row) => ({\n id: row.id,\n title: row.attendeeId\n ? `${t('eventfullymanaged.registration.registrations.attendeePrefix', 'Attendee')} ${row.attendeeId}`\n : `${t('eventfullymanaged.registration.registrations.itemPrefix', 'Registration')} ${row.id}`,\n subtitle: [\n row.quantity\n ? `${t('eventfullymanaged.registration.registrations.quantityPrefix', 'Qty')} ${row.quantity}`\n : undefined,\n row.totalAmount ? `${row.totalAmount} ${row.currency ?? ''}` : undefined,\n ]\n .filter(Boolean)\n .join(' • '),\n status: row.status,\n }))}\n />\n\n <EntityListSection\n heading={\n attendeesQuery.loading\n ? t('eventfullymanaged.registration.attendees.loading', 'Loading attendees...')\n : `${t('eventfullymanaged.registration.attendees.heading', 'Attendees')} (${attendees.length})`\n }\n emptyLabel={t('eventfullymanaged.registration.attendees.empty', 'No attendees found.')}\n emptyIllustration=\"empty-people\"\n rows={attendees.map((row) => ({\n id: row.id,\n title: row.fullName ?? row.id,\n subtitle: [row.email, row.company, row.role].filter(Boolean).join(' • '),\n }))}\n />\n\n <EntityListSection\n heading={\n promoCodesQuery.loading\n ? t('eventfullymanaged.registration.promoCodes.loading', 'Loading promo codes...')\n : `${t('eventfullymanaged.registration.promoCodes.heading', 'Promo Codes')} (${promoCodes.length})`\n }\n emptyLabel={t('eventfullymanaged.registration.promoCodes.empty', 'No promo codes configured.')}\n rows={promoCodes.map((row) => ({\n id: row.id,\n title: row.code,\n subtitle: `${row.discountType} ${row.discountValue}`,\n status: row.isActive ? 'active' : 'inactive',\n }))}\n />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title={t('eventfullymanaged.registration.nextSteps.title', 'Grow your registrations')}\n steps={[\n {\n id: 'view-networking',\n label: t('eventfullymanaged.registration.nextSteps.networking', 'Set up attendee networking'),\n description: t(\n 'eventfullymanaged.registration.nextSteps.networkingDesc',\n 'Turn registered attendees into connections with matchmaking.'\n ),\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: t('eventfullymanaged.registration.nextSteps.promote', 'Promote with a campaign'),\n description: t(\n 'eventfullymanaged.registration.nextSteps.promoteDesc',\n 'Send reminders and offers to drive more sign-ups.'\n ),\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: t('eventfullymanaged.registration.nextSteps.analyze', 'Snapshot your numbers'),\n description: t(\n 'eventfullymanaged.registration.nextSteps.analyzeDesc',\n 'Capture an analytics snapshot to track registration trends.'\n ),\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { Megaphone, Tag, TicketPlus, UserPlus, Users } from 'lucide-react';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeletePromoCodeMutation,\n usePromoCodesQuery,\n useRegistrationsQuery,\n useUpdateRegistrationMutation,\n type AttendeeGql,\n type PromoCodeGql,\n type RegistrationGql,\n} from '../../api/eventApi';\nimport { ActionMenu, type ActionMenuItem } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst REGISTRATION_STATUSES = ['PENDING', 'CONFIRMED', 'WAITLISTED', 'CANCELLED', 'CHECKED_IN'] as const;\n\nfunction statusVariant(status?: string | null): 'default' | 'secondary' | 'destructive' | 'outline' {\n switch ((status ?? '').toUpperCase()) {\n case 'CONFIRMED':\n case 'CHECKED_IN':\n return 'default';\n case 'CANCELLED':\n return 'destructive';\n case 'WAITLISTED':\n return 'outline';\n default:\n return 'secondary';\n }\n}\n\nfunction RegistrationsSection() {\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useRegistrationsQuery({ limit: pagination.limit, offset: pagination.offset });\n const [updateRegistration] = useUpdateRegistrationMutation();\n\n // Resolve attendee display names (the registrations query returns only\n // attendeeId). Pull a page of attendees to build an id -> name lookup so rows\n // read \"Ada Lovelace\" instead of a raw UUID; fall back to a short id.\n const { data: attendeesData } = useAttendeesQuery({ limit: 200, offset: 0 });\n const nameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees.items ?? []) {\n if (attendee.id && attendee.fullName) map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n const attendeeLabel = (attendeeId?: string | null): string | null => {\n if (!attendeeId) return null;\n return nameById.get(attendeeId) ?? `Attendee ${attendeeId.slice(0, 8)}…`;\n };\n\n const items = useMemo<RegistrationGql[]>(() => data?.registrations.items ?? [], [data]);\n const total = data?.registrations.total ?? 0;\n\n const changeStatus = async (row: RegistrationGql, status: string) => {\n const response = await updateRegistration({\n variables: {\n id: row.id,\n input: {\n eventId: row.eventId,\n attendeeId: row.attendeeId ?? null,\n ticketTypeId: row.ticketTypeId ?? null,\n quantity: row.quantity,\n currency: row.currency,\n totalAmount: row.totalAmount ?? null,\n status,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateRegistration?.id, 'Could not update registration')) {\n return;\n }\n notify.success('Registration updated', `Status set to ${status}`);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading registrations…' : `Registrations (${total})`}\n </h2>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No registrations yet.\"\n description=\"Register your first attendee to start tracking sign-ups.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => {\n const statusItems: ActionMenuItem[] = REGISTRATION_STATUSES.filter(\n (candidate) => candidate !== (row.status ?? '').toUpperCase()\n ).map((candidate, index) => ({\n key: candidate,\n label: `Set ${candidate.replace('_', ' ').toLowerCase()}`,\n onSelect: () => void changeStatus(row, candidate),\n separatorBefore: index === 0,\n }));\n return (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {attendeeLabel(row.attendeeId) ?? `Registration ${row.id}`}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[`Qty ${row.quantity}`, row.totalAmount ? `${row.totalAmount} ${row.currency}` : undefined]\n .filter(Boolean)\n .join(' • ')}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={statusVariant(row.status)}>{row.status ?? 'UNKNOWN'}</Badge>\n <ActionMenu ariaLabel=\"Registration actions\" extraItems={statusItems} />\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"registrations\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction AttendeesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useAttendeesQuery({ limit: pagination.limit, offset: pagination.offset });\n\n const items = useMemo<AttendeeGql[]>(() => data?.attendees.items ?? [], [data]);\n const total = data?.attendees.total ?? 0;\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading attendees…' : `Attendees (${total})`}\n </h2>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No attendees yet.\"\n description=\"Add attendees or register one to build your guest list.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.fullName}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[row.email, row.company, row.role].filter(Boolean).join(' • ')}\n </p>\n </div>\n <ActionMenu\n ariaLabel={`Actions for ${row.fullName}`}\n onView={() => navigate(`/attendees/${row.id}/profile`)}\n onEdit={() => navigate(`/registration/attendees/${row.id}/edit`)}\n />\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"attendees\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nfunction PromoCodesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = usePromoCodesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deletePromoCode] = useDeletePromoCodeMutation();\n\n const items = useMemo<PromoCodeGql[]>(() => data?.promoCodes.items ?? [], [data]);\n const total = data?.promoCodes.total ?? 0;\n\n const onDelete = async (row: PromoCodeGql) => {\n const response = await deletePromoCode({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete promo code', response.error.message);\n throw response.error;\n }\n notify.success('Promo code deleted', row.code);\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? 'Loading promo codes…' : `Promo Codes (${total})`}\n </h2>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <TicketPlus className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n {items.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No promo codes yet.\"\n description=\"Create a code to offer discounts and drive registrations.\"\n className=\"mt-4\"\n />\n ) : (\n <ul className=\"mt-4 space-y-2\">\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex items-center justify-between gap-3 rounded-input border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{row.code}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {row.discountType === 'PERCENT' ? `${row.discountValue}% off` : `${row.discountValue} off`}\n {row.maxUses != null ? ` • ${row.usedCount}/${row.maxUses} used` : ` • ${row.usedCount} used`}\n </p>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Badge variant={row.isActive ? 'default' : 'secondary'}>{row.isActive ? 'Active' : 'Inactive'}</Badge>\n <ActionMenu\n ariaLabel={`Actions for ${row.code}`}\n onEdit={() => navigate(`/registration/promo-codes/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n entityName={row.code}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"promo codes\"\n loading={loading}\n />\n </GlassCard>\n );\n}\n\nexport default function RegistrationPage() {\n const { navigate } = useEventfullyManaged();\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people\n behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a\n registration&apos;s status, and manage discount codes — all backed by live GraphQL APIs.\n </PagePurpose>\n\n <EmphasisPanel>\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\">\n <GradientText>Registration</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Track registrations, attendees, and promo codes with backend persistence.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" onClick={() => navigate('/registration/register')}>\n <TicketPlus className=\"h-4 w-4\" />\n Register attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <Tag className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <RegistrationsSection />\n <AttendeesSection />\n <PromoCodesSection />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title=\"Grow your registrations\"\n steps={[\n {\n id: 'view-networking',\n label: 'Set up attendee networking',\n description: 'Turn registered attendees into connections with matchmaking.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: 'Promote with a campaign',\n description: 'Send reminders and offers to drive more sign-ups.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: 'Snapshot your numbers',\n description: 'Capture an analytics snapshot to track registration trends.',\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { jsx as e, jsxs as t } from "react/jsx-runtime";
2
+ import { CircleDot as n, Clock as r, Compass as i, Sparkles as a } from "lucide-react";
3
+ import { Badge as o, EmphasisPanel as s, GlassCard as c, GradientText as l, PagePurpose as u } from "@burdenoff/fe-libs/ui";
4
+ //#region src/eventfullymanaged/pages/modules/RoadmapPanel.tsx
5
+ var d = {
6
+ exploring: {
7
+ label: "Exploring",
8
+ variant: "secondary",
9
+ Icon: i
10
+ },
11
+ planned: {
12
+ label: "Planned",
13
+ variant: "info",
14
+ Icon: r
15
+ },
16
+ "in-progress": {
17
+ label: "In progress",
18
+ variant: "warning",
19
+ Icon: n
20
+ }
21
+ };
22
+ function f({ title: n, summary: r, purpose: i, items: f }) {
23
+ return /* @__PURE__ */ t("div", {
24
+ className: "space-y-6 p-6",
25
+ children: [
26
+ /* @__PURE__ */ e(u, { children: i }),
27
+ /* @__PURE__ */ t(s, { children: [
28
+ /* @__PURE__ */ t("div", {
29
+ className: "flex items-center gap-2",
30
+ children: [/* @__PURE__ */ e(a, {
31
+ className: "h-5 w-5 text-action-primary-bg",
32
+ "aria-hidden": "true"
33
+ }), /* @__PURE__ */ e(o, {
34
+ variant: "secondary",
35
+ className: "uppercase",
36
+ children: "Coming soon"
37
+ })]
38
+ }),
39
+ /* @__PURE__ */ e("h1", {
40
+ className: "mt-3 text-2xl font-bold text-text-primary",
41
+ children: /* @__PURE__ */ e(l, { children: n })
42
+ }),
43
+ /* @__PURE__ */ e("p", {
44
+ className: "mt-1 text-sm text-text-secondary",
45
+ children: r
46
+ })
47
+ ] }),
48
+ /* @__PURE__ */ t("section", {
49
+ className: "space-y-3",
50
+ children: [/* @__PURE__ */ e("h2", {
51
+ className: "text-lg font-semibold text-text-primary",
52
+ children: "On the roadmap"
53
+ }), /* @__PURE__ */ e("div", {
54
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
55
+ children: f.map((n) => {
56
+ let r = d[n.stage], i = r.Icon;
57
+ return /* @__PURE__ */ t(c, {
58
+ treatment: "plain",
59
+ className: "border-border-seam p-5",
60
+ children: [/* @__PURE__ */ t("div", {
61
+ className: "flex items-start justify-between gap-3",
62
+ children: [/* @__PURE__ */ e("h3", {
63
+ className: "text-base font-semibold text-text-primary",
64
+ children: n.title
65
+ }), /* @__PURE__ */ t(o, {
66
+ variant: r.variant,
67
+ className: "shrink-0 gap-1.5 uppercase",
68
+ children: [/* @__PURE__ */ e(i, {
69
+ className: "h-3 w-3",
70
+ "aria-hidden": "true"
71
+ }), r.label]
72
+ })]
73
+ }), /* @__PURE__ */ e("p", {
74
+ className: "mt-2 text-sm text-text-secondary",
75
+ children: n.description
76
+ })]
77
+ }, n.id);
78
+ })
79
+ })]
80
+ }),
81
+ /* @__PURE__ */ e(c, {
82
+ treatment: "plain",
83
+ className: "border-border-seam p-5",
84
+ children: /* @__PURE__ */ e("p", {
85
+ className: "text-sm text-text-secondary",
86
+ children: "Nothing here is live yet. When these capabilities ship they will appear in this module with real data and controls — until then this page is an honest placeholder rather than a mock."
87
+ })
88
+ })
89
+ ]
90
+ });
91
+ }
92
+ //#endregion
93
+ export { f as default };
94
+
95
+ //# sourceMappingURL=RoadmapPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RoadmapPanel.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RoadmapPanel.tsx"],"sourcesContent":["import { CircleDot, Clock, Compass, Sparkles } from 'lucide-react';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, PagePurpose } from '@burdenoff/fe-libs/ui';\n\nexport interface RoadmapItem {\n id: string;\n title: string;\n description: string;\n /** Delivery stage — controls the badge styling only. */\n stage: 'exploring' | 'planned' | 'in-progress';\n}\n\nexport interface RoadmapPanelProps {\n /** Area name, e.g. \"API Access\" or \"Integrations\". */\n title: string;\n /** One-line honest summary of what this area is (and is not) today. */\n summary: string;\n /** Longer honest note explaining the current state. */\n purpose: string;\n items: RoadmapItem[];\n}\n\nconst STAGE_META: Record<\n RoadmapItem['stage'],\n { label: string; variant: 'info' | 'warning' | 'secondary'; Icon: typeof Clock }\n> = {\n exploring: { label: 'Exploring', variant: 'secondary', Icon: Compass },\n planned: { label: 'Planned', variant: 'info', Icon: Clock },\n 'in-progress': { label: 'In progress', variant: 'warning', Icon: CircleDot },\n};\n\n/**\n * Honest \"coming soon\" / roadmap surface for EventfullyManaged areas that do not\n * yet have a real backend feature behind them. It deliberately renders NO live\n * data and makes no functional claims — it only communicates intent and stage,\n * so the app never mislabels a generic entity dashboard as a shipped capability.\n */\nexport default function RoadmapPanel({ title, summary, purpose, items }: RoadmapPanelProps) {\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>{purpose}</PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"h-5 w-5 text-action-primary-bg\" aria-hidden=\"true\" />\n <Badge variant=\"secondary\" className=\"uppercase\">\n Coming soon\n </Badge>\n </div>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">\n <GradientText>{title}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">{summary}</p>\n </EmphasisPanel>\n\n <section className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">On the roadmap</h2>\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {items.map((item) => {\n const meta = STAGE_META[item.stage];\n const StageIcon = meta.Icon;\n return (\n <GlassCard key={item.id} treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">{item.title}</h3>\n <Badge variant={meta.variant} className=\"shrink-0 gap-1.5 uppercase\">\n <StageIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {meta.label}\n </Badge>\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">{item.description}</p>\n </GlassCard>\n );\n })}\n </div>\n </section>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <p className=\"text-sm text-text-secondary\">\n Nothing here is live yet. When these capabilities ship they will appear in this module with real data and\n controls — until then this page is an honest placeholder rather than a mock.\n </p>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":";;;;AAqBA,IAAM,IAGF;CACF,WAAW;EAAE,OAAO;EAAa,SAAS;EAAa,MAAM;EAAS;CACtE,SAAS;EAAE,OAAO;EAAW,SAAS;EAAQ,MAAM;EAAO;CAC3D,eAAe;EAAE,OAAO;EAAe,SAAS;EAAW,MAAM;EAAW;CAC7E;AAQD,SAAwB,EAAa,EAAE,UAAO,YAAS,YAAS,YAA4B;AAC1F,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAc,GAAsB,CAAA;GAEpC,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAU,WAAU;MAAiC,eAAY;MAAS,CAAA,EAC1E,kBAAC,GAAD;MAAO,SAAQ;MAAY,WAAU;gBAAY;MAEzC,CAAA,CACJ;;IACN,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAe,GAAqB,CAAA;KACjC,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAY,CAAA;IAC/C,EAAA,CAAA;GAEhB,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAmB,CAAA,EAC3E,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,MAAS;MACnB,IAAM,IAAO,EAAW,EAAK,QACvB,IAAY,EAAK;AACvB,aACE,kBAAC,GAAD;OAAyB,WAAU;OAAQ,WAAU;iBAArD,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAA6C,EAAK;SAAW,CAAA,EAC3E,kBAAC,GAAD;SAAO,SAAS,EAAK;SAAS,WAAU;mBAAxC,CACE,kBAAC,GAAD;UAAW,WAAU;UAAU,eAAY;UAAS,CAAA,EACnD,EAAK,MACA;WACJ;WACN,kBAAC,KAAD;QAAG,WAAU;kBAAoC,EAAK;QAAgB,CAAA,CAC5D;SATI,EAAK,GAST;OAEd;KACE,CAAA,CACE;;GAEV,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,KAAD;KAAG,WAAU;eAA8B;KAGvC,CAAA;IACM,CAAA;GACR"}