@burdenoff/microfe-eventfullymanaged 2026.704.7 → 2026.704.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"EventsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/EventsPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { CalendarClock, CalendarPlus, MapPin, Search, Ticket, UserCheck, Users } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n NextSteps,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useEventsQuery, useVenuesQuery } from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatDateTime } from '../events/eventFormUtils';\n\ntype EventRow = {\n id: string;\n name: string;\n type?: string;\n status?: string;\n startAt?: string;\n endAt?: string;\n capacity?: number | null;\n visibility?: string;\n metadata?: Record<string, unknown> | null;\n};\n\n// Per-event registration + check-in counts, computed once from a real registrations\n// read (no per-row queries) so each card can show live mini-stats.\nconst EVENTS_REGISTRATIONS_QUERY = gql`\n query EventfullymanagedEventsRegistrations($limit: Int) {\n registrations: eventfullyManagedRegistrations(limit: $limit, offset: 0) {\n total\n items {\n id\n eventId\n checkedInAt\n }\n }\n }\n`;\n\ntype RegistrationStat = { id: string; eventId: string; checkedInAt?: string | null };\ntype EventsRegistrationsResponse = { registrations: { total: number; items: RegistrationStat[] } };\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\nfunction formatDateRange(startAt?: string, endAt?: string): string {\n const start = startAt ? formatDateTime(startAt) : '';\n const end = endAt ? formatDateTime(endAt) : '';\n if (start && end) {\n return `${start} — ${end}`;\n }\n return start || end || 'No schedule set';\n}\n\nfunction MiniStat({\n icon: Icon,\n label,\n value,\n}: {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n value: string;\n}) {\n return (\n <div className=\"flex items-center gap-1.5\">\n <Icon className=\"h-3.5 w-3.5 text-text-muted\" />\n <span className=\"tabular-nums font-medium text-text-primary\">{value}</span>\n <span className=\"text-text-muted\">{label}</span>\n </div>\n );\n}\n\nexport default function EventsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { navigate } = useEventfullyManaged();\n const [search, setSearch] = useState('');\n const pagination = usePagination({ resetKey: search });\n const { data, loading } = useEventsQuery({ search, limit: pagination.limit, offset: pagination.offset });\n const { data: venuesData } = useVenuesQuery({ limit: 100, offset: 0 });\n const { data: registrationsData } = useQuery<EventsRegistrationsResponse, { limit: number }>(\n EVENTS_REGISTRATIONS_QUERY,\n { variables: { limit: 500 } }\n );\n\n const total = data?.events?.total ?? 0;\n const rows = useMemo(() => (data?.events?.items ?? []) as EventRow[], [data]);\n\n const venueNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const venue of venuesData?.venues?.items ?? []) {\n map.set(venue.id, venue.name);\n }\n return map;\n }, [venuesData]);\n\n const statsByEvent = useMemo(() => {\n const map = new Map<string, { registrations: number; checkedIn: number }>();\n for (const registration of registrationsData?.registrations?.items ?? []) {\n const entry = map.get(registration.eventId) ?? { registrations: 0, checkedIn: 0 };\n entry.registrations += 1;\n if (registration.checkedInAt) {\n entry.checkedIn += 1;\n }\n map.set(registration.eventId, entry);\n }\n return map;\n }, [registrationsData]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n {t(\n 'eventfullymanaged.events.purpose',\n 'Events are the heart of the platform — each one is a happening you sell tickets for, register attendees to, and run on the day. Create an event, open it to reveal its agenda, speakers and ticket types, then publish it when it is ready.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{t('eventfullymanaged.events.title', 'Events')}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {t('eventfullymanaged.events.description', 'Create and manage live events from the workspace graph.')}\n </p>\n </div>\n <Button type=\"button\" onClick={() => navigate('/events/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n {t('eventfullymanaged.events.actions.create', 'Create Event')}\n </Button>\n </div>\n </EmphasisPanel>\n\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\" />\n <Input\n type=\"text\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={t('eventfullymanaged.events.searchPlaceholder', 'Search events')}\n aria-label={t('eventfullymanaged.events.searchPlaceholder', 'Search events')}\n className=\"pl-10\"\n />\n </div>\n\n {loading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-28 w-full rounded-card\" />\n <Skeleton className=\"h-28 w-full rounded-card\" />\n <Skeleton className=\"h-28 w-full rounded-card\" />\n </div>\n ) : rows.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration={search ? 'empty-search' : 'empty-data'}\n title={\n search\n ? t('eventfullymanaged.events.list.emptySearch', 'No events match your search.')\n : t('eventfullymanaged.events.list.empty', 'No events yet.')\n }\n description={\n search\n ? t('eventfullymanaged.events.list.emptySearchHint', 'Try a different name.')\n : t('eventfullymanaged.events.list.emptyHint', 'Create your first event to get started.')\n }\n action={\n !search ? (\n <Button type=\"button\" onClick={() => navigate('/events/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n {t('eventfullymanaged.events.actions.create', 'Create Event')}\n </Button>\n ) : undefined\n }\n />\n </GlassCard>\n ) : (\n <div className=\"space-y-3\">\n {rows.map((event) => {\n const metadata = (event.metadata ?? {}) as Record<string, unknown>;\n const venueId = typeof metadata.venueId === 'string' ? metadata.venueId : '';\n const venueName = venueId ? venueNameById.get(venueId) : undefined;\n const stat = statsByEvent.get(event.id);\n const registrations = stat?.registrations ?? 0;\n const checkInPct = registrations > 0 ? Math.round(((stat?.checkedIn ?? 0) / registrations) * 100) : 0;\n return (\n <GlassCard\n key={event.id}\n treatment=\"plain\"\n role=\"button\"\n tabIndex={0}\n aria-label={event.name}\n className=\"group relative cursor-pointer overflow-hidden border-border-seam p-0 outline-none transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1 focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n onClick={() => navigate(`/events/${event.id}`)}\n onKeyDown={(keyEvent) => {\n if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {\n keyEvent.preventDefault();\n navigate(`/events/${event.id}`);\n }\n }}\n >\n {/* Cover accent strip */}\n <span\n aria-hidden=\"true\"\n className=\"absolute inset-y-0 left-0 w-1.5 bg-gradient-to-b from-[var(--color-chart-1)] to-[var(--color-chart-2)]\"\n />\n <div className=\"flex flex-col gap-3 py-4 pl-5 pr-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"truncate text-lg font-semibold text-text-primary\">{event.name}</p>\n {event.status ? (\n <Badge variant={statusVariant(event.status)} className=\"uppercase\">\n {event.status}\n </Badge>\n ) : null}\n {event.type ? (\n <Badge variant=\"secondary\" className=\"capitalize\">\n {event.type.toLowerCase().replace(/_/g, ' ')}\n </Badge>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-muted\" />\n {formatDateRange(event.startAt, event.endAt)}\n </span>\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4 shrink-0 text-text-muted\" />\n {venueName ?? t('eventfullymanaged.events.card.noVenue', 'No venue')}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs\">\n <MiniStat\n icon={UserCheck}\n label={t('eventfullymanaged.events.card.registered', 'registered')}\n value={registrations.toLocaleString()}\n />\n <MiniStat\n icon={UserCheck}\n label={t('eventfullymanaged.events.card.checkedIn', 'checked in')}\n value={`${checkInPct}%`}\n />\n <MiniStat\n icon={Users}\n label={t('eventfullymanaged.events.card.capacity', 'capacity')}\n value={event.capacity != null ? event.capacity.toLocaleString() : '—'}\n />\n </div>\n </div>\n <div\n className=\"flex shrink-0 items-center gap-2 self-start sm:self-center\"\n onClick={(clickEvent) => clickEvent.stopPropagation()}\n onKeyDown={(keyEvent) => keyEvent.stopPropagation()}\n role=\"presentation\"\n >\n <ActionMenu\n ariaLabel={`Event actions for ${event.name}`}\n onView={() => navigate(`/events/${event.id}`)}\n onEdit={() => navigate(`/events/${event.id}/edit`)}\n />\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"events\"\n loading={loading}\n />\n </div>\n )}\n\n <NextSteps\n storageKey=\"eventfullymanaged-events\"\n title={t('eventfullymanaged.events.nextSteps.title', 'Finish setting up your event')}\n steps={[\n {\n id: 'configure-registration',\n label: t('eventfullymanaged.events.nextSteps.registration', 'Configure registration & tickets'),\n description: t(\n 'eventfullymanaged.events.nextSteps.registrationDesc',\n 'Open an event and define ticket types so people can register.'\n ),\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'plan-networking',\n label: t('eventfullymanaged.events.nextSteps.networking', 'Plan networking & sessions'),\n description: t(\n 'eventfullymanaged.events.nextSteps.networkingDesc',\n 'Set up attendee matchmaking and meeting schedules.'\n ),\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'announce',\n label: t('eventfullymanaged.events.nextSteps.announce', 'Announce to attendees'),\n description: t(\n 'eventfullymanaged.events.nextSteps.announceDesc',\n 'Send announcements and run communication campaigns.'\n ),\n icon: <CalendarPlus className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EventsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/EventsPage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Award,\n CalendarClock,\n CalendarDays,\n CalendarPlus,\n MapPin,\n Search,\n Ticket,\n UserCheck,\n Users,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n NextSteps,\n PagePurpose,\n Skeleton,\n Tabs,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport { useEventsQuery, useSponsorsQuery, useVenuesQuery } from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatDateTime } from '../events/eventFormUtils';\n\ntype EventRow = {\n id: string;\n name: string;\n type?: string;\n status?: string;\n startAt?: string;\n endAt?: string;\n capacity?: number | null;\n visibility?: string;\n metadata?: Record<string, unknown> | null;\n};\n\n// Per-event registration + check-in counts, computed once from a real registrations\n// read (no per-row queries) so each card can show live mini-stats.\nconst EVENTS_REGISTRATIONS_QUERY = gql`\n query EventfullymanagedEventsRegistrations($limit: Int) {\n registrations: eventfullyManagedRegistrations(limit: $limit, offset: 0) {\n total\n items {\n id\n eventId\n checkedInAt\n }\n }\n }\n`;\n\ntype RegistrationStat = { id: string; eventId: string; checkedInAt?: string | null };\ntype EventsRegistrationsResponse = { registrations: { total: number; items: RegistrationStat[] } };\n\nfunction statusVariant(status: string): 'success' | 'warning' | 'secondary' | 'info' {\n const value = status.toLowerCase();\n if (['active', 'published', 'live', 'confirmed', 'completed', 'approved'].includes(value)) {\n return 'success';\n }\n if (['inactive', 'cancelled', 'archived', 'closed', 'expired'].includes(value)) {\n return 'warning';\n }\n if (['draft', 'created', 'pending', 'scheduled'].includes(value)) {\n return 'info';\n }\n return 'secondary';\n}\n\nfunction formatDateRange(startAt?: string, endAt?: string): string {\n const start = startAt ? formatDateTime(startAt) : '';\n const end = endAt ? formatDateTime(endAt) : '';\n if (start && end) {\n return `${start} — ${end}`;\n }\n return start || end || 'No schedule set';\n}\n\ntype Lifecycle = 'upcoming' | 'live' | 'past';\ntype LifecycleFilter = 'all' | Lifecycle;\n\n// Derive an event's lifecycle from its schedule (DateTime scalars arrive as ISO\n// strings, so parse via `new Date(...)`). Upcoming = starts in the future, Live =\n// now falls within start–end (open-ended if no end), Past = already ended.\nfunction eventLifecycle(startAt?: string, endAt?: string): Lifecycle | null {\n const now = Date.now();\n const start = startAt ? new Date(startAt).getTime() : Number.NaN;\n const end = endAt ? new Date(endAt).getTime() : Number.NaN;\n const hasStart = !Number.isNaN(start);\n const hasEnd = !Number.isNaN(end);\n if (!hasStart && !hasEnd) {\n return null;\n }\n if (hasStart && now < start) {\n return 'upcoming';\n }\n if (hasEnd && now > end) {\n return 'past';\n }\n if (hasStart) {\n return 'live';\n }\n return 'upcoming';\n}\n\nfunction PortfolioStat({\n icon: Icon,\n label,\n value,\n loading,\n}: {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n value: string;\n loading: boolean;\n}) {\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-4\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-button bg-bg-sunken text-text-secondary\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n {loading ? (\n <Skeleton className=\"h-6 w-12\" />\n ) : (\n <p className=\"truncate text-xl font-bold tabular-nums text-text-primary\">{value}</p>\n )}\n <p className=\"truncate text-xs text-text-muted\">{label}</p>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nfunction MiniStat({\n icon: Icon,\n label,\n value,\n}: {\n icon: React.ComponentType<{ className?: string }>;\n label: string;\n value: string;\n}) {\n return (\n <div className=\"flex items-center gap-1.5\">\n <Icon className=\"h-3.5 w-3.5 text-text-muted\" />\n <span className=\"tabular-nums font-medium text-text-primary\">{value}</span>\n <span className=\"text-text-muted\">{label}</span>\n </div>\n );\n}\n\nexport default function EventsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { navigate } = useEventfullyManaged();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<LifecycleFilter>('all');\n const pagination = usePagination({ resetKey: search });\n const { data, loading } = useEventsQuery({ search, limit: pagination.limit, offset: pagination.offset });\n const { data: venuesData, loading: venuesLoading } = useVenuesQuery({ limit: 100, offset: 0 });\n const { data: sponsorsData, loading: sponsorsLoading } = useSponsorsQuery({ limit: 1, offset: 0 });\n const { data: registrationsData, loading: registrationsLoading } = useQuery<\n EventsRegistrationsResponse,\n { limit: number }\n >(EVENTS_REGISTRATIONS_QUERY, { variables: { limit: 500 } });\n\n const total = data?.events?.total ?? 0;\n const rows = useMemo(() => (data?.events?.items ?? []) as EventRow[], [data]);\n\n const registrationsTotal = registrationsData?.registrations?.total ?? 0;\n const venuesTotal = venuesData?.venues?.total ?? 0;\n const sponsorsTotal = sponsorsData?.sponsors?.total ?? 0;\n\n const visibleRows = useMemo(() => {\n if (statusFilter === 'all') {\n return rows;\n }\n return rows.filter((event) => eventLifecycle(event.startAt, event.endAt) === statusFilter);\n }, [rows, statusFilter]);\n\n const venueNameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const venue of venuesData?.venues?.items ?? []) {\n map.set(venue.id, venue.name);\n }\n return map;\n }, [venuesData]);\n\n const statsByEvent = useMemo(() => {\n const map = new Map<string, { registrations: number; checkedIn: number }>();\n for (const registration of registrationsData?.registrations?.items ?? []) {\n const entry = map.get(registration.eventId) ?? { registrations: 0, checkedIn: 0 };\n entry.registrations += 1;\n if (registration.checkedInAt) {\n entry.checkedIn += 1;\n }\n map.set(registration.eventId, entry);\n }\n return map;\n }, [registrationsData]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n {t(\n 'eventfullymanaged.events.purpose',\n 'Events are the heart of the platform — each one is a happening you sell tickets for, register attendees to, and run on the day. Create an event, open it to reveal its agenda, speakers and ticket types, then publish it when it is ready.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{t('eventfullymanaged.events.title', 'Events')}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {t('eventfullymanaged.events.description', 'Create and manage live events from the workspace graph.')}\n </p>\n </div>\n <Button type=\"button\" onClick={() => navigate('/events/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n {t('eventfullymanaged.events.actions.create', 'Create Event')}\n </Button>\n </div>\n </EmphasisPanel>\n\n {/* Portfolio summary — real aggregate totals across the whole workspace graph. */}\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <PortfolioStat\n icon={CalendarDays}\n label={t('eventfullymanaged.events.portfolio.events', 'Events')}\n value={total.toLocaleString()}\n loading={loading && total === 0}\n />\n <PortfolioStat\n icon={Users}\n label={t('eventfullymanaged.events.portfolio.registrations', 'Registrations')}\n value={registrationsTotal.toLocaleString()}\n loading={registrationsLoading && !registrationsData}\n />\n <PortfolioStat\n icon={MapPin}\n label={t('eventfullymanaged.events.portfolio.venues', 'Venues')}\n value={venuesTotal.toLocaleString()}\n loading={venuesLoading && !venuesData}\n />\n <PortfolioStat\n icon={Award}\n label={t('eventfullymanaged.events.portfolio.sponsors', 'Sponsors')}\n value={sponsorsTotal.toLocaleString()}\n loading={sponsorsLoading && !sponsorsData}\n />\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\" />\n <Input\n type=\"text\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={t('eventfullymanaged.events.searchPlaceholder', 'Search events')}\n aria-label={t('eventfullymanaged.events.searchPlaceholder', 'Search events')}\n className=\"pl-10\"\n />\n </div>\n <Tabs\n value={statusFilter}\n onValueChange={(value) => setStatusFilter(value as LifecycleFilter)}\n className=\"w-full sm:w-auto\"\n >\n <TabsList className=\"w-full overflow-x-auto sm:w-auto\">\n <TabsTrigger value=\"all\">{t('eventfullymanaged.events.filters.all', 'All')}</TabsTrigger>\n <TabsTrigger value=\"upcoming\">{t('eventfullymanaged.events.filters.upcoming', 'Upcoming')}</TabsTrigger>\n <TabsTrigger value=\"live\">{t('eventfullymanaged.events.filters.live', 'Live')}</TabsTrigger>\n <TabsTrigger value=\"past\">{t('eventfullymanaged.events.filters.past', 'Past')}</TabsTrigger>\n </TabsList>\n </Tabs>\n </div>\n\n {loading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-28 w-full rounded-card\" />\n <Skeleton className=\"h-28 w-full rounded-card\" />\n <Skeleton className=\"h-28 w-full rounded-card\" />\n </div>\n ) : rows.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration={search ? 'empty-search' : 'empty-data'}\n title={\n search\n ? t('eventfullymanaged.events.list.emptySearch', 'No events match your search.')\n : t('eventfullymanaged.events.list.empty', 'No events yet.')\n }\n description={\n search\n ? t('eventfullymanaged.events.list.emptySearchHint', 'Try a different name.')\n : t('eventfullymanaged.events.list.emptyHint', 'Create your first event to get started.')\n }\n action={\n !search ? (\n <Button type=\"button\" onClick={() => navigate('/events/create')}>\n <CalendarPlus className=\"h-4 w-4\" />\n {t('eventfullymanaged.events.actions.create', 'Create Event')}\n </Button>\n ) : undefined\n }\n />\n </GlassCard>\n ) : visibleRows.length === 0 ? (\n <div className=\"space-y-3\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <p className=\"px-2 py-8 text-center text-sm text-text-secondary\">\n {t(\n 'eventfullymanaged.events.filters.empty',\n `No ${statusFilter} events on this page. Switch tabs or move to another page.`\n )}\n </p>\n </GlassCard>\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"events\"\n loading={loading}\n />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {visibleRows.map((event) => {\n const metadata = (event.metadata ?? {}) as Record<string, unknown>;\n const venueId = typeof metadata.venueId === 'string' ? metadata.venueId : '';\n const venueName = venueId ? venueNameById.get(venueId) : undefined;\n const stat = statsByEvent.get(event.id);\n const registrations = stat?.registrations ?? 0;\n const checkInPct = registrations > 0 ? Math.round(((stat?.checkedIn ?? 0) / registrations) * 100) : 0;\n return (\n <GlassCard\n key={event.id}\n treatment=\"plain\"\n role=\"button\"\n tabIndex={0}\n aria-label={event.name}\n className=\"group relative cursor-pointer overflow-hidden border-border-seam p-0 outline-none transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1 focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]\"\n onClick={() => navigate(`/events/${event.id}`)}\n onKeyDown={(keyEvent) => {\n if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {\n keyEvent.preventDefault();\n navigate(`/events/${event.id}`);\n }\n }}\n >\n {/* Cover accent strip */}\n <span\n aria-hidden=\"true\"\n className=\"absolute inset-y-0 left-0 w-1.5 bg-gradient-to-b from-[var(--color-chart-1)] to-[var(--color-chart-2)]\"\n />\n <div className=\"flex flex-col gap-3 py-4 pl-5 pr-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"truncate text-lg font-semibold text-text-primary\">{event.name}</p>\n {event.status ? (\n <Badge variant={statusVariant(event.status)} className=\"uppercase\">\n {event.status}\n </Badge>\n ) : null}\n {event.type ? (\n <Badge variant=\"secondary\" className=\"capitalize\">\n {event.type.toLowerCase().replace(/_/g, ' ')}\n </Badge>\n ) : null}\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-muted\" />\n {formatDateRange(event.startAt, event.endAt)}\n </span>\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4 shrink-0 text-text-muted\" />\n {venueName ?? t('eventfullymanaged.events.card.noVenue', 'No venue')}\n </span>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs\">\n <MiniStat\n icon={UserCheck}\n label={t('eventfullymanaged.events.card.registered', 'registered')}\n value={registrations.toLocaleString()}\n />\n <MiniStat\n icon={UserCheck}\n label={t('eventfullymanaged.events.card.checkedIn', 'checked in')}\n value={`${checkInPct}%`}\n />\n <MiniStat\n icon={Users}\n label={t('eventfullymanaged.events.card.capacity', 'capacity')}\n value={event.capacity != null ? event.capacity.toLocaleString() : '—'}\n />\n </div>\n </div>\n <div\n className=\"flex shrink-0 items-center gap-2 self-start sm:self-center\"\n onClick={(clickEvent) => clickEvent.stopPropagation()}\n onKeyDown={(keyEvent) => keyEvent.stopPropagation()}\n role=\"presentation\"\n >\n <ActionMenu\n ariaLabel={`Event actions for ${event.name}`}\n onView={() => navigate(`/events/${event.id}`)}\n onEdit={() => navigate(`/events/${event.id}/edit`)}\n />\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={rows.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"events\"\n loading={loading}\n />\n </div>\n )}\n\n <NextSteps\n storageKey=\"eventfullymanaged-events\"\n title={t('eventfullymanaged.events.nextSteps.title', 'Finish setting up your event')}\n steps={[\n {\n id: 'configure-registration',\n label: t('eventfullymanaged.events.nextSteps.registration', 'Configure registration & tickets'),\n description: t(\n 'eventfullymanaged.events.nextSteps.registrationDesc',\n 'Open an event and define ticket types so people can register.'\n ),\n icon: <Ticket className=\"h-4 w-4\" />,\n onClick: () => navigate('/registration'),\n },\n {\n id: 'plan-networking',\n label: t('eventfullymanaged.events.nextSteps.networking', 'Plan networking & sessions'),\n description: t(\n 'eventfullymanaged.events.nextSteps.networkingDesc',\n 'Set up attendee matchmaking and meeting schedules.'\n ),\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'announce',\n label: t('eventfullymanaged.events.nextSteps.announce', 'Announce to attendees'),\n description: t(\n 'eventfullymanaged.events.nextSteps.announceDesc',\n 'Send announcements and run communication campaigns.'\n ),\n icon: <CalendarPlus className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { Megaphone, Tag, TicketPlus, UserPlus, Users } from 'lucide-react';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeleteAttendeeMutation,\n useDeletePromoCodeMutation,\n useDeleteRegistrationMutation,\n usePromoCodesQuery,\n useRegistrationsQuery,\n useTicketTypesQuery,\n useUpdateRegistrationMutation,\n type AttendeeGql,\n type PromoCodeGql,\n type RegistrationGql,\n} from '../../api/eventApi';\nimport { ActionMenu, type ActionMenuItem } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatMoney } from '../../lib/format';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst REGISTRATION_STATUSES = ['PENDING', 'CONFIRMED', 'WAITLISTED', 'CANCELLED', 'CHECKED_IN'] as const;\n\nfunction statusVariant(status?: string | null): 'default' | 'secondary' | 'destructive' | 'outline' {\n switch ((status ?? '').toUpperCase()) {\n case 'CONFIRMED':\n case 'CHECKED_IN':\n return 'default';\n case 'CANCELLED':\n return 'destructive';\n case 'WAITLISTED':\n return 'outline';\n default:\n return 'secondary';\n }\n}\n\n/** Compact used/max meter for promo redemptions — semantic-token only. */\nfunction UsageMeter({ used, max }: { used: number; max?: number | null }) {\n if (max == null || max <= 0) {\n return <span className=\"text-xs text-text-secondary\">{used} used</span>;\n }\n const pct = Math.min(100, Math.round((used / max) * 100));\n const nearLimit = pct >= 90;\n return (\n <div className=\"min-w-[7rem] space-y-1\">\n <div className=\"flex items-center justify-between text-xs tabular-nums text-text-secondary\">\n <span>\n {used} / {max}\n </span>\n <span className=\"text-text-muted\">{pct}%</span>\n </div>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={`h-full rounded-full ${nearLimit ? 'bg-status-warning' : 'bg-action-primary-bg'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n}\n\nfunction SectionCard({\n icon,\n title,\n count,\n loading,\n action,\n children,\n}: {\n icon: React.ReactNode;\n title: string;\n count?: number;\n loading?: boolean;\n action?: React.ReactNode;\n children: React.ReactNode;\n}) {\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n {icon}\n </span>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? `Loading ${title.toLowerCase()}…` : title}\n {!loading && count != null ? (\n <span className=\"ml-2 text-sm font-normal text-text-secondary\">({count.toLocaleString()})</span>\n ) : null}\n </h2>\n </div>\n {action}\n </div>\n <div className=\"mt-4\">{children}</div>\n </GlassCard>\n );\n}\n\nfunction RegistrationsSection() {\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useRegistrationsQuery({ limit: pagination.limit, offset: pagination.offset });\n const [updateRegistration] = useUpdateRegistrationMutation();\n const [deleteRegistration] = useDeleteRegistrationMutation();\n\n // Resolve attendee display names and ticket-type names (the registrations query\n // returns only ids). Pull a page of each to build id -> label lookups so rows\n // read \"Ada Lovelace\" / \"VIP\" instead of raw UUIDs.\n const { data: attendeesData } = useAttendeesQuery({ limit: 200, offset: 0 });\n const { data: ticketsData } = useTicketTypesQuery({ limit: 200, offset: 0 });\n const nameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees.items ?? []) {\n if (attendee.id && attendee.fullName) map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n const ticketById = useMemo(() => {\n const map = new Map<string, string>();\n for (const ticket of ticketsData?.ticketTypes.items ?? []) {\n if (ticket.id && ticket.name) map.set(ticket.id, ticket.name);\n }\n return map;\n }, [ticketsData]);\n const attendeeLabel = (attendeeId?: string | null): string | null => {\n if (!attendeeId) return null;\n return nameById.get(attendeeId) ?? `Attendee ${attendeeId.slice(0, 8)}…`;\n };\n\n const items = useMemo<RegistrationGql[]>(() => data?.registrations.items ?? [], [data]);\n const total = data?.registrations.total ?? 0;\n\n const changeStatus = async (row: RegistrationGql, status: string) => {\n const response = await updateRegistration({\n variables: {\n id: row.id,\n input: {\n eventId: row.eventId,\n attendeeId: row.attendeeId ?? null,\n ticketTypeId: row.ticketTypeId ?? null,\n quantity: row.quantity,\n currency: row.currency,\n totalAmount: row.totalAmount ?? null,\n status,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateRegistration?.id, 'Could not update registration')) {\n return;\n }\n notify.success('Registration updated', `Status set to ${status}`);\n };\n\n const onDelete = async (row: RegistrationGql, label: string) => {\n const response = await deleteRegistration({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete registration', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteRegistration) {\n notify.error('Could not delete registration', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Registration deleted', label);\n };\n\n const rowLabel = (row: RegistrationGql) => attendeeLabel(row.attendeeId) ?? `Registration ${row.id}`;\n\n const columns: ResponsiveTableColumn<RegistrationGql>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{rowLabel(row)}</span>,\n },\n {\n key: 'ticket',\n header: 'Ticket Type',\n mobileLabel: 'Ticket',\n cell: (row) => (\n <span className=\"text-text-secondary\">\n {row.ticketTypeId ? (ticketById.get(row.ticketTypeId) ?? '—') : '—'}\n </span>\n ),\n },\n {\n key: 'quantity',\n header: 'Qty',\n mobileLabel: 'Quantity',\n priority: 'secondary',\n cell: (row) => <span className=\"tabular-nums text-text-secondary\">{row.quantity}</span>,\n },\n {\n key: 'amount',\n header: 'Amount',\n cell: (row) => (\n <span className=\"tabular-nums text-text-primary\">\n {row.totalAmount ? formatMoney(Number(row.totalAmount), row.currency) : '—'}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'primary',\n cell: (row) => <Badge variant={statusVariant(row.status)}>{row.status ?? 'UNKNOWN'}</Badge>,\n },\n ];\n\n return (\n <SectionCard icon={<TicketPlus className=\"h-4 w-4\" />} title=\"Registrations\" count={total} loading={loading}>\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No registrations yet.\"\n description=\"Register your first attendee to start tracking sign-ups.\"\n />\n )\n }\n rowActions={(row) => {\n const label = rowLabel(row);\n const statusItems: ActionMenuItem[] = REGISTRATION_STATUSES.filter(\n (candidate) => candidate !== (row.status ?? '').toUpperCase()\n ).map((candidate, index) => ({\n key: candidate,\n label: `Set ${candidate.replace('_', ' ').toLowerCase()}`,\n onSelect: () => void changeStatus(row, candidate),\n separatorBefore: index === 0,\n }));\n return (\n <ActionMenu\n ariaLabel=\"Registration actions\"\n entityName={label}\n deleteLabel=\"Delete registration\"\n extraItems={statusItems}\n onDelete={() => onDelete(row, label)}\n />\n );\n }}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"registrations\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nfunction AttendeesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useAttendeesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deleteAttendee] = useDeleteAttendeeMutation();\n\n const items = useMemo<AttendeeGql[]>(() => data?.attendees.items ?? [], [data]);\n const total = data?.attendees.total ?? 0;\n\n const onDelete = async (row: AttendeeGql) => {\n const response = await deleteAttendee({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete attendee', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteAttendee) {\n notify.error('Could not delete attendee', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Attendee deleted', row.fullName);\n };\n\n const columns: ResponsiveTableColumn<AttendeeGql>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{row.fullName}</span>,\n },\n {\n key: 'email',\n header: 'Email',\n priority: 'primary',\n cell: (row) => <span className=\"text-text-secondary\">{row.email}</span>,\n },\n {\n key: 'company',\n header: 'Company',\n cell: (row) => <span className=\"text-text-secondary\">{[row.company, row.role].filter(Boolean).join(' · ') || '—'}</span>,\n },\n ];\n\n return (\n <SectionCard\n icon={<Users className=\"h-4 w-4\" />}\n title=\"Attendees\"\n count={total}\n loading={loading}\n action={\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n }\n >\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No attendees yet.\"\n description=\"Add attendees or register one to build your guest list.\"\n />\n )\n }\n rowActions={(row) => (\n <ActionMenu\n ariaLabel={`Actions for ${row.fullName}`}\n entityName={row.fullName}\n deleteLabel=\"Delete attendee\"\n onView={() => navigate(`/attendees/${row.id}/profile`)}\n onEdit={() => navigate(`/registration/attendees/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n />\n )}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"attendees\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nfunction PromoCodesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = usePromoCodesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deletePromoCode] = useDeletePromoCodeMutation();\n\n const items = useMemo<PromoCodeGql[]>(() => data?.promoCodes.items ?? [], [data]);\n const total = data?.promoCodes.total ?? 0;\n\n const onDelete = async (row: PromoCodeGql) => {\n const response = await deletePromoCode({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete promo code', response.error.message);\n throw response.error;\n }\n notify.success('Promo code deleted', row.code);\n };\n\n const columns: ResponsiveTableColumn<PromoCodeGql>[] = [\n {\n key: 'code',\n header: 'Code',\n priority: 'primary',\n cell: (row) => <span className=\"font-mono text-sm font-semibold text-text-primary\">{row.code}</span>,\n },\n {\n key: 'discount',\n header: 'Discount',\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{row.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"text-sm text-text-secondary\">\n {row.discountType === 'PERCENT' ? `${row.discountValue}%` : row.discountValue}\n </span>\n </span>\n ),\n },\n {\n key: 'usage',\n header: 'Usage',\n cell: (row) => <UsageMeter used={row.usedCount} max={row.maxUses} />,\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (row) => (\n <Badge variant={row.isActive ? 'default' : 'secondary'}>{row.isActive ? 'Active' : 'Inactive'}</Badge>\n ),\n },\n ];\n\n return (\n <SectionCard\n icon={<Tag className=\"h-4 w-4\" />}\n title=\"Promo Codes\"\n count={total}\n loading={loading}\n action={\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <TicketPlus className=\"h-4 w-4\" />\n New promo code\n </Button>\n }\n >\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No promo codes yet.\"\n description=\"Create a code to offer discounts and drive registrations.\"\n />\n )\n }\n rowActions={(row) => (\n <ActionMenu\n ariaLabel={`Actions for ${row.code}`}\n entityName={row.code}\n onEdit={() => navigate(`/registration/promo-codes/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n />\n )}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"promo codes\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nexport default function RegistrationPage() {\n const { navigate } = useEventfullyManaged();\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people\n behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a\n registration&apos;s status, and manage discount codes — all backed by live GraphQL APIs.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Registration</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Track registrations, attendees, and promo codes with backend persistence.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" onClick={() => navigate('/registration/register')}>\n <TicketPlus className=\"h-4 w-4\" />\n Register attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <Tag className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <RegistrationsSection />\n <AttendeesSection />\n <PromoCodesSection />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title=\"Grow your registrations\"\n steps={[\n {\n id: 'view-networking',\n label: 'Set up attendee networking',\n description: 'Turn registered attendees into connections with matchmaking.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: 'Promote with a campaign',\n description: 'Send reminders and offers to drive more sign-ups.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: 'Snapshot your numbers',\n description: 'Capture an analytics snapshot to track registration trends.',\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { Megaphone, Tag, TicketPlus, UserPlus, Users } from 'lucide-react';\nimport {\n Badge,\n Button,\n EmphasisPanel,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\n\nimport {\n useAttendeesQuery,\n useDeleteAttendeeMutation,\n useDeletePromoCodeMutation,\n useDeleteRegistrationMutation,\n usePromoCodesQuery,\n useRegistrationsQuery,\n useTicketTypesQuery,\n useUpdateRegistrationMutation,\n type AttendeeGql,\n type PromoCodeGql,\n type RegistrationGql,\n} from '../../api/eventApi';\nimport { ActionMenu, type ActionMenuItem } from '../../components/ActionMenu';\nimport { Pagination, usePagination } from '../../components/Pagination';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { formatMoney } from '../../lib/format';\nimport { mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\nconst REGISTRATION_STATUSES = ['PENDING', 'CONFIRMED', 'WAITLISTED', 'CANCELLED', 'CHECKED_IN'] as const;\n\nfunction statusVariant(status?: string | null): 'default' | 'secondary' | 'destructive' | 'outline' {\n switch ((status ?? '').toUpperCase()) {\n case 'CONFIRMED':\n case 'CHECKED_IN':\n return 'default';\n case 'CANCELLED':\n return 'destructive';\n case 'WAITLISTED':\n return 'outline';\n default:\n return 'secondary';\n }\n}\n\n/** Compact used/max meter for promo redemptions — semantic-token only. */\nfunction UsageMeter({ used, max }: { used: number; max?: number | null }) {\n if (max == null || max <= 0) {\n return <span className=\"text-xs text-text-secondary\">{used} used</span>;\n }\n const pct = Math.min(100, Math.round((used / max) * 100));\n const nearLimit = pct >= 90;\n return (\n <div className=\"min-w-[7rem] space-y-1\">\n <div className=\"flex items-center justify-between text-xs tabular-nums text-text-secondary\">\n <span>\n {used} / {max}\n </span>\n <span className=\"text-text-muted\">{pct}%</span>\n </div>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={`h-full rounded-full ${nearLimit ? 'bg-status-warning' : 'bg-action-primary-bg'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n}\n\nfunction SectionCard({\n icon,\n title,\n count,\n loading,\n action,\n children,\n}: {\n icon: React.ReactNode;\n title: string;\n count?: number;\n loading?: boolean;\n action?: React.ReactNode;\n children: React.ReactNode;\n}) {\n return (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n {icon}\n </span>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {loading ? `Loading ${title.toLowerCase()}…` : title}\n {!loading && count != null ? (\n <span className=\"ml-2 text-sm font-normal text-text-secondary\">({count.toLocaleString()})</span>\n ) : null}\n </h2>\n </div>\n {action}\n </div>\n <div className=\"mt-4\">{children}</div>\n </GlassCard>\n );\n}\n\nfunction RegistrationsSection() {\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useRegistrationsQuery({ limit: pagination.limit, offset: pagination.offset });\n const [updateRegistration] = useUpdateRegistrationMutation();\n const [deleteRegistration] = useDeleteRegistrationMutation();\n\n // Resolve attendee display names and ticket-type names (the registrations query\n // returns only ids). Pull a page of each to build id -> label lookups so rows\n // read \"Ada Lovelace\" / \"VIP\" instead of raw UUIDs.\n const { data: attendeesData } = useAttendeesQuery({ limit: 200, offset: 0 });\n const { data: ticketsData } = useTicketTypesQuery({ limit: 200, offset: 0 });\n const nameById = useMemo(() => {\n const map = new Map<string, string>();\n for (const attendee of attendeesData?.attendees.items ?? []) {\n if (attendee.id && attendee.fullName) map.set(attendee.id, attendee.fullName);\n }\n return map;\n }, [attendeesData]);\n const ticketById = useMemo(() => {\n const map = new Map<string, string>();\n for (const ticket of ticketsData?.ticketTypes.items ?? []) {\n if (ticket.id && ticket.name) map.set(ticket.id, ticket.name);\n }\n return map;\n }, [ticketsData]);\n const attendeeLabel = (attendeeId?: string | null): string | null => {\n if (!attendeeId) return null;\n return nameById.get(attendeeId) ?? `Attendee ${attendeeId.slice(0, 8)}…`;\n };\n\n const items = useMemo<RegistrationGql[]>(() => data?.registrations.items ?? [], [data]);\n const total = data?.registrations.total ?? 0;\n\n const changeStatus = async (row: RegistrationGql, status: string) => {\n const response = await updateRegistration({\n variables: {\n id: row.id,\n input: {\n eventId: row.eventId,\n attendeeId: row.attendeeId ?? null,\n ticketTypeId: row.ticketTypeId ?? null,\n quantity: row.quantity,\n currency: row.currency,\n totalAmount: row.totalAmount ?? null,\n status,\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateRegistration?.id, 'Could not update registration')) {\n return;\n }\n notify.success('Registration updated', `Status set to ${status}`);\n };\n\n const onDelete = async (row: RegistrationGql, label: string) => {\n const response = await deleteRegistration({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete registration', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteRegistration) {\n notify.error('Could not delete registration', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Registration deleted', label);\n };\n\n const rowLabel = (row: RegistrationGql) => attendeeLabel(row.attendeeId) ?? `Registration ${row.id}`;\n\n const columns: ResponsiveTableColumn<RegistrationGql>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{rowLabel(row)}</span>,\n },\n {\n key: 'ticket',\n header: 'Ticket Type',\n mobileLabel: 'Ticket',\n cell: (row) => (\n <span className=\"text-text-secondary\">\n {row.ticketTypeId ? (ticketById.get(row.ticketTypeId) ?? '—') : '—'}\n </span>\n ),\n },\n {\n key: 'quantity',\n header: 'Qty',\n mobileLabel: 'Quantity',\n priority: 'secondary',\n cell: (row) => <span className=\"tabular-nums text-text-secondary\">{row.quantity}</span>,\n },\n {\n key: 'amount',\n header: 'Amount',\n cell: (row) => (\n <span className=\"tabular-nums text-text-primary\">\n {row.totalAmount ? formatMoney(Number(row.totalAmount), row.currency) : '—'}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'primary',\n cell: (row) => <Badge variant={statusVariant(row.status)}>{row.status ?? 'UNKNOWN'}</Badge>,\n },\n ];\n\n return (\n <SectionCard icon={<TicketPlus className=\"h-4 w-4\" />} title=\"Registrations\" count={total} loading={loading}>\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No registrations yet.\"\n description=\"Register your first attendee to start tracking sign-ups.\"\n />\n )\n }\n rowActions={(row) => {\n const label = rowLabel(row);\n const statusItems: ActionMenuItem[] = REGISTRATION_STATUSES.filter(\n (candidate) => candidate !== (row.status ?? '').toUpperCase()\n ).map((candidate, index) => ({\n key: candidate,\n label: `Set ${candidate.replace('_', ' ').toLowerCase()}`,\n onSelect: () => void changeStatus(row, candidate),\n separatorBefore: index === 0,\n }));\n return (\n <ActionMenu\n ariaLabel=\"Registration actions\"\n entityName={label}\n deleteLabel=\"Delete registration\"\n extraItems={statusItems}\n onDelete={() => onDelete(row, label)}\n />\n );\n }}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"registrations\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nfunction AttendeesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = useAttendeesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deleteAttendee] = useDeleteAttendeeMutation();\n\n const items = useMemo<AttendeeGql[]>(() => data?.attendees.items ?? [], [data]);\n const total = data?.attendees.total ?? 0;\n\n const onDelete = async (row: AttendeeGql) => {\n const response = await deleteAttendee({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete attendee', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteAttendee) {\n notify.error('Could not delete attendee', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n notify.success('Attendee deleted', row.fullName);\n };\n\n const columns: ResponsiveTableColumn<AttendeeGql>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{row.fullName}</span>,\n },\n {\n key: 'email',\n header: 'Email',\n priority: 'primary',\n cell: (row) => <span className=\"text-text-secondary\">{row.email}</span>,\n },\n {\n key: 'company',\n header: 'Company',\n cell: (row) => (\n <span className=\"text-text-secondary\">{[row.company, row.role].filter(Boolean).join(' · ') || '—'}</span>\n ),\n },\n ];\n\n return (\n <SectionCard\n icon={<Users className=\"h-4 w-4\" />}\n title=\"Attendees\"\n count={total}\n loading={loading}\n action={\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n }\n >\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No attendees yet.\"\n description=\"Add attendees or register one to build your guest list.\"\n />\n )\n }\n rowActions={(row) => (\n <ActionMenu\n ariaLabel={`Actions for ${row.fullName}`}\n entityName={row.fullName}\n deleteLabel=\"Delete attendee\"\n onView={() => navigate(`/attendees/${row.id}/profile`)}\n onEdit={() => navigate(`/registration/attendees/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n />\n )}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"attendees\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nfunction PromoCodesSection() {\n const { navigate } = useEventfullyManaged();\n const pagination = usePagination({ pageSize: 10 });\n const { data, loading } = usePromoCodesQuery({ limit: pagination.limit, offset: pagination.offset });\n const [deletePromoCode] = useDeletePromoCodeMutation();\n\n const items = useMemo<PromoCodeGql[]>(() => data?.promoCodes.items ?? [], [data]);\n const total = data?.promoCodes.total ?? 0;\n\n const onDelete = async (row: PromoCodeGql) => {\n const response = await deletePromoCode({ variables: { id: row.id } });\n if (response.error) {\n notify.error('Could not delete promo code', response.error.message);\n throw response.error;\n }\n notify.success('Promo code deleted', row.code);\n };\n\n const columns: ResponsiveTableColumn<PromoCodeGql>[] = [\n {\n key: 'code',\n header: 'Code',\n priority: 'primary',\n cell: (row) => <span className=\"font-mono text-sm font-semibold text-text-primary\">{row.code}</span>,\n },\n {\n key: 'discount',\n header: 'Discount',\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{row.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"text-sm text-text-secondary\">\n {row.discountType === 'PERCENT' ? `${row.discountValue}%` : row.discountValue}\n </span>\n </span>\n ),\n },\n {\n key: 'usage',\n header: 'Usage',\n cell: (row) => <UsageMeter used={row.usedCount} max={row.maxUses} />,\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (row) => (\n <Badge variant={row.isActive ? 'default' : 'secondary'}>{row.isActive ? 'Active' : 'Inactive'}</Badge>\n ),\n },\n ];\n\n return (\n <SectionCard\n icon={<Tag className=\"h-4 w-4\" />}\n title=\"Promo Codes\"\n count={total}\n loading={loading}\n action={\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <TicketPlus className=\"h-4 w-4\" />\n New promo code\n </Button>\n }\n >\n <ResponsiveTable\n columns={columns}\n data={items}\n rowKey={(row) => row.id}\n emptyState={\n loading ? null : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No promo codes yet.\"\n description=\"Create a code to offer discounts and drive registrations.\"\n />\n )\n }\n rowActions={(row) => (\n <ActionMenu\n ariaLabel={`Actions for ${row.code}`}\n entityName={row.code}\n onEdit={() => navigate(`/registration/promo-codes/${row.id}/edit`)}\n onDelete={() => onDelete(row)}\n />\n )}\n />\n <Pagination\n offset={pagination.offset}\n limit={pagination.limit}\n loadedCount={items.length}\n total={total}\n onPrev={pagination.prevPage}\n onNext={pagination.nextPage}\n itemLabel=\"promo codes\"\n loading={loading}\n />\n </SectionCard>\n );\n}\n\nexport default function RegistrationPage() {\n const { navigate } = useEventfullyManaged();\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n This is where you see who is coming. Registrations are the paid or confirmed sign-ups, attendees are the people\n behind them, and promo codes let you offer discounts. Register attendees, edit their details, change a\n registration&apos;s status, and manage discount codes — all backed by live GraphQL APIs.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>Registration</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Track registrations, attendees, and promo codes with backend persistence.\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" onClick={() => navigate('/registration/register')}>\n <TicketPlus className=\"h-4 w-4\" />\n Register attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/attendees/create')}>\n <UserPlus className=\"h-4 w-4\" />\n Add attendee\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => navigate('/registration/promo-codes/create')}>\n <Tag className=\"h-4 w-4\" />\n New promo code\n </Button>\n </div>\n </div>\n </EmphasisPanel>\n\n <RegistrationsSection />\n <AttendeesSection />\n <PromoCodesSection />\n\n <NextSteps\n storageKey=\"eventfullymanaged-registration\"\n title=\"Grow your registrations\"\n steps={[\n {\n id: 'view-networking',\n label: 'Set up attendee networking',\n description: 'Turn registered attendees into connections with matchmaking.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('/networking'),\n },\n {\n id: 'promote',\n label: 'Promote with a campaign',\n description: 'Send reminders and offers to drive more sign-ups.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n {\n id: 'analyze',\n label: 'Snapshot your numbers',\n description: 'Capture an analytics snapshot to track registration trends.',\n icon: <Tag className=\"h-4 w-4\" />,\n onClick: () => navigate('/analytics'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Building2, Coins, Magnet, Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCommunicationCampaignsQuery, useSponsorQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { SafeLink } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Sponsor ↔ organizer message thread, backed by wspace-conversations-svc and\n * scoped to this sponsor so the conversation follows the partnership.\n */\nfunction SponsorConversation({ sponsorId, sponsorName }: { sponsorId: string; sponsorName: string }) {\n // Stable reference: `useRegisterPageConversationContext` keys its effect on this\n // object's identity, so a fresh inline object every render would loop the shared\n // PageConversationContextProvider (see NetworkingPage for the full rationale).\n const conversationContext = useMemo(\n () => ({\n entityType: 'EVENTFULLYMANAGED_SPONSOR',\n entityId: sponsorId,\n contextTitle: `Sponsor · ${sponsorName}`,\n }),\n [sponsorId, sponsorName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"sponsor-message\" />;\n}\n\ntype CampaignRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nconst compactNumber = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });\n\nexport default function SponsorDetailsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const sponsorQuery = useSponsorQuery(id);\n const campaignsQuery = useCommunicationCampaignsQuery({ limit: 100, offset: 0 });\n\n const sponsor = sponsorQuery.data?.sponsor ?? null;\n const campaigns = useMemo(\n () => (campaignsQuery.data?.communicationCampaigns.items ?? []) as CampaignRow[],\n [campaignsQuery.data]\n );\n\n const metrics = (sponsor?.metrics ?? {}) as { value?: number; leads?: number; leadsTarget?: number };\n const value = Number(metrics.value ?? 0);\n const leads = Number(metrics.leads ?? 0);\n const target = Number(metrics.leadsTarget ?? 0);\n const leadsPct = target > 0 ? Math.min(100, Math.round((leads / target) * 100)) : 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Everything about one sponsor in one place — their tier, contract value, leads, contacts, and the campaigns they\n appear in. Use this to prep for a sponsor conversation, confirm details, or check how their branded campaigns are\n performing.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-card border border-border-subtle bg-bg-sunken\">\n {sponsor?.logoUrl ? (\n <img src={sponsor.logoUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <Building2 className=\"h-6 w-6 text-text-muted\" />\n )}\n </span>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{sponsor?.name ?? 'Sponsor'}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsorQuery.loading\n ? 'Loading sponsor details...'\n : sponsor\n ? 'Live sponsor profile and partnership metrics.'\n : 'Sponsor not found in current query scope.'}\n </p>\n </div>\n </div>\n {sponsor ? (\n <Badge variant=\"secondary\" className=\"shrink-0 self-start uppercase\">\n {sponsor.level}\n </Badge>\n ) : null}\n </div>\n\n {sponsor ? (\n <div className=\"mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Contract value</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">${compactNumber.format(value)}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Magnet className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Leads</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">{leads.toLocaleString()}</p>\n {target > 0 ? (\n <div className=\"mt-2\">\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full\"\n style={{\n width: `${leadsPct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {leadsPct}% of {target.toLocaleString()} target\n </p>\n </div>\n ) : null}\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">Primary contact</p>\n <p className=\"mt-1.5 truncate font-medium text-text-primary\">{sponsor.contactName ?? 'Unassigned'}</p>\n <p className=\"truncate text-xs text-text-secondary\">{sponsor.contactEmail ?? 'No email'}</p>\n {sponsor.website ? (\n <SafeLink\n href={sponsor.website}\n className=\"mt-1 block truncate text-xs text-text-link hover:underline\"\n fallbackClassName=\"mt-1 block truncate text-xs text-text-secondary\"\n >\n {sponsor.website}\n </SafeLink>\n ) : null}\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n {sponsor ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this sponsor so you can segment your portfolio — for example by industry, tier or renewal status.\n </p>\n <EntityTagsField\n entityType=\"EVENTFULLYMANAGED_SPONSOR\"\n entityId={sponsor.id}\n label=\"Sponsor tags\"\n className=\"mt-3\"\n />\n </GlassCard>\n ) : null}\n\n <EntityListSection\n heading={campaignsQuery.loading ? 'Loading campaigns...' : `Campaign Activity (${campaigns.length})`}\n emptyLabel=\"No communication campaigns available for sponsor tracking.\"\n rows={campaigns.map((campaign) => ({\n id: campaign.id,\n title: campaign.title ?? campaign.name ?? `Campaign ${campaign.id}`,\n status: campaign.status,\n }))}\n />\n\n {sponsor ? (\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Sponsor messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Coordinate with this sponsor in a dedicated thread, backed by the conversations service.\n </p>\n </div>\n <SponsorConversation sponsorId={sponsor.id} sponsorName={sponsor.name} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-sponsor-details\"\n title=\"Grow this partnership\"\n steps={[\n {\n id: 'edit-sponsor',\n label: 'Update sponsor details',\n description: 'Keep tier, contacts and contract value current.',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/edit` : '/sponsors'),\n },\n {\n id: 'track-leads',\n label: 'Track sponsor leads',\n description: 'See the leads this sponsor is generating at the event.',\n icon: <TrendingUp className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/leads` : '/sponsors'),\n },\n {\n id: 'run-campaign',\n label: 'Run a co-branded campaign',\n description: 'Promote this sponsor through a communication campaign.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SponsorDetailsPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/SponsorDetailsPage.tsx"],"sourcesContent":["import { Building2, Coins, Magnet, Megaphone, Pencil, TrendingUp } from 'lucide-react';\nimport { ContextConversationWidget } from '@burdenoff/fe-libs/shared';\nimport { useRegisterPageConversationContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Badge, EmphasisPanel, GlassCard, GradientText, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCommunicationCampaignsQuery, useSponsorQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { SafeLink } from '../../components/SafeLink';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { EntityListSection } from './EntityListSection';\n\n/**\n * Sponsor ↔ organizer message thread, backed by wspace-conversations-svc and\n * scoped to this sponsor so the conversation follows the partnership.\n */\nfunction SponsorConversation({ sponsorId, sponsorName }: { sponsorId: string; sponsorName: string }) {\n // Stable reference: `useRegisterPageConversationContext` keys its effect on this\n // object's identity, so a fresh inline object every render would loop the shared\n // PageConversationContextProvider (see NetworkingPage for the full rationale).\n const conversationContext = useMemo(\n () => ({\n entityType: 'EVENTFULLYMANAGED_SPONSOR',\n entityId: sponsorId,\n contextTitle: `Sponsor · ${sponsorName}`,\n }),\n [sponsorId, sponsorName]\n );\n useRegisterPageConversationContext(conversationContext);\n return <ContextConversationWidget widgetKey=\"sponsor-message\" />;\n}\n\ntype CampaignRow = {\n id: string;\n name?: string;\n title?: string;\n status?: string;\n};\n\nconst compactNumber = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });\n\nexport default function SponsorDetailsPage() {\n const { id = '' } = useParams();\n const { navigate } = useEventfullyManaged();\n const sponsorQuery = useSponsorQuery(id);\n const campaignsQuery = useCommunicationCampaignsQuery({ limit: 100, offset: 0 });\n\n const sponsor = sponsorQuery.data?.sponsor ?? null;\n const campaigns = useMemo(\n () => (campaignsQuery.data?.communicationCampaigns.items ?? []) as CampaignRow[],\n [campaignsQuery.data]\n );\n\n const metrics = (sponsor?.metrics ?? {}) as { value?: number; leads?: number; leadsTarget?: number };\n const value = Number(metrics.value ?? 0);\n const leads = Number(metrics.leads ?? 0);\n const target = Number(metrics.leadsTarget ?? 0);\n const leadsPct = target > 0 ? Math.min(100, Math.round((leads / target) * 100)) : 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n Everything about one sponsor in one place — their tier, contract value, leads, contacts, and the campaigns they\n appear in. Use this to prep for a sponsor conversation, confirm details, or check how their branded campaigns\n are performing.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-center gap-4\">\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-card border border-border-subtle bg-bg-sunken\">\n {sponsor?.logoUrl ? (\n <img src={sponsor.logoUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <Building2 className=\"h-6 w-6 text-text-muted\" />\n )}\n </span>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n <GradientText>{sponsor?.name ?? 'Sponsor'}</GradientText>\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {sponsorQuery.loading\n ? 'Loading sponsor details...'\n : sponsor\n ? 'Live sponsor profile and partnership metrics.'\n : 'Sponsor not found in current query scope.'}\n </p>\n </div>\n </div>\n {sponsor ? (\n <Badge variant=\"secondary\" className=\"shrink-0 self-start uppercase\">\n {sponsor.level}\n </Badge>\n ) : null}\n </div>\n\n {sponsor ? (\n <div className=\"mt-5 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Coins className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Contract value</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">${compactNumber.format(value)}</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Magnet className=\"h-4 w-4 text-accent-primary\" />\n <p className=\"text-xs font-medium uppercase tracking-wide\">Leads</p>\n </div>\n <p className=\"mt-1.5 text-2xl font-bold tabular-nums text-text-primary\">{leads.toLocaleString()}</p>\n {target > 0 ? (\n <div className=\"mt-2\">\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full\"\n style={{\n width: `${leadsPct}%`,\n background: 'linear-gradient(90deg, var(--color-chart-1), var(--color-chart-2))',\n }}\n />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {leadsPct}% of {target.toLocaleString()} target\n </p>\n </div>\n ) : null}\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">Primary contact</p>\n <p className=\"mt-1.5 truncate font-medium text-text-primary\">{sponsor.contactName ?? 'Unassigned'}</p>\n <p className=\"truncate text-xs text-text-secondary\">{sponsor.contactEmail ?? 'No email'}</p>\n {sponsor.website ? (\n <SafeLink\n href={sponsor.website}\n className=\"mt-1 block truncate text-xs text-text-link hover:underline\"\n fallbackClassName=\"mt-1 block truncate text-xs text-text-secondary\"\n >\n {sponsor.website}\n </SafeLink>\n ) : null}\n </GlassCard>\n </div>\n ) : null}\n </EmphasisPanel>\n\n {sponsor ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this sponsor so you can segment your portfolio — for example by industry, tier or renewal status.\n </p>\n <EntityTagsField\n entityType=\"EVENTFULLYMANAGED_SPONSOR\"\n entityId={sponsor.id}\n label=\"Sponsor tags\"\n className=\"mt-3\"\n />\n </GlassCard>\n ) : null}\n\n <EntityListSection\n heading={campaignsQuery.loading ? 'Loading campaigns...' : `Campaign Activity (${campaigns.length})`}\n emptyLabel=\"No communication campaigns available for sponsor tracking.\"\n rows={campaigns.map((campaign) => ({\n id: campaign.id,\n title: campaign.title ?? campaign.name ?? `Campaign ${campaign.id}`,\n status: campaign.status,\n }))}\n />\n\n {sponsor ? (\n <div className=\"space-y-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Sponsor messages</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Coordinate with this sponsor in a dedicated thread, backed by the conversations service.\n </p>\n </div>\n <SponsorConversation sponsorId={sponsor.id} sponsorName={sponsor.name} />\n </div>\n ) : null}\n\n <NextSteps\n storageKey=\"eventfullymanaged-sponsor-details\"\n title=\"Grow this partnership\"\n steps={[\n {\n id: 'edit-sponsor',\n label: 'Update sponsor details',\n description: 'Keep tier, contacts and contract value current.',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/edit` : '/sponsors'),\n },\n {\n id: 'track-leads',\n label: 'Track sponsor leads',\n description: 'See the leads this sponsor is generating at the event.',\n icon: <TrendingUp className=\"h-4 w-4\" />,\n onClick: () => navigate(id ? `/sponsors/${id}/leads` : '/sponsors'),\n },\n {\n id: 'run-campaign',\n label: 'Run a co-branded campaign',\n description: 'Promote this sponsor through a communication campaign.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate('/communication'),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAS,EAAoB,EAAE,cAAW,kBAA2D;AAanG,QADA,EAR4B,SACnB;EACL,YAAY;EACZ,UAAU;EACV,cAAc,aAAa;EAC5B,GACD,CAAC,GAAW,EAAY,CACzB,CACsD,EAChD,kBAAC,GAAD,EAA2B,WAAU,mBAAoB,CAAA;;AAUlE,IAAM,IAAgB,IAAI,KAAK,aAAa,KAAA,GAAW;CAAE,UAAU;CAAW,uBAAuB;CAAG,CAAC;AAEzG,SAAwB,IAAqB;CAC3C,IAAM,EAAE,QAAK,OAAO,GAAW,EACzB,EAAE,gBAAa,GAAsB,EACrC,IAAe,EAAgB,EAAG,EAClC,IAAiB,EAA+B;EAAE,OAAO;EAAK,QAAQ;EAAG,CAAC,EAE1E,IAAU,EAAa,MAAM,WAAW,MACxC,IAAY,QACT,EAAe,MAAM,uBAAuB,SAAS,EAAE,EAC9D,CAAC,EAAe,KAAK,CACtB,EAEK,IAAW,GAAS,WAAW,EAAE,EACjC,IAAQ,OAAO,EAAQ,SAAS,EAAE,EAClC,IAAQ,OAAO,EAAQ,SAAS,EAAE,EAClC,IAAS,OAAO,EAAQ,eAAe,EAAE,EACzC,IAAW,IAAS,IAAI,KAAK,IAAI,KAAK,KAAK,MAAO,IAAQ,IAAU,IAAI,CAAC,GAAG;AAElF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,iPAIC,CAAA;GAEd,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACb,GAAS,UACR,kBAAC,OAAD;OAAK,KAAK,EAAQ;OAAS,KAAI;OAAG,WAAU;OAA+B,CAAA,GAE3E,kBAAC,GAAD,EAAW,WAAU,2BAA4B,CAAA;MAE9C,CAAA,EACP,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAe,GAAS,QAAQ,WAAyB,CAAA;MACtD,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAa,UACV,+BACA,IACE,kDACA;MACJ,CAAA,CACA,EAAA,CAAA,CACF;QACL,IACC,kBAAC,GAAD;KAAO,SAAQ;KAAY,WAAU;eAClC,EAAQ;KACH,CAAA,GACN,KACA;OAEL,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,+BAAgC,CAAA,EACjD,kBAAC,KAAD;QAAG,WAAU;kBAA8C;QAAkB,CAAA,CACzE;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAwE,KAAE,EAAc,OAAO,EAAM,CAAK;SAChG;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,+BAAgC,CAAA,EAClD,kBAAC,KAAD;SAAG,WAAU;mBAA8C;SAAS,CAAA,CAChE;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAA4D,EAAM,gBAAgB;QAAK,CAAA;OACnG,IAAS,IACR,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UACE,WAAU;UACV,OAAO;WACL,OAAO,GAAG,EAAS;WACnB,YAAY;WACb;UACD,CAAA;SACE,CAAA,EACN,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG;UAAS;UAAM,EAAO,gBAAgB;UAAC;UACtC;WACA;YACJ;OACM;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC;OACE,kBAAC,KAAD;QAAG,WAAU;kBAAkE;QAAmB,CAAA;OAClG,kBAAC,KAAD;QAAG,WAAU;kBAAiD,EAAQ,eAAe;QAAiB,CAAA;OACtG,kBAAC,KAAD;QAAG,WAAU;kBAAwC,EAAQ,gBAAgB;QAAe,CAAA;OAC3F,EAAQ,UACP,kBAAC,GAAD;QACE,MAAM,EAAQ;QACd,WAAU;QACV,mBAAkB;kBAEjB,EAAQ;QACA,CAAA,GACT;OACM;;KACR;QACJ,KACU,EAAA,CAAA;GAEf,IACC,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC;KACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAAS,CAAA;KACjE,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAE5C,CAAA;KACJ,kBAAC,GAAD;MACE,YAAW;MACX,UAAU,EAAQ;MAClB,OAAM;MACN,WAAU;MACV,CAAA;KACQ;QACV;GAEJ,kBAAC,GAAD;IACE,SAAS,EAAe,UAAU,yBAAyB,sBAAsB,EAAU,OAAO;IAClG,YAAW;IACX,MAAM,EAAU,KAAK,OAAc;KACjC,IAAI,EAAS;KACb,OAAO,EAAS,SAAS,EAAS,QAAQ,YAAY,EAAS;KAC/D,QAAQ,EAAS;KAClB,EAAE;IACH,CAAA;GAED,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAqB,CAAA,EAC7E,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,GAAD;KAAqB,WAAW,EAAQ;KAAI,aAAa,EAAQ;KAAQ,CAAA,CACrE;QACJ;GAEJ,kBAAC,GAAD;IACE,YAAW;IACX,OAAM;IACN,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;MACpC,eAAe,EAAS,IAAK,aAAa,EAAG,SAAS,YAAY;MACnE;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,eAAe,EAAS,IAAK,aAAa,EAAG,UAAU,YAAY;MACpE;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,iBAAiB;MAC1C;KACF;IACD,CAAA;GACE"}
@@ -1 +1 @@
1
- {"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const initials = useMemo(() => {\n const parts = form.fullName.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n }, [form.fullName]);\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n {/* Identity preview */}\n <div className=\"flex items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary\">\n {initials}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {form.fullName.trim() || 'New attendee'}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[form.email.trim(), form.company.trim(), form.role.trim()].filter(Boolean).join(' · ') ||\n 'Details will appear here as you type.'}\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_ATTENDEE\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AttendeeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/AttendeeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\n\nimport {\n useAttendeesQuery,\n useCreateAttendeeMutation,\n useEventsQuery,\n useUpdateAttendeeMutation,\n type AttendeeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype AttendeeFormState = {\n eventId: string;\n fullName: string;\n email: string;\n phone: string;\n company: string;\n role: string;\n};\n\nconst emptyState: AttendeeFormState = {\n eventId: '',\n fullName: '',\n email: '',\n phone: '',\n company: '',\n role: '',\n};\n\nfunction toFormState(attendee: AttendeeGql): AttendeeFormState {\n return {\n eventId: attendee.eventId ?? '',\n fullName: attendee.fullName ?? '',\n email: attendee.email ?? '',\n phone: attendee.phone ?? '',\n company: attendee.company ?? '',\n role: attendee.role ?? '',\n };\n}\n\nexport default function AttendeeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id attendee resolver exists — derive edit initial-state from the list.\n const { data: attendeesData } = useAttendeesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(\n () => (attendeesData?.attendees.items ?? []).find((item) => item.id === id),\n [attendeesData, id]\n );\n\n const [createAttendee, { loading: creating }] = useCreateAttendeeMutation();\n const [updateAttendee, { loading: updating }] = useUpdateAttendeeMutation();\n const [form, setForm] = useState<AttendeeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = (field: keyof AttendeeFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const initials = useMemo(() => {\n const parts = form.fullName.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n return (parts[0][0] + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase();\n }, [form.fullName]);\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this attendee belongs to.');\n return;\n }\n if (!form.fullName.trim() || !form.email.trim()) {\n notify.error('Missing details', 'A full name and email are required.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n fullName: form.fullName.trim(),\n email: form.email.trim(),\n phone: form.phone.trim() || null,\n company: form.company.trim() || null,\n role: form.role.trim() || null,\n profile: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateAttendee({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updateAttendee?.id, 'Could not update attendee')) {\n return;\n }\n notify.success('Attendee updated', form.fullName.trim());\n navigate('/registration');\n return;\n }\n\n const response = await createAttendee({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createAttendee?.id, 'Could not create attendee')) {\n return;\n }\n notify.success('Attendee added', form.fullName.trim());\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save attendee', describeError(error));\n }\n };\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Attendee' : 'Add Attendee'}\n description=\"Attendee records are the people behind your registrations.\"\n purpose=\"Capture the person attending your event — who they are, how to reach them, and the organization they represent. You can tag attendees to segment them later for networking, communications, and analytics.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Attendee' : 'Add Attendee'}\n saving={saving}\n >\n {/* Identity preview */}\n <div className=\"flex items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-accent-wash text-sm font-semibold text-action-primary\">\n {initials}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{form.fullName.trim() || 'New attendee'}</p>\n <p className=\"truncate text-xs text-text-secondary\">\n {[form.email.trim(), form.company.trim(), form.role.trim()].filter(Boolean).join(' · ') ||\n 'Details will appear here as you type.'}\n </p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Full Name</span>\n <Input value={form.fullName} onChange={(e) => onChange('fullName', e.target.value)} required />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Email</span>\n <Input type=\"email\" value={form.email} onChange={(e) => onChange('email', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Phone</span>\n <Input value={form.phone} onChange={(e) => onChange('phone', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Company</span>\n <Input value={form.company} onChange={(e) => onChange('company', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Role</span>\n <Input value={form.role} onChange={(e) => onChange('role', e.target.value)} />\n </label>\n </div>\n\n {isEdit ? <EntityTagsField entityType=\"EVENTFULLYMANAGED_ATTENDEE\" entityId={id} /> : null}\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}