@burdenoff/microfe-eventfullymanaged 2026.730.1 → 2026.801.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eventfullymanaged/EventfullyManagedRoutes.d.ts.map +1 -1
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js +61 -77
- package/dist/eventfullymanaged/EventfullyManagedRoutes.js.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.d.ts +208 -0
- package/dist/eventfullymanaged/api/eventApi.d.ts.map +1 -1
- package/dist/eventfullymanaged/api/eventApi.js +293 -136
- package/dist/eventfullymanaged/api/eventApi.js.map +1 -1
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts +18 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js +110 -0
- package/dist/eventfullymanaged/hooks/useEvmSubscription.js.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts +47 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.d.ts.map +1 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js +209 -0
- package/dist/eventfullymanaged/hooks/useVirtualCall.js.map +1 -0
- package/dist/eventfullymanaged/pages/events/EventDetailPage.d.ts.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js +80 -72
- package/dist/eventfullymanaged/pages/events/EventDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/EventFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/SessionFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/events/TicketTypeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js +353 -0
- package/dist/eventfullymanaged/pages/events/VirtualSessionPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/exhibitors/ExhibitorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/AttendeeProfilePage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts +2 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.d.ts.map +1 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js +336 -0
- package/dist/eventfullymanaged/pages/modules/IntegrationsPage.js.map +1 -0
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorDetailsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/SponsorLeadsPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js +1 -1
- package/dist/eventfullymanaged/pages/modules/VenueResourcesPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/networking/MeetingFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/networking/NetworkingProfileFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/registration/AttendeeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/registration/PromoCodeFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/speakers/SpeakerFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/sponsors/SponsorFormPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/FloorPlansPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueDetailPage.js.map +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueFormPage.js +1 -1
- package/dist/eventfullymanaged/pages/venues/VenueFormPage.js.map +1 -1
- package/dist/eventfullymanaged/utils/shellBridge.d.ts +46 -0
- package/dist/eventfullymanaged/utils/shellBridge.d.ts.map +1 -0
- package/dist/eventfullymanaged/utils/shellBridge.js +19 -0
- package/dist/eventfullymanaged/utils/shellBridge.js.map +1 -0
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FloorPlansPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/FloorPlansPage.tsx"],"sourcesContent":["import { ArrowLeft, LayoutGrid, Map as MapIcon, Plus, SquareStack } from 'lucide-react';\nimport { Badge, Button, GlassCard, GradientText, IllustratedEmptyState, Input, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport {\n useCreateFloorPlanMutation,\n useDeleteFloorPlanMutation,\n useUpdateFloorPlanMutation,\n useVenueQuery,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype Json = Record<string, unknown> | null | undefined;\n\ntype FloorPlan = {\n id: string;\n venueId: string;\n name: string;\n imageUrl?: string | null;\n dimensions?: Json;\n interactiveAreas?: Json;\n};\n\ntype Zone = {\n key: string;\n label: string;\n type?: string;\n capacity?: number;\n rect?: { x: number; y: number; w: number; h: number };\n};\n\nconst FLOOR_PLAN_FOLDER = '/EventfullyManaged/FloorPlans';\n\nfunction asNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return Number(value);\n }\n return undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value : undefined;\n}\n\n// interactiveAreas is free-form JSON persisted verbatim by the backend. Parse it\n// defensively so the viewer renders whatever real shape a plan happens to hold —\n// an array of areas, a `{ zones: [...] }` / `{ areas: [...] }` wrapper, or a\n// keyed object — without ever fabricating booth data that isn't there.\nfunction extractZones(interactiveAreas: Json): Zone[] {\n if (!interactiveAreas) {\n return [];\n }\n let raw: unknown = interactiveAreas;\n if (!Array.isArray(raw) && typeof raw === 'object') {\n const obj = raw as Record<string, unknown>;\n if (Array.isArray(obj.zones)) {\n raw = obj.zones;\n } else if (Array.isArray(obj.areas)) {\n raw = obj.areas;\n } else if (Array.isArray(obj.booths)) {\n raw = obj.booths;\n }\n }\n\n const entries: Array<[string, unknown]> = Array.isArray(raw)\n ? raw.map((item, index) => [String(index), item])\n : typeof raw === 'object' && raw !== null\n ? Object.entries(raw as Record<string, unknown>)\n : [];\n\n const zones: Zone[] = [];\n for (const [key, value] of entries) {\n if (value === null || typeof value !== 'object') {\n continue;\n }\n const item = value as Record<string, unknown>;\n const label = asString(item.label) ?? asString(item.name) ?? asString(item.title) ?? asString(item.id) ?? key;\n const x = asNumber(item.x);\n const y = asNumber(item.y);\n const w = asNumber(item.w) ?? asNumber(item.width);\n const h = asNumber(item.h) ?? asNumber(item.height);\n let rect: Zone['rect'];\n if (x !== undefined && y !== undefined && w !== undefined && h !== undefined) {\n // Normalise fractional (0–1) or percentage (0–100) coordinates to percent.\n const scale = x <= 1 && y <= 1 && w <= 1 && h <= 1 ? 100 : 1;\n if (scale === 100 || (x <= 100 && y <= 100 && w <= 100 && h <= 100)) {\n rect = { x: x * scale, y: y * scale, w: w * scale, h: h * scale };\n }\n }\n zones.push({\n key: `${key}-${label}`,\n label,\n type: asString(item.type) ?? asString(item.kind),\n capacity: asNumber(item.capacity) ?? asNumber(item.seats),\n rect,\n });\n }\n return zones;\n}\n\ntype PlanMetric = { label: string; value: string };\n\nfunction buildMetrics(plan: FloorPlan, zones: Zone[]): PlanMetric[] {\n const metrics: PlanMetric[] = [];\n if (zones.length > 0) {\n metrics.push({ label: 'Zones', value: zones.length.toLocaleString() });\n }\n\n const boothCount = zones.filter((zone) => (zone.type ?? '').toLowerCase().includes('booth')).length;\n if (boothCount > 0) {\n metrics.push({ label: 'Booths', value: boothCount.toLocaleString() });\n }\n\n const zoneCapacity = zones.reduce((sum, zone) => sum + (zone.capacity ?? 0), 0);\n const dims = (plan.dimensions ?? {}) as Record<string, unknown>;\n const declaredCapacity = asNumber(dims.capacity) ?? asNumber(dims.totalCapacity);\n const capacity = zoneCapacity > 0 ? zoneCapacity : declaredCapacity;\n if (capacity !== undefined && capacity > 0) {\n metrics.push({ label: 'Total capacity', value: capacity.toLocaleString() });\n }\n\n const width = asNumber(dims.width);\n const height = asNumber(dims.height);\n if (width !== undefined && height !== undefined) {\n const unit = asString(dims.unit) ?? 'm';\n metrics.push({ label: 'Dimensions', value: `${width} × ${height} ${unit}` });\n }\n\n return metrics;\n}\n\nfunction FloorPlanViewer({\n plan,\n zones,\n onAttachImage,\n}: {\n plan: FloorPlan;\n zones: Zone[];\n onAttachImage: (plan: FloorPlan, url: string) => void;\n}) {\n const overlayZones = zones.filter((zone) => zone.rect);\n return (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden border-border-seam p-0\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam px-5 py-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-primary\">\n <MapIcon className=\"h-4 w-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-base font-semibold\">{plan.name}</h2>\n </div>\n {zones.length > 0 ? (\n <Badge variant=\"secondary\" className=\"shrink-0\">\n {zones.length} {zones.length === 1 ? 'zone' : 'zones'}\n </Badge>\n ) : null}\n </div>\n\n {plan.imageUrl ? (\n <div className=\"relative bg-bg-sunken\">\n <img\n src={plan.imageUrl}\n alt={`${plan.name} floor plan`}\n className=\"mx-auto max-h-[32rem] w-full object-contain\"\n />\n {overlayZones.map((zone) => (\n <div\n key={zone.key}\n className=\"absolute flex items-center justify-center rounded-md border border-accent-primary/70 bg-action-primary-bg/15 text-[10px] font-medium text-accent-primary\"\n style={{\n left: `${zone.rect?.x}%`,\n top: `${zone.rect?.y}%`,\n width: `${zone.rect?.w}%`,\n height: `${zone.rect?.h}%`,\n }}\n title={zone.label}\n >\n <span className=\"truncate px-1\">{zone.label}</span>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"p-6\">\n <div className=\"rounded-card border border-dashed border-border-default bg-bg-sunken p-6\">\n <p className=\"mb-3 text-center text-sm text-text-secondary\">\n No image attached yet. Upload the hall map for this plan.\n </p>\n <ImageUploadField\n label=\"Upload floor plan image\"\n folderPath={FLOOR_PLAN_FOLDER}\n value={null}\n onUploaded={(url) => onAttachImage(plan, url)}\n hint=\"Stored securely in Files and shown here once attached.\"\n />\n </div>\n </div>\n )}\n\n {zones.length > 0 ? (\n <div className=\"border-t border-border-seam px-5 py-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <LayoutGrid className=\"h-4 w-4 text-text-muted\" />\n Labeled zones\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {zones.map((zone) => (\n <Badge key={zone.key} variant=\"outline\" className=\"gap-1.5\">\n {zone.label}\n {zone.capacity ? <span className=\"text-text-muted\">· {zone.capacity}</span> : null}\n </Badge>\n ))}\n </div>\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport default function FloorPlansPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading, refetch } = useVenueQuery(id);\n const [createFloorPlan, { loading: creating }] = useCreateFloorPlanMutation();\n const [updateFloorPlan] = useUpdateFloorPlanMutation();\n const [deleteFloorPlan] = useDeleteFloorPlanMutation();\n const [name, setName] = useState('');\n const [imageUrl, setImageUrl] = useState('');\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n\n const venue = data?.venue;\n const floorPlans = useMemo(() => (data?.floorPlans?.items ?? []) as FloorPlan[], [data]);\n\n const selectedPlan = useMemo(\n () => floorPlans.find((plan) => plan.id === selectedId) ?? floorPlans[0] ?? null,\n [floorPlans, selectedId]\n );\n const selectedZones = useMemo(() => extractZones(selectedPlan?.interactiveAreas), [selectedPlan]);\n const metrics = useMemo(\n () => (selectedPlan ? buildMetrics(selectedPlan, selectedZones) : []),\n [selectedPlan, selectedZones]\n );\n\n const handleCreate = async () => {\n if (!name.trim() || !id) {\n return;\n }\n try {\n const response = await createFloorPlan({\n variables: {\n input: { venueId: id, name: name.trim(), imageUrl, dimensions: {}, interactiveAreas: {} },\n },\n });\n if (!mutationSucceeded(response, response.data?.createFloorPlan?.id, 'Could not create floor plan')) {\n return;\n }\n setName('');\n setImageUrl('');\n setShowCreate(false);\n const createdId = response.data?.createFloorPlan?.id;\n if (createdId) {\n setSelectedId(createdId);\n }\n notify.success('Floor plan created');\n await refetch();\n } catch (error) {\n notify.error('Could not create floor plan', describeError(error));\n }\n };\n\n const handleAttachImage = async (plan: FloorPlan, url: string) => {\n try {\n const response = await updateFloorPlan({\n variables: {\n id: plan.id,\n input: {\n venueId: plan.venueId,\n name: plan.name,\n imageUrl: url,\n dimensions: plan.dimensions ?? {},\n interactiveAreas: plan.interactiveAreas ?? {},\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateFloorPlan?.id, 'Could not attach image')) {\n return;\n }\n await refetch();\n } catch (error) {\n notify.error('Could not attach image', describeError(error));\n }\n };\n\n const handleDelete = async (plan: FloorPlan) => {\n const response = await deleteFloorPlan({ variables: { id: plan.id } });\n if (response.error) {\n notify.error('Could not delete floor plan', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteFloorPlan) {\n notify.error('Could not delete floor plan', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n if (selectedId === plan.id) {\n setSelectedId(null);\n }\n notify.success('Floor plan deleted', plan.name);\n await refetch();\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate(`/venues/${id}`)}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venue\n </Button>\n <GradientText as=\"h1\" className=\"text-2xl font-bold\">\n {venue?.name ? `${venue.name} — Floor Plans` : 'Floor Plans'}\n </GradientText>\n <p className=\"text-sm text-text-secondary\">\n Map the hall layout, booth positions, stage and entrances so exhibitors and attendees can navigate on the\n day.\n </p>\n </div>\n <Button type=\"button\" onClick={() => setShowCreate((value) => !value)}>\n <Plus className=\"h-4 w-4\" />\n Add floor plan\n </Button>\n </div>\n\n {loading ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <Skeleton className=\"h-96 rounded-card lg:col-span-2\" />\n <Skeleton className=\"h-96 rounded-card\" />\n </div>\n ) : floorPlans.length === 0 && !showCreate ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title=\"No floor plans yet.\"\n description=\"Add one to start mapping this venue — upload the hall map and label its zones.\"\n action={\n <Button type=\"button\" onClick={() => setShowCreate(true)}>\n <Plus className=\"h-4 w-4\" />\n Add floor plan\n </Button>\n }\n />\n </GlassCard>\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n {selectedPlan ? (\n <FloorPlanViewer plan={selectedPlan} zones={selectedZones} onAttachImage={handleAttachImage} />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex h-full items-center justify-center border-border-seam p-10\">\n <p className=\"text-sm text-text-secondary\">Add a floor plan to preview it here.</p>\n </GlassCard>\n )}\n </div>\n\n <div className=\"space-y-6\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Available Floor Plans</h2>\n <ul className=\"mt-3 space-y-2\" role=\"list\">\n {floorPlans.length === 0 ? (\n <li className=\"text-sm text-text-secondary\">None yet — create one below.</li>\n ) : (\n floorPlans.map((plan) => {\n const active = selectedPlan?.id === plan.id;\n return (\n <li key={plan.id}>\n <div\n className={`flex items-center gap-3 rounded-card border p-3 transition-colors ${\n active\n ? 'border-accent-primary bg-action-primary-bg/10'\n : 'border-border-seam hover:border-border-strong'\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setSelectedId(plan.id)}\n className=\"flex min-w-0 flex-1 items-center gap-3 text-left focus-visible:outline-none\"\n aria-pressed={active}\n >\n <span\n className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-md ${\n active\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n <SquareStack className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{plan.name}</span>\n <span className=\"block text-xs text-text-muted\">\n {plan.imageUrl ? 'Map attached' : 'No image yet'}\n </span>\n </span>\n </button>\n <ActionMenu\n ariaLabel={`Floor plan actions for ${plan.name}`}\n entityName={plan.name}\n deleteLabel=\"Delete floor plan\"\n onDelete={() => handleDelete(plan)}\n />\n </div>\n </li>\n );\n })\n )}\n </ul>\n </GlassCard>\n\n {selectedPlan ? (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Plan Details</h2>\n {metrics.length > 0 ? (\n <dl className=\"mt-3 space-y-2\">\n {metrics.map((metric) => (\n <div key={metric.label} className=\"flex items-center justify-between text-sm\">\n <dt className=\"text-text-secondary\">{metric.label}</dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">{metric.value}</dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"mt-3 text-sm text-text-secondary\">No zones or dimensions captured for this plan yet.</p>\n )}\n </GlassCard>\n ) : null}\n\n {showCreate || floorPlans.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Add a floor plan</h2>\n <div className=\"space-y-4\">\n <Input\n type=\"text\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder=\"Main Hall Layout\"\n aria-label=\"Floor plan name\"\n />\n <ImageUploadField\n label=\"Upload floor plan image\"\n folderPath={FLOOR_PLAN_FOLDER}\n value={imageUrl || null}\n onUploaded={setImageUrl}\n hint=\"Optional now — you can attach it later from the viewer.\"\n />\n <div className=\"flex justify-end gap-2\">\n {floorPlans.length > 0 ? (\n <Button type=\"button\" variant=\"ghost\" onClick={() => setShowCreate(false)}>\n Cancel\n </Button>\n ) : null}\n <Button type=\"button\" onClick={handleCreate} disabled={creating || !name.trim()}>\n <Plus className=\"h-4 w-4\" />\n {creating ? 'Creating…' : 'Create'}\n </Button>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FloorPlansPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/FloorPlansPage.tsx"],"sourcesContent":["import { ArrowLeft, LayoutGrid, Map as MapIcon, Plus, SquareStack } from 'lucide-react';\nimport { Badge, Button, GlassCard, GradientText, IllustratedEmptyState, Input, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport {\n useCreateFloorPlanMutation,\n useDeleteFloorPlanMutation,\n useUpdateFloorPlanMutation,\n useVenueQuery,\n} from '../../api/eventApi';\nimport { ActionMenu } from '../../components/ActionMenu';\nimport { ImageUploadField } from '../../components/ImageUploadField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype Json = Record<string, unknown> | null | undefined;\n\ntype FloorPlan = {\n id: string;\n venueId: string;\n name: string;\n imageUrl?: string | null;\n dimensions?: Json;\n interactiveAreas?: Json;\n};\n\ntype Zone = {\n key: string;\n label: string;\n type?: string;\n capacity?: number;\n rect?: { x: number; y: number; w: number; h: number };\n};\n\nconst FLOOR_PLAN_FOLDER = '/EventfullyManaged/FloorPlans';\n\nfunction asNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return Number(value);\n }\n return undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== '' ? value : undefined;\n}\n\n// interactiveAreas is free-form JSON persisted verbatim by the backend. Parse it\n// defensively so the viewer renders whatever real shape a plan happens to hold —\n// an array of areas, a `{ zones: [...] }` / `{ areas: [...] }` wrapper, or a\n// keyed object — without ever fabricating booth data that isn't there.\nfunction extractZones(interactiveAreas: Json): Zone[] {\n if (!interactiveAreas) {\n return [];\n }\n let raw: unknown = interactiveAreas;\n if (!Array.isArray(raw) && typeof raw === 'object') {\n const obj = raw as Record<string, unknown>;\n if (Array.isArray(obj.zones)) {\n raw = obj.zones;\n } else if (Array.isArray(obj.areas)) {\n raw = obj.areas;\n } else if (Array.isArray(obj.booths)) {\n raw = obj.booths;\n }\n }\n\n const entries: Array<[string, unknown]> = Array.isArray(raw)\n ? raw.map((item, index) => [String(index), item])\n : typeof raw === 'object' && raw !== null\n ? Object.entries(raw as Record<string, unknown>)\n : [];\n\n const zones: Zone[] = [];\n for (const [key, value] of entries) {\n if (value === null || typeof value !== 'object') {\n continue;\n }\n const item = value as Record<string, unknown>;\n const label = asString(item.label) ?? asString(item.name) ?? asString(item.title) ?? asString(item.id) ?? key;\n const x = asNumber(item.x);\n const y = asNumber(item.y);\n const w = asNumber(item.w) ?? asNumber(item.width);\n const h = asNumber(item.h) ?? asNumber(item.height);\n let rect: Zone['rect'];\n if (x !== undefined && y !== undefined && w !== undefined && h !== undefined) {\n // Normalise fractional (0–1) or percentage (0–100) coordinates to percent.\n const scale = x <= 1 && y <= 1 && w <= 1 && h <= 1 ? 100 : 1;\n if (scale === 100 || (x <= 100 && y <= 100 && w <= 100 && h <= 100)) {\n rect = { x: x * scale, y: y * scale, w: w * scale, h: h * scale };\n }\n }\n zones.push({\n key: `${key}-${label}`,\n label,\n type: asString(item.type) ?? asString(item.kind),\n capacity: asNumber(item.capacity) ?? asNumber(item.seats),\n rect,\n });\n }\n return zones;\n}\n\ntype PlanMetric = { label: string; value: string };\n\nfunction buildMetrics(plan: FloorPlan, zones: Zone[]): PlanMetric[] {\n const metrics: PlanMetric[] = [];\n if (zones.length > 0) {\n metrics.push({ label: 'Zones', value: zones.length.toLocaleString() });\n }\n\n const boothCount = zones.filter((zone) => (zone.type ?? '').toLowerCase().includes('booth')).length;\n if (boothCount > 0) {\n metrics.push({ label: 'Booths', value: boothCount.toLocaleString() });\n }\n\n const zoneCapacity = zones.reduce((sum, zone) => sum + (zone.capacity ?? 0), 0);\n const dims = (plan.dimensions ?? {}) as Record<string, unknown>;\n const declaredCapacity = asNumber(dims.capacity) ?? asNumber(dims.totalCapacity);\n const capacity = zoneCapacity > 0 ? zoneCapacity : declaredCapacity;\n if (capacity !== undefined && capacity > 0) {\n metrics.push({ label: 'Total capacity', value: capacity.toLocaleString() });\n }\n\n const width = asNumber(dims.width);\n const height = asNumber(dims.height);\n if (width !== undefined && height !== undefined) {\n const unit = asString(dims.unit) ?? 'm';\n metrics.push({ label: 'Dimensions', value: `${width} × ${height} ${unit}` });\n }\n\n return metrics;\n}\n\nfunction FloorPlanViewer({\n plan,\n zones,\n onAttachImage,\n}: {\n plan: FloorPlan;\n zones: Zone[];\n onAttachImage: (plan: FloorPlan, url: string) => void;\n}) {\n const overlayZones = zones.filter((zone) => zone.rect);\n return (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden border-border-seam p-0\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border-seam px-5 py-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-primary\">\n <MapIcon className=\"h-4 w-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-base font-semibold\">{plan.name}</h2>\n </div>\n {zones.length > 0 ? (\n <Badge variant=\"secondary\" className=\"shrink-0\">\n {zones.length} {zones.length === 1 ? 'zone' : 'zones'}\n </Badge>\n ) : null}\n </div>\n\n {plan.imageUrl ? (\n <div className=\"relative bg-bg-sunken\">\n <img\n src={plan.imageUrl}\n alt={`${plan.name} floor plan`}\n className=\"mx-auto max-h-[32rem] w-full object-contain\"\n />\n {overlayZones.map((zone) => (\n <div\n key={zone.key}\n className=\"absolute flex items-center justify-center rounded-md border border-accent-primary/70 bg-action-primary-bg/15 text-[10px] font-medium text-accent-primary\"\n style={{\n left: `${zone.rect?.x}%`,\n top: `${zone.rect?.y}%`,\n width: `${zone.rect?.w}%`,\n height: `${zone.rect?.h}%`,\n }}\n title={zone.label}\n >\n <span className=\"truncate px-1\">{zone.label}</span>\n </div>\n ))}\n </div>\n ) : (\n <div className=\"p-6\">\n <div className=\"rounded-card border border-dashed border-border-default bg-bg-sunken p-6\">\n <p className=\"mb-3 text-center text-sm text-text-secondary\">\n No image attached yet. Upload the hall map for this plan.\n </p>\n <ImageUploadField\n label=\"Upload floor plan image\"\n folderPath={FLOOR_PLAN_FOLDER}\n value={null}\n onUploaded={(url) => onAttachImage(plan, url)}\n hint=\"Stored securely in Files and shown here once attached.\"\n />\n </div>\n </div>\n )}\n\n {zones.length > 0 ? (\n <div className=\"border-t border-border-seam px-5 py-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <LayoutGrid className=\"h-4 w-4 text-text-muted\" />\n Labeled zones\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {zones.map((zone) => (\n <Badge key={zone.key} variant=\"outline\" className=\"gap-1.5\">\n {zone.label}\n {zone.capacity ? <span className=\"text-text-muted\">· {zone.capacity}</span> : null}\n </Badge>\n ))}\n </div>\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nexport default function FloorPlansPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading, refetch } = useVenueQuery(id);\n const [createFloorPlan, { loading: creating }] = useCreateFloorPlanMutation();\n const [updateFloorPlan] = useUpdateFloorPlanMutation();\n const [deleteFloorPlan] = useDeleteFloorPlanMutation();\n const [name, setName] = useState('');\n const [imageUrl, setImageUrl] = useState('');\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n\n const venue = data?.venue;\n const floorPlans = useMemo(() => (data?.floorPlans?.items ?? []) as FloorPlan[], [data]);\n\n const selectedPlan = useMemo(\n () => floorPlans.find((plan) => plan.id === selectedId) ?? floorPlans[0] ?? null,\n [floorPlans, selectedId]\n );\n const selectedZones = useMemo(() => extractZones(selectedPlan?.interactiveAreas), [selectedPlan]);\n const metrics = useMemo(\n () => (selectedPlan ? buildMetrics(selectedPlan, selectedZones) : []),\n [selectedPlan, selectedZones]\n );\n\n const handleCreate = async () => {\n if (!name.trim() || !id) {\n return;\n }\n try {\n const response = await createFloorPlan({\n variables: {\n input: { venueId: id, name: name.trim(), imageUrl, dimensions: {}, interactiveAreas: {} },\n },\n });\n if (!mutationSucceeded(response, response.data?.createFloorPlan?.id, 'Could not create floor plan')) {\n return;\n }\n setName('');\n setImageUrl('');\n setShowCreate(false);\n const createdId = response.data?.createFloorPlan?.id;\n if (createdId) {\n setSelectedId(createdId);\n }\n notify.success('Floor plan created');\n await refetch();\n } catch (error) {\n notify.error('Could not create floor plan', describeError(error));\n }\n };\n\n const handleAttachImage = async (plan: FloorPlan, url: string) => {\n try {\n const response = await updateFloorPlan({\n variables: {\n id: plan.id,\n input: {\n venueId: plan.venueId,\n name: plan.name,\n imageUrl: url,\n dimensions: plan.dimensions ?? {},\n interactiveAreas: plan.interactiveAreas ?? {},\n },\n },\n });\n if (!mutationSucceeded(response, response.data?.updateFloorPlan?.id, 'Could not attach image')) {\n return;\n }\n await refetch();\n } catch (error) {\n notify.error('Could not attach image', describeError(error));\n }\n };\n\n const handleDelete = async (plan: FloorPlan) => {\n const response = await deleteFloorPlan({ variables: { id: plan.id } });\n if (response.error) {\n notify.error('Could not delete floor plan', response.error.message);\n throw response.error;\n }\n if (!response.data?.deleteFloorPlan) {\n notify.error('Could not delete floor plan', 'The server did not confirm the change.');\n throw new Error('delete-unconfirmed');\n }\n if (selectedId === plan.id) {\n setSelectedId(null);\n }\n notify.success('Floor plan deleted', plan.name);\n await refetch();\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate(`/venues/${id}`)}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venue\n </Button>\n <GradientText as=\"h1\" className=\"text-2xl font-bold\">\n {venue?.name ? `${venue.name} — Floor Plans` : 'Floor Plans'}\n </GradientText>\n <p className=\"text-sm text-text-secondary\">\n Map the hall layout, booth positions, stage and entrances so exhibitors and attendees can navigate on the\n day.\n </p>\n </div>\n <Button type=\"button\" onClick={() => setShowCreate((value) => !value)}>\n <Plus className=\"h-4 w-4\" />\n Add floor plan\n </Button>\n </div>\n\n {loading ? (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <Skeleton className=\"h-96 rounded-card lg:col-span-2\" />\n <Skeleton className=\"h-96 rounded-card\" />\n </div>\n ) : floorPlans.length === 0 && !showCreate ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title=\"No floor plans yet.\"\n description=\"Add one to start mapping this venue — upload the hall map and label its zones.\"\n action={\n <Button type=\"button\" onClick={() => setShowCreate(true)}>\n <Plus className=\"h-4 w-4\" />\n Add floor plan\n </Button>\n }\n />\n </GlassCard>\n ) : (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n {selectedPlan ? (\n <FloorPlanViewer plan={selectedPlan} zones={selectedZones} onAttachImage={handleAttachImage} />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex h-full items-center justify-center border-border-seam p-10\">\n <p className=\"text-sm text-text-secondary\">Add a floor plan to preview it here.</p>\n </GlassCard>\n )}\n </div>\n\n <div className=\"space-y-6\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Available Floor Plans</h2>\n <ul className=\"mt-3 space-y-2\" role=\"list\">\n {floorPlans.length === 0 ? (\n <li className=\"text-sm text-text-secondary\">None yet — create one below.</li>\n ) : (\n floorPlans.map((plan) => {\n const active = selectedPlan?.id === plan.id;\n return (\n <li key={plan.id}>\n <div\n className={`flex items-center gap-3 rounded-card border p-3 transition-colors ${\n active\n ? 'border-accent-primary bg-action-primary-bg/10'\n : 'border-border-seam hover:border-border-strong'\n }`}\n >\n <button\n type=\"button\"\n onClick={() => setSelectedId(plan.id)}\n className=\"flex min-w-0 flex-1 items-center gap-3 text-left focus-visible:outline-none\"\n aria-pressed={active}\n >\n <span\n className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-md ${\n active\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n <SquareStack className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">{plan.name}</span>\n <span className=\"block text-xs text-text-muted\">\n {plan.imageUrl ? 'Map attached' : 'No image yet'}\n </span>\n </span>\n </button>\n <ActionMenu\n ariaLabel={`Floor plan actions for ${plan.name}`}\n entityName={plan.name}\n deleteLabel=\"Delete floor plan\"\n onDelete={() => handleDelete(plan)}\n />\n </div>\n </li>\n );\n })\n )}\n </ul>\n </GlassCard>\n\n {selectedPlan ? (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <h2 className=\"text-sm font-semibold text-text-primary\">Plan Details</h2>\n {metrics.length > 0 ? (\n <dl className=\"mt-3 space-y-2\">\n {metrics.map((metric) => (\n <div key={metric.label} className=\"flex items-center justify-between text-sm\">\n <dt className=\"text-text-secondary\">{metric.label}</dt>\n <dd className=\"font-semibold tabular-nums text-text-primary\">{metric.value}</dd>\n </div>\n ))}\n </dl>\n ) : (\n <p className=\"mt-3 text-sm text-text-secondary\">No zones or dimensions captured for this plan yet.</p>\n )}\n </GlassCard>\n ) : null}\n\n {showCreate || floorPlans.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">Add a floor plan</h2>\n <div className=\"space-y-4\">\n <Input\n type=\"text\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n placeholder=\"Main Hall Layout\"\n aria-label=\"Floor plan name\"\n />\n <ImageUploadField\n label=\"Upload floor plan image\"\n folderPath={FLOOR_PLAN_FOLDER}\n value={imageUrl || null}\n onUploaded={setImageUrl}\n hint=\"Optional now — you can attach it later from the viewer.\"\n />\n <div className=\"flex justify-end gap-2\">\n {floorPlans.length > 0 ? (\n <Button type=\"button\" variant=\"ghost\" onClick={() => setShowCreate(false)}>\n Cancel\n </Button>\n ) : null}\n <Button type=\"button\" onClick={handleCreate} disabled={creating || !name.trim()}>\n <Plus className=\"h-4 w-4\" />\n {creating ? 'Creating…' : 'Create'}\n </Button>\n </div>\n </div>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEventfullyManaged as e } from "../../context/EventfullyManagedContext.js";
|
|
2
2
|
import { useVenueQuery as t } from "../../api/eventApi.js";
|
|
3
3
|
import { EntityTagsField as n } from "../../components/EntityTagsField.js";
|
|
4
|
-
import { useParams as r } from "react-router
|
|
4
|
+
import { useParams as r } from "react-router";
|
|
5
5
|
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
6
|
import { ArrowLeft as o, Building2 as s, Edit as c, MapPin as l, Megaphone as u, SquareStack as d, Users as f } from "lucide-react";
|
|
7
7
|
import { Badge as p, Button as m, GlassCard as h, GradientText as g, IllustratedEmptyState as _, NextSteps as v, PagePurpose as y, Skeleton as b } from "@burdenoff/fe-libs/ui";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VenueDetailPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/VenueDetailPage.tsx"],"sourcesContent":["import { ArrowLeft, Building2, Edit, MapPin, Megaphone, SquareStack, Users } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router-dom';\n\nimport { useVenueQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\n\ntype VenueRecord = {\n id: string;\n name: string;\n description?: string | null;\n addressLine1?: string | null;\n city?: string | null;\n state?: string | null;\n country?: string | null;\n postalCode?: string | null;\n capacity?: number | null;\n status: string;\n amenities: string[];\n pricing?: Record<string, unknown> | null;\n openingHours?: Record<string, unknown> | null;\n metadata?: Record<string, unknown> | null;\n};\n\ntype FloorPlanRecord = {\n id: string;\n name: string;\n imageUrl?: string | null;\n dimensions?: Record<string, unknown> | null;\n};\n\nexport default function VenueDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useVenueQuery(id);\n\n const venue = (data?.venue ?? null) as VenueRecord | null;\n const floorPlans = (data?.floorPlans?.items ?? []) as FloorPlanRecord[];\n\n if (loading) {\n return (\n <div className=\"space-y-6 p-6\">\n <Skeleton className=\"h-8 w-32\" />\n <Skeleton className=\"h-8 w-64\" />\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <Skeleton className=\"h-24 rounded-card\" />\n <Skeleton className=\"h-24 rounded-card\" />\n <Skeleton className=\"h-24 rounded-card\" />\n </div>\n <Skeleton className=\"h-40 rounded-card\" />\n </div>\n );\n }\n\n const statusVariant: 'success' | 'secondary' | 'warning' =\n venue?.status === 'ACTIVE' ? 'success' : venue?.status === 'MAINTENANCE' ? 'warning' : 'secondary';\n\n if (!venue) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venues\n </Button>\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Venue not found.\"\n description=\"This venue may have been removed or is outside your current scope.\"\n />\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n The full record for one venue. Review its location, capacity and status, see what amenities it offers, and\n manage the floor plans attendees will navigate. Come here to confirm a space is ready before you assign an event\n to it.\n </PagePurpose>\n\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venues\n </Button>\n <div>\n <GradientText as=\"h1\" className=\"text-2xl font-bold\">\n {venue.name}\n </GradientText>\n <p className=\"mt-1 text-sm text-text-secondary\">{venue.description ?? 'No description provided.'}</p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigate(`/venues/${venue.id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit Venue\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"h-4 w-4 text-action-primary-bg\" />\n Location\n </div>\n <p className=\"text-sm text-text-secondary\">\n {[venue.addressLine1, venue.city, venue.state, venue.country, venue.postalCode]\n .filter(Boolean)\n .join(', ') || 'Location not configured'}\n </p>\n </GlassCard>\n <GlassCard treatment=\"glass\" glow className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Users className=\"h-4 w-4 text-action-primary-bg\" />\n Capacity\n </div>\n <p className=\"text-sm tabular-nums text-text-secondary\">{(venue.capacity ?? 0).toLocaleString()} attendees</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Building2 className=\"h-4 w-4 text-action-primary-bg\" />\n Status\n </div>\n <Badge variant={statusVariant} className=\"uppercase\">\n {venue.status}\n </Badge>\n </GlassCard>\n </div>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Amenities</h2>\n <div className=\"mt-3 flex flex-wrap gap-2\">\n {venue.amenities.length > 0 ? (\n venue.amenities.map((amenity) => (\n <Badge key={amenity} variant=\"secondary\">\n {amenity}\n </Badge>\n ))\n ) : (\n <p className=\"text-sm text-text-secondary\">No amenities configured.</p>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this venue so you can group and filter it — for example by region, tier or amenity profile.\n </p>\n <EntityTagsField entityType=\"EVENTFULLYMANAGED_VENUE\" entityId={venue.id} label=\"Venue tags\" className=\"mt-3\" />\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Floor Plans</h2>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate(`/venues/${venue.id}/floor-plans`)}>\n <SquareStack className=\"h-4 w-4\" />\n Manage Floor Plans\n </Button>\n </div>\n <div className=\"mt-4\">\n {floorPlans.length > 0 ? (\n <ul className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {floorPlans.map((plan) => (\n <li key={plan.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/venues/${venue.id}/floor-plans`)}\n className=\"flex w-full items-center gap-3 rounded-card border border-border-seam p-3 text-left transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-md bg-bg-sunken\">\n {plan.imageUrl ? (\n <img\n src={plan.imageUrl}\n alt={`${plan.name} floor plan`}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <SquareStack className=\"h-5 w-5 text-text-muted\" />\n )}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary\">{plan.name}</span>\n <span className=\"block text-sm text-text-secondary\">\n {plan.imageUrl ? 'Map attached' : 'No image configured'}\n </span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title=\"No floor plans have been added yet.\"\n className=\"py-8\"\n />\n )}\n </div>\n </GlassCard>\n\n <NextSteps\n storageKey=\"eventfullymanaged-venue-detail\"\n title=\"Get this venue ready to host\"\n steps={[\n {\n id: 'edit-venue',\n label: 'Edit venue details',\n description: 'Update capacity, address, status and amenities.',\n icon: <Edit className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/edit`),\n },\n {\n id: 'floor-plans',\n label: 'Add floor plans',\n description: 'Map the layout so attendees and staff can navigate.',\n icon: <SquareStack className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/floor-plans`),\n },\n {\n id: 'resources',\n label: 'Review resource readiness',\n description: 'Check capacity, amenities and plans at a glance.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/resources`),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAyCA,SAAwB,IAAkB;CACxC,IAAM,EAAE,QAAK,OAAO,GAA2B,EACzC,EAAE,gBAAa,GAAsB,EACrC,EAAE,SAAM,eAAY,EAAc,EAAG,EAErC,IAAS,GAAM,SAAS,MACxB,IAAc,GAAM,YAAY,SAAS,EAAE;AAEjD,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;GACjC,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA;GACjC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAU,WAAU,qBAAsB,CAAA;KAC1C,kBAAC,GAAD,EAAU,WAAU,qBAAsB,CAAA;KAC1C,kBAAC,GAAD,EAAU,WAAU,qBAAsB,CAAA;KACtC;;GACN,kBAAC,GAAD,EAAU,WAAU,qBAAsB,CAAA;GACtC;;CAIV,IAAM,IACJ,GAAO,WAAW,WAAW,YAAY,GAAO,WAAW,gBAAgB,YAAY;AAoBzF,QAlBK,IAmBH,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAA,UAAa,sOAIC,CAAA;GAEd,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAQ,MAAK;MAAK,eAAe,EAAS,UAAU;gBAAlF,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,iBAE1B;SACT,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAc,IAAG;MAAK,WAAU;gBAC7B,EAAM;MACM,CAAA,EACf,kBAAC,KAAD;MAAG,WAAU;gBAAoC,EAAM,eAAe;MAA+B,CAAA,CACjG,EAAA,CAAA,CACF;QACN,kBAAC,GAAD;KAAQ,MAAK;KAAS,eAAe,EAAS,WAAW,EAAM,GAAG,OAAO;eAAzE,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,aAErB;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,kCAAmC,CAAA,EAAA,WAEjD;UACN,kBAAC,KAAD;OAAG,WAAU;iBACV;QAAC,EAAM;QAAc,EAAM;QAAM,EAAM;QAAO,EAAM;QAAS,EAAM;QAAW,CAC5E,OAAO,QAAQ,CACf,KAAK,KAAK,IAAI;OACf,CAAA,CACM;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBAA5C,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,kCAAmC,CAAA,EAAA,WAEhD;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,EAA0D,EAAM,YAAY,GAAG,gBAAgB,EAAC,aAAc;SACpG;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,kCAAmC,CAAA,EAAA,SAEpD;UACN,kBAAC,GAAD;OAAO,SAAS;OAAe,WAAU;iBACtC,EAAM;OACD,CAAA,CACE;;KACR;;GAEN,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAc,CAAA,EACtE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,UAAU,SAAS,IACxB,EAAM,UAAU,KAAK,MACnB,kBAAC,GAAD;MAAqB,SAAQ;gBAC1B;MACK,EAFI,EAEJ,CACR,GAEF,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAA4B,CAAA;KAErE,CAAA,CACI;;GAEZ,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC;KACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAAS,CAAA;KACjE,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAE5C,CAAA;KACJ,kBAAC,GAAD;MAAiB,YAAW;MAA0B,UAAU,EAAM;MAAI,OAAM;MAAa,WAAU;MAAS,CAAA;KACtG;;GAEZ,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cAAvC,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAAgB,CAAA,EACxE,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAU,MAAK;MAAK,eAAe,EAAS,WAAW,EAAM,GAAG,cAAc;gBAA5G,CACE,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAAA,qBAE5B;QACL;QACN,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAW,SAAS,IACnB,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAW,KAAK,MACf,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAS,WAAW,EAAM,GAAG,cAAc;OAC1D,WAAU;iBAHZ,CAKE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK,WACJ,kBAAC,OAAD;SACE,KAAK,EAAK;SACV,KAAK,GAAG,EAAK,KAAK;SAClB,WAAU;SACV,CAAA,GAEF,kBAAC,GAAD,EAAa,WAAU,2BAA4B,CAAA;QAEhD,CAAA,EACP,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgD,EAAK;SAAY,CAAA,EACjF,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAK,WAAW,iBAAiB;SAC7B,CAAA,CACF;UACA;UACN,EAxBI,EAAK,GAwBT,CACL;MACC,CAAA,GAEL,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,WAAU;MACV,CAAA;KAEA,CAAA,CACI;;GAEZ,kBAAC,GAAD;IACE,YAAW;IACX,OAAM;IACN,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAClC,eAAe,EAAS,WAAW,EAAM,GAAG,OAAO;MACpD;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,eAAe,EAAS,WAAW,EAAM,GAAG,cAAc;MAC3D;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,eAAe,EAAS,WAAW,EAAM,GAAG,YAAY;MACzD;KACF;IACD,CAAA;GACE;MA5KJ,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAQ,MAAK;GAAS,SAAQ;GAAQ,MAAK;GAAK,eAAe,EAAS,UAAU;aAAlF,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,iBAE1B;MACT,kBAAC,GAAD;GAAW,WAAU;GAAQ,WAAU;aACrC,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,CAAA;GACQ,CAAA,CACR"}
|
|
1
|
+
{"version":3,"file":"VenueDetailPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/VenueDetailPage.tsx"],"sourcesContent":["import { ArrowLeft, Building2, Edit, MapPin, Megaphone, SquareStack, Users } from 'lucide-react';\nimport {\n Badge,\n Button,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useParams } from 'react-router';\n\nimport { useVenueQuery } from '../../api/eventApi';\nimport { EntityTagsField } from '../../components/EntityTagsField';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\n\ntype VenueRecord = {\n id: string;\n name: string;\n description?: string | null;\n addressLine1?: string | null;\n city?: string | null;\n state?: string | null;\n country?: string | null;\n postalCode?: string | null;\n capacity?: number | null;\n status: string;\n amenities: string[];\n pricing?: Record<string, unknown> | null;\n openingHours?: Record<string, unknown> | null;\n metadata?: Record<string, unknown> | null;\n};\n\ntype FloorPlanRecord = {\n id: string;\n name: string;\n imageUrl?: string | null;\n dimensions?: Record<string, unknown> | null;\n};\n\nexport default function VenueDetailPage() {\n const { id = '' } = useParams<{ id: string }>();\n const { navigate } = useEventfullyManaged();\n const { data, loading } = useVenueQuery(id);\n\n const venue = (data?.venue ?? null) as VenueRecord | null;\n const floorPlans = (data?.floorPlans?.items ?? []) as FloorPlanRecord[];\n\n if (loading) {\n return (\n <div className=\"space-y-6 p-6\">\n <Skeleton className=\"h-8 w-32\" />\n <Skeleton className=\"h-8 w-64\" />\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <Skeleton className=\"h-24 rounded-card\" />\n <Skeleton className=\"h-24 rounded-card\" />\n <Skeleton className=\"h-24 rounded-card\" />\n </div>\n <Skeleton className=\"h-40 rounded-card\" />\n </div>\n );\n }\n\n const statusVariant: 'success' | 'secondary' | 'warning' =\n venue?.status === 'ACTIVE' ? 'success' : venue?.status === 'MAINTENANCE' ? 'warning' : 'secondary';\n\n if (!venue) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venues\n </Button>\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Venue not found.\"\n description=\"This venue may have been removed or is outside your current scope.\"\n />\n </GlassCard>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PagePurpose>\n The full record for one venue. Review its location, capacity and status, see what amenities it offers, and\n manage the floor plans attendees will navigate. Come here to confirm a space is ready before you assign an event\n to it.\n </PagePurpose>\n\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-3\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Venues\n </Button>\n <div>\n <GradientText as=\"h1\" className=\"text-2xl font-bold\">\n {venue.name}\n </GradientText>\n <p className=\"mt-1 text-sm text-text-secondary\">{venue.description ?? 'No description provided.'}</p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigate(`/venues/${venue.id}/edit`)}>\n <Edit className=\"h-4 w-4\" />\n Edit Venue\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"h-4 w-4 text-action-primary-bg\" />\n Location\n </div>\n <p className=\"text-sm text-text-secondary\">\n {[venue.addressLine1, venue.city, venue.state, venue.country, venue.postalCode]\n .filter(Boolean)\n .join(', ') || 'Location not configured'}\n </p>\n </GlassCard>\n <GlassCard treatment=\"glass\" glow className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Users className=\"h-4 w-4 text-action-primary-bg\" />\n Capacity\n </div>\n <p className=\"text-sm tabular-nums text-text-secondary\">{(venue.capacity ?? 0).toLocaleString()} attendees</p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"mb-2 flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Building2 className=\"h-4 w-4 text-action-primary-bg\" />\n Status\n </div>\n <Badge variant={statusVariant} className=\"uppercase\">\n {venue.status}\n </Badge>\n </GlassCard>\n </div>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Amenities</h2>\n <div className=\"mt-3 flex flex-wrap gap-2\">\n {venue.amenities.length > 0 ? (\n venue.amenities.map((amenity) => (\n <Badge key={amenity} variant=\"secondary\">\n {amenity}\n </Badge>\n ))\n ) : (\n <p className=\"text-sm text-text-secondary\">No amenities configured.</p>\n )}\n </div>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Tags</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Label this venue so you can group and filter it — for example by region, tier or amenity profile.\n </p>\n <EntityTagsField entityType=\"EVENTFULLYMANAGED_VENUE\" entityId={venue.id} label=\"Venue tags\" className=\"mt-3\" />\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-5\">\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Floor Plans</h2>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => navigate(`/venues/${venue.id}/floor-plans`)}>\n <SquareStack className=\"h-4 w-4\" />\n Manage Floor Plans\n </Button>\n </div>\n <div className=\"mt-4\">\n {floorPlans.length > 0 ? (\n <ul className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {floorPlans.map((plan) => (\n <li key={plan.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/venues/${venue.id}/floor-plans`)}\n className=\"flex w-full items-center gap-3 rounded-card border border-border-seam p-3 text-left transition-[box-shadow,border-color] duration-200 hover:border-border-strong hover:shadow-elevation-1 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border-focus\"\n >\n <span className=\"flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-md bg-bg-sunken\">\n {plan.imageUrl ? (\n <img\n src={plan.imageUrl}\n alt={`${plan.name} floor plan`}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <SquareStack className=\"h-5 w-5 text-text-muted\" />\n )}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary\">{plan.name}</span>\n <span className=\"block text-sm text-text-secondary\">\n {plan.imageUrl ? 'Map attached' : 'No image configured'}\n </span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-files\"\n title=\"No floor plans have been added yet.\"\n className=\"py-8\"\n />\n )}\n </div>\n </GlassCard>\n\n <NextSteps\n storageKey=\"eventfullymanaged-venue-detail\"\n title=\"Get this venue ready to host\"\n steps={[\n {\n id: 'edit-venue',\n label: 'Edit venue details',\n description: 'Update capacity, address, status and amenities.',\n icon: <Edit className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/edit`),\n },\n {\n id: 'floor-plans',\n label: 'Add floor plans',\n description: 'Map the layout so attendees and staff can navigate.',\n icon: <SquareStack className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/floor-plans`),\n },\n {\n id: 'resources',\n label: 'Review resource readiness',\n description: 'Check capacity, amenities and plans at a glance.',\n icon: <Megaphone className=\"h-4 w-4\" />,\n onClick: () => navigate(`/venues/${venue.id}/resources`),\n },\n ]}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { notify as t } from "../../lib/toast.js";
|
|
|
3
3
|
import { useCreateVenueMutation as n, useUpdateVenueMutation as r, useVenueQuery as i } from "../../api/eventApi.js";
|
|
4
4
|
import { describeError as a, mutationSucceeded as o } from "../../lib/mutationResult.js";
|
|
5
5
|
import { useEffect as s, useMemo as c, useState as l } from "react";
|
|
6
|
-
import { useParams as u } from "react-router
|
|
6
|
+
import { useParams as u } from "react-router";
|
|
7
7
|
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
8
8
|
import { ArrowLeft as p, Save as m } from "lucide-react";
|
|
9
9
|
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VenueFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/VenueFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n GradientText,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\n\nimport { useCreateVenueMutation, useUpdateVenueMutation, useVenueQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype VenueFormState = {\n name: string;\n description: string;\n addressLine1: string;\n city: string;\n state: string;\n country: string;\n postalCode: string;\n capacity: string;\n amenities: string;\n status: string;\n};\n\nconst initialState: VenueFormState = {\n name: '',\n description: '',\n addressLine1: '',\n city: '',\n state: '',\n country: 'India',\n postalCode: '',\n capacity: '',\n amenities: '',\n status: 'ACTIVE',\n};\n\nexport default function VenueFormPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n const { data } = useVenueQuery(id);\n const [createVenue, { loading: creating }] = useCreateVenueMutation();\n const [updateVenue, { loading: updating }] = useUpdateVenueMutation();\n const [form, setForm] = useState<VenueFormState>(initialState);\n\n useEffect(() => {\n if (!isEdit || !data?.venue) {\n return;\n }\n setForm({\n name: data.venue.name ?? '',\n description: data.venue.description ?? '',\n addressLine1: data.venue.addressLine1 ?? '',\n city: data.venue.city ?? '',\n state: data.venue.state ?? '',\n country: data.venue.country ?? 'India',\n postalCode: data.venue.postalCode ?? '',\n capacity: data.venue.capacity ? String(data.venue.capacity) : '',\n amenities: (data.venue.amenities ?? []).join(', '),\n status: data.venue.status ?? 'ACTIVE',\n });\n }, [data, isEdit]);\n\n const onChange = (field: keyof VenueFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const payload = {\n name: form.name,\n description: form.description,\n addressLine1: form.addressLine1,\n city: form.city,\n state: form.state,\n country: form.country,\n postalCode: form.postalCode,\n capacity: form.capacity ? Number(form.capacity) : null,\n amenities: form.amenities\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean),\n status: form.status,\n pricing: {},\n openingHours: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateVenue({ variables: { id, input: payload } });\n if (!mutationSucceeded(response, response.data?.updateVenue?.id, 'Could not update venue')) {\n return;\n }\n navigate(`/venues/${id}`);\n return;\n }\n\n const response = await createVenue({ variables: { input: payload } });\n const createdId = response.data?.createVenue?.id;\n if (!mutationSucceeded(response, createdId, 'Could not create venue')) {\n return;\n }\n navigate(`/venues/${createdId}`);\n } catch (error) {\n notify.error('Could not save venue', describeError(error));\n }\n };\n\n const isSaving = creating || updating;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n {t('eventfullymanaged.venues.form.backToVenues', 'Back to Venues')}\n </Button>\n <GradientText as=\"h1\" className=\"mt-3 text-2xl font-bold\">\n {isEdit\n ? t('eventfullymanaged.venues.form.editTitle', 'Edit Venue')\n : t('eventfullymanaged.venues.form.createTitle', 'Create Venue')}\n </GradientText>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {t('eventfullymanaged.venues.form.description', 'Manage venue details with live backend persistence.')}\n </p>\n </div>\n\n <PagePurpose>\n {t(\n 'eventfullymanaged.venues.form.purpose',\n 'Describe a space so you can reuse it across events. Capacity drives how many attendees you can register, the address helps people find it, and amenities tell organizers what is already provided. Fill this in once and every future event can simply point at this venue.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.name', 'Name')}\n </span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.capacity', 'Capacity')}\n </span>\n <Input\n value={form.capacity}\n onChange={(e) => onChange('capacity', e.target.value)}\n type=\"number\"\n min=\"0\"\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.description', 'Description')}\n </span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={3} />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-4\">\n <label className=\"space-y-1 text-sm md:col-span-2\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.address', 'Address')}\n </span>\n <Input value={form.addressLine1} onChange={(e) => onChange('addressLine1', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.city', 'City')}\n </span>\n <Input value={form.city} onChange={(e) => onChange('city', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.state', 'State')}\n </span>\n <Input value={form.state} onChange={(e) => onChange('state', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.country', 'Country')}\n </span>\n <Input value={form.country} onChange={(e) => onChange('country', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.postalCode', 'Postal Code')}\n </span>\n <Input value={form.postalCode} onChange={(e) => onChange('postalCode', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.status', 'Status')}\n </span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label={t('eventfullymanaged.venues.form.fields.status', 'Status')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ACTIVE\">\n {t('eventfullymanaged.venues.form.statusOptions.active', 'Active')}\n </SelectItem>\n <SelectItem value=\"INACTIVE\">\n {t('eventfullymanaged.venues.form.statusOptions.inactive', 'Inactive')}\n </SelectItem>\n <SelectItem value=\"MAINTENANCE\">\n {t('eventfullymanaged.venues.form.statusOptions.maintenance', 'Maintenance')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.amenities', 'Amenities (comma separated)')}\n </span>\n <Input\n value={form.amenities}\n onChange={(e) => onChange('amenities', e.target.value)}\n placeholder={t('eventfullymanaged.venues.form.amenitiesPlaceholder', 'wifi, parking, catering')}\n />\n </label>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={isSaving}>\n <Save className=\"h-4 w-4\" />\n {isSaving\n ? t('eventfullymanaged.venues.form.actions.saving', 'Saving...')\n : t('eventfullymanaged.venues.form.actions.save', 'Save Venue')}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VenueFormPage.js","names":[],"sources":["../../../../src/eventfullymanaged/pages/venues/VenueFormPage.tsx"],"sourcesContent":["/* eslint-disable react-hooks/set-state-in-effect */\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport {\n Button,\n EmphasisPanel,\n GradientText,\n Input,\n PagePurpose,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\n\nimport { useCreateVenueMutation, useUpdateVenueMutation, useVenueQuery } from '../../api/eventApi';\nimport { useEventfullyManaged } from '../../context/EventfullyManagedContext';\nimport { describeError, mutationSucceeded } from '../../lib/mutationResult';\nimport { notify } from '../../lib/toast';\n\ntype VenueFormState = {\n name: string;\n description: string;\n addressLine1: string;\n city: string;\n state: string;\n country: string;\n postalCode: string;\n capacity: string;\n amenities: string;\n status: string;\n};\n\nconst initialState: VenueFormState = {\n name: '',\n description: '',\n addressLine1: '',\n city: '',\n state: '',\n country: 'India',\n postalCode: '',\n capacity: '',\n amenities: '',\n status: 'ACTIVE',\n};\n\nexport default function VenueFormPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { id = '' } = useParams<{ id: string }>();\n const isEdit = useMemo(() => Boolean(id), [id]);\n const { navigate } = useEventfullyManaged();\n const { data } = useVenueQuery(id);\n const [createVenue, { loading: creating }] = useCreateVenueMutation();\n const [updateVenue, { loading: updating }] = useUpdateVenueMutation();\n const [form, setForm] = useState<VenueFormState>(initialState);\n\n useEffect(() => {\n if (!isEdit || !data?.venue) {\n return;\n }\n setForm({\n name: data.venue.name ?? '',\n description: data.venue.description ?? '',\n addressLine1: data.venue.addressLine1 ?? '',\n city: data.venue.city ?? '',\n state: data.venue.state ?? '',\n country: data.venue.country ?? 'India',\n postalCode: data.venue.postalCode ?? '',\n capacity: data.venue.capacity ? String(data.venue.capacity) : '',\n amenities: (data.venue.amenities ?? []).join(', '),\n status: data.venue.status ?? 'ACTIVE',\n });\n }, [data, isEdit]);\n\n const onChange = (field: keyof VenueFormState, value: string) => {\n setForm((prev) => ({ ...prev, [field]: value }));\n };\n\n const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n const payload = {\n name: form.name,\n description: form.description,\n addressLine1: form.addressLine1,\n city: form.city,\n state: form.state,\n country: form.country,\n postalCode: form.postalCode,\n capacity: form.capacity ? Number(form.capacity) : null,\n amenities: form.amenities\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean),\n status: form.status,\n pricing: {},\n openingHours: {},\n metadata: {},\n };\n\n try {\n if (isEdit) {\n const response = await updateVenue({ variables: { id, input: payload } });\n if (!mutationSucceeded(response, response.data?.updateVenue?.id, 'Could not update venue')) {\n return;\n }\n navigate(`/venues/${id}`);\n return;\n }\n\n const response = await createVenue({ variables: { input: payload } });\n const createdId = response.data?.createVenue?.id;\n if (!mutationSucceeded(response, createdId, 'Could not create venue')) {\n return;\n }\n navigate(`/venues/${createdId}`);\n } catch (error) {\n notify.error('Could not save venue', describeError(error));\n }\n };\n\n const isSaving = creating || updating;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => navigate('/venues')}>\n <ArrowLeft className=\"h-4 w-4\" />\n {t('eventfullymanaged.venues.form.backToVenues', 'Back to Venues')}\n </Button>\n <GradientText as=\"h1\" className=\"mt-3 text-2xl font-bold\">\n {isEdit\n ? t('eventfullymanaged.venues.form.editTitle', 'Edit Venue')\n : t('eventfullymanaged.venues.form.createTitle', 'Create Venue')}\n </GradientText>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {t('eventfullymanaged.venues.form.description', 'Manage venue details with live backend persistence.')}\n </p>\n </div>\n\n <PagePurpose>\n {t(\n 'eventfullymanaged.venues.form.purpose',\n 'Describe a space so you can reuse it across events. Capacity drives how many attendees you can register, the address helps people find it, and amenities tell organizers what is already provided. Fill this in once and every future event can simply point at this venue.'\n )}\n </PagePurpose>\n\n <EmphasisPanel>\n <form onSubmit={onSubmit} className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.name', 'Name')}\n </span>\n <Input value={form.name} onChange={(e) => onChange('name', e.target.value)} required />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.capacity', 'Capacity')}\n </span>\n <Input\n value={form.capacity}\n onChange={(e) => onChange('capacity', e.target.value)}\n type=\"number\"\n min=\"0\"\n />\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.description', 'Description')}\n </span>\n <Textarea value={form.description} onChange={(e) => onChange('description', e.target.value)} rows={3} />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-4\">\n <label className=\"space-y-1 text-sm md:col-span-2\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.address', 'Address')}\n </span>\n <Input value={form.addressLine1} onChange={(e) => onChange('addressLine1', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.city', 'City')}\n </span>\n <Input value={form.city} onChange={(e) => onChange('city', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.state', 'State')}\n </span>\n <Input value={form.state} onChange={(e) => onChange('state', e.target.value)} />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-3\">\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.country', 'Country')}\n </span>\n <Input value={form.country} onChange={(e) => onChange('country', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.postalCode', 'Postal Code')}\n </span>\n <Input value={form.postalCode} onChange={(e) => onChange('postalCode', e.target.value)} />\n </label>\n <label className=\"space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.status', 'Status')}\n </span>\n <Select value={form.status} onValueChange={(value) => onChange('status', value)}>\n <SelectTrigger aria-label={t('eventfullymanaged.venues.form.fields.status', 'Status')}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ACTIVE\">\n {t('eventfullymanaged.venues.form.statusOptions.active', 'Active')}\n </SelectItem>\n <SelectItem value=\"INACTIVE\">\n {t('eventfullymanaged.venues.form.statusOptions.inactive', 'Inactive')}\n </SelectItem>\n <SelectItem value=\"MAINTENANCE\">\n {t('eventfullymanaged.venues.form.statusOptions.maintenance', 'Maintenance')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n </div>\n\n <label className=\"block space-y-1 text-sm\">\n <span className=\"font-medium text-text-primary\">\n {t('eventfullymanaged.venues.form.fields.amenities', 'Amenities (comma separated)')}\n </span>\n <Input\n value={form.amenities}\n onChange={(e) => onChange('amenities', e.target.value)}\n placeholder={t('eventfullymanaged.venues.form.amenitiesPlaceholder', 'wifi, parking, catering')}\n />\n </label>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={isSaving}>\n <Save className=\"h-4 w-4\" />\n {isSaving\n ? t('eventfullymanaged.venues.form.actions.saving', 'Saving...')\n : t('eventfullymanaged.venues.form.actions.save', 'Save Venue')}\n </Button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell bridge — unified accessor for capabilities exposed by the host app
|
|
3
|
+
* shell on `globalThis.__burdenoffShell`. Each accessor is fully optional:
|
|
4
|
+
* the MFE must work in isolation (Storybook/standalone Vite dev) when the
|
|
5
|
+
* shell global is absent.
|
|
6
|
+
*
|
|
7
|
+
* Capabilities consumed:
|
|
8
|
+
* - `reportError` — central SRE error sink (Rybbit + backend forward)
|
|
9
|
+
* - `graphqlWsClient` — graphql-ws Client instance (probe-only; subs fall
|
|
10
|
+
* back to polling when absent)
|
|
11
|
+
*
|
|
12
|
+
* Ported from `microfe-comradecircle/src/comradecircle/utils/shellBridge.ts`
|
|
13
|
+
* (BOFF-4455's reference pattern, itself ported from `microfe-channel`) —
|
|
14
|
+
* same shell contract, product-scoped copy per the "no cross-MFE imports"
|
|
15
|
+
* convention. Used by the BOFF-4318 virtual-session WebRTC signaling core.
|
|
16
|
+
*/
|
|
17
|
+
export interface ShellReportErrorMeta {
|
|
18
|
+
module: string;
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
componentStack?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Minimal subset of a graphql-ws Client we rely on.
|
|
24
|
+
* https://the-guild.dev/graphql/ws/recipes#use-the-client-without-apollo
|
|
25
|
+
*/
|
|
26
|
+
export interface GraphqlWsLike {
|
|
27
|
+
subscribe: (payload: {
|
|
28
|
+
query: string;
|
|
29
|
+
variables?: Record<string, unknown>;
|
|
30
|
+
operationName?: string;
|
|
31
|
+
}, sink: {
|
|
32
|
+
next: (value: {
|
|
33
|
+
data?: unknown;
|
|
34
|
+
errors?: readonly unknown[];
|
|
35
|
+
}) => void;
|
|
36
|
+
error: (err: unknown) => void;
|
|
37
|
+
complete: () => void;
|
|
38
|
+
}) => () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Forward an error to the shell's central error sink. Falls back to
|
|
42
|
+
* `console.error` only when the sink is missing.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reportShellError(error: unknown, meta: ShellReportErrorMeta, fallbackLabel?: string): void;
|
|
45
|
+
export declare function getGraphqlWsClient(): GraphqlWsLike | undefined;
|
|
46
|
+
//# sourceMappingURL=shellBridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellBridge.d.ts","sourceRoot":"","sources":["../../../src/eventfullymanaged/utils/shellBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,CACT,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,EACvF,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,KAAK,EAAE;YAAE,IAAI,CAAC,EAAE,OAAO,CAAC;YAAC,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;SAAE,KAAK,IAAI,CAAC;QACvE,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;QAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,KACE,MAAM,IAAI,CAAC;CACjB;AAeD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAgBzG;AAED,wBAAgB,kBAAkB,IAAI,aAAa,GAAG,SAAS,CAE9D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/eventfullymanaged/utils/shellBridge.ts
|
|
2
|
+
function e() {
|
|
3
|
+
return globalThis.__burdenoffShell;
|
|
4
|
+
}
|
|
5
|
+
function t(t, n, r) {
|
|
6
|
+
let i = t instanceof Error ? t : Error(String(t)), a = e();
|
|
7
|
+
if (a?.reportError) try {
|
|
8
|
+
a.reportError(i, n);
|
|
9
|
+
return;
|
|
10
|
+
} catch {}
|
|
11
|
+
console.error(r || `[${n.module}]`, i, n.context ?? {});
|
|
12
|
+
}
|
|
13
|
+
function n() {
|
|
14
|
+
return e()?.graphqlWsClient;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { n as getGraphqlWsClient, t as reportShellError };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=shellBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellBridge.js","names":[],"sources":["../../../src/eventfullymanaged/utils/shellBridge.ts"],"sourcesContent":["/**\n * Shell bridge — unified accessor for capabilities exposed by the host app\n * shell on `globalThis.__burdenoffShell`. Each accessor is fully optional:\n * the MFE must work in isolation (Storybook/standalone Vite dev) when the\n * shell global is absent.\n *\n * Capabilities consumed:\n * - `reportError` — central SRE error sink (Rybbit + backend forward)\n * - `graphqlWsClient` — graphql-ws Client instance (probe-only; subs fall\n * back to polling when absent)\n *\n * Ported from `microfe-comradecircle/src/comradecircle/utils/shellBridge.ts`\n * (BOFF-4455's reference pattern, itself ported from `microfe-channel`) —\n * same shell contract, product-scoped copy per the \"no cross-MFE imports\"\n * convention. Used by the BOFF-4318 virtual-session WebRTC signaling core.\n */\n\nexport interface ShellReportErrorMeta {\n module: string;\n context?: Record<string, unknown>;\n componentStack?: string;\n}\n\n/**\n * Minimal subset of a graphql-ws Client we rely on.\n * https://the-guild.dev/graphql/ws/recipes#use-the-client-without-apollo\n */\nexport interface GraphqlWsLike {\n subscribe: (\n payload: { query: string; variables?: Record<string, unknown>; operationName?: string },\n sink: {\n next: (value: { data?: unknown; errors?: readonly unknown[] }) => void;\n error: (err: unknown) => void;\n complete: () => void;\n }\n ) => () => void;\n}\n\ninterface BurdenoffShell {\n reportError?: (\n sourceOrError: string | Error,\n errorOrMeta?: Error | ShellReportErrorMeta,\n meta?: ShellReportErrorMeta | Record<string, unknown>\n ) => void;\n graphqlWsClient?: GraphqlWsLike;\n}\n\nfunction getShell(): BurdenoffShell | undefined {\n return (globalThis as unknown as { __burdenoffShell?: BurdenoffShell }).__burdenoffShell;\n}\n\n/**\n * Forward an error to the shell's central error sink. Falls back to\n * `console.error` only when the sink is missing.\n */\nexport function reportShellError(error: unknown, meta: ShellReportErrorMeta, fallbackLabel?: string): void {\n const err = error instanceof Error ? error : new Error(String(error));\n const sink = getShell();\n if (sink?.reportError) {\n try {\n sink.reportError(err, meta);\n return;\n } catch {\n /* fall through to console */\n }\n }\n if (fallbackLabel) {\n console.error(fallbackLabel, err, meta.context ?? {});\n } else {\n console.error(`[${meta.module}]`, err, meta.context ?? {});\n }\n}\n\nexport function getGraphqlWsClient(): GraphqlWsLike | undefined {\n return getShell()?.graphqlWsClient;\n}\n"],"mappings":";AA+CA,SAAS,IAAuC;AAC9C,QAAQ,WAAgE;;AAO1E,SAAgB,EAAiB,GAAgB,GAA4B,GAA8B;CACzG,IAAM,IAAM,aAAiB,QAAQ,IAAY,MAAM,OAAO,EAAM,CAAC,EAC/D,IAAO,GAAU;AACvB,KAAI,GAAM,YACR,KAAI;AACF,IAAK,YAAY,GAAK,EAAK;AAC3B;SACM;AAIV,CACE,QAAQ,MADN,KAGY,IAAI,EAAK,OAAO,IAFD,GAAK,EAAK,WAAW,EAAE,CAEM;;AAI9D,SAAgB,IAAgD;AAC9D,QAAO,GAAU,EAAE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-eventfullymanaged",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.801.1",
|
|
4
4
|
"description": "EventfullyManaged - End-to-end event management platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
"main": "./dist/index.js",
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "vite",
|
|
24
|
-
"build": "MFE_DTS=1 tsc && MFE_DTS=1 vite build",
|
|
25
|
-
"build:types": "MFE_DTS=1 tsc && MFE_DTS=1 vite build",
|
|
24
|
+
"build": "MFE_DTS=1 tsc && MFE_DTS=1 bash scripts/build-limits.sh --gate vite build",
|
|
25
|
+
"build:types": "MFE_DTS=1 tsc && MFE_DTS=1 bash scripts/build-limits.sh --gate vite build",
|
|
26
26
|
"lint": "eslint .",
|
|
27
27
|
"lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
|
|
28
28
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
29
|
-
"type:check": "tsc --noEmit",
|
|
30
|
-
"test": "vitest run",
|
|
29
|
+
"type:check": "bash scripts/build-limits.sh tsc --noEmit",
|
|
30
|
+
"test": "bash scripts/build-limits.sh vitest run",
|
|
31
31
|
"test:watch": "vitest",
|
|
32
|
-
"test:coverage": "vitest run --coverage",
|
|
33
|
-
"sanity": "bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build",
|
|
32
|
+
"test:coverage": "bash scripts/build-limits.sh vitest run --coverage",
|
|
33
|
+
"sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build'",
|
|
34
34
|
"storybook": "storybook dev -p 6006",
|
|
35
35
|
"build-storybook": "storybook build",
|
|
36
36
|
"test:storybook": "test-storybook",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@apollo/client": "^4.0.11",
|
|
49
|
-
"@burdenoff/fe-libs": "2026.
|
|
49
|
+
"@burdenoff/fe-libs": "2026.801.1",
|
|
50
50
|
"date-fns": "^4.1.0",
|
|
51
51
|
"lucide-react": "^0.468.0",
|
|
52
52
|
"react": "^19.0.0",
|
|
53
53
|
"react-dom": "^19.0.0",
|
|
54
|
-
"react-router
|
|
54
|
+
"react-router": "^8.3.0",
|
|
55
55
|
"graphql": "^16.12.0",
|
|
56
56
|
"recharts": "^2.15.0",
|
|
57
57
|
"sonner": "^2.0.7",
|