@burdenoff/microfe-eventfullymanaged 2026.704.1 → 2026.704.3
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.js +0 -4
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +2 -2
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +13 -12
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +18 -11
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +2 -2
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +2 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,11 +26,18 @@ function O(e) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function k() {
|
|
29
|
-
let e = l({ pageSize: 10 }), { data:
|
|
29
|
+
let e = l({ pageSize: 10 }), { data: r, loading: i } = a({
|
|
30
30
|
limit: e.limit,
|
|
31
31
|
offset: e.offset
|
|
32
|
-
}), [
|
|
33
|
-
|
|
32
|
+
}), [m] = o(), { data: h } = n({
|
|
33
|
+
limit: 200,
|
|
34
|
+
offset: 0
|
|
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: {
|
|
34
41
|
id: e.id,
|
|
35
42
|
input: {
|
|
36
43
|
eventId: e.eventId,
|
|
@@ -50,20 +57,20 @@ function k() {
|
|
|
50
57
|
children: [
|
|
51
58
|
/* @__PURE__ */ f("h2", {
|
|
52
59
|
className: "text-lg font-semibold text-text-primary",
|
|
53
|
-
children:
|
|
60
|
+
children: i ? "Loading registrations…" : `Registrations (${b})`
|
|
54
61
|
}),
|
|
55
|
-
|
|
62
|
+
v.length === 0 && !i ? /* @__PURE__ */ f(w, {
|
|
56
63
|
illustration: "empty-data",
|
|
57
64
|
title: "No registrations yet.",
|
|
58
65
|
description: "Register your first attendee to start tracking sign-ups.",
|
|
59
66
|
className: "mt-4"
|
|
60
67
|
}) : /* @__PURE__ */ f("ul", {
|
|
61
68
|
className: "mt-4 space-y-2",
|
|
62
|
-
children:
|
|
69
|
+
children: v.map((e) => {
|
|
63
70
|
let t = D.filter((t) => t !== (e.status ?? "").toUpperCase()).map((t, n) => ({
|
|
64
71
|
key: t,
|
|
65
72
|
label: `Set ${t.replace("_", " ").toLowerCase()}`,
|
|
66
|
-
onSelect: () => void
|
|
73
|
+
onSelect: () => void x(e, t),
|
|
67
74
|
separatorBefore: n === 0
|
|
68
75
|
}));
|
|
69
76
|
return /* @__PURE__ */ p("li", {
|
|
@@ -72,7 +79,7 @@ function k() {
|
|
|
72
79
|
className: "min-w-0",
|
|
73
80
|
children: [/* @__PURE__ */ f("p", {
|
|
74
81
|
className: "truncate text-sm font-medium text-text-primary",
|
|
75
|
-
children: e.attendeeId
|
|
82
|
+
children: _(e.attendeeId) ?? `Registration ${e.id}`
|
|
76
83
|
}), /* @__PURE__ */ f("p", {
|
|
77
84
|
className: "truncate text-xs text-text-secondary",
|
|
78
85
|
children: [`Qty ${e.quantity}`, e.totalAmount ? `${e.totalAmount} ${e.currency}` : void 0].filter(Boolean).join(" • ")
|
|
@@ -93,12 +100,12 @@ function k() {
|
|
|
93
100
|
/* @__PURE__ */ f(c, {
|
|
94
101
|
offset: e.offset,
|
|
95
102
|
limit: e.limit,
|
|
96
|
-
loadedCount:
|
|
97
|
-
total:
|
|
103
|
+
loadedCount: v.length,
|
|
104
|
+
total: b,
|
|
98
105
|
onPrev: e.prevPage,
|
|
99
106
|
onNext: e.nextPage,
|
|
100
107
|
itemLabel: "registrations",
|
|
101
|
-
loading:
|
|
108
|
+
loading: i
|
|
102
109
|
})
|
|
103
110
|
]
|
|
104
111
|
});
|
|
@@ -1 +1 @@
|
|
|
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 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 {row.attendeeId ? `Attendee ${row.attendeeId}` : `Registration ${row.id}`}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[\n `Qty ${row.quantity}`,\n row.totalAmount ? `${row.totalAmount} ${row.currency}` : undefined,\n ]\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\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => navigate('/registration/promo-codes/create')}\n >\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'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":";;;;;;;;;;;AA6BA,IAAM,IAAwB;CAAC;CAAW;CAAa;CAAc;CAAa;CAAa;AAE/F,SAAS,EAAc,GAA6E;AAClG,UAAS,KAAU,IAAI,aAAa,EAApC;EACE,KAAK;EACL,KAAK,aACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,aACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,IAAuB;CAC9B,IAAM,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAsB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EACjG,CAAC,KAAsB,GAA+B,EAEtD,IAAQ,QAAiC,GAAM,cAAc,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EACjF,IAAQ,GAAM,cAAc,SAAS,GAErC,IAAe,OAAO,GAAsB,MAAmB;EACnE,IAAM,IAAW,MAAM,EAAmB,EACxC,WAAW;GACT,IAAI,EAAI;GACR,OAAO;IACL,SAAS,EAAI;IACb,YAAY,EAAI,cAAc;IAC9B,cAAc,EAAI,gBAAgB;IAClC,UAAU,EAAI;IACd,UAAU,EAAI;IACd,aAAa,EAAI,eAAe;IAChC;IACD;GACF,EACF,CAAC;AACG,IAAkB,GAAU,EAAS,MAAM,oBAAoB,IAAI,gCAAgC,IAGxG,EAAO,QAAQ,wBAAwB,iBAAiB,IAAS;;AAGnE,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,MAAD;IAAI,WAAU;cACX,IAAU,2BAA2B,kBAAkB,EAAM;IAC3D,CAAA;GACJ,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MAAQ;KAClB,IAAM,IAAgC,EAAsB,QACzD,MAAc,OAAe,EAAI,UAAU,IAAI,aAAa,CAC9D,CAAC,KAAK,GAAW,OAAW;MAC3B,KAAK;MACL,OAAO,OAAO,EAAU,QAAQ,KAAK,IAAI,CAAC,aAAa;MACvD,gBAAgB,KAAK,EAAa,GAAK,EAAU;MACjD,iBAAiB,MAAU;MAC5B,EAAE;AACH,YACE,kBAAC,MAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI,aAAa,YAAY,EAAI,eAAe,gBAAgB,EAAI;QACnE,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,CACC,OAAO,EAAI,YACX,EAAI,cAAc,GAAG,EAAI,YAAY,GAAG,EAAI,aAAa,KAAA,EAC1D,CACE,OAAO,QAAQ,CACf,KAAK,MAAM;QACZ,CAAA,CACA;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAS,EAAc,EAAI,OAAO;kBAAG,EAAI,UAAU;QAAkB,CAAA,EAC5E,kBAAC,GAAD;QAAY,WAAU;QAAuB,YAAY;QAAe,CAAA,CACpE;SACH;QApBE,EAAI,GAoBN;MAEP;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAS,IAAmB;CAC1B,IAAM,EAAE,gBAAa,GAAsB,EACrC,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAkB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EAE7F,IAAQ,QAA6B,GAAM,UAAU,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EACzE,IAAQ,GAAM,UAAU,SAAS;AAEvC,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,IAAU,uBAAuB,cAAc,EAAM;KACnD,CAAA,EACL,kBAAC,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAU,eAAe,EAAS,iCAAiC;eAA3G,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,eAEzB;OACL;;GACL,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MACV,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAI;OAAa,CAAA,EAChF,kBAAC,KAAD;OAAG,WAAU;iBACV;QAAC,EAAI;QAAO,EAAI;QAAS,EAAI;QAAK,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM;OAC7D,CAAA,CACA;SACN,kBAAC,GAAD;MACE,WAAW,eAAe,EAAI;MAC9B,cAAc,EAAS,cAAc,EAAI,GAAG,UAAU;MACtD,cAAc,EAAS,2BAA2B,EAAI,GAAG,OAAO;MAChE,CAAA,CACC;OAdE,EAAI,GAcN,CACL;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAS,IAAoB;CAC3B,IAAM,EAAE,gBAAa,GAAsB,EACrC,IAAa,EAAc,EAAE,UAAU,IAAI,CAAC,EAC5C,EAAE,SAAM,eAAY,EAAmB;EAAE,OAAO,EAAW;EAAO,QAAQ,EAAW;EAAQ,CAAC,EAC9F,CAAC,KAAmB,GAA4B,EAEhD,IAAQ,QAA8B,GAAM,WAAW,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAC3E,IAAQ,GAAM,WAAW,SAAS,GAElC,IAAW,OAAO,MAAsB;EAC5C,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW,EAAE,IAAI,EAAI,IAAI,EAAE,CAAC;AACrE,MAAI,EAAS,MAEX,OADA,EAAO,MAAM,+BAA+B,EAAS,MAAM,QAAQ,EAC7D,EAAS;AAEjB,IAAO,QAAQ,sBAAsB,EAAI,KAAK;;AAGhD,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,IAAU,yBAAyB,gBAAgB,EAAM;KACvD,CAAA,EACL,kBAAC,GAAD;KACE,MAAK;KACL,MAAK;KACL,SAAQ;KACR,eAAe,EAAS,mCAAmC;eAJ7D,CAME,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,iBAE3B;OACL;;GACL,EAAM,WAAW,KAAK,CAAC,IACtB,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,WAAU;IACV,CAAA,GAEF,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MACV,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAI;OAAS,CAAA,EAC5E,kBAAC,KAAD;OAAG,WAAU;iBAAb,CACG,EAAI,iBAAiB,YAAY,GAAG,EAAI,cAAc,SAAS,GAAG,EAAI,cAAc,OACpF,EAAI,WAAW,OAAmD,MAAM,EAAI,UAAU,SAAhE,MAAM,EAAI,UAAU,GAAG,EAAI,QAAQ,OACxD;SACA;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAO,SAAS,EAAI,WAAW,YAAY;iBAAc,EAAI,WAAW,WAAW;OAAmB,CAAA,EACtG,kBAAC,GAAD;OACE,WAAW,eAAe,EAAI;OAC9B,cAAc,EAAS,6BAA6B,EAAI,GAAG,OAAO;OAClE,gBAAgB,EAAS,EAAI;OAC7B,YAAY,EAAI;OAChB,CAAA,CACE;QACH;OAnBE,EAAI,GAmBN,CACL;IACC,CAAA;GAEP,kBAAC,GAAD;IACE,QAAQ,EAAW;IACnB,OAAO,EAAW;IAClB,aAAa,EAAM;IACZ;IACP,QAAQ,EAAW;IACnB,QAAQ,EAAW;IACnB,WAAU;IACD;IACT,CAAA;GACQ;;;AAIhB,SAAwB,IAAmB;CACzC,IAAM,EAAE,gBAAa,GAAsB;AAE3C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,8SAIC,CAAA;GAEd,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,gBAA2B,CAAA;KACtC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,eAAe,EAAS,yBAAyB;iBAAvE,CACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,EAAA,oBAE3B;;MACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,eAAe,EAAS,iCAAiC;iBAAjG,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,eAEzB;;MACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,eAAe,EAAS,mCAAmC;iBAAnG,CACE,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA,EAAA,iBAEpB;;MACL;OACF;OACQ,CAAA;GAEhB,kBAAC,GAAD,EAAwB,CAAA;GACxB,kBAAC,GAAD,EAAoB,CAAA;GACpB,kBAAC,GAAD,EAAqB,CAAA;GAErB,kBAAC,GAAD;IACE,YAAW;IACX,OAAM;IACN,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,eAAe,EAAS,cAAc;MACvC;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,iBAAiB;MAC1C;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,eAAe,EAAS,aAAa;MACtC;KACF;IACD,CAAA;GACE"}
|
|
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'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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"}
|
|
@@ -1 +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
|
|
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"}
|
|
@@ -11,11 +11,11 @@ import { ContextConversationWidget as _ } from "@burdenoff/fe-libs/shared";
|
|
|
11
11
|
import { useRegisterPageConversationContext as v } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
12
12
|
//#region src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx
|
|
13
13
|
function y({ sponsorId: e, sponsorName: t }) {
|
|
14
|
-
return v({
|
|
14
|
+
return v(a(() => ({
|
|
15
15
|
entityType: "EVENTFULLYMANAGED_SPONSOR",
|
|
16
16
|
entityId: e,
|
|
17
17
|
contextTitle: `Sponsor · ${t}`
|
|
18
|
-
}), /* @__PURE__ */ s(_, { widgetKey: "sponsor-message" });
|
|
18
|
+
}), [e, t])), /* @__PURE__ */ s(_, { widgetKey: "sponsor-message" });
|
|
19
19
|
}
|
|
20
20
|
function b() {
|
|
21
21
|
let { id: _ = "" } = o(), { navigate: v } = e(), b = n({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCommunicationCampaignsQuery, useSponsorsQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\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 useRegisterPageConversationContext({\n
|
|
1
|
+
{"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCommunicationCampaignsQuery, useSponsorsQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\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 SponsorRow = {\n id: string;\n name: string;\n level: string;\n website?: string | null;\n contactName?: string | null;\n contactEmail?: string | null;\n};\n\ntype CampaignRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nexport default function SponsorDetailsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const sponsorsQuery = useSponsorsQuery({ limit: 100, offset: 0 });\n const campaignsQuery = useCommunicationCampaignsQuery({ 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 campaigns = useMemo(\n () => (campaignsQuery.data?.communicationCampaigns.items ?? []) as CampaignRow[],\n [campaignsQuery.data]\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Everything about one sponsor in one place — their tier, contacts, and the campaigns they appear in. Use this to\n prep for a sponsor conversation, confirm their contact details, or check how their branded campaigns are\n performing.\n </PagePurpose>\n\n <EmphasisPanel>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Sponsor</GradientText> Details\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsorsQuery.loading\n ? 'Loading sponsor details...'\n : sponsor\n ? `Live sponsor profile for ${sponsor.name}.`\n : 'Sponsor not found in current query scope.'}\n </p>\n {sponsor ? (\n <div className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Level</p>\n <p className=\"mt-1 font-medium text-text-primary\">{sponsor.level}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Contact</p>\n <p className=\"mt-1 font-medium text-text-primary\">{sponsor.contactName ?? 'Unassigned'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Email</p>\n <p className=\"mt-1 font-medium text-text-primary\">{sponsor.contactEmail ?? 'Unavailable'}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-3\">\n <p className=\"text-xs uppercase tracking-wide text-text-secondary\">Website</p>\n <p className=\"mt-1 truncate font-medium text-text-primary\">{sponsor.website ?? 'Unavailable'}</p>\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 entityType=\"sponsor\" entityId={sponsor.id} label=\"Sponsor tags\" className=\"mt-3\" />\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/MeetingFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateMeetingMutation,\n useEventsQuery,\n useMeetingsQuery,\n useNetworkingProfilesQuery,\n useUpdateMeetingMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype MeetingFormState = {\n eventId: string;\n title: string;\n sourceProfileId: string;\n targetProfileId: string;\n startsAt: string;\n endsAt: string;\n location: string;\n bookingPageId: string;\n bookerName: string;\n bookerEmail: string;\n notes: string;\n status: string;\n};\n\nconst initialState: MeetingFormState = {\n eventId: '',\n title: '',\n sourceProfileId: '',\n targetProfileId: '',\n startsAt: '',\n endsAt: '',\n location: '',\n bookingPageId: '',\n bookerName: '',\n bookerEmail: '',\n notes: '',\n status: 'SCHEDULED',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\n/** Convert an ISO timestamp to the local `datetime-local` input format. */\nfunction isoToLocalInput(iso: string | undefined): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60 * 1000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** Convert a `datetime-local` value to an ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction readString(metadata: Record<string, unknown> | null | undefined, key: string): string {\n const value = metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nexport default function MeetingFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // The meetings list resolver returns title/status/metadata only (no by-id\n // resolver), so create/update mirror scheduling detail into metadata and the\n // edit initial-state is rehydrated from there.\n const { data: meetingsData } = useMeetingsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (meetingsData?.meetings?.items ?? []).find((item) => item.id === id),\n [meetingsData, id]\n );\n\n const [createMeeting, { loading: creating }] = useCreateMeetingMutation();\n const [updateMeeting, { loading: updating }] = useUpdateMeetingMutation();\n const [form, setForm] = useState<MeetingFormState>(initialState);\n\n const { data: profilesData } = useNetworkingProfilesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(\n () =>\n (profilesData?.networkingProfiles?.items ?? []).map((profile) => ({\n id: profile.id,\n label: attendeeNames.get(profile.attendeeId) ?? profile.role ?? `Profile ${profile.id.slice(0, 8)}`,\n })),\n [profilesData, attendeeNames]\n );\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n setForm({\n eventId: existing.eventId ?? readString(metadata, 'eventId'),\n title: existing.title ?? existing.name ?? readString(metadata, 'title'),\n sourceProfileId: readString(metadata, 'sourceProfileId'),\n targetProfileId: readString(metadata, 'targetProfileId'),\n startsAt: isoToLocalInput(readString(metadata, 'startsAt')),\n endsAt: isoToLocalInput(readString(metadata, 'endsAt')),\n location: readString(metadata, 'location'),\n bookingPageId: readString(metadata, 'bookingPageId'),\n bookerName: readString(metadata, 'bookerName'),\n bookerEmail: readString(metadata, 'bookerEmail'),\n notes: readString(metadata, 'notes'),\n status: existing.status ?? 'SCHEDULED',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof MeetingFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this meeting belongs to.');\n return;\n }\n const startsAtIso = localInputToIso(form.startsAt);\n const endsAtIso = localInputToIso(form.endsAt);\n if (!startsAtIso || !endsAtIso) {\n notify.error('Time required', 'Set both a start and end time for the meeting.');\n return;\n }\n if (new Date(endsAtIso).getTime() <= new Date(startsAtIso).getTime()) {\n notify.error('Invalid time range', 'The meeting must end after it starts.');\n return;\n }\n\n // Mirror scheduling detail into metadata so the calendar-svc integration and\n // the edit form (list resolver returns metadata only) both round-trip.\n const metadata: Record<string, unknown> = {\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n title: form.title,\n sourceProfileId: form.sourceProfileId,\n targetProfileId: form.targetProfileId,\n };\n if (form.bookingPageId.trim()) {\n metadata.bookingPageId = form.bookingPageId.trim();\n }\n if (form.bookerName.trim()) {\n metadata.bookerName = form.bookerName.trim();\n }\n if (form.bookerEmail.trim()) {\n metadata.bookerEmail = form.bookerEmail.trim();\n }\n\n const input: Record<string, unknown> = {\n eventId: form.eventId,\n title: form.title,\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n status: form.status,\n metadata,\n };\n if (form.sourceProfileId) {\n input.sourceProfileId = form.sourceProfileId;\n }\n if (form.targetProfileId) {\n input.targetProfileId = form.targetProfileId;\n }\n // Passing a booking-page id lets the backend mirror the meeting into\n // wspace-calendar-svc rather than the MFE managing a local calendar.\n if (form.bookingPageId.trim()) {\n input.bookingPageId = form.bookingPageId.trim();\n }\n\n try {\n if (isEdit) {\n const response = await updateMeeting({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateMeeting?.id, 'Could not update meeting')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createMeeting({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createMeeting?.id, 'Could not book meeting')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save meeting', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Meeting' : 'Book a Meeting'}\n description=\"Schedule a 1:1 between two networking profiles.\"\n purpose=\"Meetings are the 1:1s attendees book with each other. Pick the two participants, set a time and place, and — if you use a shared booking page — link it so the meeting mirrors into the calendar service. Everyone sees the same schedule without a separate calendar here.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Meeting' : 'Book Meeting'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} required />\n </label>\n </div>\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\">Participant A</span>\n <Select\n value={form.sourceProfileId}\n onValueChange={(value) => onChange('sourceProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant A\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant B</span>\n <Select\n value={form.targetProfileId}\n onValueChange={(value) => onChange('targetProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant B\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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\">Starts at</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends at</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"Networking lounge, Table 4\"\n />\n </label>\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\">Booking page ID</span>\n <Input\n value={form.bookingPageId}\n onChange={(e) => onChange('bookingPageId', e.target.value)}\n placeholder=\"Optional — mirrors to calendar\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker name</span>\n <Input value={form.bookerName} onChange={(e) => onChange('bookerName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker email</span>\n <Input\n type=\"email\"\n value={form.bookerEmail}\n onChange={(e) => onChange('bookerEmail', e.target.value)}\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Notes</span>\n <Textarea value={form.notes} onChange={(e) => onChange('notes', e.target.value)} rows={3} />\n </label>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeetingFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/MeetingFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateMeetingMutation,\n useEventsQuery,\n useMeetingsQuery,\n useNetworkingProfilesQuery,\n useUpdateMeetingMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype MeetingFormState = {\n eventId: string;\n title: string;\n sourceProfileId: string;\n targetProfileId: string;\n startsAt: string;\n endsAt: string;\n location: string;\n bookingPageId: string;\n bookerName: string;\n bookerEmail: string;\n notes: string;\n status: string;\n};\n\nconst initialState: MeetingFormState = {\n eventId: '',\n title: '',\n sourceProfileId: '',\n targetProfileId: '',\n startsAt: '',\n endsAt: '',\n location: '',\n bookingPageId: '',\n bookerName: '',\n bookerEmail: '',\n notes: '',\n status: 'SCHEDULED',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\n/** Convert an ISO timestamp to the local `datetime-local` input format. */\nfunction isoToLocalInput(iso: string | undefined): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60 * 1000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** Convert a `datetime-local` value to an ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction readString(metadata: Record<string, unknown> | null | undefined, key: string): string {\n const value = metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nexport default function MeetingFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // The meetings list resolver returns title/status/metadata only (no by-id\n // resolver), so create/update mirror scheduling detail into metadata and the\n // edit initial-state is rehydrated from there.\n const { data: meetingsData } = useMeetingsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (meetingsData?.meetings?.items ?? []).find((item) => item.id === id),\n [meetingsData, id]\n );\n\n const [createMeeting, { loading: creating }] = useCreateMeetingMutation();\n const [updateMeeting, { loading: updating }] = useUpdateMeetingMutation();\n const [form, setForm] = useState<MeetingFormState>(initialState);\n\n const { data: profilesData } = useNetworkingProfilesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(\n () =>\n (profilesData?.networkingProfiles?.items ?? []).map((profile) => ({\n id: profile.id,\n label: attendeeNames.get(profile.attendeeId) ?? profile.role ?? `Profile ${profile.id.slice(0, 8)}`,\n })),\n [profilesData, attendeeNames]\n );\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n setForm({\n eventId: existing.eventId ?? readString(metadata, 'eventId'),\n title: existing.title ?? existing.name ?? readString(metadata, 'title'),\n sourceProfileId: readString(metadata, 'sourceProfileId'),\n targetProfileId: readString(metadata, 'targetProfileId'),\n startsAt: isoToLocalInput(readString(metadata, 'startsAt')),\n endsAt: isoToLocalInput(readString(metadata, 'endsAt')),\n location: readString(metadata, 'location'),\n bookingPageId: readString(metadata, 'bookingPageId'),\n bookerName: readString(metadata, 'bookerName'),\n bookerEmail: readString(metadata, 'bookerEmail'),\n notes: readString(metadata, 'notes'),\n status: existing.status ?? 'SCHEDULED',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof MeetingFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this meeting belongs to.');\n return;\n }\n const startsAtIso = localInputToIso(form.startsAt);\n const endsAtIso = localInputToIso(form.endsAt);\n if (!startsAtIso || !endsAtIso) {\n notify.error('Time required', 'Set both a start and end time for the meeting.');\n return;\n }\n if (new Date(endsAtIso).getTime() <= new Date(startsAtIso).getTime()) {\n notify.error('Invalid time range', 'The meeting must end after it starts.');\n return;\n }\n\n // Mirror scheduling detail into metadata so the calendar-svc integration and\n // the edit form (list resolver returns metadata only) both round-trip.\n const metadata: Record<string, unknown> = {\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n title: form.title,\n sourceProfileId: form.sourceProfileId,\n targetProfileId: form.targetProfileId,\n };\n if (form.bookingPageId.trim()) {\n metadata.bookingPageId = form.bookingPageId.trim();\n }\n if (form.bookerName.trim()) {\n metadata.bookerName = form.bookerName.trim();\n }\n if (form.bookerEmail.trim()) {\n metadata.bookerEmail = form.bookerEmail.trim();\n }\n\n const input: Record<string, unknown> = {\n eventId: form.eventId,\n title: form.title,\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n status: form.status,\n metadata,\n };\n if (form.sourceProfileId) {\n input.sourceProfileId = form.sourceProfileId;\n }\n if (form.targetProfileId) {\n input.targetProfileId = form.targetProfileId;\n }\n // Passing a booking-page id lets the backend mirror the meeting into\n // wspace-calendar-svc rather than the MFE managing a local calendar.\n if (form.bookingPageId.trim()) {\n input.bookingPageId = form.bookingPageId.trim();\n }\n\n try {\n if (isEdit) {\n const response = await updateMeeting({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateMeeting?.id, 'Could not update meeting')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createMeeting({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createMeeting?.id, 'Could not book meeting')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save meeting', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Meeting' : 'Book a Meeting'}\n description=\"Schedule a 1:1 between two networking profiles.\"\n purpose=\"Meetings are the 1:1s attendees book with each other. Pick the two participants, set a time and place, and — if you use a shared booking page — link it so the meeting mirrors into the calendar service. Everyone sees the same schedule without a separate calendar here.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Meeting' : 'Book Meeting'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} required />\n </label>\n </div>\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\">Participant A</span>\n <Select\n value={form.sourceProfileId}\n onValueChange={(value) => onChange('sourceProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant A\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant B</span>\n <Select\n value={form.targetProfileId}\n onValueChange={(value) => onChange('targetProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant B\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\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\">Starts at</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends at</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"Networking lounge, Table 4\"\n />\n </label>\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\">Booking page ID</span>\n <Input\n value={form.bookingPageId}\n onChange={(e) => onChange('bookingPageId', e.target.value)}\n placeholder=\"Optional — mirrors to calendar\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker name</span>\n <Input value={form.bookerName} onChange={(e) => onChange('bookerName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker email</span>\n <Input type=\"email\" value={form.bookerEmail} onChange={(e) => onChange('bookerEmail', e.target.value)} />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Notes</span>\n <Textarea value={form.notes} onChange={(e) => onChange('notes', e.target.value)} rows={3} />\n </label>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkingProfileFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/NetworkingProfileFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateNetworkingProfileMutation,\n useEventsQuery,\n useNetworkingProfilesQuery,\n useUpdateNetworkingProfileMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ProfileFormState = {\n eventId: string;\n attendeeId: string;\n interests: string;\n goals: string;\n availability: string;\n industry: string;\n role: string;\n visibility: string;\n};\n\nconst initialState: ProfileFormState = {\n eventId: '',\n attendeeId: '',\n interests: '',\n goals: '',\n availability: '',\n industry: '',\n role: '',\n visibility: 'ATTENDEES',\n};\n\nconst VISIBILITY_OPTIONS = [\n { value: 'PUBLIC', label: 'Public' },\n { value: 'ATTENDEES', label: 'Attendees only' },\n { value: 'PRIVATE', label: 'Private' },\n];\n\nfunction splitList(value: string): string[] {\n return value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nexport default function NetworkingProfileFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver for profiles — derive edit state from the list row.\n const { data: profilesData } = useNetworkingProfilesQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (profilesData?.networkingProfiles?.items ?? []).find((item) => item.id === id),\n [profilesData, id]\n );\n\n const [createProfile, { loading: creating }] = useCreateNetworkingProfileMutation();\n const [updateProfile, { loading: updating }] = useUpdateNetworkingProfileMutation();\n const [form, setForm] = useState<ProfileFormState>(initialState);\n\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const attendees = attendeesData?.attendees?.items ?? [];\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const availability = typeof metadata.availability === 'string' ? metadata.availability : '';\n setForm({\n eventId: existing.eventId ?? '',\n attendeeId: existing.attendeeId ?? '',\n interests: (existing.interests ?? []).join(', '),\n goals: (existing.lookingFor ?? []).join(', '),\n availability,\n industry: existing.industry ?? '',\n role: existing.role ?? '',\n visibility: existing.visibility ?? 'ATTENDEES',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ProfileFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this profile is opting into.');\n return;\n }\n if (!form.attendeeId) {\n notify.error('Attendee required', 'Choose the attendee this profile represents.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n attendeeId: form.attendeeId,\n interests: splitList(form.interests),\n lookingFor: splitList(form.goals),\n industry: form.industry,\n role: form.role,\n visibility: form.visibility,\n metadata: { ...existingMetadata, availability: form.availability },\n };\n\n try {\n if (isEdit) {\n const response = await updateProfile({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateNetworkingProfile?.id, 'Could not update profile')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createProfile({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createNetworkingProfile?.id, 'Could not create profile')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save profile', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Networking Profile' : 'Opt in to Networking'}\n description=\"Describe an attendee so the matchmaking engine can suggest the right connections.\"\n purpose=\"A networking profile is how an attendee tells the matchmaking engine who they are and who they want to meet. Interests and goals drive the suggested matches, availability tells others when to book time, and visibility controls who can see the profile.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Profile' : 'Opt in'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select\n value={form.attendeeId}\n onValueChange={(value) => onChange('attendeeId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue placeholder={form.eventId ? 'Select attendee' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Interests (comma separated)</span>\n <Input\n value={form.interests}\n onChange={(e) => onChange('interests', e.target.value)}\n placeholder=\"AI, fintech, design\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Goals — who they want to meet (comma separated)</span>\n <Input\n value={form.goals}\n onChange={(e) => onChange('goals', e.target.value)}\n placeholder=\"investors, hiring partners, mentors\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Availability</span>\n <Textarea\n value={form.availability}\n onChange={(e) => onChange('availability', e.target.value)}\n rows={2}\n placeholder=\"Free during lunch and after the keynote\"\n />\n </label>\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\">Industry</span>\n <Input value={form.industry} onChange={(e) => onChange('industry', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Visibility</span>\n <Select value={form.visibility} onValueChange={(value) => onChange('visibility', value)}>\n <SelectTrigger aria-label=\"Visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NetworkingProfileFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/NetworkingProfileFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateNetworkingProfileMutation,\n useEventsQuery,\n useNetworkingProfilesQuery,\n useUpdateNetworkingProfileMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ProfileFormState = {\n eventId: string;\n attendeeId: string;\n interests: string;\n goals: string;\n availability: string;\n industry: string;\n role: string;\n visibility: string;\n};\n\nconst initialState: ProfileFormState = {\n eventId: '',\n attendeeId: '',\n interests: '',\n goals: '',\n availability: '',\n industry: '',\n role: '',\n visibility: 'ATTENDEES',\n};\n\nconst VISIBILITY_OPTIONS = [\n { value: 'PUBLIC', label: 'Public' },\n { value: 'ATTENDEES', label: 'Attendees only' },\n { value: 'PRIVATE', label: 'Private' },\n];\n\nfunction splitList(value: string): string[] {\n return value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nexport default function NetworkingProfileFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver for profiles — derive edit state from the list row.\n const { data: profilesData } = useNetworkingProfilesQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (profilesData?.networkingProfiles?.items ?? []).find((item) => item.id === id),\n [profilesData, id]\n );\n\n const [createProfile, { loading: creating }] = useCreateNetworkingProfileMutation();\n const [updateProfile, { loading: updating }] = useUpdateNetworkingProfileMutation();\n const [form, setForm] = useState<ProfileFormState>(initialState);\n\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const attendees = attendeesData?.attendees?.items ?? [];\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const availability = typeof metadata.availability === 'string' ? metadata.availability : '';\n setForm({\n eventId: existing.eventId ?? '',\n attendeeId: existing.attendeeId ?? '',\n interests: (existing.interests ?? []).join(', '),\n goals: (existing.lookingFor ?? []).join(', '),\n availability,\n industry: existing.industry ?? '',\n role: existing.role ?? '',\n visibility: existing.visibility ?? 'ATTENDEES',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ProfileFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this profile is opting into.');\n return;\n }\n if (!form.attendeeId) {\n notify.error('Attendee required', 'Choose the attendee this profile represents.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n attendeeId: form.attendeeId,\n interests: splitList(form.interests),\n lookingFor: splitList(form.goals),\n industry: form.industry,\n role: form.role,\n visibility: form.visibility,\n metadata: { ...existingMetadata, availability: form.availability },\n };\n\n try {\n if (isEdit) {\n const response = await updateProfile({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateNetworkingProfile?.id, 'Could not update profile')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createProfile({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createNetworkingProfile?.id, 'Could not create profile')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save profile', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Networking Profile' : 'Opt in to Networking'}\n description=\"Describe an attendee so the matchmaking engine can suggest the right connections.\"\n purpose=\"A networking profile is how an attendee tells the matchmaking engine who they are and who they want to meet. Interests and goals drive the suggested matches, availability tells others when to book time, and visibility controls who can see the profile.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Profile' : 'Opt in'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select\n value={form.attendeeId}\n onValueChange={(value) => onChange('attendeeId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue placeholder={form.eventId ? 'Select attendee' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Interests (comma separated)</span>\n <Input\n value={form.interests}\n onChange={(e) => onChange('interests', e.target.value)}\n placeholder=\"AI, fintech, design\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Goals — who they want to meet (comma separated)</span>\n <Input\n value={form.goals}\n onChange={(e) => onChange('goals', e.target.value)}\n placeholder=\"investors, hiring partners, mentors\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Availability</span>\n <Textarea\n value={form.availability}\n onChange={(e) => onChange('availability', e.target.value)}\n rows={2}\n placeholder=\"Free during lunch and after the keynote\"\n />\n </label>\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\">Industry</span>\n <Input value={form.industry} onChange={(e) => onChange('industry', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Visibility</span>\n <Select value={form.visibility} onValueChange={(value) => onChange('visibility', value)}>\n <SelectTrigger aria-label=\"Visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\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\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\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=\"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\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? (\n <EntityTagsField entityType=\"attendee\" entityId={id} />\n ) : (\n <EntityTagsField entityType=\"attendee\" entityId=\"\" />\n )}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\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\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\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=\"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\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? (\n <EntityTagsField entityType=\"attendee\" entityId={id} />\n ) : (\n <EntityTagsField entityType=\"attendee\" entityId=\"\" />\n )}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|