@burdenoff/microfe-eventfullymanaged 2026.730.1 → 2026.801.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/eventfullymanaged/EventfullyManagedRoutes.d.ts.map +1 -1
  2. package/dist/eventfullymanaged/EventfullyManagedRoutes.js +61 -77
  3. package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
  4. package/dist/eventfullymanaged/api/eventApi.d.ts +208 -0
  5. package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
  6. package/dist/eventfullymanaged/api/eventApi.js +293 -136
  7. package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
  8. package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts +18 -0
  9. package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts.map +1 -0
  10. package/dist/eventfullymanaged/hooks/useEvmSubscription.js +110 -0
  11. package/dist/eventfullymanaged/hooks/useEvmSubscription.js.map +1 -0
  12. package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts +47 -0
  13. package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts.map +1 -0
  14. package/dist/eventfullymanaged/hooks/useVirtualCall.js +209 -0
  15. package/dist/eventfullymanaged/hooks/useVirtualCall.js.map +1 -0
  16. package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
  17. package/dist/eventfullymanaged/pages/events/EventDetailPage.js +80 -72
  18. package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
  19. package/dist/eventfullymanaged/pages/events/EventFormPage.js +1 -1
  20. package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
  21. package/dist/eventfullymanaged/pages/events/SessionFormPage.js +1 -1
  22. package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
  23. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +1 -1
  24. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -1
  25. package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts +2 -0
  26. package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts.map +1 -0
  27. package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js +353 -0
  28. package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js.map +1 -0
  29. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +1 -1
  30. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
  31. package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +1 -1
  32. package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
  33. package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts +2 -0
  34. package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts.map +1 -0
  35. package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js +336 -0
  36. package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js.map +1 -0
  37. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +1 -1
  38. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
  39. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +1 -1
  40. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
  41. package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js +1 -1
  42. package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js.map +1 -1
  43. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +1 -1
  44. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
  45. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +1 -1
  46. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
  47. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +1 -1
  48. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
  49. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +1 -1
  50. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
  51. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +1 -1
  52. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
  53. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +1 -1
  54. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
  55. package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js +1 -1
  56. package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js.map +1 -1
  57. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +1 -1
  58. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
  59. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +1 -1
  60. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
  61. package/dist/eventfullymanaged/utils/shellBridge.d.ts +46 -0
  62. package/dist/eventfullymanaged/utils/shellBridge.d.ts.map +1 -0
  63. package/dist/eventfullymanaged/utils/shellBridge.js +19 -0
  64. package/dist/eventfullymanaged/utils/shellBridge.js.map +1 -0
  65. package/package.json +9 -9
