@burdenoff/microfe-eventfullymanaged 2026.825.1 → 2026.828.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eventfullymanaged/hooks/index.d.ts +1 -0
- package/dist/eventfullymanaged/hooks/index.d.ts.map +1 -1
- package/dist/eventfullymanaged/hooks/index.js +3 -0
- package/dist/eventfullymanaged/hooks/useEventDelete.d.ts +19 -0
- package/dist/eventfullymanaged/hooks/useEventDelete.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useEventDelete.js +16 -0
- package/dist/eventfullymanaged/hooks/useEventDelete.js.map +1 -0
- package/dist/eventfullymanaged/lib/format.d.ts +9 -0
- package/dist/eventfullymanaged/lib/format.d.ts.map +1 -1
- package/dist/eventfullymanaged/lib/format.js +11 -3
- package/dist/eventfullymanaged/lib/format.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js +315 -277
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js +91 -81
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/EventsPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/EventsPage.js +231 -229
- package/dist/eventfullymanaged/pages/modules/EventsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +120 -102
- package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +128 -120
- package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +68 -75
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +41 -41
- package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -1
- package/dist/eventfullymanaged/store/eventfullyManagedStore.js +15 -0
- package/dist/eventfullymanaged/store/eventfullyManagedStore.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegistrationPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/modules/RegistrationPage.tsx"],"sourcesContent":["import { useApolloClient } from '@apollo/client/react';\nimport { Badge as BadgeIcon, Megaphone, QrCode, 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 ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport {\n BADGE_PDF_QUERY,\n downloadBase64Pdf,\n useAttendeesQuery,\n useCheckInTokenQuery,\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 { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/**\n * BOFF-4330 — displays a registration's signed QR check-in token. Fetched on\n * demand (never persisted client-side) so it always reflects the\n * registration's current signature; a kiosk scans this exact image/token via\n * `eventfullyManagedScanCheckIn`.\n */\nfunction CheckInQrDialog({\n registrationId,\n label,\n onOpenChange,\n}: {\n registrationId: string;\n label: string;\n onOpenChange: (open: boolean) => void;\n}) {\n const { data, loading, error } = useCheckInTokenQuery(registrationId);\n const token = data?.checkInToken;\n\n return (\n <ResponsiveDialog open onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-sm\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Check-in QR</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {label} scans this code at any check-in station to check in — it works even if the station has no connection\n at the moment.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex flex-col items-center gap-3 py-2\">\n {loading ? (\n <p className=\"text-sm text-text-secondary\">Generating…</p>\n ) : error || !token ? (\n <p className=\"text-sm text-status-danger-text\">Could not generate a check-in QR for this registration.</p>\n ) : (\n <>\n <img\n src={token.qrDataUrl}\n alt={`Check-in QR code for ${label}`}\n className=\"h-56 w-56 rounded-card border border-border-seam bg-white p-2\"\n />\n <code className=\"max-w-full break-all rounded bg-bg-sunken px-2 py-1 text-xs text-text-secondary\">\n {token.token}\n </code>\n </>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\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 // BOFF-4330 — registration id currently showing its check-in QR, if any.\n const [qrRegistrationId, setQrRegistrationId] = useState<string | null>(null);\n // BOFF-4322 — on-demand badge PDF download. A one-off query (not a hook)\n // since it's a rarely-used, heavy payload — same pattern as TimeCampus's\n // invoice PDF download.\n const apollo = useApolloClient();\n const [downloadingBadgeId, setDownloadingBadgeId] = useState<string | null>(null);\n const onDownloadBadge = async (registrationId: string, label: string) => {\n setDownloadingBadgeId(registrationId);\n try {\n const { data, error } = await apollo.query<{ badgePdf: string }>({\n query: BADGE_PDF_QUERY,\n variables: { registrationId },\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n });\n if (error || !data?.badgePdf) {\n notify.error('Could not generate badge', error?.message ?? 'Please try again.');\n return;\n }\n downloadBase64Pdf(data.badgePdf, `${label.replace(/[^a-z0-9]+/gi, '-')}-badge.pdf`);\n notify.success('Badge ready', `${label}'s badge downloaded.`);\n } catch (err) {\n notify.error('Could not generate badge', describeError(err));\n } finally {\n setDownloadingBadgeId(null);\n }\n };\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 const extraItems: ActionMenuItem[] = [\n {\n key: 'view-qr',\n label: 'View check-in QR',\n icon: QrCode,\n onSelect: () => setQrRegistrationId(row.id),\n },\n {\n key: 'download-badge',\n label: downloadingBadgeId === row.id ? 'Generating badge…' : 'Download badge',\n icon: BadgeIcon,\n disabled: downloadingBadgeId === row.id,\n onSelect: () => void onDownloadBadge(row.id, label),\n },\n ...statusItems,\n ];\n return (\n <ActionMenu\n ariaLabel=\"Registration actions\"\n entityName={label}\n deleteLabel=\"Delete registration\"\n extraItems={extraItems}\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 {qrRegistrationId\n ? (() => {\n const qrRow = items.find((r) => r.id === qrRegistrationId);\n const qrLabel = qrRow ? rowLabel(qrRow) : `Registration ${qrRegistrationId}`;\n return (\n <CheckInQrDialog\n registrationId={qrRegistrationId}\n label={qrLabel}\n onOpenChange={(open) => {\n if (!open) setQrRegistrationId(null);\n }}\n />\n );\n })()\n : null}\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'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 { useApolloClient } from '@apollo/client/react';\nimport { Badge as BadgeIcon, Megaphone, QrCode, 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 ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\n\nimport {\n BADGE_PDF_QUERY,\n downloadBase64Pdf,\n useAttendeesQuery,\n useCheckInTokenQuery,\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 { describeValidity, formatMoney } from '../../lib/format';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/**\n * BOFF-4330 — displays a registration's signed QR check-in token. Fetched on\n * demand (never persisted client-side) so it always reflects the\n * registration's current signature; a kiosk scans this exact image/token via\n * `eventfullyManagedScanCheckIn`.\n */\nfunction CheckInQrDialog({\n registrationId,\n label,\n onOpenChange,\n}: {\n registrationId: string;\n label: string;\n onOpenChange: (open: boolean) => void;\n}) {\n const { data, loading, error } = useCheckInTokenQuery(registrationId);\n const token = data?.checkInToken;\n\n return (\n <ResponsiveDialog open onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-w-sm\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Check-in QR</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {label} scans this code at any check-in station to check in — it works even if the station has no connection\n at the moment.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"flex flex-col items-center gap-3 py-2\">\n {loading ? (\n <p className=\"text-sm text-text-secondary\">Generating…</p>\n ) : error || !token ? (\n <p className=\"text-sm text-status-danger-text\">Could not generate a check-in QR for this registration.</p>\n ) : (\n <>\n <img\n src={token.qrDataUrl}\n alt={`Check-in QR code for ${label}`}\n className=\"h-56 w-56 rounded-card border border-border-seam bg-white p-2\"\n />\n <code className=\"max-w-full break-all rounded bg-bg-sunken px-2 py-1 text-xs text-text-secondary\">\n {token.token}\n </code>\n </>\n )}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n Close\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\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 // BOFF-4330 — registration id currently showing its check-in QR, if any.\n const [qrRegistrationId, setQrRegistrationId] = useState<string | null>(null);\n // BOFF-4322 — on-demand badge PDF download. A one-off query (not a hook)\n // since it's a rarely-used, heavy payload — same pattern as TimeCampus's\n // invoice PDF download.\n const apollo = useApolloClient();\n const [downloadingBadgeId, setDownloadingBadgeId] = useState<string | null>(null);\n const onDownloadBadge = async (registrationId: string, label: string) => {\n setDownloadingBadgeId(registrationId);\n try {\n const { data, error } = await apollo.query<{ badgePdf: string }>({\n query: BADGE_PDF_QUERY,\n variables: { registrationId },\n fetchPolicy: 'network-only',\n errorPolicy: 'all',\n });\n if (error || !data?.badgePdf) {\n notify.error('Could not generate badge', error?.message ?? 'Please try again.');\n return;\n }\n downloadBase64Pdf(data.badgePdf, `${label.replace(/[^a-z0-9]+/gi, '-')}-badge.pdf`);\n notify.success('Badge ready', `${label}'s badge downloaded.`);\n } catch (err) {\n notify.error('Could not generate badge', describeError(err));\n } finally {\n setDownloadingBadgeId(null);\n }\n };\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 const extraItems: ActionMenuItem[] = [\n {\n key: 'view-qr',\n label: 'View check-in QR',\n icon: QrCode,\n onSelect: () => setQrRegistrationId(row.id),\n },\n {\n key: 'download-badge',\n label: downloadingBadgeId === row.id ? 'Generating badge…' : 'Download badge',\n icon: BadgeIcon,\n disabled: downloadingBadgeId === row.id,\n onSelect: () => void onDownloadBadge(row.id, label),\n },\n ...statusItems,\n ];\n return (\n <ActionMenu\n ariaLabel=\"Registration actions\"\n entityName={label}\n deleteLabel=\"Delete registration\"\n extraItems={extraItems}\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 {qrRegistrationId\n ? (() => {\n const qrRow = items.find((r) => r.id === qrRegistrationId);\n const qrLabel = qrRow ? rowLabel(qrRow) : `Registration ${qrRegistrationId}`;\n return (\n <CheckInQrDialog\n registrationId={qrRegistrationId}\n label={qrLabel}\n onOpenChange={(open) => {\n if (!open) setQrRegistrationId(null);\n }}\n />\n );\n })()\n : null}\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: 'validity',\n header: 'Validity',\n cell: (row) => (\n <span className=\"whitespace-nowrap text-sm text-text-secondary\">\n {describeValidity(row.startsAt ?? '', row.endsAt ?? '')}\n </span>\n ),\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'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":"PromoCodeFormPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PromoCodeFormPage.d.ts","sourceRoot":"","sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"names":[],"mappings":"AA4FA,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAkNxC"}
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
|
|
2
2
|
import { notify as t } from "../../lib/toast.js";
|
|
3
3
|
import { useCreatePromoCodeMutation as n, useEventsQuery as r, usePromoCodesQuery as i, useUpdatePromoCodeMutation as a } from "../../api/eventApi.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { describeValidity as o } from "../../lib/format.js";
|
|
5
|
+
import { EntityFormShell as s } from "../../components/EntityFormShell.js";
|
|
6
|
+
import { describeError as c, mutationSucceeded as l } from "../../lib/mutationResult.js";
|
|
7
|
+
import { useEffect as u, useMemo as d, useState as f } from "react";
|
|
8
|
+
import { useParams as p } from "react-router";
|
|
9
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
|
+
import { BadgePercent as g, Ticket as _ } from "lucide-react";
|
|
11
|
+
import { Badge as v, Input as y, Select as b, SelectContent as x, SelectItem as S, SelectTrigger as C, SelectValue as w, Switch as T, Textarea as E } from "@burdenoff/fe-libs/ui";
|
|
11
12
|
//#region src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx
|
|
12
|
-
function E(e, t) {
|
|
13
|
-
let n = (e) => new Date(e).toLocaleDateString(void 0, {
|
|
14
|
-
month: "short",
|
|
15
|
-
day: "numeric",
|
|
16
|
-
year: "numeric"
|
|
17
|
-
});
|
|
18
|
-
return e && t ? `${n(e)} – ${n(t)}` : e ? `From ${n(e)}` : t ? `Until ${n(t)}` : "Ongoing";
|
|
19
|
-
}
|
|
20
13
|
var D = {
|
|
21
14
|
eventId: "",
|
|
22
15
|
code: "",
|
|
@@ -54,14 +47,14 @@ function A(e) {
|
|
|
54
47
|
};
|
|
55
48
|
}
|
|
56
49
|
function j() {
|
|
57
|
-
let { id: O = "" } =
|
|
50
|
+
let { id: O = "" } = p(), j = d(() => !!O, [O]), { navigate: M } = e(), { data: N } = r({
|
|
58
51
|
limit: 100,
|
|
59
52
|
offset: 0
|
|
60
53
|
}), { data: P } = i({
|
|
61
54
|
limit: 100,
|
|
62
55
|
offset: 0
|
|
63
|
-
}), F =
|
|
64
|
-
|
|
56
|
+
}), F = d(() => (P?.promoCodes.items ?? []).find((e) => e.id === O), [P, O]), [I, { loading: L }] = n(), [R, { loading: z }] = a(), [B, V] = f(() => F ? A(F) : D);
|
|
57
|
+
u(() => {
|
|
65
58
|
F && V(A(F));
|
|
66
59
|
}, [F]);
|
|
67
60
|
let H = (e, t) => {
|
|
@@ -100,18 +93,18 @@ function j() {
|
|
|
100
93
|
id: O,
|
|
101
94
|
input: n
|
|
102
95
|
} });
|
|
103
|
-
if (!
|
|
96
|
+
if (!l(e, e.data?.updatePromoCode?.id, "Could not update promo code")) return;
|
|
104
97
|
t.success("Promo code updated", n.code), M("/registration");
|
|
105
98
|
return;
|
|
106
99
|
}
|
|
107
100
|
let e = await I({ variables: { input: n } });
|
|
108
|
-
if (!
|
|
101
|
+
if (!l(e, e.data?.createPromoCode?.id, "Could not create promo code")) return;
|
|
109
102
|
t.success("Promo code created", n.code), M("/registration");
|
|
110
103
|
} catch (e) {
|
|
111
|
-
t.error("Could not save promo code",
|
|
104
|
+
t.error("Could not save promo code", c(e));
|
|
112
105
|
}
|
|
113
106
|
}, K = B.discountType === "PERCENT" ? "Percentage off, e.g. 10 for 10%." : "Fixed amount off, e.g. 500.";
|
|
114
|
-
return /* @__PURE__ */
|
|
107
|
+
return /* @__PURE__ */ h(s, {
|
|
115
108
|
title: j ? "Edit Promo Code" : "New Promo Code",
|
|
116
109
|
description: "Promo codes let you offer discounts to drive registrations.",
|
|
117
110
|
purpose: "Create a discount attendees can apply at checkout. Choose whether it takes a percentage or a fixed amount off, cap how many times it can be used, and set an active window. Deactivate a code to stop it being redeemed without deleting its history.",
|
|
@@ -121,79 +114,79 @@ function j() {
|
|
|
121
114
|
submitLabel: j ? "Save Promo Code" : "Create Promo Code",
|
|
122
115
|
saving: W,
|
|
123
116
|
children: [
|
|
124
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ h("div", {
|
|
125
118
|
className: "rounded-card border border-border-seam bg-bg-surface p-4",
|
|
126
119
|
children: [
|
|
127
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ m("span", {
|
|
128
121
|
className: "text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
129
122
|
children: "Preview"
|
|
130
123
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ h("div", {
|
|
132
125
|
className: "mt-2 flex flex-wrap items-center gap-3",
|
|
133
126
|
children: [
|
|
134
|
-
/* @__PURE__ */
|
|
127
|
+
/* @__PURE__ */ m("span", {
|
|
135
128
|
className: "flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary",
|
|
136
|
-
children: /* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ m(_, {
|
|
137
130
|
className: "h-4 w-4",
|
|
138
131
|
"aria-hidden": "true"
|
|
139
132
|
})
|
|
140
133
|
}),
|
|
141
|
-
/* @__PURE__ */
|
|
134
|
+
/* @__PURE__ */ m("span", {
|
|
142
135
|
className: "font-mono text-base font-semibold text-text-primary",
|
|
143
136
|
children: B.code.trim() ? B.code.trim().toUpperCase() : "YOURCODE"
|
|
144
137
|
}),
|
|
145
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ m(v, {
|
|
146
139
|
variant: "secondary",
|
|
147
140
|
children: B.discountType === "PERCENT" ? "PERCENT" : "FIXED"
|
|
148
141
|
}),
|
|
149
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ h("span", {
|
|
150
143
|
className: "inline-flex items-center gap-1 text-sm text-text-secondary",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
144
|
+
children: [/* @__PURE__ */ m(g, {
|
|
152
145
|
className: "h-3.5 w-3.5 text-text-muted",
|
|
153
146
|
"aria-hidden": "true"
|
|
154
147
|
}), B.discountValue.trim() ? B.discountType === "PERCENT" ? `${B.discountValue.trim()}% off` : `${B.discountValue.trim()} off` : "No discount set"]
|
|
155
148
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ h("span", {
|
|
157
150
|
className: "text-xs text-text-muted",
|
|
158
|
-
children: ["· ",
|
|
151
|
+
children: ["· ", o(B.startsAt, B.endsAt)]
|
|
159
152
|
}),
|
|
160
|
-
/* @__PURE__ */
|
|
153
|
+
/* @__PURE__ */ m(v, {
|
|
161
154
|
variant: B.isActive ? "default" : "secondary",
|
|
162
155
|
className: "ml-auto",
|
|
163
156
|
children: B.isActive ? "Active" : "Inactive"
|
|
164
157
|
})
|
|
165
158
|
]
|
|
166
159
|
}),
|
|
167
|
-
/* @__PURE__ */
|
|
160
|
+
/* @__PURE__ */ m("p", {
|
|
168
161
|
className: "mt-2 text-xs text-text-muted",
|
|
169
162
|
children: B.maxUses.trim() ? `Redeemable up to ${B.maxUses.trim()} times.` : "Unlimited redemptions."
|
|
170
163
|
})
|
|
171
164
|
]
|
|
172
165
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ h("div", {
|
|
174
167
|
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
175
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ h("label", {
|
|
176
169
|
className: "space-y-1 text-sm",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ m("span", {
|
|
178
171
|
className: "font-medium text-text-primary",
|
|
179
172
|
children: "Event"
|
|
180
|
-
}), /* @__PURE__ */
|
|
173
|
+
}), /* @__PURE__ */ h(b, {
|
|
181
174
|
value: B.eventId,
|
|
182
175
|
onValueChange: (e) => H("eventId", e),
|
|
183
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ m(C, {
|
|
184
177
|
"aria-label": "Event",
|
|
185
|
-
children: /* @__PURE__ */
|
|
186
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: /* @__PURE__ */ m(w, { placeholder: "Select Event" })
|
|
179
|
+
}), /* @__PURE__ */ m(x, { children: U.map((e) => /* @__PURE__ */ m(S, {
|
|
187
180
|
value: e.id,
|
|
188
181
|
children: e.name
|
|
189
182
|
}, e.id)) })]
|
|
190
183
|
})]
|
|
191
|
-
}), /* @__PURE__ */
|
|
184
|
+
}), /* @__PURE__ */ h("label", {
|
|
192
185
|
className: "space-y-1 text-sm",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
186
|
+
children: [/* @__PURE__ */ m("span", {
|
|
194
187
|
className: "font-medium text-text-primary",
|
|
195
188
|
children: "Code"
|
|
196
|
-
}), /* @__PURE__ */
|
|
189
|
+
}), /* @__PURE__ */ m(y, {
|
|
197
190
|
value: B.code,
|
|
198
191
|
onChange: (e) => H("code", e.target.value),
|
|
199
192
|
placeholder: "EARLYBIRD",
|
|
@@ -201,48 +194,48 @@ function j() {
|
|
|
201
194
|
})]
|
|
202
195
|
})]
|
|
203
196
|
}),
|
|
204
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ h("label", {
|
|
205
198
|
className: "block space-y-1 text-sm",
|
|
206
|
-
children: [/* @__PURE__ */
|
|
199
|
+
children: [/* @__PURE__ */ m("span", {
|
|
207
200
|
className: "font-medium text-text-primary",
|
|
208
201
|
children: "Description"
|
|
209
|
-
}), /* @__PURE__ */
|
|
202
|
+
}), /* @__PURE__ */ m(E, {
|
|
210
203
|
value: B.description,
|
|
211
204
|
onChange: (e) => H("description", e.target.value),
|
|
212
205
|
rows: 2
|
|
213
206
|
})]
|
|
214
207
|
}),
|
|
215
|
-
/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ h("div", {
|
|
216
209
|
className: "grid grid-cols-1 gap-4 md:grid-cols-3",
|
|
217
210
|
children: [
|
|
218
|
-
/* @__PURE__ */
|
|
211
|
+
/* @__PURE__ */ h("label", {
|
|
219
212
|
className: "space-y-1 text-sm",
|
|
220
|
-
children: [/* @__PURE__ */
|
|
213
|
+
children: [/* @__PURE__ */ m("span", {
|
|
221
214
|
className: "font-medium text-text-primary",
|
|
222
215
|
children: "Discount Type"
|
|
223
|
-
}), /* @__PURE__ */
|
|
216
|
+
}), /* @__PURE__ */ h(b, {
|
|
224
217
|
value: B.discountType,
|
|
225
218
|
onValueChange: (e) => H("discountType", e),
|
|
226
|
-
children: [/* @__PURE__ */
|
|
219
|
+
children: [/* @__PURE__ */ m(C, {
|
|
227
220
|
"aria-label": "Discount Type",
|
|
228
|
-
children: /* @__PURE__ */
|
|
229
|
-
}), /* @__PURE__ */
|
|
221
|
+
children: /* @__PURE__ */ m(w, {})
|
|
222
|
+
}), /* @__PURE__ */ h(x, { children: [/* @__PURE__ */ m(S, {
|
|
230
223
|
value: "PERCENT",
|
|
231
224
|
children: "Percentage (%)"
|
|
232
|
-
}), /* @__PURE__ */
|
|
225
|
+
}), /* @__PURE__ */ m(S, {
|
|
233
226
|
value: "FIXED",
|
|
234
227
|
children: "Fixed amount"
|
|
235
228
|
})] })]
|
|
236
229
|
})]
|
|
237
230
|
}),
|
|
238
|
-
/* @__PURE__ */
|
|
231
|
+
/* @__PURE__ */ h("label", {
|
|
239
232
|
className: "space-y-1 text-sm",
|
|
240
233
|
children: [
|
|
241
|
-
/* @__PURE__ */
|
|
234
|
+
/* @__PURE__ */ m("span", {
|
|
242
235
|
className: "font-medium text-text-primary",
|
|
243
236
|
children: "Discount Value"
|
|
244
237
|
}),
|
|
245
|
-
/* @__PURE__ */
|
|
238
|
+
/* @__PURE__ */ m(y, {
|
|
246
239
|
type: "number",
|
|
247
240
|
min: "0",
|
|
248
241
|
step: "any",
|
|
@@ -250,18 +243,18 @@ function j() {
|
|
|
250
243
|
onChange: (e) => H("discountValue", e.target.value),
|
|
251
244
|
required: !0
|
|
252
245
|
}),
|
|
253
|
-
/* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ m("span", {
|
|
254
247
|
className: "text-xs text-text-secondary",
|
|
255
248
|
children: K
|
|
256
249
|
})
|
|
257
250
|
]
|
|
258
251
|
}),
|
|
259
|
-
/* @__PURE__ */
|
|
252
|
+
/* @__PURE__ */ h("label", {
|
|
260
253
|
className: "space-y-1 text-sm",
|
|
261
|
-
children: [/* @__PURE__ */
|
|
254
|
+
children: [/* @__PURE__ */ m("span", {
|
|
262
255
|
className: "font-medium text-text-primary",
|
|
263
256
|
children: "Max Uses"
|
|
264
|
-
}), /* @__PURE__ */
|
|
257
|
+
}), /* @__PURE__ */ m(y, {
|
|
265
258
|
type: "number",
|
|
266
259
|
min: "0",
|
|
267
260
|
value: B.maxUses,
|
|
@@ -271,42 +264,42 @@ function j() {
|
|
|
271
264
|
})
|
|
272
265
|
]
|
|
273
266
|
}),
|
|
274
|
-
/* @__PURE__ */
|
|
267
|
+
/* @__PURE__ */ h("div", {
|
|
275
268
|
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
276
|
-
children: [/* @__PURE__ */
|
|
269
|
+
children: [/* @__PURE__ */ h("label", {
|
|
277
270
|
className: "space-y-1 text-sm",
|
|
278
|
-
children: [/* @__PURE__ */
|
|
271
|
+
children: [/* @__PURE__ */ m("span", {
|
|
279
272
|
className: "font-medium text-text-primary",
|
|
280
273
|
children: "Starts At"
|
|
281
|
-
}), /* @__PURE__ */
|
|
274
|
+
}), /* @__PURE__ */ m(y, {
|
|
282
275
|
type: "datetime-local",
|
|
283
276
|
value: B.startsAt,
|
|
284
277
|
onChange: (e) => H("startsAt", e.target.value)
|
|
285
278
|
})]
|
|
286
|
-
}), /* @__PURE__ */
|
|
279
|
+
}), /* @__PURE__ */ h("label", {
|
|
287
280
|
className: "space-y-1 text-sm",
|
|
288
|
-
children: [/* @__PURE__ */
|
|
281
|
+
children: [/* @__PURE__ */ m("span", {
|
|
289
282
|
className: "font-medium text-text-primary",
|
|
290
283
|
children: "Ends At"
|
|
291
|
-
}), /* @__PURE__ */
|
|
284
|
+
}), /* @__PURE__ */ m(y, {
|
|
292
285
|
type: "datetime-local",
|
|
293
286
|
value: B.endsAt,
|
|
294
287
|
onChange: (e) => H("endsAt", e.target.value)
|
|
295
288
|
})]
|
|
296
289
|
})]
|
|
297
290
|
}),
|
|
298
|
-
/* @__PURE__ */
|
|
291
|
+
/* @__PURE__ */ h("div", {
|
|
299
292
|
className: "flex items-center justify-between rounded-input border border-border-default bg-bg-surface px-3 py-2",
|
|
300
|
-
children: [/* @__PURE__ */
|
|
293
|
+
children: [/* @__PURE__ */ h("div", {
|
|
301
294
|
className: "text-sm",
|
|
302
|
-
children: [/* @__PURE__ */
|
|
295
|
+
children: [/* @__PURE__ */ m("span", {
|
|
303
296
|
className: "font-medium text-text-primary",
|
|
304
297
|
children: "Active"
|
|
305
|
-
}), /* @__PURE__ */
|
|
298
|
+
}), /* @__PURE__ */ m("p", {
|
|
306
299
|
className: "text-xs text-text-secondary",
|
|
307
300
|
children: "Only active codes can be redeemed at checkout."
|
|
308
301
|
})]
|
|
309
|
-
}), /* @__PURE__ */
|
|
302
|
+
}), /* @__PURE__ */ m(T, {
|
|
310
303
|
checked: B.isActive,
|
|
311
304
|
onCheckedChange: (e) => H("isActive", e)
|
|
312
305
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromoCodeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { BadgePercent, Ticket } from 'lucide-react';\nimport {\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCreatePromoCodeMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useUpdatePromoCodeMutation,\n type PromoCodeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\n/** Human validity window from the two datetime-local field values. */\nfunction describeValidity(startsAt: string, endsAt: string): string {\n const fmt = (value: string) =>\n new Date(value).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });\n if (startsAt && endsAt) return `${fmt(startsAt)} – ${fmt(endsAt)}`;\n if (startsAt) return `From ${fmt(startsAt)}`;\n if (endsAt) return `Until ${fmt(endsAt)}`;\n return 'Ongoing';\n}\n\ntype PromoCodeFormState = {\n eventId: string;\n code: string;\n description: string;\n discountType: string;\n discountValue: string;\n maxUses: string;\n startsAt: string;\n endsAt: string;\n isActive: boolean;\n};\n\nconst emptyState: PromoCodeFormState = {\n eventId: '',\n code: '',\n description: '',\n discountType: 'PERCENT',\n discountValue: '',\n maxUses: '',\n startsAt: '',\n endsAt: '',\n isActive: true,\n};\n\n/** ISO timestamp -> value for <input type=\"datetime-local\"> (local time, minute precision). */\nfunction isoToLocalInput(iso?: string | null): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60_000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** datetime-local value -> ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return null;\n }\n return date.toISOString();\n}\n\nfunction toFormState(promo: PromoCodeGql): PromoCodeFormState {\n return {\n eventId: promo.eventId ?? '',\n code: promo.code ?? '',\n description: promo.description ?? '',\n discountType: promo.discountType ?? 'PERCENT',\n discountValue: promo.discountValue ?? '',\n maxUses: promo.maxUses != null ? String(promo.maxUses) : '',\n startsAt: isoToLocalInput(promo.startsAt),\n endsAt: isoToLocalInput(promo.endsAt),\n isActive: promo.isActive ?? true,\n };\n}\n\nexport default function PromoCodeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id promo resolver exists — derive edit initial-state from the list.\n const { data: promoData } = usePromoCodesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(() => (promoData?.promoCodes.items ?? []).find((item) => item.id === id), [promoData, id]);\n\n const [createPromoCode, { loading: creating }] = useCreatePromoCodeMutation();\n const [updatePromoCode, { loading: updating }] = useUpdatePromoCodeMutation();\n const [form, setForm] = useState<PromoCodeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = <K extends keyof PromoCodeFormState>(field: K, value: PromoCodeFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this promo code applies to.');\n return;\n }\n if (!form.code.trim()) {\n notify.error('Code required', 'Enter the code attendees will type at checkout.');\n return;\n }\n if (!form.discountValue.trim()) {\n notify.error('Discount required', 'Enter a discount value.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n code: form.code.trim().toUpperCase(),\n description: form.description.trim() || null,\n discountType: form.discountType,\n discountValue: form.discountValue.trim(),\n maxUses: form.maxUses.trim() ? Number(form.maxUses) : null,\n startsAt: localInputToIso(form.startsAt),\n endsAt: localInputToIso(form.endsAt),\n isActive: form.isActive,\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updatePromoCode({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updatePromoCode?.id, 'Could not update promo code')) {\n return;\n }\n notify.success('Promo code updated', input.code);\n navigate('/registration');\n return;\n }\n\n const response = await createPromoCode({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createPromoCode?.id, 'Could not create promo code')) {\n return;\n }\n notify.success('Promo code created', input.code);\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save promo code', describeError(error));\n }\n };\n\n const valueHint =\n form.discountType === 'PERCENT' ? 'Percentage off, e.g. 10 for 10%.' : 'Fixed amount off, e.g. 500.';\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Promo Code' : 'New Promo Code'}\n description=\"Promo codes let you offer discounts to drive registrations.\"\n purpose=\"Create a discount attendees can apply at checkout. Choose whether it takes a percentage or a fixed amount off, cap how many times it can be used, and set an active window. Deactivate a code to stop it being redeemed without deleting its history.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Promo Code' : 'Create Promo Code'}\n saving={saving}\n >\n {/* Live preview — mirrors how the code appears on the promo list */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Preview</span>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n <Ticket className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"font-mono text-base font-semibold text-text-primary\">\n {form.code.trim() ? form.code.trim().toUpperCase() : 'YOURCODE'}\n </span>\n <Badge variant=\"secondary\">{form.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <BadgePercent className=\"h-3.5 w-3.5 text-text-muted\" aria-hidden=\"true\" />\n {form.discountValue.trim()\n ? form.discountType === 'PERCENT'\n ? `${form.discountValue.trim()}% off`\n : `${form.discountValue.trim()} off`\n : 'No discount set'}\n </span>\n <span className=\"text-xs text-text-muted\">· {describeValidity(form.startsAt, form.endsAt)}</span>\n <Badge variant={form.isActive ? 'default' : 'secondary'} className=\"ml-auto\">\n {form.isActive ? 'Active' : 'Inactive'}\n </Badge>\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">\n {form.maxUses.trim() ? `Redeemable up to ${form.maxUses.trim()} times.` : 'Unlimited redemptions.'}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"EARLYBIRD\"\n required\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Description</span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={2} />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Type</span>\n <Select value={form.discountType} onValueChange={(value) => onChange('discountType', value)}>\n <SelectTrigger aria-label=\"Discount Type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PERCENT\">Percentage (%)</SelectItem>\n <SelectItem value=\"FIXED\">Fixed amount</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Value</span>\n <Input\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.discountValue}\n onChange={(e) => onChange('discountValue', e.target.value)}\n required\n />\n <span className=\"text-xs text-text-secondary\">{valueHint}</span>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Max Uses</span>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.maxUses}\n onChange={(e) => onChange('maxUses', e.target.value)}\n placeholder=\"Unlimited\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Starts At</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends At</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n </div>\n\n <div className=\"flex items-center justify-between rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <div className=\"text-sm\">\n <span className=\"font-medium text-text-primary\">Active</span>\n <p className=\"text-xs text-text-secondary\">Only active codes can be redeemed at checkout.</p>\n </div>\n <Switch checked={form.isActive} onCheckedChange={(checked) => onChange('isActive', checked)} />\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PromoCodeFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/registration/PromoCodeFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { BadgePercent, Ticket } from 'lucide-react';\nimport {\n Badge,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useCreatePromoCodeMutation,\n useEventsQuery,\n usePromoCodesQuery,\n useUpdatePromoCodeMutation,\n type PromoCodeGql,\n} from '../../api/eventApi';\nimport { EntityFormShell } from '../../components/EntityFormShell';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeValidity } from '../../lib/format';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype PromoCodeFormState = {\n eventId: string;\n code: string;\n description: string;\n discountType: string;\n discountValue: string;\n maxUses: string;\n startsAt: string;\n endsAt: string;\n isActive: boolean;\n};\n\nconst emptyState: PromoCodeFormState = {\n eventId: '',\n code: '',\n description: '',\n discountType: 'PERCENT',\n discountValue: '',\n maxUses: '',\n startsAt: '',\n endsAt: '',\n isActive: true,\n};\n\n/** ISO timestamp -> value for <input type=\"datetime-local\"> (local time, minute precision). */\nfunction isoToLocalInput(iso?: string | null): string {\n if (!iso) {\n return '';\n }\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) {\n return '';\n }\n const offsetMs = date.getTimezoneOffset() * 60_000;\n return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);\n}\n\n/** datetime-local value -> ISO timestamp for the DateTime scalar. */\nfunction localInputToIso(value: string): string | null {\n if (!value) {\n return null;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return null;\n }\n return date.toISOString();\n}\n\nfunction toFormState(promo: PromoCodeGql): PromoCodeFormState {\n return {\n eventId: promo.eventId ?? '',\n code: promo.code ?? '',\n description: promo.description ?? '',\n discountType: promo.discountType ?? 'PERCENT',\n discountValue: promo.discountValue ?? '',\n maxUses: promo.maxUses != null ? String(promo.maxUses) : '',\n startsAt: isoToLocalInput(promo.startsAt),\n endsAt: isoToLocalInput(promo.endsAt),\n isActive: promo.isActive ?? true,\n };\n}\n\nexport default function PromoCodeFormPage() {\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n\n const { data: eventsData } = useEventsQuery({ limit: 100, offset: 0 });\n // No by-id promo resolver exists — derive edit initial-state from the list.\n const { data: promoData } = usePromoCodesQuery({ limit: 100, offset: 0 });\n const existing = useMemo(() => (promoData?.promoCodes.items ?? []).find((item) => item.id === id), [promoData, id]);\n\n const [createPromoCode, { loading: creating }] = useCreatePromoCodeMutation();\n const [updatePromoCode, { loading: updating }] = useUpdatePromoCodeMutation();\n const [form, setForm] = useState<PromoCodeFormState>(() => (existing ? toFormState(existing) : emptyState));\n\n useEffect(() => {\n if (existing) {\n setForm(toFormState(existing));\n }\n }, [existing]);\n\n const onChange = <K extends keyof PromoCodeFormState>(field: K, value: PromoCodeFormState[K]) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const events = eventsData?.events.items ?? [];\n const saving = creating || updating;\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n\n if (!form.eventId) {\n notify.error('Event required', 'Choose the event this promo code applies to.');\n return;\n }\n if (!form.code.trim()) {\n notify.error('Code required', 'Enter the code attendees will type at checkout.');\n return;\n }\n if (!form.discountValue.trim()) {\n notify.error('Discount required', 'Enter a discount value.');\n return;\n }\n\n const input = {\n eventId: form.eventId,\n code: form.code.trim().toUpperCase(),\n description: form.description.trim() || null,\n discountType: form.discountType,\n discountValue: form.discountValue.trim(),\n maxUses: form.maxUses.trim() ? Number(form.maxUses) : null,\n startsAt: localInputToIso(form.startsAt),\n endsAt: localInputToIso(form.endsAt),\n isActive: form.isActive,\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updatePromoCode({ variables: { id, input } });\n if (!mutationSucceeded(response, response.data?.updatePromoCode?.id, 'Could not update promo code')) {\n return;\n }\n notify.success('Promo code updated', input.code);\n navigate('/registration');\n return;\n }\n\n const response = await createPromoCode({ variables: { input } });\n if (!mutationSucceeded(response, response.data?.createPromoCode?.id, 'Could not create promo code')) {\n return;\n }\n notify.success('Promo code created', input.code);\n navigate('/registration');\n } catch (error) {\n notify.error('Could not save promo code', describeError(error));\n }\n };\n\n const valueHint =\n form.discountType === 'PERCENT' ? 'Percentage off, e.g. 10 for 10%.' : 'Fixed amount off, e.g. 500.';\n\n return (\n <EntityFormShell\n title={isEdit ? 'Edit Promo Code' : 'New Promo Code'}\n description=\"Promo codes let you offer discounts to drive registrations.\"\n purpose=\"Create a discount attendees can apply at checkout. Choose whether it takes a percentage or a fixed amount off, cap how many times it can be used, and set an active window. Deactivate a code to stop it being redeemed without deleting its history.\"\n backLabel=\"Back to Registration\"\n onBack={() => navigate('/registration')}\n onSubmit={onSubmit}\n submitLabel={isEdit ? 'Save Promo Code' : 'Create Promo Code'}\n saving={saving}\n >\n {/* Live preview — mirrors how the code appears on the promo list */}\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">Preview</span>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-input bg-accent-wash text-action-primary\">\n <Ticket className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n <span className=\"font-mono text-base font-semibold text-text-primary\">\n {form.code.trim() ? form.code.trim().toUpperCase() : 'YOURCODE'}\n </span>\n <Badge variant=\"secondary\">{form.discountType === 'PERCENT' ? 'PERCENT' : 'FIXED'}</Badge>\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <BadgePercent className=\"h-3.5 w-3.5 text-text-muted\" aria-hidden=\"true\" />\n {form.discountValue.trim()\n ? form.discountType === 'PERCENT'\n ? `${form.discountValue.trim()}% off`\n : `${form.discountValue.trim()} off`\n : 'No discount set'}\n </span>\n <span className=\"text-xs text-text-muted\">· {describeValidity(form.startsAt, form.endsAt)}</span>\n <Badge variant={form.isActive ? 'default' : 'secondary'} className=\"ml-auto\">\n {form.isActive ? 'Active' : 'Inactive'}\n </Badge>\n </div>\n <p className=\"mt-2 text-xs text-text-muted\">\n {form.maxUses.trim() ? `Redeemable up to ${form.maxUses.trim()} times.` : 'Unlimited redemptions.'}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Event</span>\n <Select value={form.eventId} onValueChange={(value) => onChange('eventId', value)}>\n <SelectTrigger aria-label=\"Event\">\n <SelectValue placeholder=\"Select Event\" />\n </SelectTrigger>\n <SelectContent>\n {events.map((eventItem) => (\n <SelectItem key={eventItem.id} value={eventItem.id}>\n {eventItem.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Code</span>\n <Input\n value={form.code}\n onChange={(e) => onChange('code', e.target.value)}\n placeholder=\"EARLYBIRD\"\n required\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Description</span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={2} />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Type</span>\n <Select value={form.discountType} onValueChange={(value) => onChange('discountType', value)}>\n <SelectTrigger aria-label=\"Discount Type\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"PERCENT\">Percentage (%)</SelectItem>\n <SelectItem value=\"FIXED\">Fixed amount</SelectItem>\n </SelectContent>\n </Select>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Discount Value</span>\n <Input\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.discountValue}\n onChange={(e) => onChange('discountValue', e.target.value)}\n required\n />\n <span className=\"text-xs text-text-secondary\">{valueHint}</span>\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Max Uses</span>\n <Input\n type=\"number\"\n min=\"0\"\n value={form.maxUses}\n onChange={(e) => onChange('maxUses', e.target.value)}\n placeholder=\"Unlimited\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Starts At</span>\n <Input type=\"datetime-local\" value={form.startsAt} onChange={(e) => onChange('startsAt', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">Ends At</span>\n <Input type=\"datetime-local\" value={form.endsAt} onChange={(e) => onChange('endsAt', e.target.value)} />\n </label>\n </div>\n\n <div className=\"flex items-center justify-between rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <div className=\"text-sm\">\n <span className=\"font-medium text-text-primary\">Active</span>\n <p className=\"text-xs text-text-secondary\">Only active codes can be redeemed at checkout.</p>\n </div>\n <Switch checked={form.isActive} onCheckedChange={(checked) => onChange('isActive', checked)} />\n </div>\n </EntityFormShell>\n );\n}\n"],"mappings":"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"}
|