@burdenoff/microfe-eventfullymanaged 2026.704.1 → 2026.704.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js +0 -4
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +18 -11
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +2 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/MeetingFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateMeetingMutation,\n useEventsQuery,\n useMeetingsQuery,\n useNetworkingProfilesQuery,\n useUpdateMeetingMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype MeetingFormState = {\n eventId: string;\n title: string;\n sourceProfileId: string;\n targetProfileId: string;\n startsAt: string;\n endsAt: string;\n location: string;\n bookingPageId: string;\n bookerName: string;\n bookerEmail: string;\n notes: string;\n status: string;\n};\n\nconst initialState: MeetingFormState = {\n eventId: '',\n title: '',\n sourceProfileId: '',\n targetProfileId: '',\n startsAt: '',\n endsAt: '',\n location: '',\n bookingPageId: '',\n bookerName: '',\n bookerEmail: '',\n notes: '',\n status: 'SCHEDULED',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\n/** Convert an ISO timestamp to the local `datetime-local` input format. */\nfunction isoToLocalInput(iso: string | undefined): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60 * 1000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** Convert a `datetime-local` value to an ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction readString(metadata: Record<string, unknown> | null | undefined, key: string): string {\n const value = metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nexport default function MeetingFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // The meetings list resolver returns title/status/metadata only (no by-id\n // resolver), so create/update mirror scheduling detail into metadata and the\n // edit initial-state is rehydrated from there.\n const { data: meetingsData } = useMeetingsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (meetingsData?.meetings?.items ?? []).find((item) => item.id === id),\n [meetingsData, id]\n );\n\n const [createMeeting, { loading: creating }] = useCreateMeetingMutation();\n const [updateMeeting, { loading: updating }] = useUpdateMeetingMutation();\n const [form, setForm] = useState<MeetingFormState>(initialState);\n\n const { data: profilesData } = useNetworkingProfilesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(\n () =>\n (profilesData?.networkingProfiles?.items ?? []).map((profile) => ({\n id: profile.id,\n label: attendeeNames.get(profile.attendeeId) ?? profile.role ?? `Profile ${profile.id.slice(0, 8)}`,\n })),\n [profilesData, attendeeNames]\n );\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n setForm({\n eventId: existing.eventId ?? readString(metadata, 'eventId'),\n title: existing.title ?? existing.name ?? readString(metadata, 'title'),\n sourceProfileId: readString(metadata, 'sourceProfileId'),\n targetProfileId: readString(metadata, 'targetProfileId'),\n startsAt: isoToLocalInput(readString(metadata, 'startsAt')),\n endsAt: isoToLocalInput(readString(metadata, 'endsAt')),\n location: readString(metadata, 'location'),\n bookingPageId: readString(metadata, 'bookingPageId'),\n bookerName: readString(metadata, 'bookerName'),\n bookerEmail: readString(metadata, 'bookerEmail'),\n notes: readString(metadata, 'notes'),\n status: existing.status ?? 'SCHEDULED',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof MeetingFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this meeting belongs to.');\n return;\n }\n const startsAtIso = localInputToIso(form.startsAt);\n const endsAtIso = localInputToIso(form.endsAt);\n if (!startsAtIso || !endsAtIso) {\n notify.error('Time required', 'Set both a start and end time for the meeting.');\n return;\n }\n if (new Date(endsAtIso).getTime() <= new Date(startsAtIso).getTime()) {\n notify.error('Invalid time range', 'The meeting must end after it starts.');\n return;\n }\n\n // Mirror scheduling detail into metadata so the calendar-svc integration and\n // the edit form (list resolver returns metadata only) both round-trip.\n const metadata: Record<string, unknown> = {\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n title: form.title,\n sourceProfileId: form.sourceProfileId,\n targetProfileId: form.targetProfileId,\n };\n if (form.bookingPageId.trim()) {\n metadata.bookingPageId = form.bookingPageId.trim();\n }\n if (form.bookerName.trim()) {\n metadata.bookerName = form.bookerName.trim();\n }\n if (form.bookerEmail.trim()) {\n metadata.bookerEmail = form.bookerEmail.trim();\n }\n\n const input: Record<string, unknown> = {\n eventId: form.eventId,\n title: form.title,\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n status: form.status,\n metadata,\n };\n if (form.sourceProfileId) {\n input.sourceProfileId = form.sourceProfileId;\n }\n if (form.targetProfileId) {\n input.targetProfileId = form.targetProfileId;\n }\n // Passing a booking-page id lets the backend mirror the meeting into\n // wspace-calendar-svc rather than the MFE managing a local calendar.\n if (form.bookingPageId.trim()) {\n input.bookingPageId = form.bookingPageId.trim();\n }\n\n try {\n if (isEdit) {\n const response = await updateMeeting({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateMeeting?.id, 'Could not update meeting')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createMeeting({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createMeeting?.id, 'Could not book meeting')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save meeting', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Meeting' : 'Book a Meeting'}\n description=\"Schedule a 1:1 between two networking profiles.\"\n purpose=\"Meetings are the 1:1s attendees book with each other. Pick the two participants, set a time and place, and — if you use a shared booking page — link it so the meeting mirrors into the calendar service. Everyone sees the same schedule without a separate calendar here.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Meeting' : 'Book Meeting'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant A</span>\n <Select\n value={form.sourceProfileId}\n onValueChange={(value) => onChange('sourceProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant A\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant B</span>\n <Select\n value={form.targetProfileId}\n onValueChange={(value) => onChange('targetProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant B\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Starts at</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends at</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"Networking lounge, Table 4\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booking page ID</span>\n <Input\n value={form.bookingPageId}\n onChange={(e) => onChange('bookingPageId', e.target.value)}\n placeholder=\"Optional — mirrors to calendar\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker name</span>\n <Input value={form.bookerName} onChange={(e) => onChange('bookerName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker email</span>\n <Input\n type=\"email\"\n value={form.bookerEmail}\n onChange={(e) => onChange('bookerEmail', e.target.value)}\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Notes</span>\n <Textarea value={form.notes} onChange={(e) => onChange('notes', e.target.value)} rows={3} />\n </label>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeetingFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/MeetingFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateMeetingMutation,\n useEventsQuery,\n useMeetingsQuery,\n useNetworkingProfilesQuery,\n useUpdateMeetingMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype MeetingFormState = {\n eventId: string;\n title: string;\n sourceProfileId: string;\n targetProfileId: string;\n startsAt: string;\n endsAt: string;\n location: string;\n bookingPageId: string;\n bookerName: string;\n bookerEmail: string;\n notes: string;\n status: string;\n};\n\nconst initialState: MeetingFormState = {\n eventId: '',\n title: '',\n sourceProfileId: '',\n targetProfileId: '',\n startsAt: '',\n endsAt: '',\n location: '',\n bookingPageId: '',\n bookerName: '',\n bookerEmail: '',\n notes: '',\n status: 'SCHEDULED',\n};\n\nconst STATUS_OPTIONS = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\n/** Convert an ISO timestamp to the local `datetime-local` input format. */\nfunction isoToLocalInput(iso: string | undefined): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60 * 1000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** Convert a `datetime-local` value to an ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction readString(metadata: Record<string, unknown> | null | undefined, key: string): string {\n const value = metadata?.[key];\n return typeof value === 'string' ? value : '';\n}\n\nexport default function MeetingFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // The meetings list resolver returns title/status/metadata only (no by-id\n // resolver), so create/update mirror scheduling detail into metadata and the\n // edit initial-state is rehydrated from there.\n const { data: meetingsData } = useMeetingsQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (meetingsData?.meetings?.items ?? []).find((item) => item.id === id),\n [meetingsData, id]\n );\n\n const [createMeeting, { loading: creating }] = useCreateMeetingMutation();\n const [updateMeeting, { loading: updating }] = useUpdateMeetingMutation();\n const [form, setForm] = useState<MeetingFormState>(initialState);\n\n const { data: profilesData } = useNetworkingProfilesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n\n const attendeeNames = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees?.items ?? []) {\n map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n\n const profiles = useMemo(\n () =>\n (profilesData?.networkingProfiles?.items ?? []).map((profile) => ({\n id: profile.id,\n label: attendeeNames.get(profile.attendeeId) ?? profile.role ?? `Profile ${profile.id.slice(0, 8)}`,\n })),\n [profilesData, attendeeNames]\n );\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n setForm({\n eventId: existing.eventId ?? readString(metadata, 'eventId'),\n title: existing.title ?? existing.name ?? readString(metadata, 'title'),\n sourceProfileId: readString(metadata, 'sourceProfileId'),\n targetProfileId: readString(metadata, 'targetProfileId'),\n startsAt: isoToLocalInput(readString(metadata, 'startsAt')),\n endsAt: isoToLocalInput(readString(metadata, 'endsAt')),\n location: readString(metadata, 'location'),\n bookingPageId: readString(metadata, 'bookingPageId'),\n bookerName: readString(metadata, 'bookerName'),\n bookerEmail: readString(metadata, 'bookerEmail'),\n notes: readString(metadata, 'notes'),\n status: existing.status ?? 'SCHEDULED',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof MeetingFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this meeting belongs to.');\n return;\n }\n const startsAtIso = localInputToIso(form.startsAt);\n const endsAtIso = localInputToIso(form.endsAt);\n if (!startsAtIso || !endsAtIso) {\n notify.error('Time required', 'Set both a start and end time for the meeting.');\n return;\n }\n if (new Date(endsAtIso).getTime() <= new Date(startsAtIso).getTime()) {\n notify.error('Invalid time range', 'The meeting must end after it starts.');\n return;\n }\n\n // Mirror scheduling detail into metadata so the calendar-svc integration and\n // the edit form (list resolver returns metadata only) both round-trip.\n const metadata: Record<string, unknown> = {\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n title: form.title,\n sourceProfileId: form.sourceProfileId,\n targetProfileId: form.targetProfileId,\n };\n if (form.bookingPageId.trim()) {\n metadata.bookingPageId = form.bookingPageId.trim();\n }\n if (form.bookerName.trim()) {\n metadata.bookerName = form.bookerName.trim();\n }\n if (form.bookerEmail.trim()) {\n metadata.bookerEmail = form.bookerEmail.trim();\n }\n\n const input: Record<string, unknown> = {\n eventId: form.eventId,\n title: form.title,\n startsAt: startsAtIso,\n endsAt: endsAtIso,\n location: form.location,\n notes: form.notes,\n status: form.status,\n metadata,\n };\n if (form.sourceProfileId) {\n input.sourceProfileId = form.sourceProfileId;\n }\n if (form.targetProfileId) {\n input.targetProfileId = form.targetProfileId;\n }\n // Passing a booking-page id lets the backend mirror the meeting into\n // wspace-calendar-svc rather than the MFE managing a local calendar.\n if (form.bookingPageId.trim()) {\n input.bookingPageId = form.bookingPageId.trim();\n }\n\n try {\n if (isEdit) {\n const response = await updateMeeting({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateMeeting?.id, 'Could not update meeting')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createMeeting({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createMeeting?.id, 'Could not book meeting')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save meeting', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Meeting' : 'Book a Meeting'}\n description=\"Schedule a 1:1 between two networking profiles.\"\n purpose=\"Meetings are the 1:1s attendees book with each other. Pick the two participants, set a time and place, and — if you use a shared booking page — link it so the meeting mirrors into the calendar service. Everyone sees the same schedule without a separate calendar here.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Meeting' : 'Book Meeting'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input value={form.title} onChange={(e) => onChange('title', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant A</span>\n <Select\n value={form.sourceProfileId}\n onValueChange={(value) => onChange('sourceProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant A\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Participant B</span>\n <Select\n value={form.targetProfileId}\n onValueChange={(value) => onChange('targetProfileId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Participant B\">\n <SelectValue placeholder={form.eventId ? 'Select profile' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {profiles.map((profile) => (\n <SelectItem key={profile.id} value={profile.id}>\n {profile.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Starts at</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends at</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Status</span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label=\"Status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Location</span>\n <Input\n value={form.location}\n onChange={(e) => onChange('location', e.target.value)}\n placeholder=\"Networking lounge, Table 4\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booking page ID</span>\n <Input\n value={form.bookingPageId}\n onChange={(e) => onChange('bookingPageId', e.target.value)}\n placeholder=\"Optional — mirrors to calendar\"\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker name</span>\n <Input value={form.bookerName} onChange={(e) => onChange('bookerName', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Booker email</span>\n <Input type=\"email\" value={form.bookerEmail} onChange={(e) => onChange('bookerEmail', e.target.value)} />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Notes</span>\n <Textarea value={form.notes} onChange={(e) => onChange('notes', e.target.value)} rows={3} />\n </label>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkingProfileFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/NetworkingProfileFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateNetworkingProfileMutation,\n useEventsQuery,\n useNetworkingProfilesQuery,\n useUpdateNetworkingProfileMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ProfileFormState = {\n eventId: string;\n attendeeId: string;\n interests: string;\n goals: string;\n availability: string;\n industry: string;\n role: string;\n visibility: string;\n};\n\nconst initialState: ProfileFormState = {\n eventId: '',\n attendeeId: '',\n interests: '',\n goals: '',\n availability: '',\n industry: '',\n role: '',\n visibility: 'ATTENDEES',\n};\n\nconst VISIBILITY_OPTIONS = [\n { value: 'PUBLIC', label: 'Public' },\n { value: 'ATTENDEES', label: 'Attendees only' },\n { value: 'PRIVATE', label: 'Private' },\n];\n\nfunction splitList(value: string): string[] {\n return value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nexport default function NetworkingProfileFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver for profiles — derive edit state from the list row.\n const { data: profilesData } = useNetworkingProfilesQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (profilesData?.networkingProfiles?.items ?? []).find((item) => item.id === id),\n [profilesData, id]\n );\n\n const [createProfile, { loading: creating }] = useCreateNetworkingProfileMutation();\n const [updateProfile, { loading: updating }] = useUpdateNetworkingProfileMutation();\n const [form, setForm] = useState<ProfileFormState>(initialState);\n\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const attendees = attendeesData?.attendees?.items ?? [];\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const availability = typeof metadata.availability === 'string' ? metadata.availability : '';\n setForm({\n eventId: existing.eventId ?? '',\n attendeeId: existing.attendeeId ?? '',\n interests: (existing.interests ?? []).join(', '),\n goals: (existing.lookingFor ?? []).join(', '),\n availability,\n industry: existing.industry ?? '',\n role: existing.role ?? '',\n visibility: existing.visibility ?? 'ATTENDEES',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ProfileFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this profile is opting into.');\n return;\n }\n if (!form.attendeeId) {\n notify.error('Attendee required', 'Choose the attendee this profile represents.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n attendeeId: form.attendeeId,\n interests: splitList(form.interests),\n lookingFor: splitList(form.goals),\n industry: form.industry,\n role: form.role,\n visibility: form.visibility,\n metadata: { ...existingMetadata, availability: form.availability },\n };\n\n try {\n if (isEdit) {\n const response = await updateProfile({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateNetworkingProfile?.id, 'Could not update profile')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createProfile({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createNetworkingProfile?.id, 'Could not create profile')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save profile', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Networking Profile' : 'Opt in to Networking'}\n description=\"Describe an attendee so the matchmaking engine can suggest the right connections.\"\n purpose=\"A networking profile is how an attendee tells the matchmaking engine who they are and who they want to meet. Interests and goals drive the suggested matches, availability tells others when to book time, and visibility controls who can see the profile.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Profile' : 'Opt in'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select\n value={form.attendeeId}\n onValueChange={(value) => onChange('attendeeId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue placeholder={form.eventId ? 'Select attendee' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Interests (comma separated)</span>\n <Input\n value={form.interests}\n onChange={(e) => onChange('interests', e.target.value)}\n placeholder=\"AI, fintech, design\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Goals — who they want to meet (comma separated)</span>\n <Input\n value={form.goals}\n onChange={(e) => onChange('goals', e.target.value)}\n placeholder=\"investors, hiring partners, mentors\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Availability</span>\n <Textarea\n value={form.availability}\n onChange={(e) => onChange('availability', e.target.value)}\n rows={2}\n placeholder=\"Free during lunch and after the keynote\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Industry</span>\n <Input value={form.industry} onChange={(e) => onChange('industry', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Visibility</span>\n <Select value={form.visibility} onValueChange={(value) => onChange('visibility', value)}>\n <SelectTrigger aria-label=\"Visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NetworkingProfileFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/networking/NetworkingProfileFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea } from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useAttendeesQuery,\n useCreateNetworkingProfileMutation,\n useEventsQuery,\n useNetworkingProfilesQuery,\n useUpdateNetworkingProfileMutation,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype ProfileFormState = {\n eventId: string;\n attendeeId: string;\n interests: string;\n goals: string;\n availability: string;\n industry: string;\n role: string;\n visibility: string;\n};\n\nconst initialState: ProfileFormState = {\n eventId: '',\n attendeeId: '',\n interests: '',\n goals: '',\n availability: '',\n industry: '',\n role: '',\n visibility: 'ATTENDEES',\n};\n\nconst VISIBILITY_OPTIONS = [\n { value: 'PUBLIC', label: 'Public' },\n { value: 'ATTENDEES', label: 'Attendees only' },\n { value: 'PRIVATE', label: 'Private' },\n];\n\nfunction splitList(value: string): string[] {\n return value\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nexport default function NetworkingProfileFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id resolver for profiles — derive edit state from the list row.\n const { data: profilesData } = useNetworkingProfilesQuery({ limit: 100, offset: 0 });\n\n const existing = useMemo(\n () => (profilesData?.networkingProfiles?.items ?? []).find((item) => item.id === id),\n [profilesData, id]\n );\n\n const [createProfile, { loading: creating }] = useCreateNetworkingProfileMutation();\n const [updateProfile, { loading: updating }] = useUpdateNetworkingProfileMutation();\n const [form, setForm] = useState<ProfileFormState>(initialState);\n\n const { data: attendeesData } = useAttendeesQuery({\n eventId: form.eventId || undefined,\n limit: 100,\n offset: 0,\n });\n const attendees = attendeesData?.attendees?.items ?? [];\n\n useEffect(() => {\n if (!isEdit || !existing) {\n return;\n }\n const metadata = existing.metadata ?? {};\n const availability = typeof metadata.availability === 'string' ? metadata.availability : '';\n setForm({\n eventId: existing.eventId ?? '',\n attendeeId: existing.attendeeId ?? '',\n interests: (existing.interests ?? []).join(', '),\n goals: (existing.lookingFor ?? []).join(', '),\n availability,\n industry: existing.industry ?? '',\n role: existing.role ?? '',\n visibility: existing.visibility ?? 'ATTENDEES',\n });\n }, [existing, isEdit]);\n\n const onChange = (field: keyof ProfileFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this profile is opting into.');\n return;\n }\n if (!form.attendeeId) {\n notify.error('Attendee required', 'Choose the attendee this profile represents.');\n return;\n }\n\n const existingMetadata = existing?.metadata ?? {};\n const input = {\n eventId: form.eventId,\n attendeeId: form.attendeeId,\n interests: splitList(form.interests),\n lookingFor: splitList(form.goals),\n industry: form.industry,\n role: form.role,\n visibility: form.visibility,\n metadata: { ...existingMetadata, availability: form.availability },\n };\n\n try {\n if (isEdit) {\n const response = await updateProfile({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateNetworkingProfile?.id, 'Could not update profile')) {\n return;\n }\n navigate('/networking');\n return;\n }\n\n const response = await createProfile({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createNetworkingProfile?.id, 'Could not create profile')) {\n return;\n }\n navigate('/networking');\n } catch (error) {\n notify.error('Could not save profile', describeError(error));\n }\n };\n\n const saving = creating || updating;\n const events = eventsData?.events?.items ?? [];\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Networking Profile' : 'Opt in to Networking'}\n description=\"Describe an attendee so the matchmaking engine can suggest the right connections.\"\n purpose=\"A networking profile is how an attendee tells the matchmaking engine who they are and who they want to meet. Interests and goals drive the suggested matches, availability tells others when to book time, and visibility controls who can see the profile.\"\n backLabel=\"Back to Networking\"\n onBack={() => navigate('/networking')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Profile' : 'Opt in'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select\n value={form.attendeeId}\n onValueChange={(value) => onChange('attendeeId', value)}\n disabled={!form.eventId}\n >\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue placeholder={form.eventId ? 'Select attendee' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Interests (comma separated)</span>\n <Input\n value={form.interests}\n onChange={(e) => onChange('interests', e.target.value)}\n placeholder=\"AI, fintech, design\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Goals — who they want to meet (comma separated)</span>\n <Input\n value={form.goals}\n onChange={(e) => onChange('goals', e.target.value)}\n placeholder=\"investors, hiring partners, mentors\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Availability</span>\n <Textarea\n value={form.availability}\n onChange={(e) => onChange('availability', e.target.value)}\n rows={2}\n placeholder=\"Free during lunch and after the keynote\"\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Industry</span>\n <Input value={form.industry} onChange={(e) => onChange('industry', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Visibility</span>\n <Select value={form.visibility} onValueChange={(value) => onChange('visibility', value)}>\n <SelectTrigger aria-label=\"Visibility\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {VISIBILITY_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? (\n <EntityTagsField entityType=\"attendee\" entityId={id} />\n ) : (\n <EntityTagsField entityType=\"attendee\" entityId=\"\" />\n )}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? (\n <EntityTagsField entityType=\"attendee\" entityId={id} />\n ) : (\n <EntityTagsField entityType=\"attendee\" entityId=\"\" />\n )}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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 {\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\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(\n () => (promoData?.promoCodes.items ?? []).find((item) => item.id === id),\n [promoData, id]\n );\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 <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":";;;;;;;;;;AAsCA,IAAM,IAAiC;CACrC,SAAS;CACT,MAAM;CACN,aAAa;CACb,cAAc;CACd,eAAe;CACf,SAAS;CACT,UAAU;CACV,QAAQ;CACR,UAAU;CACX;AAGD,SAAS,EAAgB,GAA6B;AACpD,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;AAC5C,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;AAI5B,QAHI,OAAO,MAAM,EAAK,SAAS,CAAC,GACvB,OAEF,EAAK,aAAa;;AAG3B,SAAS,EAAY,GAAyC;AAC5D,QAAO;EACL,SAAS,EAAM,WAAW;EAC1B,MAAM,EAAM,QAAQ;EACpB,aAAa,EAAM,eAAe;EAClC,cAAc,EAAM,gBAAgB;EACpC,eAAe,EAAM,iBAAiB;EACtC,SAAS,EAAM,WAAW,OAA+B,KAAxB,OAAO,EAAM,QAAQ;EACtD,UAAU,EAAgB,EAAM,SAAS;EACzC,QAAQ,EAAgB,EAAM,OAAO;EACrC,UAAU,EAAM,YAAY;EAC7B;;AAGH,SAAwB,IAAoB;CAC1C,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,EAEhE,EAAE,MAAM,MAAc,EAAmB;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EACnE,IAAW,SACR,GAAW,WAAW,SAAS,EAAE,EAAE,MAAM,MAAS,EAAK,OAAO,EAAG,EACxE,CAAC,GAAW,EAAG,CAChB,EAEK,CAAC,GAAiB,EAAE,SAAS,OAAc,GAA4B,EACvE,CAAC,GAAiB,EAAE,SAAS,OAAc,GAA4B,EACvE,CAAC,GAAM,KAAW,QAAoC,IAAW,EAAY,EAAS,GAAG,EAAY;AAE3G,SAAgB;AACd,EAAI,KACF,EAAQ,EAAY,EAAS,CAAC;IAE/B,CAAC,EAAS,CAAC;CAEd,IAAM,KAAgD,GAAU,MAAiC;AAC/F,KAAS,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAO,EAAE;IAG5C,IAAS,GAAY,OAAO,SAAS,EAAE,EACvC,IAAS,KAAY,GAErB,IAAW,OAAO,MAA4C;AAGlE,MAFA,EAAM,gBAAgB,EAElB,CAAC,EAAK,SAAS;AACjB,KAAO,MAAM,kBAAkB,+CAA+C;AAC9E;;AAEF,MAAI,CAAC,EAAK,KAAK,MAAM,EAAE;AACrB,KAAO,MAAM,iBAAiB,kDAAkD;AAChF;;AAEF,MAAI,CAAC,EAAK,cAAc,MAAM,EAAE;AAC9B,KAAO,MAAM,qBAAqB,0BAA0B;AAC5D;;EAGF,IAAM,IAAQ;GACZ,SAAS,EAAK;GACd,MAAM,EAAK,KAAK,MAAM,CAAC,aAAa;GACpC,aAAa,EAAK,YAAY,MAAM,IAAI;GACxC,cAAc,EAAK;GACnB,eAAe,EAAK,cAAc,MAAM;GACxC,SAAS,EAAK,QAAQ,MAAM,GAAG,OAAO,EAAK,QAAQ,GAAG;GACtD,UAAU,EAAgB,EAAK,SAAS;GACxC,QAAQ,EAAgB,EAAK,OAAO;GACpC,UAAU,EAAK;GACf,UAAU,EAAE;GACb;AAED,MAAI;AACF,OAAI,GAAQ;IACV,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW;KAAE;KAAI;KAAO,EAAE,CAAC;AACpE,QAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,iBAAiB,IAAI,8BAA8B,CACjG;AAGF,IADA,EAAO,QAAQ,sBAAsB,EAAM,KAAK,EAChD,EAAS,gBAAgB;AACzB;;GAGF,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC;AAChE,OAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,iBAAiB,IAAI,8BAA8B,CACjG;AAGF,GADA,EAAO,QAAQ,sBAAsB,EAAM,KAAK,EAChD,EAAS,gBAAgB;WAClB,GAAO;AACd,KAAO,MAAM,6BAA6B,EAAc,EAAM,CAAC;;IAI7D,IACJ,EAAK,iBAAiB,YAAY,qCAAqC;AAEzE,QACE,kBAAC,GAAD;EACE,OAAO,IAAS,oBAAoB;EACpC,aAAY;EACZ,SAAQ;EACR,WAAU;EACV,cAAc,EAAS,gBAAgB;EAC7B;EACV,aAAa,IAAS,oBAAoB;EAClC;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;MAAW,CAAA,EAC3D,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;MACjD,aAAY;MACZ,UAAA;MACA,CAAA,CACI;OACJ;;GAEN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAkB,CAAA,EAClE,kBAAC,GAAD;KAAU,OAAO,EAAK;KAAa,WAAW,MAAM,EAAS,eAAe,EAAE,OAAO,MAAM;KAAE,MAAM;KAAK,CAAA,CAClG;;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAoB,CAAA,EACpE,kBAAC,GAAD;OAAQ,OAAO,EAAK;OAAc,gBAAgB,MAAU,EAAS,gBAAgB,EAAM;iBAA3F,CACE,kBAAC,GAAD;QAAe,cAAW;kBACxB,kBAAC,GAAD,EAAe,CAAA;QACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QAAY,OAAM;kBAAU;QAA2B,CAAA,EACvD,kBAAC,GAAD;QAAY,OAAM;kBAAQ;QAAyB,CAAA,CACrC,EAAA,CAAA,CACT;SACH;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAgC;QAAqB,CAAA;OACrE,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,MAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAS,iBAAiB,EAAE,OAAO,MAAM;QAC1D,UAAA;QACA,CAAA;OACF,kBAAC,QAAD;QAAM,WAAU;kBAA+B;QAAiB,CAAA;OAC1D;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAe,CAAA,EAC/D,kBAAC,GAAD;OACE,MAAK;OACL,KAAI;OACJ,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,MAAM;OACpD,aAAY;OACZ,CAAA,CACI;;KACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAgB,CAAA,EAChE,kBAAC,GAAD;MAAO,MAAK;MAAiB,OAAO,EAAK;MAAU,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;MAAI,CAAA,CACtG;QACR,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAc,CAAA,EAC9D,kBAAC,GAAD;MAAO,MAAK;MAAiB,OAAO,EAAK;MAAQ,WAAW,MAAM,EAAS,UAAU,EAAE,OAAO,MAAM;MAAI,CAAA,CAClG;OACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAa,CAAA,EAC7D,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAkD,CAAA,CACzF;QACN,kBAAC,GAAD;KAAQ,SAAS,EAAK;KAAU,kBAAkB,MAAY,EAAS,YAAY,EAAQ;KAAI,CAAA,CAC3F;;GACU"}
|
|
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 {\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\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 <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":";;;;;;;;;;AAsCA,IAAM,IAAiC;CACrC,SAAS;CACT,MAAM;CACN,aAAa;CACb,cAAc;CACd,eAAe;CACf,SAAS;CACT,UAAU;CACV,QAAQ;CACR,UAAU;CACX;AAGD,SAAS,EAAgB,GAA6B;AACpD,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;AAC5C,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;AAI5B,QAHI,OAAO,MAAM,EAAK,SAAS,CAAC,GACvB,OAEF,EAAK,aAAa;;AAG3B,SAAS,EAAY,GAAyC;AAC5D,QAAO;EACL,SAAS,EAAM,WAAW;EAC1B,MAAM,EAAM,QAAQ;EACpB,aAAa,EAAM,eAAe;EAClC,cAAc,EAAM,gBAAgB;EACpC,eAAe,EAAM,iBAAiB;EACtC,SAAS,EAAM,WAAW,OAA+B,KAAxB,OAAO,EAAM,QAAQ;EACtD,UAAU,EAAgB,EAAM,SAAS;EACzC,QAAQ,EAAgB,EAAM,OAAO;EACrC,UAAU,EAAM,YAAY;EAC7B;;AAGH,SAAwB,IAAoB;CAC1C,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,EAEhE,EAAE,MAAM,MAAc,EAAmB;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EACnE,IAAW,SAAe,GAAW,WAAW,SAAS,EAAE,EAAE,MAAM,MAAS,EAAK,OAAO,EAAG,EAAE,CAAC,GAAW,EAAG,CAAC,EAE7G,CAAC,GAAiB,EAAE,SAAS,OAAc,GAA4B,EACvE,CAAC,GAAiB,EAAE,SAAS,OAAc,GAA4B,EACvE,CAAC,GAAM,KAAW,QAAoC,IAAW,EAAY,EAAS,GAAG,EAAY;AAE3G,SAAgB;AACd,EAAI,KACF,EAAQ,EAAY,EAAS,CAAC;IAE/B,CAAC,EAAS,CAAC;CAEd,IAAM,KAAgD,GAAU,MAAiC;AAC/F,KAAS,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAO,EAAE;IAG5C,IAAS,GAAY,OAAO,SAAS,EAAE,EACvC,IAAS,KAAY,GAErB,IAAW,OAAO,MAA4C;AAGlE,MAFA,EAAM,gBAAgB,EAElB,CAAC,EAAK,SAAS;AACjB,KAAO,MAAM,kBAAkB,+CAA+C;AAC9E;;AAEF,MAAI,CAAC,EAAK,KAAK,MAAM,EAAE;AACrB,KAAO,MAAM,iBAAiB,kDAAkD;AAChF;;AAEF,MAAI,CAAC,EAAK,cAAc,MAAM,EAAE;AAC9B,KAAO,MAAM,qBAAqB,0BAA0B;AAC5D;;EAGF,IAAM,IAAQ;GACZ,SAAS,EAAK;GACd,MAAM,EAAK,KAAK,MAAM,CAAC,aAAa;GACpC,aAAa,EAAK,YAAY,MAAM,IAAI;GACxC,cAAc,EAAK;GACnB,eAAe,EAAK,cAAc,MAAM;GACxC,SAAS,EAAK,QAAQ,MAAM,GAAG,OAAO,EAAK,QAAQ,GAAG;GACtD,UAAU,EAAgB,EAAK,SAAS;GACxC,QAAQ,EAAgB,EAAK,OAAO;GACpC,UAAU,EAAK;GACf,UAAU,EAAE;GACb;AAED,MAAI;AACF,OAAI,GAAQ;IACV,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW;KAAE;KAAI;KAAO,EAAE,CAAC;AACpE,QAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,iBAAiB,IAAI,8BAA8B,CACjG;AAGF,IADA,EAAO,QAAQ,sBAAsB,EAAM,KAAK,EAChD,EAAS,gBAAgB;AACzB;;GAGF,IAAM,IAAW,MAAM,EAAgB,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC;AAChE,OAAI,CAAC,EAAkB,GAAU,EAAS,MAAM,iBAAiB,IAAI,8BAA8B,CACjG;AAGF,GADA,EAAO,QAAQ,sBAAsB,EAAM,KAAK,EAChD,EAAS,gBAAgB;WAClB,GAAO;AACd,KAAO,MAAM,6BAA6B,EAAc,EAAM,CAAC;;IAI7D,IACJ,EAAK,iBAAiB,YAAY,qCAAqC;AAEzE,QACE,kBAAC,GAAD;EACE,OAAO,IAAS,oBAAoB;EACpC,aAAY;EACZ,SAAQ;EACR,WAAU;EACV,cAAc,EAAS,gBAAgB;EAC7B;EACV,aAAa,IAAS,oBAAoB;EAClC;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;MAAW,CAAA,EAC3D,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS,QAAQ,EAAE,OAAO,MAAM;MACjD,aAAY;MACZ,UAAA;MACA,CAAA,CACI;OACJ;;GAEN,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAkB,CAAA,EAClE,kBAAC,GAAD;KAAU,OAAO,EAAK;KAAa,WAAW,MAAM,EAAS,eAAe,EAAE,OAAO,MAAM;KAAE,MAAM;KAAK,CAAA,CAClG;;GAER,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAoB,CAAA,EACpE,kBAAC,GAAD;OAAQ,OAAO,EAAK;OAAc,gBAAgB,MAAU,EAAS,gBAAgB,EAAM;iBAA3F,CACE,kBAAC,GAAD;QAAe,cAAW;kBACxB,kBAAC,GAAD,EAAe,CAAA;QACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;QAAY,OAAM;kBAAU;QAA2B,CAAA,EACvD,kBAAC,GAAD;QAAY,OAAM;kBAAQ;QAAyB,CAAA,CACrC,EAAA,CAAA,CACT;SACH;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAgC;QAAqB,CAAA;OACrE,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,MAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAS,iBAAiB,EAAE,OAAO,MAAM;QAC1D,UAAA;QACA,CAAA;OACF,kBAAC,QAAD;QAAM,WAAU;kBAA+B;QAAiB,CAAA;OAC1D;;KACR,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC;OAAe,CAAA,EAC/D,kBAAC,GAAD;OACE,MAAK;OACL,KAAI;OACJ,OAAO,EAAK;OACZ,WAAW,MAAM,EAAS,WAAW,EAAE,OAAO,MAAM;OACpD,aAAY;OACZ,CAAA,CACI;;KACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAgB,CAAA,EAChE,kBAAC,GAAD;MAAO,MAAK;MAAiB,OAAO,EAAK;MAAU,WAAW,MAAM,EAAS,YAAY,EAAE,OAAO,MAAM;MAAI,CAAA,CACtG;QACR,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAc,CAAA,EAC9D,kBAAC,GAAD;MAAO,MAAK;MAAiB,OAAO,EAAK;MAAQ,WAAW,MAAM,EAAS,UAAU,EAAE,OAAO,MAAM;MAAI,CAAA,CAClG;OACJ;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAgC;MAAa,CAAA,EAC7D,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAAkD,CAAA,CACzF;QACN,kBAAC,GAAD;KAAQ,SAAS,EAAK;KAAU,kBAAkB,MAAY,EAAS,YAAY,EAAQ;KAAI,CAAA,CAC3F;;GACU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegisterAttendeePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/RegisterAttendeePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { ArrowLeft, CheckCircle2, Ticket } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n GlassCard,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useCreateRegistrationMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useTicketTypesQuery,\n} from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype Confirmation = {\n attendeeName: string;\n eventName: string;\n ticketName: string;\n quantity: number;\n status: string;\n amountLabel: string;\n};\n\nconst NEW_ATTENDEE = '__new__';\n\nexport default function RegisterAttendeePage() {\n const { navigate } = useEventfullyManaged();\n\n const [eventId, setEventId] = useState('');\n const [ticketTypeId, setTicketTypeId] = useState('');\n const [attendeeId, setAttendeeId] = useState(NEW_ATTENDEE);\n const [fullName, setFullName] = useState('');\n const [email, setEmail] = useState('');\n const [company, setCompany] = useState('');\n const [phone, setPhone] = useState('');\n const [quantity, setQuantity] = useState('1');\n const [promoCode, setPromoCode] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [confirmation, setConfirmation] = useState<Confirmation | null>(null);\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n const { data: ticketData } = useTicketTypesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n const { data: attendeesData } = useAttendeesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n const { data: promoData } = usePromoCodesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n\n const [createAttendee, { loading: creatingAttendee }] = useCreateAttendeeMutation();\n const [createRegistration, { loading: creatingRegistration }] = useCreateRegistrationMutation();\n\n const events = eventsData?.events.items ?? [];\n const tickets = useMemo(\n () => (ticketData?.ticketTypes.items ?? []).filter((ticket) => !eventId || ticket.eventId === eventId),\n [ticketData, eventId]\n );\n const attendees = useMemo(\n () => (attendeesData?.attendees.items ?? []).filter((attendee) => !eventId || attendee.eventId === eventId),\n [attendeesData, eventId]\n );\n const promoCodes = useMemo(\n () =>\n (promoData?.promoCodes.items ?? []).filter(\n (promo) => promo.isActive && (!eventId || promo.eventId === eventId)\n ),\n [promoData, eventId]\n );\n\n const selectedTicket = useMemo(() => tickets.find((ticket) => ticket.id === ticketTypeId), [tickets, ticketTypeId]);\n const qty = Math.max(1, Number(quantity) || 1);\n const unitPrice = selectedTicket ? Number(selectedTicket.price) : 0;\n const currency = selectedTicket?.currency ?? 'USD';\n const grossAmount = Number.isFinite(unitPrice) ? unitPrice * qty : 0;\n const hasPositiveAmount = grossAmount > 0;\n\n const onEventChange = (value: string) => {\n setEventId(value);\n // Reset dependent selections so the user can't submit a stale ticket/attendee.\n setTicketTypeId('');\n setAttendeeId(NEW_ATTENDEE);\n setPromoCode('');\n };\n\n const saving = creatingAttendee || creatingRegistration;\n\n const resetForm = () => {\n setTicketTypeId('');\n setAttendeeId(NEW_ATTENDEE);\n setFullName('');\n setEmail('');\n setCompany('');\n setPhone('');\n setQuantity('1');\n setPromoCode('');\n setBillingAccountId('');\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!eventId) {\n notify.error('Event required', 'Choose the event to register for.');\n return;\n }\n if (!ticketTypeId) {\n notify.error('Ticket required', 'Choose a ticket type.');\n return;\n }\n\n const isNewAttendee = attendeeId === NEW_ATTENDEE;\n if (isNewAttendee && (!fullName.trim() || !email.trim())) {\n notify.error('Attendee required', 'Enter a name and email, or pick an existing attendee.');\n return;\n }\n\n try {\n let resolvedAttendeeId = attendeeId;\n let resolvedAttendeeName = attendees.find((attendee) => attendee.id === attendeeId)?.fullName ?? '';\n\n if (isNewAttendee) {\n const attendeeResponse = await createAttendee({\n variables: {\n input: {\n eventId,\n fullName: fullName.trim(),\n email: email.trim(),\n company: company.trim() || null,\n phone: phone.trim() || null,\n profile: {},\n metadata: {},\n },\n },\n });\n const createdId = attendeeResponse.data?.createAttendee?.id;\n if (!mutationSucceeded(attendeeResponse, createdId, 'Could not create attendee') || !createdId) {\n return;\n }\n resolvedAttendeeId = createdId;\n resolvedAttendeeName = fullName.trim();\n }\n\n // The money leg is owned by global-billing-svc — never price locally. We\n // only forward a billing account reference (when supplied) via metadata so\n // the backend can attach the charge; totalAmount is left for the backend.\n const metadata: Record<string, unknown> = {};\n if (promoCode) {\n metadata.promoCode = promoCode;\n }\n if (hasPositiveAmount && billingAccountId.trim()) {\n metadata.billingAccountId = billingAccountId.trim();\n }\n\n const registrationResponse = await createRegistration({\n variables: {\n input: {\n eventId,\n attendeeId: resolvedAttendeeId,\n ticketTypeId,\n quantity: qty,\n currency,\n status: 'CONFIRMED',\n metadata,\n },\n },\n });\n if (\n !mutationSucceeded(\n registrationResponse,\n registrationResponse.data?.createRegistration?.id,\n 'Could not create registration'\n )\n ) {\n return;\n }\n\n const registration = registrationResponse.data?.createRegistration;\n setConfirmation({\n attendeeName: resolvedAttendeeName || 'Attendee',\n eventName: events.find((item) => item.id === eventId)?.name ?? 'Event',\n ticketName: selectedTicket?.name ?? 'Ticket',\n quantity: qty,\n status: registration?.status ?? 'CONFIRMED',\n amountLabel: hasPositiveAmount\n ? `${grossAmount.toLocaleString()} ${currency} — billed by the billing service`\n : 'Free',\n });\n notify.success('Registration confirmed', resolvedAttendeeName || undefined);\n resetForm();\n } catch (error) {\n notify.error('Could not register attendee', describeError(error));\n }\n };\n\n if (confirmation) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/registration')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Registration\n </Button>\n </div>\n <GlassCard treatment=\"glass\" glow className=\"p-8 text-center\">\n <CheckCircle2 className=\"mx-auto h-12 w-12 text-status-success-text\" aria-hidden=\"true\" />\n <h1 className=\"mt-4 text-2xl font-bold text-text-primary\">Registration confirmed</h1>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {confirmation.attendeeName} is registered for {confirmation.eventName}.\n </p>\n <dl className=\"mx-auto mt-6 max-w-sm space-y-2 text-left text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Ticket</dt>\n <dd className=\"font-medium text-text-primary\">{confirmation.ticketName}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Quantity</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{confirmation.quantity}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd className=\"font-medium text-text-primary\">{confirmation.status}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Amount</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{confirmation.amountLabel}</dd>\n </div>\n </dl>\n <div className=\"mt-8 flex justify-center gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration')}>\n Back to Registrations\n </Button>\n <Button type=\"button\" onClick={() => setConfirmation(null)}>\n Register another\n </Button>\n </div>\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/registration')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Registration\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">Register Attendee</h1>\n <p className=\"mt-1 text-sm text-text-secondary\">Sign someone up for an event against a ticket type.</p>\n </div>\n\n <PagePurpose>\n Register a person for an event in one step. Pick the event and the ticket they are buying, then either choose an\n existing attendee or enter a new one. Apply a promo code if they have one. Pricing and payment are handled by the\n billing service — you just confirm who is coming and on which ticket.\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={eventId} onValueChange={onEventChange}>\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\">Ticket Type</span>\n <Select value={ticketTypeId} onValueChange={setTicketTypeId} disabled={!eventId}>\n <SelectTrigger aria-label=\"Ticket Type\">\n <SelectValue placeholder={eventId ? 'Select Ticket' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {tickets.map((ticket) => (\n <SelectItem key={ticket.id} value={ticket.id}>\n {ticket.name} — {ticket.price} {ticket.currency}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select value={attendeeId} onValueChange={setAttendeeId}>\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NEW_ATTENDEE}>+ New attendee</SelectItem>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName} · {attendee.email}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n {attendeeId === NEW_ATTENDEE ? (\n <div className=\"grid grid-cols-1 gap-4 rounded-input border border-border-seam bg-bg-surface p-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={fullName} onChange={(e) => setFullName(e.target.value)} required />\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={email} onChange={(e) => setEmail(e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={company} onChange={(e) => setCompany(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={phone} onChange={(e) => setPhone(e.target.value)} />\n </label>\n </div>\n ) : null}\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\">Quantity</span>\n <Input\n type=\"number\"\n min=\"1\"\n value={quantity}\n onChange={(e) => setQuantity(e.target.value)}\n required\n />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Promo Code (optional)</span>\n <Select value={promoCode} onValueChange={setPromoCode} disabled={promoCodes.length === 0}>\n <SelectTrigger aria-label=\"Promo Code\">\n <SelectValue placeholder={promoCodes.length === 0 ? 'No active codes' : 'No promo code'} />\n </SelectTrigger>\n <SelectContent>\n {promoCodes.map((promo) => (\n <SelectItem key={promo.id} value={promo.code}>\n {promo.code} — {promo.discountType === 'PERCENT' ? `${promo.discountValue}%` : promo.discountValue}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {hasPositiveAmount ? (\n <div className=\"space-y-3 rounded-input border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Ticket className=\"h-4 w-4 text-text-muted\" aria-hidden=\"true\" />\n <span>\n Estimated total{' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {grossAmount.toLocaleString()} {currency}\n </span>{' '}\n — final pricing and payment are handled by the billing service.\n </span>\n </div>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Billing Account ID (optional)</span>\n <Input\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n placeholder=\"Attach an existing billing account\"\n />\n <span className=\"text-xs text-text-secondary\">\n When provided, the charge is attached to this billing account.\n </span>\n </label>\n </div>\n ) : null}\n\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration')} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n <Ticket className=\"h-4 w-4\" />\n {saving ? 'Registering…' : 'Register Attendee'}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RegisterAttendeePage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/RegisterAttendeePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { ArrowLeft, CheckCircle2, Ticket } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n GlassCard,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useCreateRegistrationMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useTicketTypesQuery,\n} from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype Confirmation = {\n attendeeName: string;\n eventName: string;\n ticketName: string;\n quantity: number;\n status: string;\n amountLabel: string;\n};\n\nconst NEW_ATTENDEE = '__new__';\n\nexport default function RegisterAttendeePage() {\n const { navigate } = useEventfullyManaged();\n\n const [eventId, setEventId] = useState('');\n const [ticketTypeId, setTicketTypeId] = useState('');\n const [attendeeId, setAttendeeId] = useState(NEW_ATTENDEE);\n const [fullName, setFullName] = useState('');\n const [email, setEmail] = useState('');\n const [company, setCompany] = useState('');\n const [phone, setPhone] = useState('');\n const [quantity, setQuantity] = useState('1');\n const [promoCode, setPromoCode] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [confirmation, setConfirmation] = useState<Confirmation | null>(null);\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n const { data: ticketData } = useTicketTypesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n const { data: attendeesData } = useAttendeesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n const { data: promoData } = usePromoCodesQuery({ eventId: eventId || undefined, limit: 100, offset: 0 });\n\n const [createAttendee, { loading: creatingAttendee }] = useCreateAttendeeMutation();\n const [createRegistration, { loading: creatingRegistration }] = useCreateRegistrationMutation();\n\n const events = eventsData?.events.items ?? [];\n const tickets = useMemo(\n () => (ticketData?.ticketTypes.items ?? []).filter((ticket) => !eventId || ticket.eventId === eventId),\n [ticketData, eventId]\n );\n const attendees = useMemo(\n () => (attendeesData?.attendees.items ?? []).filter((attendee) => !eventId || attendee.eventId === eventId),\n [attendeesData, eventId]\n );\n const promoCodes = useMemo(\n () =>\n (promoData?.promoCodes.items ?? []).filter((promo) => promo.isActive && (!eventId || promo.eventId === eventId)),\n [promoData, eventId]\n );\n\n const selectedTicket = useMemo(() => tickets.find((ticket) => ticket.id === ticketTypeId), [tickets, ticketTypeId]);\n const qty = Math.max(1, Number(quantity) || 1);\n const unitPrice = selectedTicket ? Number(selectedTicket.price) : 0;\n const currency = selectedTicket?.currency ?? 'USD';\n const grossAmount = Number.isFinite(unitPrice) ? unitPrice * qty : 0;\n const hasPositiveAmount = grossAmount > 0;\n\n const onEventChange = (value: string) => {\n setEventId(value);\n // Reset dependent selections so the user can't submit a stale ticket/attendee.\n setTicketTypeId('');\n setAttendeeId(NEW_ATTENDEE);\n setPromoCode('');\n };\n\n const saving = creatingAttendee || creatingRegistration;\n\n const resetForm = () => {\n setTicketTypeId('');\n setAttendeeId(NEW_ATTENDEE);\n setFullName('');\n setEmail('');\n setCompany('');\n setPhone('');\n setQuantity('1');\n setPromoCode('');\n setBillingAccountId('');\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!eventId) {\n notify.error('Event required', 'Choose the event to register for.');\n return;\n }\n if (!ticketTypeId) {\n notify.error('Ticket required', 'Choose a ticket type.');\n return;\n }\n\n const isNewAttendee = attendeeId === NEW_ATTENDEE;\n if (isNewAttendee && (!fullName.trim() || !email.trim())) {\n notify.error('Attendee required', 'Enter a name and email, or pick an existing attendee.');\n return;\n }\n\n try {\n let resolvedAttendeeId = attendeeId;\n let resolvedAttendeeName = attendees.find((attendee) => attendee.id === attendeeId)?.fullName ?? '';\n\n if (isNewAttendee) {\n const attendeeResponse = await createAttendee({\n variables: {\n input: {\n eventId,\n fullName: fullName.trim(),\n email: email.trim(),\n company: company.trim() || null,\n phone: phone.trim() || null,\n profile: {},\n metadata: {},\n },\n },\n });\n const createdId = attendeeResponse.data?.createAttendee?.id;\n if (!mutationSucceeded(attendeeResponse, createdId, 'Could not create attendee') || !createdId) {\n return;\n }\n resolvedAttendeeId = createdId;\n resolvedAttendeeName = fullName.trim();\n }\n\n // The money leg is owned by global-billing-svc — never price locally. We\n // only forward a billing account reference (when supplied) via metadata so\n // the backend can attach the charge; totalAmount is left for the backend.\n const metadata: Record<string, unknown> = {};\n if (promoCode) {\n metadata.promoCode = promoCode;\n }\n if (hasPositiveAmount && billingAccountId.trim()) {\n metadata.billingAccountId = billingAccountId.trim();\n }\n\n const registrationResponse = await createRegistration({\n variables: {\n input: {\n eventId,\n attendeeId: resolvedAttendeeId,\n ticketTypeId,\n quantity: qty,\n currency,\n status: 'CONFIRMED',\n metadata,\n },\n },\n });\n if (\n !mutationSucceeded(\n registrationResponse,\n registrationResponse.data?.createRegistration?.id,\n 'Could not create registration'\n )\n ) {\n return;\n }\n\n const registration = registrationResponse.data?.createRegistration;\n setConfirmation({\n attendeeName: resolvedAttendeeName || 'Attendee',\n eventName: events.find((item) => item.id === eventId)?.name ?? 'Event',\n ticketName: selectedTicket?.name ?? 'Ticket',\n quantity: qty,\n status: registration?.status ?? 'CONFIRMED',\n amountLabel: hasPositiveAmount\n ? `${grossAmount.toLocaleString()} ${currency} — billed by the billing service`\n : 'Free',\n });\n notify.success('Registration confirmed', resolvedAttendeeName || undefined);\n resetForm();\n } catch (error) {\n notify.error('Could not register attendee', describeError(error));\n }\n };\n\n if (confirmation) {\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/registration')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Registration\n </Button>\n </div>\n <GlassCard treatment=\"glass\" glow className=\"p-8 text-center\">\n <CheckCircle2 className=\"mx-auto h-12 w-12 text-status-success-text\" aria-hidden=\"true\" />\n <h1 className=\"mt-4 text-2xl font-bold text-text-primary\">Registration confirmed</h1>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {confirmation.attendeeName} is registered for {confirmation.eventName}.\n </p>\n <dl className=\"mx-auto mt-6 max-w-sm space-y-2 text-left text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Ticket</dt>\n <dd className=\"font-medium text-text-primary\">{confirmation.ticketName}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Quantity</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{confirmation.quantity}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Status</dt>\n <dd className=\"font-medium text-text-primary\">{confirmation.status}</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-text-secondary\">Amount</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{confirmation.amountLabel}</dd>\n </div>\n </dl>\n <div className=\"mt-8 flex justify-center gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration')}>\n Back to Registrations\n </Button>\n <Button type=\"button\" onClick={() => setConfirmation(null)}>\n Register another\n </Button>\n </div>\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/registration')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Registration\n </Button>\n <h1 className=\"mt-3 text-2xl font-bold text-text-primary\">Register Attendee</h1>\n <p className=\"mt-1 text-sm text-text-secondary\">Sign someone up for an event against a ticket type.</p>\n </div>\n\n <PagePurpose>\n Register a person for an event in one step. Pick the event and the ticket they are buying, then either choose an\n existing attendee or enter a new one. Apply a promo code if they have one. Pricing and payment are handled by\n the billing service — you just confirm who is coming and on which ticket.\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={eventId} onValueChange={onEventChange}>\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\">Ticket Type</span>\n <Select value={ticketTypeId} onValueChange={setTicketTypeId} disabled={!eventId}>\n <SelectTrigger aria-label=\"Ticket Type\">\n <SelectValue placeholder={eventId ? 'Select Ticket' : 'Select an event first'} />\n </SelectTrigger>\n <SelectContent>\n {tickets.map((ticket) => (\n <SelectItem key={ticket.id} value={ticket.id}>\n {ticket.name} — {ticket.price} {ticket.currency}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Attendee</span>\n <Select value={attendeeId} onValueChange={setAttendeeId}>\n <SelectTrigger aria-label=\"Attendee\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NEW_ATTENDEE}>+ New attendee</SelectItem>\n {attendees.map((attendee) => (\n <SelectItem key={attendee.id} value={attendee.id}>\n {attendee.fullName} · {attendee.email}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n {attendeeId === NEW_ATTENDEE ? (\n <div className=\"grid grid-cols-1 gap-4 rounded-input border border-border-seam bg-bg-surface p-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={fullName} onChange={(e) => setFullName(e.target.value)} required />\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={email} onChange={(e) => setEmail(e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={company} onChange={(e) => setCompany(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={phone} onChange={(e) => setPhone(e.target.value)} />\n </label>\n </div>\n ) : null}\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\">Quantity</span>\n <Input type=\"number\" min=\"1\" value={quantity} onChange={(e) => setQuantity(e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Promo Code (optional)</span>\n <Select value={promoCode} onValueChange={setPromoCode} disabled={promoCodes.length === 0}>\n <SelectTrigger aria-label=\"Promo Code\">\n <SelectValue placeholder={promoCodes.length === 0 ? 'No active codes' : 'No promo code'} />\n </SelectTrigger>\n <SelectContent>\n {promoCodes.map((promo) => (\n <SelectItem key={promo.id} value={promo.code}>\n {promo.code} —{' '}\n {promo.discountType === 'PERCENT' ? `${promo.discountValue}%` : promo.discountValue}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n </div>\n\n {hasPositiveAmount ? (\n <div className=\"space-y-3 rounded-input border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Ticket className=\"h-4 w-4 text-text-muted\" aria-hidden=\"true\" />\n <span>\n Estimated total{' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {grossAmount.toLocaleString()} {currency}\n </span>{' '}\n — final pricing and payment are handled by the billing service.\n </span>\n </div>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Billing Account ID (optional)</span>\n <Input\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n placeholder=\"Attach an existing billing account\"\n />\n <span className=\"text-xs text-text-secondary\">\n When provided, the charge is attached to this billing account.\n </span>\n </label>\n </div>\n ) : null}\n\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration')} disabled={saving}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n <Ticket className=\"h-4 w-4\" />\n {saving ? 'Registering…' : 'Register Attendee'}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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(\n () => (data?.speakers?.items ?? []).find((item) => item.id === id),\n [data, id]\n );\n\n useEffect(() => {\n if (!isEdit || !existingSpeaker) {\n return;\n }\n setForm({\n fullName: existingSpeaker.fullName ?? '',\n title: existingSpeaker.title ?? '',\n organization: existingSpeaker.organization ?? '',\n email: existingSpeaker.email ?? '',\n phone: existingSpeaker.phone ?? '',\n bio: existingSpeaker.bio ?? '',\n avatarUrl: existingSpeaker.avatarUrl ?? '',\n website: readWebsite(existingSpeaker.socialLinks),\n });\n }, [existingSpeaker, isEdit]);\n\n const onChange = (field: keyof SpeakerFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.fullName.trim()) {\n notify.error('Name required', 'Give the speaker a name before saving.');\n return;\n }\n\n const normalizedWebsite = safeUrl(form.website);\n if (form.website.trim() && !normalizedWebsite) {\n notify.error('Invalid website', 'Enter a valid http(s) URL, or leave it blank.');\n return;\n }\n\n const input = {\n fullName: form.fullName.trim(),\n title: form.title || null,\n organization: form.organization || null,\n email: form.email || null,\n phone: form.phone || null,\n bio: form.bio || null,\n avatarUrl: form.avatarUrl || null,\n socialLinks: normalizedWebsite ? { website: normalizedWebsite } : {},\n };\n\n try {\n if (isEdit) {\n const response = await updateSpeaker({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateSpeaker?.id, 'Could not update speaker')) {\n return;\n }\n notify.success('Speaker updated');\n navigate('/speakers');\n return;\n }\n const response = await createSpeaker({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createSpeaker?.id, 'Could not create speaker')) {\n return;\n }\n notify.success('Speaker created');\n navigate('/speakers');\n } catch (error) {\n notify.error('Could not save speaker', describeError(error));\n }\n };\n\n const saving = creating || updating;\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Speaker' : 'Add Speaker'}\n description=\"A person who presents sessions at your events.\"\n purpose=\"A speaker profile is what you attach to sessions and show to attendees. Capture who they are, where they work, and a short bio or talk abstract so the agenda tells a compelling story. The headshot appears anywhere the speaker is listed.\"\n backLabel=\"Back to Speakers\"\n onBack={() => navigate('/speakers')}\n onSubmit={onSubmit}\n submitLabel=\"Save Speaker\"\n saving={saving}\n >\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Title</span>\n <Input\n value={form.title}\n onChange={(e) => onChange('title', e.target.value)}\n placeholder=\"Head of Product\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.organization} onChange={(e) => onChange('organization', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Headshot</span>\n <ImageUploadField\n label=\"Upload headshot\"\n folderPath=\"/EventfullyManaged/Speakers\"\n value={form.avatarUrl || null}\n onUploaded={(url) => onChange('avatarUrl', url)}\n hint=\"Stored securely in Files. Shown wherever the speaker is listed.\"\n />\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Bio & talk abstract</span>\n <Textarea\n value={form.bio}\n onChange={(e) => onChange('bio', e.target.value)}\n rows={5}\n placeholder=\"Short professional bio and a summary of the talk they will give.\"\n />\n </label>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Website / profile URL</span>\n <Input\n value={form.website}\n onChange={(e) => onChange('website', e.target.value)}\n placeholder=\"https://…\"\n />\n </label>\n\n {isEdit ? <EntityTagsField entityType=\"speaker\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
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 & 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=\"speaker\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|