@burdenoff/microfe-eventfullymanaged 2026.703.2 → 2026.704.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/eventfullymanaged/EventfullyManagedRoutes.js +154 -66
  2. package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
  3. package/dist/eventfullymanaged/api/eventApi.js +546 -67
  4. package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
  5. package/dist/eventfullymanaged/components/ActionMenu.js +113 -0
  6. package/dist/eventfullymanaged/components/ActionMenu.js.map +1 -0
  7. package/dist/eventfullymanaged/components/EntityFormShell.js +51 -0
  8. package/dist/eventfullymanaged/components/EntityFormShell.js.map +1 -0
  9. package/dist/eventfullymanaged/components/EntityTagsField.js +146 -0
  10. package/dist/eventfullymanaged/components/EntityTagsField.js.map +1 -0
  11. package/dist/eventfullymanaged/components/Pagination.js +84 -0
  12. package/dist/eventfullymanaged/components/Pagination.js.map +1 -0
  13. package/dist/eventfullymanaged/components/SafeLink.js +32 -0
  14. package/dist/eventfullymanaged/components/SafeLink.js.map +1 -0
  15. package/dist/eventfullymanaged/components/index.js +7 -0
  16. package/dist/eventfullymanaged/pages/events/EventDetailPage.js +458 -0
  17. package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -0
  18. package/dist/eventfullymanaged/pages/events/EventFormPage.js +267 -0
  19. package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -0
  20. package/dist/eventfullymanaged/pages/events/SessionFormPage.js +308 -0
  21. package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -0
  22. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +214 -0
  23. package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -0
  24. package/dist/eventfullymanaged/pages/events/eventFormUtils.js +128 -0
  25. package/dist/eventfullymanaged/pages/events/eventFormUtils.js.map +1 -0
  26. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +233 -0
  27. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -0
  28. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js +165 -0
  29. package/dist/eventfullymanaged/pages/exhibitors/ExhibitorsPage.js.map +1 -0
  30. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js +141 -0
  31. package/dist/eventfullymanaged/pages/leads/LeadCaptureDialog.js.map +1 -0
  32. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js +178 -45
  33. package/dist/eventfullymanaged/pages/modules/AnalyticsPage.js.map +1 -1
  34. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js +240 -70
  35. package/dist/eventfullymanaged/pages/modules/CommunicationPage.js.map +1 -1
  36. package/dist/eventfullymanaged/pages/modules/EventsPage.js +133 -97
  37. package/dist/eventfullymanaged/pages/modules/EventsPage.js.map +1 -1
  38. package/dist/eventfullymanaged/pages/modules/KioskPage.js +746 -36
  39. package/dist/eventfullymanaged/pages/modules/KioskPage.js.map +1 -1
  40. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js +210 -69
  41. package/dist/eventfullymanaged/pages/modules/NetworkingPage.js.map +1 -1
  42. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js +289 -77
  43. package/dist/eventfullymanaged/pages/modules/RegistrationPage.js.map +1 -1
  44. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js +95 -0
  45. package/dist/eventfullymanaged/pages/modules/RoadmapPanel.js.map +1 -0
  46. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +77 -56
  47. package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
  48. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +70 -36
  49. package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
  50. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +343 -0
  51. package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -0
  52. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +236 -0
  53. package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -0
  54. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +181 -0
  55. package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -0
  56. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +262 -0
  57. package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -0
  58. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js +414 -0
  59. package/dist/eventfullymanaged/pages/registration/RegisterAttendeePage.js.map +1 -0
  60. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +224 -0
  61. package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -0
  62. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js +96 -0
  63. package/dist/eventfullymanaged/pages/speakers/SpeakersPage.js.map +1 -0
  64. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +149 -140
  65. package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
  66. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js +97 -67
  67. package/dist/eventfullymanaged/pages/sponsors/SponsorsListPage.js.map +1 -1
  68. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +93 -72
  69. package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
  70. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +91 -89
  71. package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
  72. package/dist/eventfullymanaged/pages/venues/VenueListPage.js +83 -53
  73. package/dist/eventfullymanaged/pages/venues/VenueListPage.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityTagsField.js","names":[],"sources":["../../../src/eventfullymanaged/components/EntityTagsField.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Plus, Tag as TagIcon, X } from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n useAvailableTagsQuery,\n useCreateEntityTagMutation,\n useEntityTagsQuery,\n useTagEntityMutation,\n useUntagEntityMutation,\n} from '../api/eventApi';\nimport { describeError, mutationSucceeded } from '../lib/mutationResult';\nimport { notify } from '../lib/toast';\n\nexport interface EntityTagsFieldProps {\n /** Entity kind, e.g. \"sponsor\", \"event\", \"session\". Namespaces the assignments. */\n entityType: string;\n /** Target row id. When empty (create form) the field prompts to save first. */\n entityId: string;\n label?: string;\n className?: string;\n}\n\n/**\n * Inline tag editor for any EventfullyManaged entity, keyed by\n * `(entityType, entityId)`. Reads current assignments via\n * `eventfullyManagedEntityTags` and renders each chip from the INLINE\n * `tagInfo { label, color }` (never the federated `tag {}` field, which the\n * gateway does not resolve here). Supports attaching an existing workspace tag,\n * minting a brand-new tag, and detaching — each mutation refetches the\n * assignment list so the field stays in sync.\n */\nexport function EntityTagsField({ entityType, entityId, label = 'Tags', className }: EntityTagsFieldProps) {\n const [newLabel, setNewLabel] = useState('');\n const [selectedTagId, setSelectedTagId] = useState('');\n\n const enabled = Boolean(entityId);\n const assignmentsQuery = useEntityTagsQuery({ input: { entityType, entityId } });\n const { data: availableData } = useAvailableTagsQuery({ limit: 100 });\n const [tagEntity, { loading: tagging }] = useTagEntityMutation();\n const [untagEntity, { loading: untagging }] = useUntagEntityMutation();\n const [createTag, { loading: creating }] = useCreateEntityTagMutation();\n\n const assignments = useMemo(\n () => assignmentsQuery.data?.eventfullyManagedEntityTags ?? [],\n [assignmentsQuery.data]\n );\n const assignedTagIds = useMemo(() => new Set(assignments.map((a) => a.tagId)), [assignments]);\n const available = useMemo(\n () => (availableData?.availableEventfullyManagedTags ?? []).filter((tag) => !assignedTagIds.has(tag.id)),\n [availableData, assignedTagIds]\n );\n\n const busy = tagging || untagging || creating;\n\n const attachTag = async (tagId: string) => {\n const response = await tagEntity({ variables: { input: { entityType, entityId, tagId, source: 'manual' } } });\n if (!mutationSucceeded(response, response.data?.tagEventfullyManagedEntity?.id, 'Could not add tag')) {\n return false;\n }\n await assignmentsQuery.refetch();\n return true;\n };\n\n const onAttachExisting = async () => {\n if (!selectedTagId) {\n return;\n }\n try {\n if (await attachTag(selectedTagId)) {\n setSelectedTagId('');\n }\n } catch (error) {\n notify.error('Could not add tag', describeError(error));\n }\n };\n\n const onCreateAndAttach = async () => {\n const trimmed = newLabel.trim();\n if (!trimmed) {\n return;\n }\n try {\n const created = await createTag({ variables: { input: { label: trimmed } } });\n const createdId = created.data?.createEventfullyManagedEntityTag?.id;\n if (!mutationSucceeded(created, createdId, 'Could not create tag') || !createdId) {\n return;\n }\n if (await attachTag(createdId)) {\n setNewLabel('');\n }\n } catch (error) {\n notify.error('Could not create tag', describeError(error));\n }\n };\n\n const onRemove = async (tagId: string) => {\n try {\n const response = await untagEntity({ variables: { input: { entityType, entityId, tagId } } });\n if (response.error) {\n notify.error('Could not remove tag', response.error.message);\n return;\n }\n await assignmentsQuery.refetch();\n } catch (error) {\n notify.error('Could not remove tag', describeError(error));\n }\n };\n\n return (\n <div className={`space-y-2 ${className ?? ''}`}>\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <TagIcon className=\"h-4 w-4 text-text-muted\" aria-hidden=\"true\" />\n <span>{label}</span>\n </div>\n\n {!enabled ? (\n <p className=\"text-sm text-text-secondary\">Save this record first to start adding tags.</p>\n ) : (\n <>\n <div className=\"flex flex-wrap gap-2\" role=\"list\" aria-label={label}>\n {assignments.length === 0 ? (\n <span className=\"text-sm text-text-secondary\">No tags yet.</span>\n ) : (\n assignments.map((assignment) => {\n const info = assignment.tagInfo;\n const color = info?.color ?? undefined;\n return (\n <Badge key={assignment.id} variant=\"secondary\" className=\"gap-1.5\" role=\"listitem\">\n {color ? (\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: color }}\n aria-hidden=\"true\"\n />\n ) : null}\n <span>{info?.label ?? info?.key ?? 'Tag'}</span>\n <button\n type=\"button\"\n onClick={() => onRemove(assignment.tagId)}\n disabled={busy}\n aria-label={`Remove ${info?.label ?? 'tag'}`}\n className=\"rounded-full text-text-muted transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus disabled:opacity-50\"\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n </Badge>\n );\n })\n )}\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n {available.length > 0 ? (\n <div className=\"flex items-center gap-2\">\n <Select value={selectedTagId} onValueChange={setSelectedTagId}>\n <SelectTrigger className=\"w-48\" aria-label=\"Choose an existing tag\">\n <SelectValue placeholder=\"Add existing tag\" />\n </SelectTrigger>\n <SelectContent>\n {available.map((tag) => (\n <SelectItem key={tag.id} value={tag.id}>\n {tag.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onAttachExisting} disabled={!selectedTagId || busy}>\n Add\n </Button>\n </div>\n ) : null}\n\n <div className=\"flex items-center gap-2\">\n <Input\n value={newLabel}\n onChange={(event) => setNewLabel(event.target.value)}\n placeholder=\"New tag label\"\n aria-label=\"New tag label\"\n className=\"w-48\"\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n void onCreateAndAttach();\n }\n }}\n />\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={onCreateAndAttach} disabled={!newLabel.trim() || busy}>\n <Plus className=\"h-4 w-4\" />\n Create\n </Button>\n </div>\n </div>\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { useState as e } from "react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ import { ChevronLeft as r, ChevronRight as i } from "lucide-react";
4
+ import { Button as a } from "@burdenoff/fe-libs/ui";
5
+ //#region src/eventfullymanaged/components/Pagination.tsx
6
+ var o = 20;
7
+ function s(t = {}) {
8
+ let { pageSize: n = o, resetKey: r } = t, [i, a] = e(n), [s, c] = e(0), [l, u] = e(r);
9
+ return l !== r && (u(r), s !== 0 && c(0)), {
10
+ limit: i,
11
+ offset: s,
12
+ page: Math.floor(s / Math.max(1, i)) + 1,
13
+ nextPage: () => c((e) => e + i),
14
+ prevPage: () => c((e) => Math.max(0, e - i)),
15
+ setPageSize: (e) => {
16
+ a(Math.max(1, e)), c(0);
17
+ },
18
+ reset: () => c(0)
19
+ };
20
+ }
21
+ function c({ offset: e, limit: o, loadedCount: s, total: c, onPrev: l, onNext: u, itemLabel: d = "items", variant: f = "pager", onLoadMore: p, loading: m = !1, className: h }) {
22
+ if (c === 0) return null;
23
+ let g = e + 1, _ = e + s, v = e > 0, y = e + s < c, b = Math.floor(e / Math.max(1, o)) + 1, x = Math.max(1, Math.ceil(c / Math.max(1, o)));
24
+ return f === "load-more" ? y ? /* @__PURE__ */ t("div", {
25
+ className: `flex justify-center pt-4 ${h ?? ""}`,
26
+ children: /* @__PURE__ */ t(a, {
27
+ size: "sm",
28
+ variant: "outline",
29
+ onClick: p ?? u,
30
+ disabled: m,
31
+ children: m ? "Loading…" : `Load more ${d}`
32
+ })
33
+ }) : null : /* @__PURE__ */ n("div", {
34
+ className: `flex flex-wrap items-center justify-between gap-3 pt-4 ${h ?? ""}`,
35
+ role: "navigation",
36
+ "aria-label": "Pagination",
37
+ children: [/* @__PURE__ */ n("p", {
38
+ className: "text-xs tabular-nums text-text-secondary",
39
+ children: [
40
+ "Showing ",
41
+ g,
42
+ "–",
43
+ _,
44
+ " of ",
45
+ c,
46
+ " ",
47
+ d
48
+ ]
49
+ }), /* @__PURE__ */ n("div", {
50
+ className: "flex items-center gap-2",
51
+ children: [
52
+ /* @__PURE__ */ n("span", {
53
+ className: "text-xs tabular-nums text-text-secondary",
54
+ children: [
55
+ "Page ",
56
+ b,
57
+ " of ",
58
+ x
59
+ ]
60
+ }),
61
+ /* @__PURE__ */ n(a, {
62
+ size: "sm",
63
+ variant: "outline",
64
+ onClick: l,
65
+ disabled: !v || m,
66
+ "aria-label": "Previous page",
67
+ children: [/* @__PURE__ */ t(r, { className: "h-4 w-4" }), "Prev"]
68
+ }),
69
+ /* @__PURE__ */ n(a, {
70
+ size: "sm",
71
+ variant: "outline",
72
+ onClick: u,
73
+ disabled: !y || m,
74
+ "aria-label": "Next page",
75
+ children: ["Next", /* @__PURE__ */ t(i, { className: "h-4 w-4" })]
76
+ })
77
+ ]
78
+ })]
79
+ });
80
+ }
81
+ //#endregion
82
+ export { c as Pagination, s as usePagination };
83
+
84
+ //# sourceMappingURL=Pagination.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.js","names":[],"sources":["../../../src/eventfullymanaged/components/Pagination.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\n/**\n * Server-side offset/limit pagination shared by every EventfullyManaged list page.\n *\n * The backend list resolvers (`eventfullyManagedEvents`, `...Sponsors`, etc.) all\n * accept `{ limit, offset }` and return `{ total, items }`. `usePagination` owns\n * the `limit`/`offset` window; the page feeds them straight into its query, then\n * renders `<Pagination>` with the page's `total` and the number of rows actually\n * returned so Prev/Next gate correctly on `offset + loadedCount < total`.\n */\n\nconst DEFAULT_PAGE_SIZE = 20;\n\nexport interface UsePaginationOptions {\n pageSize?: number;\n /**\n * When this value changes (e.g. a search term or active-event filter) the\n * window resets to the first page so the user is never stranded on an offset\n * that no longer has rows.\n */\n resetKey?: unknown;\n}\n\nexport interface UsePaginationResult {\n /** Page size — pass as the query's `limit`. */\n limit: number;\n /** Row offset — pass as the query's `offset`. */\n offset: number;\n /** 1-based page index, derived from offset/limit. */\n page: number;\n nextPage: () => void;\n prevPage: () => void;\n setPageSize: (size: number) => void;\n reset: () => void;\n}\n\nexport function usePagination(options: UsePaginationOptions = {}): UsePaginationResult {\n const { pageSize: initialPageSize = DEFAULT_PAGE_SIZE, resetKey } = options;\n const [limit, setLimit] = useState(initialPageSize);\n const [offset, setOffset] = useState(0);\n\n // Reset to the first page whenever the resetKey changes. Storing the previous\n // key in state and adjusting during render is React's documented pattern for\n // deriving state from props — it avoids an extra effect + render pass.\n const [prevResetKey, setPrevResetKey] = useState(resetKey);\n if (prevResetKey !== resetKey) {\n setPrevResetKey(resetKey);\n if (offset !== 0) {\n setOffset(0);\n }\n }\n\n return {\n limit,\n offset,\n page: Math.floor(offset / Math.max(1, limit)) + 1,\n nextPage: () => setOffset((current) => current + limit),\n prevPage: () => setOffset((current) => Math.max(0, current - limit)),\n setPageSize: (size: number) => {\n setLimit(Math.max(1, size));\n setOffset(0);\n },\n reset: () => setOffset(0),\n };\n}\n\nexport interface PaginationProps {\n offset: number;\n limit: number;\n /** Number of rows returned for the current page (`items.length`). */\n loadedCount: number;\n /** Total rows available across all pages (list `total`). */\n total: number;\n onPrev: () => void;\n onNext: () => void;\n /** Plural noun for the range label, e.g. \"sponsors\". Defaults to \"items\". */\n itemLabel?: string;\n /** Render a single \"Load more\" button instead of Prev/Next. */\n variant?: 'pager' | 'load-more';\n onLoadMore?: () => void;\n loading?: boolean;\n className?: string;\n}\n\nexport function Pagination({\n offset,\n limit,\n loadedCount,\n total,\n onPrev,\n onNext,\n itemLabel = 'items',\n variant = 'pager',\n onLoadMore,\n loading = false,\n className,\n}: PaginationProps) {\n if (total === 0) {\n return null;\n }\n\n const rangeStart = offset + 1;\n const rangeEnd = offset + loadedCount;\n const canPrev = offset > 0;\n const canNext = offset + loadedCount < total;\n const page = Math.floor(offset / Math.max(1, limit)) + 1;\n const pageCount = Math.max(1, Math.ceil(total / Math.max(1, limit)));\n\n if (variant === 'load-more') {\n if (!canNext) {\n return null;\n }\n return (\n <div className={`flex justify-center pt-4 ${className ?? ''}`}>\n <Button size=\"sm\" variant=\"outline\" onClick={onLoadMore ?? onNext} disabled={loading}>\n {loading ? 'Loading…' : `Load more ${itemLabel}`}\n </Button>\n </div>\n );\n }\n\n return (\n <div\n className={`flex flex-wrap items-center justify-between gap-3 pt-4 ${className ?? ''}`}\n role=\"navigation\"\n aria-label=\"Pagination\"\n >\n <p className=\"text-xs tabular-nums text-text-secondary\">\n Showing {rangeStart}–{rangeEnd} of {total} {itemLabel}\n </p>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs tabular-nums text-text-secondary\">\n Page {page} of {pageCount}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={onPrev} disabled={!canPrev || loading} aria-label=\"Previous page\">\n <ChevronLeft className=\"h-4 w-4\" />\n Prev\n </Button>\n <Button size=\"sm\" variant=\"outline\" onClick={onNext} disabled={!canNext || loading} aria-label=\"Next page\">\n Next\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAcA,IAAM,IAAoB;AAyB1B,SAAgB,EAAc,IAAgC,EAAE,EAAuB;CACrF,IAAM,EAAE,UAAU,IAAkB,GAAmB,gBAAa,GAC9D,CAAC,GAAO,KAAY,EAAS,EAAgB,EAC7C,CAAC,GAAQ,KAAa,EAAS,EAAE,EAKjC,CAAC,GAAc,KAAmB,EAAS,EAAS;AAQ1D,QAPI,MAAiB,MACnB,EAAgB,EAAS,EACrB,MAAW,KACb,EAAU,EAAE,GAIT;EACL;EACA;EACA,MAAM,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,EAAM,CAAC,GAAG;EAChD,gBAAgB,GAAW,MAAY,IAAU,EAAM;EACvD,gBAAgB,GAAW,MAAY,KAAK,IAAI,GAAG,IAAU,EAAM,CAAC;EACpE,cAAc,MAAiB;AAE7B,GADA,EAAS,KAAK,IAAI,GAAG,EAAK,CAAC,EAC3B,EAAU,EAAE;;EAEd,aAAa,EAAU,EAAE;EAC1B;;AAqBH,SAAgB,EAAW,EACzB,WACA,UACA,gBACA,UACA,WACA,WACA,eAAY,SACZ,aAAU,SACV,eACA,aAAU,IACV,gBACkB;AAClB,KAAI,MAAU,EACZ,QAAO;CAGT,IAAM,IAAa,IAAS,GACtB,IAAW,IAAS,GACpB,IAAU,IAAS,GACnB,IAAU,IAAS,IAAc,GACjC,IAAO,KAAK,MAAM,IAAS,KAAK,IAAI,GAAG,EAAM,CAAC,GAAG,GACjD,IAAY,KAAK,IAAI,GAAG,KAAK,KAAK,IAAQ,KAAK,IAAI,GAAG,EAAM,CAAC,CAAC;AAepE,QAbI,MAAY,cACT,IAIH,kBAAC,OAAD;EAAK,WAAW,4BAA4B,KAAa;YACvD,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS,KAAc;GAAQ,UAAU;aAC1E,IAAU,aAAa,aAAa;GAC9B,CAAA;EACL,CAAA,GAPC,OAYT,kBAAC,OAAD;EACE,WAAW,0DAA0D,KAAa;EAClF,MAAK;EACL,cAAW;YAHb,CAKE,kBAAC,KAAD;GAAG,WAAU;aAAb;IAAwD;IAC7C;IAAW;IAAE;IAAS;IAAK;IAAM;IAAE;IAC1C;MACJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MAA2D;MACnD;MAAK;MAAK;MACX;;IACP,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,SAAS;KAAQ,UAAU,CAAC,KAAW;KAAS,cAAW;eAA/F,CACE,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAAA,OAE5B;;IACT,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,SAAS;KAAQ,UAAU,CAAC,KAAW;KAAS,cAAW;eAA/F,CAA2G,QAEzG,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;IACL;KACF"}
@@ -0,0 +1,32 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ //#region src/eventfullymanaged/components/SafeLink.tsx
3
+ function t(e) {
4
+ if (!e) return null;
5
+ let t = e.trim();
6
+ if (!t) return null;
7
+ let n = /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(t) ? t : `https://${t}`;
8
+ try {
9
+ let e = new URL(n);
10
+ return e.protocol === "http:" || e.protocol === "https:" ? e.toString() : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function n({ href: n, children: r, className: i, fallbackClassName: a, ...o }) {
16
+ let s = t(n);
17
+ return s ? /* @__PURE__ */ e("a", {
18
+ href: s,
19
+ target: "_blank",
20
+ rel: "noopener noreferrer nofollow",
21
+ className: i,
22
+ ...o,
23
+ children: r ?? s
24
+ }) : /* @__PURE__ */ e("span", {
25
+ className: a ?? i,
26
+ children: r ?? n ?? ""
27
+ });
28
+ }
29
+ //#endregion
30
+ export { n as SafeLink, t as safeUrl };
31
+
32
+ //# sourceMappingURL=SafeLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SafeLink.js","names":[],"sources":["../../../src/eventfullymanaged/components/SafeLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\n\n/**\n * Validate and normalise a user-supplied URL before it is ever placed in an\n * `href`. Sponsor/exhibitor websites are free-text fields persisted verbatim by\n * the backend, so rendering them directly into an anchor is a stored-XSS sink:\n * a value like `javascript:alert(1)` or `data:text/html,...` would execute on\n * click. This helper is the single choke point that neutralises that.\n *\n * Rules:\n * - empty / whitespace → `null` (caller renders plain text)\n * - bare domain → prefixed with `https://` (`acme.com` → `https://acme.com`)\n * - `http:` / `https:` → accepted, returned normalised\n * - anything else → `null` (rejects `javascript:`, `data:`, `file:`, `mailto:`…)\n */\nexport function safeUrl(raw: string | null | undefined): string | null {\n if (!raw) {\n return null;\n }\n const trimmed = raw.trim();\n if (!trimmed) {\n return null;\n }\n\n // A value carries an explicit scheme when it starts with `<scheme>:` per the\n // RFC-3986 scheme grammar (ALPHA followed by ALPHA/DIGIT/+/-/.). If it does\n // not, treat it as a bare domain/path and default it to https.\n const hasScheme = /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(trimmed);\n const candidate = hasScheme ? trimmed : `https://${trimmed}`;\n\n try {\n const url = new URL(candidate);\n if (url.protocol === 'http:' || url.protocol === 'https:') {\n return url.toString();\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport interface SafeLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n href: string | null | undefined;\n children?: ReactNode;\n /** Class applied to the rendered plain-text span when the href is unsafe. */\n fallbackClassName?: string;\n}\n\n/**\n * Anchor that only renders a clickable link when `href` passes `safeUrl`.\n * Unsafe/blank values fall back to inert text so a malicious `javascript:` URL\n * can never be activated. Opens in a new tab with `noopener noreferrer` to\n * prevent reverse-tabnabbing.\n */\nexport function SafeLink({ href, children, className, fallbackClassName, ...rest }: SafeLinkProps) {\n const safe = safeUrl(href);\n\n if (!safe) {\n return <span className={fallbackClassName ?? className}>{children ?? href ?? ''}</span>;\n }\n\n return (\n <a href={safe} target=\"_blank\" rel=\"noopener noreferrer nofollow\" className={className} {...rest}>\n {children ?? safe}\n </a>\n );\n}\n"],"mappings":";;AAeA,SAAgB,EAAQ,GAA+C;AACrE,KAAI,CAAC,EACH,QAAO;CAET,IAAM,IAAU,EAAI,MAAM;AAC1B,KAAI,CAAC,EACH,QAAO;CAOT,IAAM,IADY,2BAA2B,KAAK,EAAQ,GAC5B,IAAU,WAAW;AAEnD,KAAI;EACF,IAAM,IAAM,IAAI,IAAI,EAAU;AAI9B,SAHI,EAAI,aAAa,WAAW,EAAI,aAAa,WACxC,EAAI,UAAU,GAEhB;SACD;AACN,SAAO;;;AAiBX,SAAgB,EAAS,EAAE,SAAM,aAAU,cAAW,sBAAmB,GAAG,KAAuB;CACjG,IAAM,IAAO,EAAQ,EAAK;AAM1B,QAJK,IAKH,kBAAC,KAAD;EAAG,MAAM;EAAM,QAAO;EAAS,KAAI;EAA0C;EAAW,GAAI;YACzF,KAAY;EACX,CAAA,GANG,kBAAC,QAAD;EAAM,WAAW,KAAqB;YAAY,KAAY,KAAQ;EAAU,CAAA"}
@@ -0,0 +1,7 @@
1
+ import "./PageLayout.js";
2
+ import "./ActionMenu.js";
3
+ import "./Pagination.js";
4
+ import "./EntityFormShell.js";
5
+ import "./EntityTagsField.js";
6
+ import "./SafeLink.js";
7
+ import "./ImageUploadField.js";
@@ -0,0 +1,458 @@
1
+ import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
2
+ import { notify as t } from "../../lib/toast.js";
3
+ import { useDeleteTicketTypeMutation as n, useEventQuery as r, useEventSessionsQuery as i, useSpeakersQuery as a, useTicketTypesQuery as o } from "../../api/eventApi.js";
4
+ import { ActionMenu as s } from "../../components/ActionMenu.js";
5
+ import { Pagination as c, usePagination as l } from "../../components/Pagination.js";
6
+ import { formatDateTime as u } from "./eventFormUtils.js";
7
+ import { useMemo as d, useState as f } from "react";
8
+ import { useParams as p } from "react-router-dom";
9
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
10
+ import { ArrowLeft as g, CalendarClock as _, Edit as v, MapPin as y, Plus as b, Ticket as x, Users as S } from "lucide-react";
11
+ import { Badge as C, Button as w, GlassCard as T, IllustratedEmptyState as E, Tabs as D, TabsContent as O, TabsList as k, TabsTrigger as A } from "@burdenoff/fe-libs/ui";
12
+ //#region src/eventfullymanaged/pages/events/EventDetailPage.tsx
13
+ function j(e) {
14
+ let t = e.toLowerCase();
15
+ return [
16
+ "active",
17
+ "published",
18
+ "live",
19
+ "confirmed",
20
+ "completed",
21
+ "approved"
22
+ ].includes(t) ? "success" : [
23
+ "inactive",
24
+ "cancelled",
25
+ "archived",
26
+ "closed",
27
+ "expired"
28
+ ].includes(t) ? "warning" : [
29
+ "draft",
30
+ "created",
31
+ "pending",
32
+ "scheduled"
33
+ ].includes(t) ? "info" : "secondary";
34
+ }
35
+ function M({ eventId: t }) {
36
+ let { navigate: n } = e(), r = l(), { data: a, loading: o } = i({
37
+ eventId: t,
38
+ limit: r.limit,
39
+ offset: r.offset
40
+ }), d = a?.eventSessions?.total ?? 0, f = a?.eventSessions?.items ?? [];
41
+ return /* @__PURE__ */ h("div", {
42
+ className: "space-y-4",
43
+ children: [
44
+ /* @__PURE__ */ h("div", {
45
+ className: "flex items-center justify-between gap-3",
46
+ children: [/* @__PURE__ */ m("h2", {
47
+ className: "text-lg font-semibold text-text-primary",
48
+ children: "Agenda & Sessions"
49
+ }), /* @__PURE__ */ h(w, {
50
+ type: "button",
51
+ size: "sm",
52
+ onClick: () => n(`/events/${t}/sessions/create`),
53
+ children: [/* @__PURE__ */ m(b, { className: "h-4 w-4" }), "New Session"]
54
+ })]
55
+ }),
56
+ o ? /* @__PURE__ */ m("p", {
57
+ className: "text-sm text-text-secondary",
58
+ children: "Loading sessions…"
59
+ }) : f.length === 0 ? /* @__PURE__ */ m(E, {
60
+ illustration: "empty-data",
61
+ title: "No sessions yet.",
62
+ description: "Build the agenda by adding your first session.",
63
+ className: "py-8"
64
+ }) : /* @__PURE__ */ m("ul", {
65
+ className: "space-y-2.5",
66
+ children: f.map((e) => /* @__PURE__ */ m("li", {
67
+ className: "rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1",
68
+ children: /* @__PURE__ */ h("div", {
69
+ className: "flex items-center justify-between gap-3",
70
+ children: [/* @__PURE__ */ h("div", {
71
+ className: "min-w-0",
72
+ children: [/* @__PURE__ */ m("p", {
73
+ className: "truncate font-medium text-text-primary",
74
+ children: e.title
75
+ }), /* @__PURE__ */ m("p", {
76
+ className: "mt-0.5 truncate text-sm text-text-secondary",
77
+ children: [e.track, e.startsAt ? u(e.startsAt) : void 0].filter(Boolean).join(" • ")
78
+ })]
79
+ }), /* @__PURE__ */ h("div", {
80
+ className: "flex shrink-0 items-center gap-2",
81
+ children: [/* @__PURE__ */ m(C, {
82
+ variant: j(e.status),
83
+ className: "uppercase",
84
+ children: e.status
85
+ }), /* @__PURE__ */ m(s, {
86
+ ariaLabel: `Session actions for ${e.title}`,
87
+ onEdit: () => n(`/events/${t}/sessions/${e.id}/edit`)
88
+ })]
89
+ })]
90
+ })
91
+ }, e.id))
92
+ }),
93
+ /* @__PURE__ */ m(c, {
94
+ offset: r.offset,
95
+ limit: r.limit,
96
+ loadedCount: f.length,
97
+ total: d,
98
+ onPrev: r.prevPage,
99
+ onNext: r.nextPage,
100
+ itemLabel: "sessions",
101
+ loading: o
102
+ })
103
+ ]
104
+ });
105
+ }
106
+ function N() {
107
+ let { navigate: t } = e(), n = l(), { data: r, loading: i } = a({
108
+ limit: n.limit,
109
+ offset: n.offset
110
+ }), o = r?.speakers?.total ?? 0, u = r?.speakers?.items ?? [];
111
+ return /* @__PURE__ */ h("div", {
112
+ className: "space-y-4",
113
+ children: [
114
+ /* @__PURE__ */ h("div", {
115
+ className: "flex items-center justify-between gap-3",
116
+ children: [/* @__PURE__ */ m("h2", {
117
+ className: "text-lg font-semibold text-text-primary",
118
+ children: "Speakers"
119
+ }), /* @__PURE__ */ h("div", {
120
+ className: "flex items-center gap-2",
121
+ children: [/* @__PURE__ */ m(w, {
122
+ type: "button",
123
+ variant: "outline",
124
+ size: "sm",
125
+ onClick: () => t("/speakers"),
126
+ children: "Manage speakers"
127
+ }), /* @__PURE__ */ h(w, {
128
+ type: "button",
129
+ size: "sm",
130
+ onClick: () => t("/speakers/create"),
131
+ children: [/* @__PURE__ */ m(b, { className: "h-4 w-4" }), "New Speaker"]
132
+ })]
133
+ })]
134
+ }),
135
+ i ? /* @__PURE__ */ m("p", {
136
+ className: "text-sm text-text-secondary",
137
+ children: "Loading speakers…"
138
+ }) : u.length === 0 ? /* @__PURE__ */ m(E, {
139
+ illustration: "empty-people",
140
+ title: "No speakers yet.",
141
+ description: "Add speakers so you can assign them to sessions.",
142
+ className: "py-8"
143
+ }) : /* @__PURE__ */ m("ul", {
144
+ className: "space-y-2.5",
145
+ children: u.map((e) => /* @__PURE__ */ m("li", {
146
+ className: "rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1",
147
+ children: /* @__PURE__ */ h("div", {
148
+ className: "flex items-center justify-between gap-3",
149
+ children: [/* @__PURE__ */ h("div", {
150
+ className: "min-w-0",
151
+ children: [/* @__PURE__ */ m("p", {
152
+ className: "truncate font-medium text-text-primary",
153
+ children: e.fullName
154
+ }), /* @__PURE__ */ m("p", {
155
+ className: "mt-0.5 truncate text-sm text-text-secondary",
156
+ children: [e.title, e.organization].filter(Boolean).join(" • ") || "Speaker"
157
+ })]
158
+ }), /* @__PURE__ */ m(s, {
159
+ ariaLabel: `Speaker actions for ${e.fullName}`,
160
+ onEdit: () => t(`/speakers/${e.id}/edit`)
161
+ })]
162
+ })
163
+ }, e.id))
164
+ }),
165
+ /* @__PURE__ */ m(c, {
166
+ offset: n.offset,
167
+ limit: n.limit,
168
+ loadedCount: u.length,
169
+ total: o,
170
+ onPrev: n.prevPage,
171
+ onNext: n.nextPage,
172
+ itemLabel: "speakers",
173
+ loading: i
174
+ })
175
+ ]
176
+ });
177
+ }
178
+ function P({ eventId: r }) {
179
+ let { navigate: i } = e(), a = l(), { data: u, loading: d, refetch: f } = o({
180
+ eventId: r,
181
+ limit: a.limit,
182
+ offset: a.offset
183
+ }), [p] = n(), g = u?.ticketTypes?.total ?? 0, _ = u?.ticketTypes?.items ?? [], v = async (e) => {
184
+ let n = await p({ variables: { id: e.id } });
185
+ if (n.error) throw t.error("Could not delete ticket type", n.error.message), n.error;
186
+ if (!n.data?.deleteTicketType) throw t.error("Could not delete ticket type", "The server did not confirm the change."), Error("delete-unconfirmed");
187
+ t.success("Ticket type deleted"), await f();
188
+ };
189
+ return /* @__PURE__ */ h("div", {
190
+ className: "space-y-4",
191
+ children: [
192
+ /* @__PURE__ */ h("div", {
193
+ className: "flex items-center justify-between gap-3",
194
+ children: [/* @__PURE__ */ m("h2", {
195
+ className: "text-lg font-semibold text-text-primary",
196
+ children: "Ticket Types"
197
+ }), /* @__PURE__ */ h(w, {
198
+ type: "button",
199
+ size: "sm",
200
+ onClick: () => i(`/events/${r}/tickets/create`),
201
+ children: [/* @__PURE__ */ m(b, { className: "h-4 w-4" }), "New Ticket Type"]
202
+ })]
203
+ }),
204
+ d ? /* @__PURE__ */ m("p", {
205
+ className: "text-sm text-text-secondary",
206
+ children: "Loading ticket types…"
207
+ }) : _.length === 0 ? /* @__PURE__ */ m(E, {
208
+ illustration: "empty-data",
209
+ title: "No ticket types yet.",
210
+ description: "Add a ticket type so attendees can register.",
211
+ className: "py-8"
212
+ }) : /* @__PURE__ */ m("ul", {
213
+ className: "space-y-2.5",
214
+ children: _.map((e) => /* @__PURE__ */ m("li", {
215
+ className: "rounded-card border border-border-seam bg-bg-surface p-3 transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1",
216
+ children: /* @__PURE__ */ h("div", {
217
+ className: "flex items-center justify-between gap-3",
218
+ children: [/* @__PURE__ */ h("div", {
219
+ className: "min-w-0",
220
+ children: [/* @__PURE__ */ m("p", {
221
+ className: "truncate font-medium text-text-primary",
222
+ children: e.name
223
+ }), /* @__PURE__ */ h("p", {
224
+ className: "mt-0.5 truncate text-sm text-text-secondary",
225
+ children: [
226
+ e.price,
227
+ " ",
228
+ e.currency,
229
+ " • Sold ",
230
+ e.soldCount,
231
+ e.capacity == null ? "" : ` / ${e.capacity}`
232
+ ]
233
+ })]
234
+ }), /* @__PURE__ */ h("div", {
235
+ className: "flex shrink-0 items-center gap-2",
236
+ children: [/* @__PURE__ */ m(C, {
237
+ variant: e.isActive ? "success" : "warning",
238
+ className: "uppercase",
239
+ children: e.isActive ? "Active" : "Inactive"
240
+ }), /* @__PURE__ */ m(s, {
241
+ ariaLabel: `Ticket actions for ${e.name}`,
242
+ entityName: e.name,
243
+ deleteLabel: "Delete ticket type",
244
+ onEdit: () => i(`/events/${r}/tickets/${e.id}/edit`),
245
+ onDelete: () => v(e)
246
+ })]
247
+ })]
248
+ })
249
+ }, e.id))
250
+ }),
251
+ /* @__PURE__ */ m(c, {
252
+ offset: a.offset,
253
+ limit: a.limit,
254
+ loadedCount: _.length,
255
+ total: g,
256
+ onPrev: a.prevPage,
257
+ onNext: a.nextPage,
258
+ itemLabel: "ticket types",
259
+ loading: d
260
+ })
261
+ ]
262
+ });
263
+ }
264
+ function F() {
265
+ let { id: t = "" } = p(), { navigate: n } = e(), { data: i, loading: a } = r(t), [o, s] = f("overview"), c = i?.event ?? null, l = d(() => {
266
+ let e = c?.metadata ?? {};
267
+ return typeof e.venueId == "string" ? e.venueId : "";
268
+ }, [c]);
269
+ return a ? /* @__PURE__ */ m("div", {
270
+ className: "p-6 text-sm text-text-secondary",
271
+ children: "Loading event…"
272
+ }) : c ? /* @__PURE__ */ h("div", {
273
+ className: "space-y-6 p-6",
274
+ children: [/* @__PURE__ */ h("div", {
275
+ className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
276
+ children: [/* @__PURE__ */ h("div", {
277
+ className: "space-y-3",
278
+ children: [/* @__PURE__ */ h(w, {
279
+ type: "button",
280
+ variant: "ghost",
281
+ size: "sm",
282
+ onClick: () => n("/events"),
283
+ children: [/* @__PURE__ */ m(g, { className: "h-4 w-4" }), "Back to Events"]
284
+ }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("div", {
285
+ className: "flex flex-wrap items-center gap-3",
286
+ children: [/* @__PURE__ */ m("h1", {
287
+ className: "text-2xl font-bold text-text-primary",
288
+ children: c.name
289
+ }), /* @__PURE__ */ m(C, {
290
+ variant: j(c.status),
291
+ className: "uppercase",
292
+ children: c.status
293
+ })]
294
+ }), /* @__PURE__ */ h("p", {
295
+ className: "mt-1 flex items-center gap-2 text-sm text-text-secondary",
296
+ children: [
297
+ /* @__PURE__ */ m(_, { className: "h-4 w-4" }),
298
+ u(c.startAt),
299
+ " — ",
300
+ u(c.endAt)
301
+ ]
302
+ })] })]
303
+ }), /* @__PURE__ */ h(w, {
304
+ type: "button",
305
+ onClick: () => n(`/events/${t}/edit`),
306
+ children: [/* @__PURE__ */ m(v, { className: "h-4 w-4" }), "Edit Event"]
307
+ })]
308
+ }), /* @__PURE__ */ h(D, {
309
+ value: o,
310
+ onValueChange: s,
311
+ children: [
312
+ /* @__PURE__ */ h(k, { children: [
313
+ /* @__PURE__ */ m(A, {
314
+ value: "overview",
315
+ children: "Overview"
316
+ }),
317
+ /* @__PURE__ */ m(A, {
318
+ value: "sessions",
319
+ children: "Sessions"
320
+ }),
321
+ /* @__PURE__ */ m(A, {
322
+ value: "speakers",
323
+ children: "Speakers"
324
+ }),
325
+ /* @__PURE__ */ m(A, {
326
+ value: "tickets",
327
+ children: "Tickets"
328
+ })
329
+ ] }),
330
+ /* @__PURE__ */ h(O, {
331
+ value: "overview",
332
+ className: "mt-4",
333
+ children: [/* @__PURE__ */ h("div", {
334
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
335
+ children: [
336
+ /* @__PURE__ */ h(T, {
337
+ treatment: "metric",
338
+ className: "p-4",
339
+ children: [/* @__PURE__ */ h("div", {
340
+ className: "mb-2 flex items-center gap-2 text-sm font-medium text-text-primary",
341
+ children: [/* @__PURE__ */ m(x, { className: "h-4 w-4 text-action-primary-bg" }), "Type"]
342
+ }), /* @__PURE__ */ m("p", {
343
+ className: "text-sm uppercase text-text-secondary",
344
+ children: c.type
345
+ })]
346
+ }),
347
+ /* @__PURE__ */ h(T, {
348
+ treatment: "glass",
349
+ glow: !0,
350
+ className: "p-4",
351
+ children: [/* @__PURE__ */ h("div", {
352
+ className: "mb-2 flex items-center gap-2 text-sm font-medium text-text-primary",
353
+ children: [/* @__PURE__ */ m(S, { className: "h-4 w-4 text-action-primary-bg" }), "Capacity"]
354
+ }), /* @__PURE__ */ h("p", {
355
+ className: "text-sm tabular-nums text-text-secondary",
356
+ children: [c.capacity ?? 0, " attendees"]
357
+ })]
358
+ }),
359
+ /* @__PURE__ */ h(T, {
360
+ treatment: "metric",
361
+ className: "p-4",
362
+ children: [/* @__PURE__ */ h("div", {
363
+ className: "mb-2 flex items-center gap-2 text-sm font-medium text-text-primary",
364
+ children: [/* @__PURE__ */ m(y, { className: "h-4 w-4 text-action-primary-bg" }), "Timezone"]
365
+ }), /* @__PURE__ */ m("p", {
366
+ className: "text-sm text-text-secondary",
367
+ children: c.timezone
368
+ })]
369
+ })
370
+ ]
371
+ }), /* @__PURE__ */ h(T, {
372
+ treatment: "plain",
373
+ className: "mt-4 border-border-seam p-5",
374
+ children: [
375
+ /* @__PURE__ */ m("h2", {
376
+ className: "text-lg font-semibold text-text-primary",
377
+ children: "About"
378
+ }),
379
+ /* @__PURE__ */ m("p", {
380
+ className: "mt-2 text-sm text-text-secondary",
381
+ children: c.description?.trim() ? c.description : "No description provided."
382
+ }),
383
+ /* @__PURE__ */ h("dl", {
384
+ className: "mt-4 grid grid-cols-1 gap-3 text-sm sm:grid-cols-2",
385
+ children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("dt", {
386
+ className: "text-text-muted",
387
+ children: "Visibility"
388
+ }), /* @__PURE__ */ m("dd", {
389
+ className: "capitalize text-text-primary",
390
+ children: c.visibility
391
+ })] }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("dt", {
392
+ className: "text-text-muted",
393
+ children: "Venue"
394
+ }), /* @__PURE__ */ m("dd", {
395
+ className: "text-text-primary",
396
+ children: l ? /* @__PURE__ */ m(w, {
397
+ type: "button",
398
+ variant: "link",
399
+ size: "sm",
400
+ className: "h-auto p-0",
401
+ onClick: () => n(`/venues/${l}`),
402
+ children: "View venue"
403
+ }) : "No venue assigned"
404
+ })] })]
405
+ }),
406
+ /* @__PURE__ */ m("div", {
407
+ className: "mt-4",
408
+ children: /* @__PURE__ */ h(w, {
409
+ type: "button",
410
+ variant: "outline",
411
+ size: "sm",
412
+ onClick: () => n(`/events/${t}/edit`),
413
+ children: [/* @__PURE__ */ m(v, { className: "h-4 w-4" }), "Edit event details"]
414
+ })
415
+ })
416
+ ]
417
+ })]
418
+ }),
419
+ /* @__PURE__ */ m(O, {
420
+ value: "sessions",
421
+ className: "mt-4",
422
+ children: /* @__PURE__ */ m(M, { eventId: t })
423
+ }),
424
+ /* @__PURE__ */ m(O, {
425
+ value: "speakers",
426
+ className: "mt-4",
427
+ children: /* @__PURE__ */ m(N, {})
428
+ }),
429
+ /* @__PURE__ */ m(O, {
430
+ value: "tickets",
431
+ className: "mt-4",
432
+ children: /* @__PURE__ */ m(P, { eventId: t })
433
+ })
434
+ ]
435
+ })]
436
+ }) : /* @__PURE__ */ h("div", {
437
+ className: "space-y-4 p-6",
438
+ children: [/* @__PURE__ */ h(w, {
439
+ type: "button",
440
+ variant: "ghost",
441
+ size: "sm",
442
+ onClick: () => n("/events"),
443
+ children: [/* @__PURE__ */ m(g, { className: "h-4 w-4" }), "Back to Events"]
444
+ }), /* @__PURE__ */ m(T, {
445
+ treatment: "plain",
446
+ className: "border-border-seam",
447
+ children: /* @__PURE__ */ m(E, {
448
+ illustration: "empty-search",
449
+ title: "Event not found.",
450
+ description: "This event may have been removed or is outside your current scope."
451
+ })
452
+ })]
453
+ });
454
+ }
455
+ //#endregion
456
+ export { F as default };
457
+
458
+ //# sourceMappingURL=EventDetailPage.js.map