@@ -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 { 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":";;;;;;;;;;AAiCA,IAAM,IAAiC;CACrC,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,iBAAiB;CACjB,UAAU;CACV,QAAQ;CACR,UAAU;CACV,eAAe;CACf,YAAY;CACZ,aAAa;CACb,OAAO;CACP,QAAQ;CACT,EAEK,IAAiB;CACrB;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC3C;AAGD,SAAS,EAAgB,GAAiC;AACxD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAO,IAAI,KAAK,EAAI;AAC1B,KAAI,OAAO,MAAM,EAAK,SAAS,CAAC,CAC9B,QAAO;CAET,IAAM,IAAW,EAAK,mBAAmB,GAAG,KAAK;AACjD,QAAO,IAAI,KAAK,EAAK,SAAS,GAAG,EAAS,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;AAIvE,SAAS,EAAgB,GAA8B;AACrD,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAO,IAAI,KAAK,EAAM;AAC5B,QAAO,OAAO,MAAM,EAAK,SAAS,CAAC,GAAG,OAAO,EAAK,aAAa;;AAGjE,SAAS,EAAW,GAAsD,GAAqB;CAC7F,IAAM,IAAQ,IAAW;AACzB,QAAO,OAAO,KAAU,WAAW,IAAQ;;AAG7C,SAAwB,IAAkB;CACxC,IAAM,EAAE,QAAK,OAAO,GAA2B,EACzC,IAAS,QAAc,EAAQ,GAAK,CAAC,EAAG,CAAC,EACzC,EAAE,gBAAa,GAAsB,EAErC,EAAE,MAAM,MAAe,EAAe;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EAIhE,EAAE,MAAM,MAAiB,EAAiB;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EAEpE,IAAW,SACR,GAAc,UAAU,SAAS,EAAE,EAAE,MAAM,MAAS,EAAK,OAAO,EAAG,EAC1E,CAAC,GAAc,EAAG,CACnB,EAEK,CAAC,GAAe,EAAE,SAAS,OAAc,GAA0B,EACnE,CAAC,GAAe,EAAE,SAAS,OAAc,GAA0B,EACnE,CAAC,GAAM,KAAW,EAA2B,EAAa,EAE1D,EAAE,MAAM,MAAiB,EAA2B;EACxD,SAAS,EAAK,WAAW,KAAA;EACzB,OAAO;EACP,QAAQ;EACT,CAAC,EACI,EAAE,MAAM,MAAkB,EAAkB;EAChD,SAAS,EAAK,WAAW,KAAA;EACzB,OAAO;EACP,QAAQ;EACT,CAAC,EAEI,IAAgB,QAAc;EAClC,IAAM,oBAAM,IAAI,KAAqB;AACrC,OAAK,IAAM,KAAY,GAAe,WAAW,SAAS,EAAE,CAC1D,GAAI,IAAI,EAAS,IAAI,EAAS,SAAS;AAEzC,SAAO;IACN,CAAC,EAAc,CAAC,EAEb,IAAW,SAEZ,GAAc,oBAAoB,SAAS,EAAE,EAAE,KAAK,OAAa;EAChE,IAAI,EAAQ;EACZ,OAAO,EAAc,IAAI,EAAQ,WAAW,IAAI,EAAQ,QAAQ,WAAW,EAAQ,GAAG,MAAM,GAAG,EAAE;EAClG,EAAE,EACL,CAAC,GAAc,EAAc,CAC9B;AAED,SAAgB;AACd,MAAI,CAAC,KAAU,CAAC,EACd;EAEF,IAAM,IAAW,EAAS,YAAY,EAAE;AACxC,IAAQ;GACN,SAAS,EAAS,WAAW,EAAW,GAAU,UAAU;GAC5D,OAAO,EAAS,SAAS,EAAS,QAAQ,EAAW,GAAU,QAAQ;GACvE,iBAAiB,EAAW,GAAU,kBAAkB;GACxD,iBAAiB,EAAW,GAAU,kBAAkB;GACxD,UAAU,EAAgB,EAAW,GAAU,WAAW,CAAC;GAC3D,QAAQ,EAAgB,EAAW,GAAU,SAAS,CAAC;GACvD,UAAU,EAAW,GAAU,WAAW;GAC1C,eAAe,EAAW,GAAU,gBAAgB;GACpD,YAAY,EAAW,GAAU,aAAa;GAC9C,aAAa,EAAW,GAAU,cAAc;GAChD,OAAO,EAAW,GAAU,QAAQ;GACpC,QAAQ,EAAS,UAAU;GAC5B,CAAC;IACD,CAAC,GAAU,EAAO,CAAC;CAEtB,IAAM,KAAY,GAA+B,MAAkB;AACjE,KAAS,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAO,EAAE;IAG5C,IAAW,OAAO,MAA4C;AAGlE,MAFA,EAAM,gBAAgB,EAElB,CAAC,EAAK,SAAS;AACjB,KAAO,MAAM,kBAAkB,4CAA4C;AAC3E;;EAEF,IAAM,IAAc,EAAgB,EAAK,SAAS,EAC5C,IAAY,EAAgB,EAAK,OAAO;AAC9C,MAAI,CAAC,KAAe,CAAC,GAAW;AAC9B,KAAO,MAAM,iBAAiB,iDAAiD;AAC/E;;AAEF,MAAI,IAAI,KAAK,EAAU,CAAC,SAAS,IAAI,IAAI,KAAK,EAAY,CAAC,SAAS,EAAE;AACpE,KAAO,MAAM,sBAAsB,wCAAwC;AAC3E;;EAKF,IAAM,IAAoC;GACxC,UAAU;GACV,QAAQ;GACR,UAAU,EAAK;GACf,OAAO,EAAK;GACZ,OAAO,EAAK;GACZ,iBAAiB,EAAK;GACtB,iBAAiB,EAAK;GACvB;AAOD,EANI,EAAK,cAAc,MAAM,KAC3B,EAAS,gBAAgB,EAAK,cAAc,MAAM,GAEhD,EAAK,WAAW,MAAM,KACxB,EAAS,aAAa,EAAK,WAAW,MAAM,GAE1C,EAAK,YAAY,MAAM,KACzB,EAAS,cAAc,EAAK,YAAY,MAAM;EAGhD,IAAM,IAAiC;GACrC,SAAS,EAAK;GACd,OAAO,EAAK;GACZ,UAAU;GACV,QAAQ;GACR,UAAU,EAAK;GACf,OAAO,EAAK;GACZ,QAAQ,EAAK;GACb;GACD;AASD,EARI,EAAK,oBACP,EAAM,kBAAkB,EAAK,kBAE3B,EAAK,oBACP,EAAM,kBAAkB,EAAK,kBAI3B,EAAK,cAAc,MAAM,KAC3B,EAAM,gBAAgB,EAAK,cAAc,MAAM;AAGjD,MAAI;AACF,OAAI,GAAQ;IACV,IAAM,IAAW,MAAM,EAAc,EAAE,WAAW;KAAE;KAAI;KAAO,EAAE,CAAC;AAClE,QAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,eAAe,IAAI,2BAA2B,CAC5F;AAEF,MAAS,cAAc;AACvB;;GAGF,IAAM,IAAW,MAAM,EAAc,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC;AAC9D,OAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,eAAe,IAAI,yBAAyB,CAC1F;AAEF,KAAS,cAAc;WAChB,GAAO;AACd,KAAO,MAAM,0BAA0B,EAAc,EAAM,CAAC;;IAI1D,IAAS,KAAY,GACrB,IAAS,GAAY,QAAQ,SAAS,EAAE;AAE9C,QACE,kBAAC,GAAD;EACE,OAAO,IAAS,iBAAiB;EACjC,aAAY;EACZ,SAAQ;EACR,WAAU;EACV,cAAc,EAAS,cAAc;EAC3B;EACV,aAAa,IAAS,iBAAiB;EAC/B;YARV;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAY,CAAA,EAC5D,kBAAC,GAAD;MAAQ,OAAO,EAAK;MAAS,gBAAgB,MAAU,EAAS,WAAW,EAAM;gBAAjF,CACE,kBAAC,GAAD;OAAe,cAAW;iBACxB,kBAAC,GAAD,EAAa,aAAY,gBAAiB,CAAA;OAC5B,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAO,KAAK,MACX,kBAAC,GAAD;OAA+B,OAAO,EAAU;iBAC7C,EAAU;OACA,EAFI,EAAU,GAEd,CACb,EACY,CAAA,CACT;QACH;QACR,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAY,CAAA,EAC5D,kBAAC,GAAD;MAAO,OAAO,EAAK;MAAO,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,MAAM;MAAE,UAAA;MAAW,CAAA,CACnF;OACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAoB,CAAA,EACpE,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,gBAAgB,MAAU,EAAS,mBAAmB,EAAM;MAC5D,UAAU,CAAC,EAAK;gBAHlB,CAKE,kBAAC,GAAD;OAAe,cAAW;iBACxB,kBAAC,GAAD,EAAa,aAAa,EAAK,UAAU,mBAAmB,yBAA2B,CAAA;OACzE,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;OAA6B,OAAO,EAAQ;iBACzC,EAAQ;OACE,EAFI,EAAQ,GAEZ,CACb,EACY,CAAA,CACT;QACH;QACR,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAoB,CAAA,EACpE,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,gBAAgB,MAAU,EAAS,mBAAmB,EAAM;MAC5D,UAAU,CAAC,EAAK;gBAHlB,CAKE,kBAAC,GAAD;OAAe,cAAW;iBACxB,kBAAC,GAAD,EAAa,aAAa,EAAK,UAAU,mBAAmB,yBAA2B,CAAA;OACzE,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAS,KAAK,MACb,kBAAC,GAAD;OAA6B,OAAO,EAAQ;iBACzC,EAAQ;OACE,EAFI,EAAQ,GAEZ,CACb,EACY,CAAA,CACT;QACH;OACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAgB,CAAA,EAChE,kBAAC,GAAD;OAAO,MAAK;OAAiB,OAAO,EAAK;OAAU,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;OAAI,CAAA,CACtG;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAc,CAAA,EAC9D,kBAAC,GAAD;OAAO,MAAK;OAAiB,OAAO,EAAK;OAAQ,WAAW,MAAM,EAAS,UAAU,EAAE,OAAO,MAAM;OAAI,CAAA,CAClG;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAa,CAAA,EAC7D,kBAAC,GAAD;OAAQ,OAAO,EAAK;OAAQ,gBAAgB,MAAU,EAAS,UAAU,EAAM;iBAA/E,CACE,kBAAC,GAAD;QAAe,cAAW;kBACxB,kBAAC,GAAD,EAAe,CAAA;QACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAe,KAAK,MACnB,kBAAC,GAAD;QAA+B,OAAO,EAAO;kBAC1C,EAAO;QACG,EAFI,EAAO,MAEX,CACb,EACY,CAAA,CACT;SACH;;KACJ;;GAEN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAe,CAAA,EAC/D,kBAAC,GAAD;KACE,OAAO,EAAK;KACZ,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;KACrD,aAAY;KACZ,CAAA,CACI;;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAsB,CAAA,EACtE,kBAAC,GAAD;OACE,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,iBAAiB,EAAE,OAAO,MAAM;OAC1D,aAAY;OACZ,CAAA,CACI;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAkB,CAAA,EAClE,kBAAC,GAAD;OAAO,OAAO,EAAK;OAAY,WAAW,MAAM,EAAS,cAAc,EAAE,OAAO,MAAM;OAAI,CAAA,CACpF;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAmB,CAAA,EACnE,kBAAC,GAAD;OAAO,MAAK;OAAQ,OAAO,EAAK;OAAa,WAAW,MAAM,EAAS,eAAe,EAAE,OAAO,MAAM;OAAI,CAAA,CACnG;;KACJ;;GAEN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAY,CAAA,EAC5D,kBAAC,GAAD;KAAU,OAAO,EAAK;KAAO,WAAW,MAAM,EAAS,SAAS,EAAE,OAAO,MAAM;KAAE,MAAM;KAAK,CAAA,CACtF;;GACQ"}
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';\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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"}
@@ -4,7 +4,7 @@ import { useAttendeesQuery as n, useCreateNetworkingProfileMutation as r, useEve
4
4
  import { EntityFormShell as s } from "../../components/EntityFormShell.js";
5
5
  import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
6
6
  import { useEffect as u, useMemo as d, useState as f } from "react";
7
- import { useParams as p } from "react-router-dom";
7
+ import { useParams as p } from "react-router";
8
8
  import { jsx as m, jsxs as h } from "react/jsx-runtime";
9
9
  import { Input as g, Select as _, SelectContent as v, SelectItem as y, SelectTrigger as b, SelectValue as x, Textarea as S } from "@burdenoff/fe-libs/ui";
10
10
  //#region src/eventfullymanaged/pages/networking/NetworkingProfileFormPage.tsx
@@ -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 { 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
+ {"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';\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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"}
@@ -5,7 +5,7 @@ import { EntityFormShell as o } from "../../components/EntityFormShell.js";
5
5
  import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
6
6
  import { EntityTagsField as l } from "../../components/EntityTagsField.js";
7
7
  import { useEffect as u, useMemo as d, useState as f } from "react";
8
- import { useParams as p } from "react-router-dom";
8
+ import { useParams as p } from "react-router";
9
9
  import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
10
  import { Input as g, Select as _, SelectContent as v, SelectItem as y, SelectTrigger as b, SelectValue as x } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx
@@ -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 { 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 initials = useMemo(() => {\n const parts = form.fullName.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n }, [form.fullName]);\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 {/* Identity preview */}\n <div className=\"flex items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary\">\n {initials}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{form.fullName.trim() || 'New attendee'}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[form.email.trim(), form.company.trim(), form.role.trim()].filter(Boolean).join(' · ') ||\n 'Details will appear here as you type.'}\n </p>\n </div>\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\">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 ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_ATTENDEE\" entityId={id} /> : null}\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';\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 initials = useMemo(() => {\n const parts = form.fullName.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n }, [form.fullName]);\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 {/* Identity preview */}\n <div className=\"flex items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary\">\n {initials}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{form.fullName.trim() || 'New attendee'}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[form.email.trim(), form.company.trim(), form.role.trim()].filter(Boolean).join(' · ') ||\n 'Details will appear here as you type.'}\n </p>\n </div>\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\">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 ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_ATTENDEE\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { useCreatePromoCodeMutation as n, useEventsQuery as r, usePromoCodesQuer
4
4
  import { EntityFormShell as o } from "../../components/EntityFormShell.js";
5
5
  import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
6
6
  import { useEffect as l, useMemo as u, useState as d } from "react";
7
- import { useParams as f } from "react-router-dom";
7
+ import { useParams as f } from "react-router";
8
8
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
9
9
  import { BadgePercent as h, Ticket as g } from "lucide-react";
10
10
  import { Badge as _, Input as v, Select as y, SelectContent as b, SelectItem as x, SelectTrigger as S, SelectValue as C, Switch as w, Textarea as T } from "@burdenoff/fe-libs/ui";
@@ -1 +1 @@
1
- {"version":3,"file":"PromoCodeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { BadgePercent, Ticket } from 'lucide-react';\nimport {\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCreatePromoCodeMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useUpdatePromoCodeMutation,\n type PromoCodeGql,\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\n/** Human validity window from the two datetime-local field values. */\nfunction describeValidity(startsAt: string, endsAt: string): string {\n const fmt = (value: string) =>\n new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });\n if (startsAt && endsAt) return `${fmt(startsAt)} – ${fmt(endsAt)}`;\n if (startsAt) return `From ${fmt(startsAt)}`;\n if (endsAt) return `Until ${fmt(endsAt)}`;\n return 'Ongoing';\n}\n\ntype PromoCodeFormState = {\n eventId: string;\n code: string;\n description: string;\n discountType: string;\n discountValue: string;\n maxUses: string;\n startsAt: string;\n endsAt: string;\n isActive: boolean;\n};\n\nconst emptyState: PromoCodeFormState = {\n eventId: '',\n code: '',\n description: '',\n discountType: 'PERCENT',\n discountValue: '',\n maxUses: '',\n startsAt: '',\n endsAt: '',\n isActive: true,\n};\n\n/** ISO timestamp -> value for <input type=\"datetime-local\"> (local time, minute precision). */\nfunction isoToLocalInput(iso?: string | null): 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_000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** datetime-local value -> 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 if (Number.isNaN(date.getTime())) {\n return null;\n }\n return date.toISOString();\n}\n\nfunction toFormState(promo: PromoCodeGql): PromoCodeFormState {\n return {\n eventId: promo.eventId ?? '',\n code: promo.code ?? '',\n description: promo.description ?? '',\n discountType: promo.discountType ?? 'PERCENT',\n discountValue: promo.discountValue ?? '',\n maxUses: promo.maxUses != null ? String(promo.maxUses) : '',\n startsAt: isoToLocalInput(promo.startsAt),\n endsAt: isoToLocalInput(promo.endsAt),\n isActive: promo.isActive ?? true,\n };\n}\n\nexport default function PromoCodeFormPage() {\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 promo resolver exists — derive edit initial-state from the list.\n const { data: promoData } = usePromoCodesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(() => (promoData?.promoCodes.items ?? []).find((item) => item.id === id), [promoData, id]);\n\n const [createPromoCode, { loading: creating }] = useCreatePromoCodeMutation();\n const [updatePromoCode, { loading: updating }] = useUpdatePromoCodeMutation();\n const [form, setForm] = useState<PromoCodeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = <K extends keyof PromoCodeFormState>(field: K, value: PromoCodeFormState[K]) => {\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 promo code applies to.');\n return;\n }\n if (!form.code.trim()) {\n notify.error('Code required', 'Enter the code attendees will type at checkout.');\n return;\n }\n if (!form.discountValue.trim()) {\n notify.error('Discount required', 'Enter a discount value.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n code: form.code.trim().toUpperCase(),\n description: form.description.trim() || null,\n discountType: form.discountType,\n discountValue: form.discountValue.trim(),\n maxUses: form.maxUses.trim() ? Number(form.maxUses) : null,\n startsAt: localInputToIso(form.startsAt),\n endsAt: localInputToIso(form.endsAt),\n isActive: form.isActive,\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updatePromoCode({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updatePromoCode?.id, 'Could not update promo code')) {\n return;\n }\n notify.success('Promo code updated', input.code);\n navigate('/registration');\n return;\n }\n\n const response = await createPromoCode({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createPromoCode?.id, 'Could not create promo code')) {\n return;\n }\n notify.success('Promo code created', input.code);\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save promo code', describeError(error));\n }\n };\n\n const valueHint =\n form.discountType === 'PERCENT' ? 'Percentage off, e.g. 10 for 10%.' : 'Fixed amount off, e.g. 500.';\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Promo Code' : 'New Promo Code'}\n description=\"Promo codes let you offer discounts to drive registrations.\"\n purpose=\"Create a discount attendees can apply at checkout. Choose whether it takes a percentage or a fixed amount off, cap how many times it can be used, and set an active window. Deactivate a code to stop it being redeemed without deleting its history.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Promo Code' : 'Create Promo Code'}\n saving={saving}\n >\n {/* Live preview — mirrors how the code appears on the promo list */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Preview</span>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n <Ticket className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"font-mono text-base font-semibold text-text-primary\">\n {form.code.trim() ? form.code.trim().toUpperCase() : 'YOURCODE'}\n </span>\n <Badge variant=\"secondary\">{form.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <BadgePercent className=\"h-3.5 w-3.5 text-text-muted\" aria-hidden=\"true\" />\n {form.discountValue.trim()\n ? form.discountType === 'PERCENT'\n ? `${form.discountValue.trim()}% off`\n : `${form.discountValue.trim()} off`\n : 'No discount set'}\n </span>\n <span className=\"text-xs text-text-muted\">· {describeValidity(form.startsAt, form.endsAt)}</span>\n <Badge variant={form.isActive ? 'default' : 'secondary'} className=\"ml-auto\">\n {form.isActive ? 'Active' : 'Inactive'}\n </Badge>\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">\n {form.maxUses.trim() ? `Redeemable up to ${form.maxUses.trim()} times.` : 'Unlimited redemptions.'}\n </p>\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\">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\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"EARLYBIRD\"\n required\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Description</span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={2} />\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\">Discount Type</span>\n <Select value={form.discountType} onValueChange={(value) => onChange('discountType', value)}>\n <SelectTrigger aria-label=\"Discount Type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PERCENT\">Percentage (%)</SelectItem>\n <SelectItem value=\"FIXED\">Fixed amount</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Value</span>\n <Input\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.discountValue}\n onChange={(e) => onChange('discountValue', e.target.value)}\n required\n />\n <span className=\"text-xs text-text-secondary\">{valueHint}</span>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Max Uses</span>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.maxUses}\n onChange={(e) => onChange('maxUses', e.target.value)}\n placeholder=\"Unlimited\"\n />\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\">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 </div>\n\n <div className=\"flex items-center justify-between rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <div className=\"text-sm\">\n <span className=\"font-medium text-text-primary\">Active</span>\n <p className=\"text-xs text-text-secondary\">Only active codes can be redeemed at checkout.</p>\n </div>\n <Switch checked={form.isActive} onCheckedChange={(checked) => onChange('isActive', checked)} />\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PromoCodeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { BadgePercent, Ticket } from 'lucide-react';\nimport {\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCreatePromoCodeMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useUpdatePromoCodeMutation,\n type PromoCodeGql,\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\n/** Human validity window from the two datetime-local field values. */\nfunction describeValidity(startsAt: string, endsAt: string): string {\n const fmt = (value: string) =>\n new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });\n if (startsAt && endsAt) return `${fmt(startsAt)} – ${fmt(endsAt)}`;\n if (startsAt) return `From ${fmt(startsAt)}`;\n if (endsAt) return `Until ${fmt(endsAt)}`;\n return 'Ongoing';\n}\n\ntype PromoCodeFormState = {\n eventId: string;\n code: string;\n description: string;\n discountType: string;\n discountValue: string;\n maxUses: string;\n startsAt: string;\n endsAt: string;\n isActive: boolean;\n};\n\nconst emptyState: PromoCodeFormState = {\n eventId: '',\n code: '',\n description: '',\n discountType: 'PERCENT',\n discountValue: '',\n maxUses: '',\n startsAt: '',\n endsAt: '',\n isActive: true,\n};\n\n/** ISO timestamp -> value for <input type=\"datetime-local\"> (local time, minute precision). */\nfunction isoToLocalInput(iso?: string | null): 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_000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** datetime-local value -> 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 if (Number.isNaN(date.getTime())) {\n return null;\n }\n return date.toISOString();\n}\n\nfunction toFormState(promo: PromoCodeGql): PromoCodeFormState {\n return {\n eventId: promo.eventId ?? '',\n code: promo.code ?? '',\n description: promo.description ?? '',\n discountType: promo.discountType ?? 'PERCENT',\n discountValue: promo.discountValue ?? '',\n maxUses: promo.maxUses != null ? String(promo.maxUses) : '',\n startsAt: isoToLocalInput(promo.startsAt),\n endsAt: isoToLocalInput(promo.endsAt),\n isActive: promo.isActive ?? true,\n };\n}\n\nexport default function PromoCodeFormPage() {\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 promo resolver exists — derive edit initial-state from the list.\n const { data: promoData } = usePromoCodesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(() => (promoData?.promoCodes.items ?? []).find((item) => item.id === id), [promoData, id]);\n\n const [createPromoCode, { loading: creating }] = useCreatePromoCodeMutation();\n const [updatePromoCode, { loading: updating }] = useUpdatePromoCodeMutation();\n const [form, setForm] = useState<PromoCodeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = <K extends keyof PromoCodeFormState>(field: K, value: PromoCodeFormState[K]) => {\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 promo code applies to.');\n return;\n }\n if (!form.code.trim()) {\n notify.error('Code required', 'Enter the code attendees will type at checkout.');\n return;\n }\n if (!form.discountValue.trim()) {\n notify.error('Discount required', 'Enter a discount value.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n code: form.code.trim().toUpperCase(),\n description: form.description.trim() || null,\n discountType: form.discountType,\n discountValue: form.discountValue.trim(),\n maxUses: form.maxUses.trim() ? Number(form.maxUses) : null,\n startsAt: localInputToIso(form.startsAt),\n endsAt: localInputToIso(form.endsAt),\n isActive: form.isActive,\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updatePromoCode({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updatePromoCode?.id, 'Could not update promo code')) {\n return;\n }\n notify.success('Promo code updated', input.code);\n navigate('/registration');\n return;\n }\n\n const response = await createPromoCode({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createPromoCode?.id, 'Could not create promo code')) {\n return;\n }\n notify.success('Promo code created', input.code);\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save promo code', describeError(error));\n }\n };\n\n const valueHint =\n form.discountType === 'PERCENT' ? 'Percentage off, e.g. 10 for 10%.' : 'Fixed amount off, e.g. 500.';\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Promo Code' : 'New Promo Code'}\n description=\"Promo codes let you offer discounts to drive registrations.\"\n purpose=\"Create a discount attendees can apply at checkout. Choose whether it takes a percentage or a fixed amount off, cap how many times it can be used, and set an active window. Deactivate a code to stop it being redeemed without deleting its history.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Promo Code' : 'Create Promo Code'}\n saving={saving}\n >\n {/* Live preview — mirrors how the code appears on the promo list */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Preview</span>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n <Ticket className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"font-mono text-base font-semibold text-text-primary\">\n {form.code.trim() ? form.code.trim().toUpperCase() : 'YOURCODE'}\n </span>\n <Badge variant=\"secondary\">{form.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <BadgePercent className=\"h-3.5 w-3.5 text-text-muted\" aria-hidden=\"true\" />\n {form.discountValue.trim()\n ? form.discountType === 'PERCENT'\n ? `${form.discountValue.trim()}% off`\n : `${form.discountValue.trim()} off`\n : 'No discount set'}\n </span>\n <span className=\"text-xs text-text-muted\">· {describeValidity(form.startsAt, form.endsAt)}</span>\n <Badge variant={form.isActive ? 'default' : 'secondary'} className=\"ml-auto\">\n {form.isActive ? 'Active' : 'Inactive'}\n </Badge>\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">\n {form.maxUses.trim() ? `Redeemable up to ${form.maxUses.trim()} times.` : 'Unlimited redemptions.'}\n </p>\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\">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\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"EARLYBIRD\"\n required\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Description</span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={2} />\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\">Discount Type</span>\n <Select value={form.discountType} onValueChange={(value) => onChange('discountType', value)}>\n <SelectTrigger aria-label=\"Discount Type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PERCENT\">Percentage (%)</SelectItem>\n <SelectItem value=\"FIXED\">Fixed amount</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Value</span>\n <Input\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.discountValue}\n onChange={(e) => onChange('discountValue', e.target.value)}\n required\n />\n <span className=\"text-xs text-text-secondary\">{valueHint}</span>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Max Uses</span>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.maxUses}\n onChange={(e) => onChange('maxUses', e.target.value)}\n placeholder=\"Unlimited\"\n />\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\">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 </div>\n\n <div className=\"flex items-center justify-between rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <div className=\"text-sm\">\n <span className=\"font-medium text-text-primary\">Active</span>\n <p className=\"text-xs text-text-secondary\">Only active codes can be redeemed at checkout.</p>\n </div>\n <Switch checked={form.isActive} onCheckedChange={(checked) => onChange('isActive', checked)} />\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { EntityTagsField as l } from "../../components/EntityTagsField.js";
9
9
  import { safeUrl as u } from "../../components/SafeLink.js";
10
10
  import { ImageUploadField as d } from "../../components/ImageUploadField.js";
11
11
  import { useEffect as f, useMemo as p, useState as m } from "react";
12
- import { useParams as h } from "react-router-dom";
12
+ import { useParams as h } from "react-router";
13
13
  import { jsx as g, jsxs as _ } from "react/jsx-runtime";
14
14
  import { Input as v, Textarea as y } from "@burdenoff/fe-libs/ui";
15
15
  //#region src/eventfullymanaged/pages/speakers/SpeakerFormPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"SpeakerFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/speakers/SpeakerFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCreateSpeakerMutation, useUpdateSpeakerMutation } from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n// The backend `updateSpeaker` resolver is a FULL replace and there is no by-id\n// speaker resolver, so this fuller list query — selecting every editable field,\n// not just the columns the foundation SPEAKERS_QUERY lists — is the edit-hydration\n// source. The form finds its row by id client-side. Without the full selection an\n// edit would blank bio / avatar / phone / socialLinks.\nconst FULL_SPEAKERS_QUERY = gql`\n query EventfullymanagedSpeakerFormSpeakers($limit: Int, $offset: Int) {\n speakers: eventfullyManagedSpeakers(limit: $limit, offset: $offset) {\n total\n items {\n id\n fullName\n title\n organization\n bio\n avatarUrl\n email\n phone\n socialLinks\n }\n }\n }\n`;\n\ntype SpeakerRecord = {\n id: string;\n fullName: string;\n title?: string | null;\n organization?: string | null;\n bio?: string | null;\n avatarUrl?: string | null;\n email?: string | null;\n phone?: string | null;\n socialLinks?: Record<string, unknown> | null;\n};\n\ntype FullSpeakersResponse = { speakers: { total: number; items: SpeakerRecord[] } };\n\ntype SpeakerFormState = {\n fullName: string;\n title: string;\n organization: string;\n email: string;\n phone: string;\n bio: string;\n avatarUrl: string;\n website: string;\n};\n\nconst initialState: SpeakerFormState = {\n fullName: '',\n title: '',\n organization: '',\n email: '',\n phone: '',\n bio: '',\n avatarUrl: '',\n website: '',\n};\n\nfunction readWebsite(socialLinks: Record<string, unknown> | null | undefined): string {\n if (socialLinks && typeof socialLinks.website === 'string') {\n return socialLinks.website;\n }\n return '';\n}\n\nexport default function SpeakerFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data } = useQuery<FullSpeakersResponse, { limit?: number; offset?: number }>(FULL_SPEAKERS_QUERY, {\n variables: { limit: 100, offset: 0 },\n skip: !id,\n });\n const [createSpeaker, { loading: creating }] = useCreateSpeakerMutation();\n const [updateSpeaker, { loading: updating }] = useUpdateSpeakerMutation();\n const [form, setForm] = useState<SpeakerFormState>(initialState);\n\n const existingSpeaker = useMemo(() => (data?.speakers?.items ?? []).find((item) => item.id === id), [data, id]);\n\n useEffect(() => {\n if (!isEdit || !existingSpeaker) {\n return;\n }\n setForm({\n fullName: existingSpeaker.fullName ?? '',\n title: existingSpeaker.title ?? '',\n organization: existingSpeaker.organization ?? '',\n email: existingSpeaker.email ?? '',\n phone: existingSpeaker.phone ?? '',\n bio: existingSpeaker.bio ?? '',\n avatarUrl: existingSpeaker.avatarUrl ?? '',\n website: readWebsite(existingSpeaker.socialLinks),\n });\n }, [existingSpeaker, isEdit]);\n\n const onChange = (field: keyof SpeakerFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.fullName.trim()) {\n notify.error('Name required', 'Give the speaker a name before saving.');\n return;\n }\n\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const input = {\n fullName: form.fullName.trim(),\n title: form.title || null,\n organization: form.organization || null,\n email: form.email || null,\n phone: form.phone || null,\n bio: form.bio || null,\n avatarUrl: form.avatarUrl || null,\n socialLinks: normalizedWebsite ? { website: normalizedWebsite } : {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSpeaker({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSpeaker?.id, 'Could not update speaker')) {\n return;\n }\n notify.success('Speaker updated');\n navigate('/speakers');\n return;\n }\n const response = await createSpeaker({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSpeaker?.id, 'Could not create speaker')) {\n return;\n }\n notify.success('Speaker created');\n navigate('/speakers');\n } catch (error) {\n notify.error('Could not save speaker', describeError(error));\n }\n };\n\n const saving = creating || updating;\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Speaker' : 'Add Speaker'}\n description=\"A person who presents sessions at your events.\"\n purpose=\"A speaker profile is what you attach to sessions and show to attendees. Capture who they are, where they work, and a short bio or talk abstract so the agenda tells a compelling story. The headshot appears anywhere the speaker is listed.\"\n backLabel=\"Back to Speakers\"\n onBack={() => navigate('/speakers')}\n onSubmit={onSubmit}\n submitLabel=\"Save Speaker\"\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} placeholder=\"Head of Product\" />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.organization} onChange={(e) => onChange('organization', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Headshot</span>\n <ImageUploadField\n label=\"Upload headshot\"\n folderPath=\"/EventfullyManaged/Speakers\"\n value={form.avatarUrl || null}\n onUploaded={(url) => onChange('avatarUrl', url)}\n hint=\"Stored securely in Files. Shown wherever the speaker is listed.\"\n />\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Bio &amp; talk abstract</span>\n <Textarea\n value={form.bio}\n onChange={(e) => onChange('bio', e.target.value)}\n rows={5}\n placeholder=\"Short professional bio and a summary of the talk they will give.\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website / profile URL</span>\n <Input value={form.website} onChange={(e) => onChange('website', e.target.value)} placeholder=\"https://…\" />\n </label>\n\n {isEdit ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_SPEAKER\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SpeakerFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/speakers/SpeakerFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useCreateSpeakerMutation, useUpdateSpeakerMutation } from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n// The backend `updateSpeaker` resolver is a FULL replace and there is no by-id\n// speaker resolver, so this fuller list query — selecting every editable field,\n// not just the columns the foundation SPEAKERS_QUERY lists — is the edit-hydration\n// source. The form finds its row by id client-side. Without the full selection an\n// edit would blank bio / avatar / phone / socialLinks.\nconst FULL_SPEAKERS_QUERY = gql`\n query EventfullymanagedSpeakerFormSpeakers($limit: Int, $offset: Int) {\n speakers: eventfullyManagedSpeakers(limit: $limit, offset: $offset) {\n total\n items {\n id\n fullName\n title\n organization\n bio\n avatarUrl\n email\n phone\n socialLinks\n }\n }\n }\n`;\n\ntype SpeakerRecord = {\n id: string;\n fullName: string;\n title?: string | null;\n organization?: string | null;\n bio?: string | null;\n avatarUrl?: string | null;\n email?: string | null;\n phone?: string | null;\n socialLinks?: Record<string, unknown> | null;\n};\n\ntype FullSpeakersResponse = { speakers: { total: number; items: SpeakerRecord[] } };\n\ntype SpeakerFormState = {\n fullName: string;\n title: string;\n organization: string;\n email: string;\n phone: string;\n bio: string;\n avatarUrl: string;\n website: string;\n};\n\nconst initialState: SpeakerFormState = {\n fullName: '',\n title: '',\n organization: '',\n email: '',\n phone: '',\n bio: '',\n avatarUrl: '',\n website: '',\n};\n\nfunction readWebsite(socialLinks: Record<string, unknown> | null | undefined): string {\n if (socialLinks && typeof socialLinks.website === 'string') {\n return socialLinks.website;\n }\n return '';\n}\n\nexport default function SpeakerFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data } = useQuery<FullSpeakersResponse, { limit?: number; offset?: number }>(FULL_SPEAKERS_QUERY, {\n variables: { limit: 100, offset: 0 },\n skip: !id,\n });\n const [createSpeaker, { loading: creating }] = useCreateSpeakerMutation();\n const [updateSpeaker, { loading: updating }] = useUpdateSpeakerMutation();\n const [form, setForm] = useState<SpeakerFormState>(initialState);\n\n const existingSpeaker = useMemo(() => (data?.speakers?.items ?? []).find((item) => item.id === id), [data, id]);\n\n useEffect(() => {\n if (!isEdit || !existingSpeaker) {\n return;\n }\n setForm({\n fullName: existingSpeaker.fullName ?? '',\n title: existingSpeaker.title ?? '',\n organization: existingSpeaker.organization ?? '',\n email: existingSpeaker.email ?? '',\n phone: existingSpeaker.phone ?? '',\n bio: existingSpeaker.bio ?? '',\n avatarUrl: existingSpeaker.avatarUrl ?? '',\n website: readWebsite(existingSpeaker.socialLinks),\n });\n }, [existingSpeaker, isEdit]);\n\n const onChange = (field: keyof SpeakerFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.fullName.trim()) {\n notify.error('Name required', 'Give the speaker a name before saving.');\n return;\n }\n\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const input = {\n fullName: form.fullName.trim(),\n title: form.title || null,\n organization: form.organization || null,\n email: form.email || null,\n phone: form.phone || null,\n bio: form.bio || null,\n avatarUrl: form.avatarUrl || null,\n socialLinks: normalizedWebsite ? { website: normalizedWebsite } : {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSpeaker({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSpeaker?.id, 'Could not update speaker')) {\n return;\n }\n notify.success('Speaker updated');\n navigate('/speakers');\n return;\n }\n const response = await createSpeaker({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSpeaker?.id, 'Could not create speaker')) {\n return;\n }\n notify.success('Speaker created');\n navigate('/speakers');\n } catch (error) {\n notify.error('Could not save speaker', describeError(error));\n }\n };\n\n const saving = creating || updating;\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Speaker' : 'Add Speaker'}\n description=\"A person who presents sessions at your events.\"\n purpose=\"A speaker profile is what you attach to sessions and show to attendees. Capture who they are, where they work, and a short bio or talk abstract so the agenda tells a compelling story. The headshot appears anywhere the speaker is listed.\"\n backLabel=\"Back to Speakers\"\n onBack={() => navigate('/speakers')}\n onSubmit={onSubmit}\n submitLabel=\"Save Speaker\"\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} placeholder=\"Head of Product\" />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.organization} onChange={(e) => onChange('organization', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Headshot</span>\n <ImageUploadField\n label=\"Upload headshot\"\n folderPath=\"/EventfullyManaged/Speakers\"\n value={form.avatarUrl || null}\n onUploaded={(url) => onChange('avatarUrl', url)}\n hint=\"Stored securely in Files. Shown wherever the speaker is listed.\"\n />\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Bio &amp; talk abstract</span>\n <Textarea\n value={form.bio}\n onChange={(e) => onChange('bio', e.target.value)}\n rows={5}\n placeholder=\"Short professional bio and a summary of the talk they will give.\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website / profile URL</span>\n <Input value={form.website} onChange={(e) => onChange('website', e.target.value)} placeholder=\"https://…\" />\n </label>\n\n {isEdit ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_SPEAKER\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { describeError as o, mutationSucceeded as s } from "../../lib/mutationRe
5
5
  import { safeUrl as c } from "../../components/SafeLink.js";
6
6
  import { ImageUploadField as l } from "../../components/ImageUploadField.js";
7
7
  import { useEffect as u, useMemo as d, useState as f } from "react";
8
- import { useParams as p } from "react-router-dom";
8
+ import { useParams as p } from "react-router";
9
9
  import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
10
  import { ArrowLeft as g, Save as _ } from "lucide-react";
11
11
  import { Button as v, EmphasisPanel as y, Input as b, PagePurpose as x, Select as S, SelectContent as C, SelectItem as w, SelectTrigger as T, SelectValue as E } from "@burdenoff/fe-libs/ui";
@@ -1 +1 @@
1
- {"version":3,"file":"SponsorFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/sponsors/SponsorFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { ArrowLeft, Save } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useCreateSponsorMutation,\n useEventsQuery,\n useSponsorsQuery,\n useUpdateSponsorMutation,\n} from '../../api/eventApi';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype SponsorFormState = {\n eventId: string;\n name: string;\n level: string;\n logoUrl: string;\n website: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n value: string;\n leads: string;\n};\n\nconst initialState: SponsorFormState = {\n eventId: '',\n name: '',\n level: 'BRONZE',\n logoUrl: '',\n website: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n value: '0',\n leads: '0',\n};\n\nexport default function SponsorFormPage() {\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 const { data: sponsorsData } = useSponsorsQuery({ limit: 100, offset: 0 });\n\n const existingSponsor = useMemo(\n () => (sponsorsData?.sponsors?.items ?? []).find((item: { id: string }) => item.id === id),\n [id, sponsorsData]\n );\n\n const [createSponsor, { loading: creating }] = useCreateSponsorMutation();\n const [updateSponsor, { loading: updating }] = useUpdateSponsorMutation();\n const [form, setForm] = useState<SponsorFormState>(() => {\n if (!existingSponsor) {\n return initialState;\n }\n return {\n eventId: existingSponsor.eventId ?? '',\n name: existingSponsor.name ?? '',\n level: existingSponsor.level ?? 'BRONZE',\n logoUrl: existingSponsor.logoUrl ?? '',\n website: existingSponsor.website ?? '',\n contactName: existingSponsor.contactName ?? '',\n contactEmail: existingSponsor.contactEmail ?? '',\n contactPhone: existingSponsor.contactPhone ?? '',\n value: String(existingSponsor.metrics?.value ?? 0),\n leads: String(existingSponsor.metrics?.leads ?? 0),\n };\n });\n\n useEffect(() => {\n if (!existingSponsor) {\n return;\n }\n setForm({\n eventId: existingSponsor.eventId ?? '',\n name: existingSponsor.name ?? '',\n level: existingSponsor.level ?? 'BRONZE',\n logoUrl: existingSponsor.logoUrl ?? '',\n website: existingSponsor.website ?? '',\n contactName: existingSponsor.contactName ?? '',\n contactEmail: existingSponsor.contactEmail ?? '',\n contactPhone: existingSponsor.contactPhone ?? '',\n value: String(existingSponsor.metrics?.value ?? 0),\n leads: String(existingSponsor.metrics?.leads ?? 0),\n });\n }, [existingSponsor]);\n\n const onChange = (field: keyof SponsorFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n // Reject/normalise the free-text website before it is persisted so a hostile\n // value (e.g. javascript:) can never be stored and later rendered as a link.\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this sponsor belongs to.');\n return;\n }\n\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n name: form.name,\n level: form.level,\n logoUrl: form.logoUrl,\n website: normalizedWebsite ?? '',\n contactName: form.contactName,\n contactEmail: form.contactEmail,\n contactPhone: form.contactPhone,\n metrics: {\n value: Number(form.value || 0),\n leads: Number(form.leads || 0),\n },\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSponsor({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSponsor?.id, 'Could not update sponsor')) {\n return;\n }\n navigate('/sponsors');\n return;\n }\n\n const response = await createSponsor({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSponsor?.id, 'Could not create sponsor')) {\n return;\n }\n navigate('/sponsors');\n } catch (error) {\n notify.error('Could not save sponsor', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/sponsors')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Sponsors\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">{isEdit ? 'Edit Sponsor' : 'Create Sponsor'}</h1>\n </div>\n\n <PagePurpose>\n Capture a sponsor and the deal behind it. Link them to an event, set their tier, and record the contract value\n and leads target so you can measure ROI later. The contact fields are who you will work with day to day, so keep\n them accurate.\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={onSubmit} className=\"space-y-4\">\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: { id: string; name: string }) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Sponsor Name</span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Level</span>\n <Select value={form.level} onValueChange={(value) => onChange('level', value)}>\n <SelectTrigger aria-label=\"Level\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PLATINUM\">Platinum</SelectItem>\n <SelectItem value=\"GOLD\">Gold</SelectItem>\n <SelectItem value=\"SILVER\">Silver</SelectItem>\n <SelectItem value=\"BRONZE\">Bronze</SelectItem>\n <SelectItem value=\"COMMUNITY\">Community</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Value</span>\n <Input type=\"number\" min=\"0\" value={form.value} onChange={(e) => onChange('value', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Leads</span>\n <Input type=\"number\" min=\"0\" value={form.leads} onChange={(e) => onChange('leads', 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\">Website</span>\n <Input value={form.website} onChange={(e) => onChange('website', e.target.value)} />\n </label>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Logo</span>\n <ImageUploadField\n label=\"Upload logo\"\n folderPath=\"/EventfullyManaged/Sponsors\"\n value={form.logoUrl || null}\n onUploaded={(url) => onChange('logoUrl', url)}\n hint=\"Stored securely in Files. Shown across sponsor listings and detail pages.\"\n />\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\">Contact Name</span>\n <Input value={form.contactName} onChange={(e) => onChange('contactName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Email</span>\n <Input\n type=\"email\"\n value={form.contactEmail}\n onChange={(e) => onChange('contactEmail', e.target.value)}\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Phone</span>\n <Input value={form.contactPhone} onChange={(e) => onChange('contactPhone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={saving}>\n <Save className=\"h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Sponsor'}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SponsorFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/sponsors/SponsorFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { ArrowLeft, Save } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport {\n useCreateSponsorMutation,\n useEventsQuery,\n useSponsorsQuery,\n useUpdateSponsorMutation,\n} from '../../api/eventApi';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { safeUrl } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype SponsorFormState = {\n eventId: string;\n name: string;\n level: string;\n logoUrl: string;\n website: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n value: string;\n leads: string;\n};\n\nconst initialState: SponsorFormState = {\n eventId: '',\n name: '',\n level: 'BRONZE',\n logoUrl: '',\n website: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n value: '0',\n leads: '0',\n};\n\nexport default function SponsorFormPage() {\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 const { data: sponsorsData } = useSponsorsQuery({ limit: 100, offset: 0 });\n\n const existingSponsor = useMemo(\n () => (sponsorsData?.sponsors?.items ?? []).find((item: { id: string }) => item.id === id),\n [id, sponsorsData]\n );\n\n const [createSponsor, { loading: creating }] = useCreateSponsorMutation();\n const [updateSponsor, { loading: updating }] = useUpdateSponsorMutation();\n const [form, setForm] = useState<SponsorFormState>(() => {\n if (!existingSponsor) {\n return initialState;\n }\n return {\n eventId: existingSponsor.eventId ?? '',\n name: existingSponsor.name ?? '',\n level: existingSponsor.level ?? 'BRONZE',\n logoUrl: existingSponsor.logoUrl ?? '',\n website: existingSponsor.website ?? '',\n contactName: existingSponsor.contactName ?? '',\n contactEmail: existingSponsor.contactEmail ?? '',\n contactPhone: existingSponsor.contactPhone ?? '',\n value: String(existingSponsor.metrics?.value ?? 0),\n leads: String(existingSponsor.metrics?.leads ?? 0),\n };\n });\n\n useEffect(() => {\n if (!existingSponsor) {\n return;\n }\n setForm({\n eventId: existingSponsor.eventId ?? '',\n name: existingSponsor.name ?? '',\n level: existingSponsor.level ?? 'BRONZE',\n logoUrl: existingSponsor.logoUrl ?? '',\n website: existingSponsor.website ?? '',\n contactName: existingSponsor.contactName ?? '',\n contactEmail: existingSponsor.contactEmail ?? '',\n contactPhone: existingSponsor.contactPhone ?? '',\n value: String(existingSponsor.metrics?.value ?? 0),\n leads: String(existingSponsor.metrics?.leads ?? 0),\n });\n }, [existingSponsor]);\n\n const onChange = (field: keyof SponsorFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n // Reject/normalise the free-text website before it is persisted so a hostile\n // value (e.g. javascript:) can never be stored and later rendered as a link.\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this sponsor belongs to.');\n return;\n }\n\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n name: form.name,\n level: form.level,\n logoUrl: form.logoUrl,\n website: normalizedWebsite ?? '',\n contactName: form.contactName,\n contactEmail: form.contactEmail,\n contactPhone: form.contactPhone,\n metrics: {\n value: Number(form.value || 0),\n leads: Number(form.leads || 0),\n },\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSponsor({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSponsor?.id, 'Could not update sponsor')) {\n return;\n }\n navigate('/sponsors');\n return;\n }\n\n const response = await createSponsor({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSponsor?.id, 'Could not create sponsor')) {\n return;\n }\n navigate('/sponsors');\n } catch (error) {\n notify.error('Could not save sponsor', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/sponsors')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Sponsors\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">{isEdit ? 'Edit Sponsor' : 'Create Sponsor'}</h1>\n </div>\n\n <PagePurpose>\n Capture a sponsor and the deal behind it. Link them to an event, set their tier, and record the contract value\n and leads target so you can measure ROI later. The contact fields are who you will work with day to day, so keep\n them accurate.\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={onSubmit} className=\"space-y-4\">\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: { id: string; name: string }) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Sponsor Name</span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Level</span>\n <Select value={form.level} onValueChange={(value) => onChange('level', value)}>\n <SelectTrigger aria-label=\"Level\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PLATINUM\">Platinum</SelectItem>\n <SelectItem value=\"GOLD\">Gold</SelectItem>\n <SelectItem value=\"SILVER\">Silver</SelectItem>\n <SelectItem value=\"BRONZE\">Bronze</SelectItem>\n <SelectItem value=\"COMMUNITY\">Community</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Value</span>\n <Input type=\"number\" min=\"0\" value={form.value} onChange={(e) => onChange('value', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Leads</span>\n <Input type=\"number\" min=\"0\" value={form.leads} onChange={(e) => onChange('leads', 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\">Website</span>\n <Input value={form.website} onChange={(e) => onChange('website', e.target.value)} />\n </label>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Logo</span>\n <ImageUploadField\n label=\"Upload logo\"\n folderPath=\"/EventfullyManaged/Sponsors\"\n value={form.logoUrl || null}\n onUploaded={(url) => onChange('logoUrl', url)}\n hint=\"Stored securely in Files. Shown across sponsor listings and detail pages.\"\n />\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\">Contact Name</span>\n <Input value={form.contactName} onChange={(e) => onChange('contactName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Email</span>\n <Input\n type=\"email\"\n value={form.contactEmail}\n onChange={(e) => onChange('contactEmail', e.target.value)}\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Contact Phone</span>\n <Input value={form.contactPhone} onChange={(e) => onChange('contactPhone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={saving}>\n <Save className=\"h-4 w-4\" />\n {saving ? 'Saving...' : 'Save Sponsor'}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { ActionMenu as o } from "../../components/ActionMenu.js";
5
5
  import { describeError as s, mutationSucceeded as c } from "../../lib/mutationResult.js";
6
6
  import { ImageUploadField as l } from "../../components/ImageUploadField.js";
7
7
  import { useMemo as u, useState as d } from "react";
8
- import { useParams as f } from "react-router-dom";
8
+ import { useParams as f } from "react-router";
9
9
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
10
10
  import { ArrowLeft as h, LayoutGrid as g, Map as _, Plus as v, SquareStack as y } from "lucide-react";
11
11
  import { Badge as b, Button as x, GlassCard as S, GradientText as C, IllustratedEmptyState as w, Input as T, Skeleton as E } from "@burdenoff/fe-libs/ui";