@burdenoff/microfe-channel 2026.1013.2 → 2026.1114.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 (161) hide show
  1. package/dist/channel/ChannelRoot.d.ts +0 -2
  2. package/dist/channel/ChannelRoot.js +9 -6
  3. package/dist/channel/ChannelRoot.js.map +1 -1
  4. package/dist/channel/ChannelRoutes.d.ts +1 -2
  5. package/dist/channel/ChannelRoutes.js +5 -6
  6. package/dist/channel/ChannelRoutes.js.map +1 -1
  7. package/dist/channel/components/AdapterCredentialsForm.d.ts +2 -4
  8. package/dist/channel/components/AdapterCredentialsForm.js +1 -2
  9. package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
  10. package/dist/channel/components/FeatureNotEnabled.d.ts +1 -2
  11. package/dist/channel/components/FeatureNotEnabled.js +1 -2
  12. package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
  13. package/dist/channel/components/MarkdownEditor.d.ts +1 -2
  14. package/dist/channel/components/MarkdownEditor.js +2 -5
  15. package/dist/channel/components/MarkdownEditor.js.map +1 -1
  16. package/dist/channel/components/PermissionGuard.d.ts +0 -2
  17. package/dist/channel/components/PermissionGuard.js +1 -2
  18. package/dist/channel/components/PermissionGuard.js.map +1 -1
  19. package/dist/channel/config/adapterCredentials.d.ts +0 -2
  20. package/dist/channel/config/adapterCredentials.js +3 -7
  21. package/dist/channel/config/adapterCredentials.js.map +1 -1
  22. package/dist/channel/constants/permissions.d.ts +0 -1
  23. package/dist/channel/constants/permissions.js +1 -2
  24. package/dist/channel/constants/permissions.js.map +1 -1
  25. package/dist/channel/graphql/global-client.d.ts +0 -2
  26. package/dist/channel/graphql/global-client.js +5 -6
  27. package/dist/channel/graphql/global-client.js.map +1 -1
  28. package/dist/channel/hooks/useChannelOrgSettings.d.ts +0 -2
  29. package/dist/channel/hooks/useChannelOrgSettings.js +20 -21
  30. package/dist/channel/hooks/useChannelOrgSettings.js.map +1 -1
  31. package/dist/channel/hooks/useChannelSubscription.d.ts +0 -1
  32. package/dist/channel/hooks/useChannelSubscription.js +7 -12
  33. package/dist/channel/hooks/useChannelSubscription.js.map +1 -1
  34. package/dist/channel/hooks/useChannels.d.ts +20 -30
  35. package/dist/channel/hooks/useChannels.js +100 -101
  36. package/dist/channel/hooks/useChannels.js.map +1 -1
  37. package/dist/channel/hooks/useTr.d.ts +0 -1
  38. package/dist/channel/hooks/useTr.js +1 -2
  39. package/dist/channel/hooks/useTr.js.map +1 -1
  40. package/dist/channel/index.d.ts +0 -1
  41. package/dist/channel/index.js +3 -2
  42. package/dist/channel/modules/channels/hooks/useExport.d.ts +0 -2
  43. package/dist/channel/modules/channels/hooks/useExport.js +77 -80
  44. package/dist/channel/modules/channels/hooks/useExport.js.map +1 -1
  45. package/dist/channel/modules/channels/i18n.d.ts +0 -2
  46. package/dist/channel/modules/channels/i18n.js +1 -2
  47. package/dist/channel/modules/channels/i18n.js.map +1 -1
  48. package/dist/channel/modules/channels/pages/ChannelDashboardPage.d.ts +1 -2
  49. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +10 -11
  50. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  51. package/dist/channel/modules/channels/pages/ChannelDetailsPage.d.ts +1 -2
  52. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +400 -401
  53. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  54. package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts +1 -2
  55. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +8 -7
  56. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  57. package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts +1 -2
  58. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +5 -10
  59. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  60. package/dist/channel/modules/channels/pages/ChannelsListPage.d.ts +1 -2
  61. package/dist/channel/modules/channels/pages/ChannelsListPage.js +287 -288
  62. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  63. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts +1 -2
  64. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +11 -12
  65. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  66. package/dist/channel/modules/channels/pages/ConnectionsListPage.d.ts +1 -2
  67. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +1 -2
  68. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  69. package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts +1 -2
  70. package/dist/channel/modules/channels/pages/CreateChannelPage.js +4 -5
  71. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  72. package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts +1 -2
  73. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +37 -38
  74. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  75. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.d.ts +1 -2
  76. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +7 -12
  77. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  78. package/dist/channel/modules/channels/pages/EditChannelPage.d.ts +1 -2
  79. package/dist/channel/modules/channels/pages/EditChannelPage.js +105 -103
  80. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  81. package/dist/channel/modules/channels/pages/EditConnectionPage.d.ts +1 -2
  82. package/dist/channel/modules/channels/pages/EditConnectionPage.js +22 -23
  83. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  84. package/dist/channel/modules/channels/pages/SendMessagePage.d.ts +1 -2
  85. package/dist/channel/modules/channels/pages/SendMessagePage.js +180 -187
  86. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  87. package/dist/channel/modules/channels/pages/WebhookEventsPage.d.ts +1 -2
  88. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +144 -149
  89. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  90. package/dist/channel/pages/ChannelLandingPage.d.ts +1 -2
  91. package/dist/channel/pages/ChannelLandingPage.js +20 -21
  92. package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
  93. package/dist/channel/providers/ChannelProvider.d.ts +0 -2
  94. package/dist/channel/providers/ChannelProvider.js +1 -2
  95. package/dist/channel/providers/ChannelProvider.js.map +1 -1
  96. package/dist/channel/types/index.d.ts +0 -2
  97. package/dist/channel/utils/shellBridge.d.ts +0 -1
  98. package/dist/channel/utils/shellBridge.js +1 -2
  99. package/dist/channel/utils/shellBridge.js.map +1 -1
  100. package/dist/dev/App.d.ts +1 -2
  101. package/dist/dev/main.d.ts +0 -2
  102. package/dist/generated/global-operations.d.ts +885 -336
  103. package/dist/generated/global-operations.js +2 -3
  104. package/dist/generated/global-operations.js.map +1 -1
  105. package/dist/generated/global-types.d.ts +2 -22
  106. package/dist/generated/global-types.js +1 -2
  107. package/dist/generated/global-types.js.map +1 -1
  108. package/dist/generated/wspace-operations.d.ts +4 -2
  109. package/dist/generated/wspace-types.d.ts +4 -24
  110. package/dist/index.d.ts +0 -1
  111. package/dist/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js +1 -2
  112. package/dist/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js.map +1 -1
  113. package/dist/node_modules/@apollo/client/errors/utils.js +1 -2
  114. package/dist/node_modules/@apollo/client/errors/utils.js.map +1 -1
  115. package/dist/translations/en.d.ts +0 -1
  116. package/dist/utils/nativeBridge.d.ts +0 -1
  117. package/dist/utils/nativeBridge.js +1 -2
  118. package/package.json +65 -43
  119. package/dist/channel/ChannelRoot.d.ts.map +0 -1
  120. package/dist/channel/ChannelRoutes.d.ts.map +0 -1
  121. package/dist/channel/components/AdapterCredentialsForm.d.ts.map +0 -1
  122. package/dist/channel/components/FeatureNotEnabled.d.ts.map +0 -1
  123. package/dist/channel/components/MarkdownEditor.d.ts.map +0 -1
  124. package/dist/channel/components/PermissionGuard.d.ts.map +0 -1
  125. package/dist/channel/config/adapterCredentials.d.ts.map +0 -1
  126. package/dist/channel/constants/permissions.d.ts.map +0 -1
  127. package/dist/channel/graphql/global-client.d.ts.map +0 -1
  128. package/dist/channel/hooks/useChannelOrgSettings.d.ts.map +0 -1
  129. package/dist/channel/hooks/useChannelSubscription.d.ts.map +0 -1
  130. package/dist/channel/hooks/useChannels.d.ts.map +0 -1
  131. package/dist/channel/hooks/useTr.d.ts.map +0 -1
  132. package/dist/channel/index.d.ts.map +0 -1
  133. package/dist/channel/modules/channels/hooks/useExport.d.ts.map +0 -1
  134. package/dist/channel/modules/channels/i18n.d.ts.map +0 -1
  135. package/dist/channel/modules/channels/pages/ChannelDashboardPage.d.ts.map +0 -1
  136. package/dist/channel/modules/channels/pages/ChannelDetailsPage.d.ts.map +0 -1
  137. package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts.map +0 -1
  138. package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts.map +0 -1
  139. package/dist/channel/modules/channels/pages/ChannelsListPage.d.ts.map +0 -1
  140. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts.map +0 -1
  141. package/dist/channel/modules/channels/pages/ConnectionsListPage.d.ts.map +0 -1
  142. package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts.map +0 -1
  143. package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts.map +0 -1
  144. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.d.ts.map +0 -1
  145. package/dist/channel/modules/channels/pages/EditChannelPage.d.ts.map +0 -1
  146. package/dist/channel/modules/channels/pages/EditConnectionPage.d.ts.map +0 -1
  147. package/dist/channel/modules/channels/pages/SendMessagePage.d.ts.map +0 -1
  148. package/dist/channel/modules/channels/pages/WebhookEventsPage.d.ts.map +0 -1
  149. package/dist/channel/pages/ChannelLandingPage.d.ts.map +0 -1
  150. package/dist/channel/providers/ChannelProvider.d.ts.map +0 -1
  151. package/dist/channel/types/index.d.ts.map +0 -1
  152. package/dist/channel/utils/shellBridge.d.ts.map +0 -1
  153. package/dist/dev/App.d.ts.map +0 -1
  154. package/dist/dev/main.d.ts.map +0 -1
  155. package/dist/generated/global-operations.d.ts.map +0 -1
  156. package/dist/generated/global-types.d.ts.map +0 -1
  157. package/dist/generated/wspace-operations.d.ts.map +0 -1
  158. package/dist/generated/wspace-types.d.ts.map +0 -1
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/translations/en.d.ts.map +0 -1
  161. package/dist/utils/nativeBridge.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WebhookEventsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/WebhookEventsPage.tsx"],"sourcesContent":["/**\n * Webhook Events Page\n *\n * Displays webhook event history for a specific channel\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Eye,\n EyeOff,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type WebhookEvent as WebhookEventType } from '../../../hooks/useChannels';\nimport type { WebhookProcessingStatus } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent } from '../../../utils/shellBridge';\nimport {\n WEBHOOK_RECEIVED_SUBSCRIPTION,\n useChannelSubscription,\n} from '../../../hooks/useChannelSubscription';\nimport { useExportWebhookEvents } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n WebhookProcessingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n PROCESSING: {\n label: 'Processing',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n COMPLETED: {\n label: 'Completed',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n FAILED: {\n label: 'Failed',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n IGNORED: {\n label: 'Ignored',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: EyeOff,\n },\n};\n\nexport function WebhookEventsPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_WEBHOOK_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportWebhooks,\n isExporting: isExportingWebhooks,\n exportError: exportWebhooksError,\n } = useExportWebhookEvents(organizationId ?? undefined, channelId);\n const [isLoadingEvents, setIsLoadingEvents] = useState(true);\n const [events, setEvents] = useState<WebhookEventType[]>([]);\n const [expandedEvent, setExpandedEvent] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchWebhookEvents } = useChannel(channelId);\n\n const loadEvents = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingEvents(true);\n try {\n const result = await fetchWebhookEvents(limit, cursor);\n setEvents(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingEvents(false);\n }\n },\n [channelId, fetchWebhookEvents, limit]\n );\n\n // Keep refs current after every render so pagination and subscription callbacks\n // always call the latest function/value without those variables in deps.\n const loadEventsRef = useRef(loadEvents);\n const cursorStackRef = useRef(cursorStack);\n const currentPageIdxRef = useRef(currentPageIdx);\n useEffect(() => {\n loadEventsRef.current = loadEvents;\n cursorStackRef.current = cursorStack;\n currentPageIdxRef.current = currentPageIdx;\n });\n // Tracks whether the [limit] effect has already fired on mount so the\n // [currentPageIdx] effect skips its first run (avoiding a double load).\n const isFirstPageRender = useRef(true);\n\n // Reset cursor stack and reload whenever page size changes (also fires on mount).\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadEventsRef.current(undefined);\n });\n }, [limit]);\n\n // Navigate to a specific page; skip the very first render to avoid\n // a duplicate load alongside the [limit] effect above.\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadEventsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadEvents(cursorStack[currentPageIdx]);\n if (nextCursor) {\n setCursorStack((prev) => {\n const next = [...prev];\n if (!next[currentPageIdx + 1]) {\n next[currentPageIdx + 1] = nextCursor;\n }\n return next;\n });\n }\n setCurrentPageIdx((p) => p + 1);\n }, [cursorStack, currentPageIdx, loadEvents]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadEvents(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadEvents]);\n\n const hasNextPage = events.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n // Real-time webhook stream. Subscribes when the gateway exposes\n // `webhookReceived`; otherwise polls every 60s as a graceful fallback.\n useChannelSubscription({\n query: WEBHOOK_RECEIVED_SUBSCRIPTION,\n variables: { channelId },\n operationName: 'WebhookReceived',\n enabled: Boolean(channelId),\n onData: useCallback(() => {\n emitShellEvent('webhook.received', { channelId });\n void loadEventsRef.current(cursorStackRef.current[currentPageIdxRef.current]);\n }, [channelId]),\n pollFn: useCallback(() => {\n void loadEventsRef.current(cursorStackRef.current[currentPageIdxRef.current]);\n }, []),\n pollIntervalMs: 60_000,\n });\n\n const getStatusConfig = (status: WebhookProcessingStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n // Loading state (only show full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_WEBHOOK_READ}>\n <div className=\"space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.webhookEvents', 'Webhook Events')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">{channel.name} - Event history</p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingEvents ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingEvents,\n onSelect: () => void loadEvents(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingWebhooks ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingWebhooks,\n onSelect: () => void exportWebhooks(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportWebhooksError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportWebhooksError}</p>\n </div>\n )}\n\n {/* Page purpose */}\n <PagePurpose icon={<Clock size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.webhookEventsPurpose',\n \"Inbound events the provider pushed to this channel — delivery receipts, replies, status callbacks and the like. Each row shows how the event was processed, whether it was a duplicate, and the raw payload so you can inspect exactly what arrived. Use it to verify your webhook endpoint is receiving traffic and to debug why an event wasn't handled. Records are kept for 7 days.\"\n )}\n </PagePurpose>\n\n {/* Emphasis — webhook total summary */}\n <EmphasisPanel className=\"flex items-center gap-3 p-4\">\n <span className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <Clock size={18} className=\"text-accent-purple\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {total}{' '}\n {total === 1\n ? tr('channel.webhookEventSingular', 'webhook event')\n : tr('channel.webhookEventsPlural', 'webhook events')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.webhookEventsBannerHint',\n 'All incoming webhooks received by this channel, retained for 7 days. Expand a row to inspect its raw payload.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Events List */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam rounded-lg overflow-hidden p-0\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full min-w-[600px]\">\n <thead className=\"bg-bg-sunken/50 border-b border-border-subtle\">\n <tr>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.eventType', 'Event Type')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.duplicate', 'Duplicate')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.processedAt', 'Processed At')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.error', 'Error')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {events.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-8\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('channel.noWebhookEventsFound', 'No webhook events found')}\n />\n </td>\n </tr>\n ) : (\n events.map((event) => {\n const statusConfig = getStatusConfig(event.status as WebhookProcessingStatus);\n const StatusIcon = statusConfig.icon;\n const isExpanded = expandedEvent === event.id;\n return (\n <>\n <tr key={event.id} className=\"hover:bg-bg-sunken/30 transition-colors\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span className={`text-sm font-medium ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-text-primary\">\n {event.eventType}\n </td>\n <td className=\"px-4 py-3\">\n {event.isDuplicate ? (\n <span className=\"text-xs text-status-warning-text bg-status-warning-bg-subtle px-2 py-1 rounded\">\n Duplicate\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">No</span>\n )}\n </td>\n <td className=\"px-4 py-3 text-sm text-text-secondary\">\n {event.processedAt ? new Date(event.processedAt).toLocaleString() : '-'}\n </td>\n <td className=\"px-4 py-3 text-sm text-status-error-text max-w-xs truncate\">\n {event.errorMessage || '-'}\n </td>\n <td className=\"px-4 py-3\">\n <button\n type=\"button\"\n onClick={() => setExpandedEvent(isExpanded ? null : event.id)}\n aria-label={\n isExpanded\n ? tr('channel.hidePayload', 'Hide payload')\n : tr('channel.viewPayload', 'View payload')\n }\n aria-expanded={isExpanded}\n className=\"p-1 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n {isExpanded ? (\n <EyeOff size={16} aria-hidden=\"true\" />\n ) : (\n <Eye size={16} aria-hidden=\"true\" />\n )}\n </button>\n </td>\n </tr>\n {isExpanded && (\n <tr key={`${event.id}-payload`}>\n <td colSpan={6} className=\"px-4 py-3 bg-bg-sunken/30\">\n <div className=\"text-xs\">\n <p className=\"text-text-secondary mb-1\">\n {tr('channel.payload', 'Payload')}:\n </p>\n <pre className=\"bg-surface p-2 rounded border border-border-subtle overflow-auto\">\n {JSON.stringify(event.payload, null, 2)}\n </pre>\n </div>\n </td>\n </tr>\n )}\n </>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </GlassCard>\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.page', 'Page')}{' '}\n <span className=\"font-medium\">{currentPageIdx + 1}</span>\n {' · '}\n {tr('channel.totalRecords', 'Total')}: <span className=\"font-medium\">{total}</span>\n </>\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage || isLoadingEvents}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft size={16} />\n {tr('channel.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleNextPage()}\n disabled={!hasNextPage || isLoadingEvents}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WebhookEventsPage.js","names":["useChannelContext","_Fragment","useChannel$1","STATUS_CONFIG","Clock","RefreshCw","CheckCircle2","XCircle","EyeOff","WebhookEventsPage","useParams","navigate","useNavigate","usePermissions","canExport","hasPermission","CHANNEL_PERMISSIONS","useI18n","tr","value","t","key","fallback","exportWebhooks","isExportingWebhooks","exportWebhooksError","useExportWebhookEvents","organizationId","channelId","isLoadingEvents","setIsLoadingEvents","useState","events","setEvents","expandedEvent","setExpandedEvent","limit","setLimit","total","setTotal","cursorStack","setCursorStack","currentPageIdx","setCurrentPageIdx","useChannel","loadEvents","useCallback","result","fetchWebhookEvents","cursor","loadEventsRef","useRef","cursorStackRef","currentPageIdxRef","isFirstPageRender","useEffect","handleNextPage","nextCursor","next","prev","p","handlePrevPage","prevIdx","hasNextPage","hasPrevPage","WEBHOOK_RECEIVED_SUBSCRIPTION","emitShellEvent","getStatusConfig","status","isLoading","Loader2","error","channel","AlertCircle","PermissionGuard","ArrowLeft","CTAOverflowMenu","Download","PagePurpose","EmphasisPanel","GlassCard","IllustratedEmptyState","statusConfig","event","StatusIcon","isExpanded","Fragment","jsx","Eye","ChevronLeft","ChevronRight"],"sources":["../../../../../src/channel/modules/channels/pages/WebhookEventsPage.tsx"],"sourcesContent":["/**\n * Webhook Events Page\n *\n * Displays webhook event history for a specific channel\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Eye,\n EyeOff,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type WebhookEvent as WebhookEventType } from '../../../hooks/useChannels';\nimport type { WebhookProcessingStatus } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent } from '../../../utils/shellBridge';\nimport {\n WEBHOOK_RECEIVED_SUBSCRIPTION,\n useChannelSubscription,\n} from '../../../hooks/useChannelSubscription';\nimport { useExportWebhookEvents } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n WebhookProcessingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n PROCESSING: {\n label: 'Processing',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n COMPLETED: {\n label: 'Completed',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n FAILED: {\n label: 'Failed',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n IGNORED: {\n label: 'Ignored',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: EyeOff,\n },\n};\n\nexport function WebhookEventsPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_WEBHOOK_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportWebhooks,\n isExporting: isExportingWebhooks,\n exportError: exportWebhooksError,\n } = useExportWebhookEvents(organizationId ?? undefined, channelId);\n const [isLoadingEvents, setIsLoadingEvents] = useState(true);\n const [events, setEvents] = useState<WebhookEventType[]>([]);\n const [expandedEvent, setExpandedEvent] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchWebhookEvents } = useChannel(channelId);\n\n const loadEvents = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingEvents(true);\n try {\n const result = await fetchWebhookEvents(limit, cursor);\n setEvents(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingEvents(false);\n }\n },\n [channelId, fetchWebhookEvents, limit]\n );\n\n // Keep refs current after every render so pagination and subscription callbacks\n // always call the latest function/value without those variables in deps.\n const loadEventsRef = useRef(loadEvents);\n const cursorStackRef = useRef(cursorStack);\n const currentPageIdxRef = useRef(currentPageIdx);\n useEffect(() => {\n loadEventsRef.current = loadEvents;\n cursorStackRef.current = cursorStack;\n currentPageIdxRef.current = currentPageIdx;\n });\n // Tracks whether the [limit] effect has already fired on mount so the\n // [currentPageIdx] effect skips its first run (avoiding a double load).\n const isFirstPageRender = useRef(true);\n\n // Reset cursor stack and reload whenever page size changes (also fires on mount).\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadEventsRef.current(undefined);\n });\n }, [limit]);\n\n // Navigate to a specific page; skip the very first render to avoid\n // a duplicate load alongside the [limit] effect above.\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadEventsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadEvents(cursorStack[currentPageIdx]);\n if (nextCursor) {\n setCursorStack((prev) => {\n const next = [...prev];\n if (!next[currentPageIdx + 1]) {\n next[currentPageIdx + 1] = nextCursor;\n }\n return next;\n });\n }\n setCurrentPageIdx((p) => p + 1);\n }, [cursorStack, currentPageIdx, loadEvents]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadEvents(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadEvents]);\n\n const hasNextPage = events.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n // Real-time webhook stream. Subscribes when the gateway exposes\n // `webhookReceived`; otherwise polls every 60s as a graceful fallback.\n useChannelSubscription({\n query: WEBHOOK_RECEIVED_SUBSCRIPTION,\n variables: { channelId },\n operationName: 'WebhookReceived',\n enabled: Boolean(channelId),\n onData: useCallback(() => {\n emitShellEvent('webhook.received', { channelId });\n void loadEventsRef.current(cursorStackRef.current[currentPageIdxRef.current]);\n }, [channelId]),\n pollFn: useCallback(() => {\n void loadEventsRef.current(cursorStackRef.current[currentPageIdxRef.current]);\n }, []),\n pollIntervalMs: 60_000,\n });\n\n const getStatusConfig = (status: WebhookProcessingStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n // Loading state (only show full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_WEBHOOK_READ}>\n <div className=\"space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.webhookEvents', 'Webhook Events')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">{channel.name} - Event history</p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingEvents ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingEvents,\n onSelect: () => void loadEvents(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingWebhooks ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingWebhooks,\n onSelect: () => void exportWebhooks(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportWebhooksError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportWebhooksError}</p>\n </div>\n )}\n\n {/* Page purpose */}\n <PagePurpose icon={<Clock size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.webhookEventsPurpose',\n \"Inbound events the provider pushed to this channel — delivery receipts, replies, status callbacks and the like. Each row shows how the event was processed, whether it was a duplicate, and the raw payload so you can inspect exactly what arrived. Use it to verify your webhook endpoint is receiving traffic and to debug why an event wasn't handled. Records are kept for 7 days.\"\n )}\n </PagePurpose>\n\n {/* Emphasis — webhook total summary */}\n <EmphasisPanel className=\"flex items-center gap-3 p-4\">\n <span className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <Clock size={18} className=\"text-accent-purple\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {total}{' '}\n {total === 1\n ? tr('channel.webhookEventSingular', 'webhook event')\n : tr('channel.webhookEventsPlural', 'webhook events')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.webhookEventsBannerHint',\n 'All incoming webhooks received by this channel, retained for 7 days. Expand a row to inspect its raw payload.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Events List */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam rounded-lg overflow-hidden p-0\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full min-w-[600px]\">\n <thead className=\"bg-bg-sunken/50 border-b border-border-subtle\">\n <tr>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.eventType', 'Event Type')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.duplicate', 'Duplicate')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.processedAt', 'Processed At')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.error', 'Error')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {events.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-8\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('channel.noWebhookEventsFound', 'No webhook events found')}\n />\n </td>\n </tr>\n ) : (\n events.map((event) => {\n const statusConfig = getStatusConfig(event.status as WebhookProcessingStatus);\n const StatusIcon = statusConfig.icon;\n const isExpanded = expandedEvent === event.id;\n return (\n <>\n <tr key={event.id} className=\"hover:bg-bg-sunken/30 transition-colors\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span className={`text-sm font-medium ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-text-primary\">\n {event.eventType}\n </td>\n <td className=\"px-4 py-3\">\n {event.isDuplicate ? (\n <span className=\"text-xs text-status-warning-text bg-status-warning-bg-subtle px-2 py-1 rounded\">\n Duplicate\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">No</span>\n )}\n </td>\n <td className=\"px-4 py-3 text-sm text-text-secondary\">\n {event.processedAt ? new Date(event.processedAt).toLocaleString() : '-'}\n </td>\n <td className=\"px-4 py-3 text-sm text-status-error-text max-w-xs truncate\">\n {event.errorMessage || '-'}\n </td>\n <td className=\"px-4 py-3\">\n <button\n type=\"button\"\n onClick={() => setExpandedEvent(isExpanded ? null : event.id)}\n aria-label={\n isExpanded\n ? tr('channel.hidePayload', 'Hide payload')\n : tr('channel.viewPayload', 'View payload')\n }\n aria-expanded={isExpanded}\n className=\"p-1 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded transition-colors\"\n >\n {isExpanded ? (\n <EyeOff size={16} aria-hidden=\"true\" />\n ) : (\n <Eye size={16} aria-hidden=\"true\" />\n )}\n </button>\n </td>\n </tr>\n {isExpanded && (\n <tr key={`${event.id}-payload`}>\n <td colSpan={6} className=\"px-4 py-3 bg-bg-sunken/30\">\n <div className=\"text-xs\">\n <p className=\"text-text-secondary mb-1\">\n {tr('channel.payload', 'Payload')}:\n </p>\n <pre className=\"bg-surface p-2 rounded border border-border-subtle overflow-auto\">\n {JSON.stringify(event.payload, null, 2)}\n </pre>\n </div>\n </td>\n </tr>\n )}\n </>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </GlassCard>\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.page', 'Page')}{' '}\n <span className=\"font-medium\">{currentPageIdx + 1}</span>\n {' · '}\n {tr('channel.totalRecords', 'Total')}: <span className=\"font-medium\">{total}</span>\n </>\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage || isLoadingEvents}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft size={16} />\n {tr('channel.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleNextPage()}\n disabled={!hasNextPage || isLoadingEvents}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -5,5 +5,4 @@
5
5
  * Detail routes (e.g. /:channelId, /:channelId/edit) remain registered in
6
6
  * ChannelRoutes so existing deep links continue to work.
7
7
  */
8
- export declare function ChannelLandingPage(): import("react").JSX.Element;
9
- //# sourceMappingURL=ChannelLandingPage.d.ts.map
8
+ export declare function ChannelLandingPage(): import('react').JSX.Element;
@@ -1,29 +1,29 @@
1
1
  import { useTr as e } from "../hooks/useTr.js";
2
- import { PermissionGuard as t } from "../components/PermissionGuard.js";
3
- import { CHANNEL_PERMISSIONS as n } from "../constants/permissions.js";
4
- import { ChannelDashboardPage as r } from "../modules/channels/pages/ChannelDashboardPage.js";
5
- import { ChannelsListPage as i } from "../modules/channels/pages/ChannelsListPage.js";
6
- import { SendMessagePage as a } from "../modules/channels/pages/SendMessagePage.js";
7
- import { ConnectionsListPage as o } from "../modules/channels/pages/ConnectionsListPage.js";
8
- import { ChannelSettingsPage as s } from "../modules/channels/pages/ChannelSettingsPage.js";
9
- import { FeatureNotEnabled as c } from "../components/FeatureNotEnabled.js";
2
+ import { ChannelDashboardPage as t } from "../modules/channels/pages/ChannelDashboardPage.js";
3
+ import { ChannelsListPage as n } from "../modules/channels/pages/ChannelsListPage.js";
4
+ import { ConnectionsListPage as r } from "../modules/channels/pages/ConnectionsListPage.js";
5
+ import { SendMessagePage as i } from "../modules/channels/pages/SendMessagePage.js";
6
+ import { ChannelSettingsPage as a } from "../modules/channels/pages/ChannelSettingsPage.js";
7
+ import { PermissionGuard as o } from "../components/PermissionGuard.js";
8
+ import { FeatureNotEnabled as s } from "../components/FeatureNotEnabled.js";
9
+ import { CHANNEL_PERMISSIONS as c } from "../constants/permissions.js";
10
10
  import { useMemo as l } from "react";
11
11
  import { useSearchParams as u } from "react-router";
12
12
  import { usePermissions as d } from "@burdenoff/fe-libs/shared/providers/shell";
13
13
  import { jsx as f, jsxs as p } from "react/jsx-runtime";
14
14
  import { FeatureGate as m } from "@burdenoff/fe-libs/shared/feature-flags";
15
15
  import { Tabs as h } from "@burdenoff/fe-libs/chrome";
16
- //#region src/channel/pages/ChannelLandingPage.tsx
16
+ // src/channel/pages/ChannelLandingPage.tsx
17
17
  function g(e, t) {
18
18
  return /* @__PURE__ */ f(m, {
19
19
  flag: e,
20
20
  defaultValue: !0,
21
- fallback: /* @__PURE__ */ f(c, { flag: e }),
21
+ fallback: /* @__PURE__ */ f(s, { flag: e }),
22
22
  children: t
23
23
  });
24
24
  }
25
25
  function _() {
26
- let [c, m] = u(), _ = e(), { hasPermission: v } = d(), y = v(n.CHANNEL_DELIVERY_SEND), b = v(n.CHANNEL_UPDATE), x = l(() => [
26
+ let [s, m] = u(), _ = e(), { hasPermission: v } = d(), y = v(c.CHANNEL_DELIVERY_SEND), b = v(c.CHANNEL_UPDATE), x = l(() => [
27
27
  {
28
28
  id: "dashboard",
29
29
  label: _("channel.dashboard", "Dashboard")
@@ -48,9 +48,9 @@ function _() {
48
48
  _,
49
49
  y,
50
50
  b
51
- ]), S = c.get("tab") ?? x[0].id, C = x.some((e) => e.id === S) ? S : x[0].id;
52
- return /* @__PURE__ */ f(t, {
53
- permission: n.CHANNEL_READ,
51
+ ]), S = s.get("tab") ?? x[0].id, C = x.some((e) => e.id === S) ? S : x[0].id;
52
+ return /* @__PURE__ */ f(o, {
53
+ permission: c.CHANNEL_READ,
54
54
  children: /* @__PURE__ */ p("div", {
55
55
  className: "flex flex-col h-full",
56
56
  children: [/* @__PURE__ */ f("div", {
@@ -60,7 +60,7 @@ function _() {
60
60
  items: x,
61
61
  activeTabId: C,
62
62
  onTabChange: (e) => {
63
- let t = new URLSearchParams(c);
63
+ let t = new URLSearchParams(s);
64
64
  e === x[0].id ? t.delete("tab") : t.set("tab", e), m(t, { replace: !0 });
65
65
  },
66
66
  variant: "underline"
@@ -68,17 +68,16 @@ function _() {
68
68
  }), /* @__PURE__ */ p("div", {
69
69
  className: "flex-1 min-h-0 overflow-auto",
70
70
  children: [
71
- C === "dashboard" && g("channels.dashboard", /* @__PURE__ */ f(r, {})),
72
- C === "all" && g("channels.list", /* @__PURE__ */ f(i, {})),
73
- C === "connections" && g("channels.connections.list", /* @__PURE__ */ f(o, {})),
74
- C === "send" && g("channels.send", /* @__PURE__ */ f(a, {})),
75
- C === "settings" && g("channels.settings", /* @__PURE__ */ f(s, {}))
71
+ C === "dashboard" && g("channels.dashboard", /* @__PURE__ */ f(t, {})),
72
+ C === "all" && g("channels.list", /* @__PURE__ */ f(n, {})),
73
+ C === "connections" && g("channels.connections.list", /* @__PURE__ */ f(r, {})),
74
+ C === "send" && g("channels.send", /* @__PURE__ */ f(i, {})),
75
+ C === "settings" && g("channels.settings", /* @__PURE__ */ f(a, {}))
76
76
  ]
77
77
  })]
78
78
  })
79
79
  });
80
80
  }
81
- //#endregion
82
81
  export { _ as ChannelLandingPage };
83
82
 
84
83
  //# sourceMappingURL=ChannelLandingPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelLandingPage.js","names":[],"sources":["../../../src/channel/pages/ChannelLandingPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useSearchParams } from 'react-router';\nimport type { ReactNode } from 'react';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useTr } from '../hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\n\nimport { ChannelDashboardPage } from '../modules/channels/pages/ChannelDashboardPage';\nimport { ChannelsListPage } from '../modules/channels/pages/ChannelsListPage';\nimport { ConnectionsListPage } from '../modules/channels/pages/ConnectionsListPage';\nimport { SendMessagePage } from '../modules/channels/pages/SendMessagePage';\nimport { ChannelSettingsPage } from '../modules/channels/pages/ChannelSettingsPage';\nimport { PermissionGuard } from '../components/PermissionGuard';\nimport { FeatureNotEnabled } from '../components/FeatureNotEnabled';\nimport { CHANNEL_PERMISSIONS } from '../constants/permissions';\n\n/**\n * Gate a landing tab's content by the SAME `channels.*` flag its standalone route\n * uses (see ChannelRoutes), so an explicit kill-switch flag is honoured here too.\n * defaultValue=true matches the route gates + the backend's default-on seeding.\n */\nfunction gatedTab(flag: string, element: ReactNode): ReactNode {\n return (\n <FeatureGate flag={flag} defaultValue={true} fallback={<FeatureNotEnabled flag={flag} />}>\n {element}\n </FeatureGate>\n );\n}\n\n/**\n * Tabbed landing page for the Channel microfrontend.\n *\n * The first tab (\"dashboard\") uses a clean URL with no `tab` query param.\n * Detail routes (e.g. /:channelId, /:channelId/edit) remain registered in\n * ChannelRoutes so existing deep links continue to work.\n */\nexport function ChannelLandingPage() {\n const [params, setParams] = useSearchParams();\n const tr = useTr();\n const { hasPermission } = usePermissions();\n // Send Message and Settings are write/admin surfaces; only surface their tabs\n // to users who can use them (otherwise a viewer clicks through to AccessDenied).\n const canSend = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_SEND);\n const canSettings = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'dashboard', label: tr('channel.dashboard', 'Dashboard') },\n { id: 'all', label: tr('channel.allChannels', 'All Channels') },\n { id: 'connections', label: tr('channel.connections', 'Connections') },\n ...(canSend ? [{ id: 'send', label: tr('channel.sendMessage', 'Send Message') }] : []),\n ...(canSettings ? [{ id: 'settings', label: tr('channel.settings', 'Settings') }] : []),\n ],\n [tr, canSend, canSettings]\n );\n\n // Clamp the URL tab to the permission-filtered `tabs` list so a user cannot\n // deep-link to a hidden tab (e.g. ?tab=send without channel-delivery:send) and\n // have its content render. Unknown/hidden tabs fall back to the default tab.\n const requestedId = params.get('tab') ?? tabs[0].id;\n const activeId = tabs.some((tab) => tab.id === requestedId) ? requestedId : tabs[0].id;\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) {\n next.delete('tab');\n } else {\n next.set('tab', id);\n }\n setParams(next, { replace: true });\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"flex flex-col h-full\">\n <div className=\"border-b border-border-subtle px-4 pt-3\" data-tour=\"channels-landing-tabs\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto\">\n {activeId === 'dashboard' && gatedTab('channels.dashboard', <ChannelDashboardPage />)}\n {activeId === 'all' && gatedTab('channels.list', <ChannelsListPage />)}\n {activeId === 'connections' &&\n gatedTab('channels.connections.list', <ConnectionsListPage />)}\n {activeId === 'send' && gatedTab('channels.send', <SendMessagePage />)}\n {activeId === 'settings' && gatedTab('channels.settings', <ChannelSettingsPage />)}\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAAS,EAAS,GAAc,GAA+B;AAC7D,QACE,kBAAC,GAAD;EAAmB;EAAM,cAAc;EAAM,UAAU,kBAAC,GAAD,EAAyB,SAAQ,CAAA;YACrF;EACW,CAAA;;AAWlB,SAAgB,IAAqB;CACnC,IAAM,CAAC,GAAQ,KAAa,GAAiB,EACvC,IAAK,GAAO,EACZ,EAAE,qBAAkB,GAAgB,EAGpC,IAAU,EAAc,EAAoB,sBAAsB,EAClE,IAAc,EAAc,EAAoB,eAAe,EAE/D,IAAkB,QAChB;EACJ;GAAE,IAAI;GAAa,OAAO,EAAG,qBAAqB,YAAY;GAAE;EAChE;GAAE,IAAI;GAAO,OAAO,EAAG,uBAAuB,eAAe;GAAE;EAC/D;GAAE,IAAI;GAAe,OAAO,EAAG,uBAAuB,cAAc;GAAE;EACtE,GAAI,IAAU,CAAC;GAAE,IAAI;GAAQ,OAAO,EAAG,uBAAuB,eAAe;GAAE,CAAC,GAAG,EAAE;EACrF,GAAI,IAAc,CAAC;GAAE,IAAI;GAAY,OAAO,EAAG,oBAAoB,WAAW;GAAE,CAAC,GAAG,EAAE;EACvF,EACD;EAAC;EAAI;EAAS;EAAY,CAC3B,EAKK,IAAc,EAAO,IAAI,MAAM,IAAI,EAAK,GAAG,IAC3C,IAAW,EAAK,MAAM,MAAQ,EAAI,OAAO,EAAY,GAAG,IAAc,EAAK,GAAG;AAYpF,QACE,kBAAC,GAAD;EAAiB,YAAY,EAAoB;YAC/C,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;IAA0C,aAAU;cACjE,kBAAC,GAAD;KACE,OAAO;KACP,aAAa;KACb,cAjBe,MAAe;MACtC,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAMxC,MALI,MAAO,EAAK,GAAG,KACjB,EAAK,OAAO,MAAM,GAElB,EAAK,IAAI,OAAO,EAAG,EAErB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;;KAW1B,SAAQ;KACR,CAAA;IACE,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,MAAa,eAAe,EAAS,sBAAsB,kBAAC,GAAD,EAAwB,CAAA,CAAC;KACpF,MAAa,SAAS,EAAS,iBAAiB,kBAAC,GAAD,EAAoB,CAAA,CAAC;KACrE,MAAa,iBACZ,EAAS,6BAA6B,kBAAC,GAAD,EAAuB,CAAA,CAAC;KAC/D,MAAa,UAAU,EAAS,iBAAiB,kBAAC,GAAD,EAAmB,CAAA,CAAC;KACrE,MAAa,cAAc,EAAS,qBAAqB,kBAAC,GAAD,EAAuB,CAAA,CAAC;KAC9E;MACF;;EACU,CAAA"}
1
+ {"version":3,"file":"ChannelLandingPage.js","names":["gatedTab","FeatureGate","FeatureNotEnabled","element","ChannelLandingPage","params","setParams","useSearchParams","tr","useTr","usePermissions","canSend","hasPermission","CHANNEL_PERMISSIONS","canSettings","tabs","useMemo","requestedId","activeId","tab","PermissionGuard","Tabs","next","id","ChannelDashboardPage","ChannelsListPage","ConnectionsListPage","SendMessagePage","ChannelSettingsPage"],"sources":["../../../src/channel/pages/ChannelLandingPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useSearchParams } from 'react-router';\nimport type { ReactNode } from 'react';\nimport { Tabs, type TabItem } from '@burdenoff/fe-libs/chrome';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useTr } from '../hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\n\nimport { ChannelDashboardPage } from '../modules/channels/pages/ChannelDashboardPage';\nimport { ChannelsListPage } from '../modules/channels/pages/ChannelsListPage';\nimport { ConnectionsListPage } from '../modules/channels/pages/ConnectionsListPage';\nimport { SendMessagePage } from '../modules/channels/pages/SendMessagePage';\nimport { ChannelSettingsPage } from '../modules/channels/pages/ChannelSettingsPage';\nimport { PermissionGuard } from '../components/PermissionGuard';\nimport { FeatureNotEnabled } from '../components/FeatureNotEnabled';\nimport { CHANNEL_PERMISSIONS } from '../constants/permissions';\n\n/**\n * Gate a landing tab's content by the SAME `channels.*` flag its standalone route\n * uses (see ChannelRoutes), so an explicit kill-switch flag is honoured here too.\n * defaultValue=true matches the route gates + the backend's default-on seeding.\n */\nfunction gatedTab(flag: string, element: ReactNode): ReactNode {\n return (\n <FeatureGate flag={flag} defaultValue={true} fallback={<FeatureNotEnabled flag={flag} />}>\n {element}\n </FeatureGate>\n );\n}\n\n/**\n * Tabbed landing page for the Channel microfrontend.\n *\n * The first tab (\"dashboard\") uses a clean URL with no `tab` query param.\n * Detail routes (e.g. /:channelId, /:channelId/edit) remain registered in\n * ChannelRoutes so existing deep links continue to work.\n */\nexport function ChannelLandingPage() {\n const [params, setParams] = useSearchParams();\n const tr = useTr();\n const { hasPermission } = usePermissions();\n // Send Message and Settings are write/admin surfaces; only surface their tabs\n // to users who can use them (otherwise a viewer clicks through to AccessDenied).\n const canSend = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_SEND);\n const canSettings = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n\n const tabs: TabItem[] = useMemo(\n () => [\n { id: 'dashboard', label: tr('channel.dashboard', 'Dashboard') },\n { id: 'all', label: tr('channel.allChannels', 'All Channels') },\n { id: 'connections', label: tr('channel.connections', 'Connections') },\n ...(canSend ? [{ id: 'send', label: tr('channel.sendMessage', 'Send Message') }] : []),\n ...(canSettings ? [{ id: 'settings', label: tr('channel.settings', 'Settings') }] : []),\n ],\n [tr, canSend, canSettings]\n );\n\n // Clamp the URL tab to the permission-filtered `tabs` list so a user cannot\n // deep-link to a hidden tab (e.g. ?tab=send without channel-delivery:send) and\n // have its content render. Unknown/hidden tabs fall back to the default tab.\n const requestedId = params.get('tab') ?? tabs[0].id;\n const activeId = tabs.some((tab) => tab.id === requestedId) ? requestedId : tabs[0].id;\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) {\n next.delete('tab');\n } else {\n next.set('tab', id);\n }\n setParams(next, { replace: true });\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"flex flex-col h-full\">\n <div className=\"border-b border-border-subtle px-4 pt-3\" data-tour=\"channels-landing-tabs\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto\">\n {activeId === 'dashboard' && gatedTab('channels.dashboard', <ChannelDashboardPage />)}\n {activeId === 'all' && gatedTab('channels.list', <ChannelsListPage />)}\n {activeId === 'connections' &&\n gatedTab('channels.connections.list', <ConnectionsListPage />)}\n {activeId === 'send' && gatedTab('channels.send', <SendMessagePage />)}\n {activeId === 'settings' && gatedTab('channels.settings', <ChannelSettingsPage />)}\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAASA,EAAS,GAAc,GAA+B;CAC7D,OACE,kBAACC,GAAD;EAAmB;EAAM,cAAc;EAAM,UAAU,kBAACC,GAAD,EAAyB,QAAO,CAAA;EACpF,UAAAC;CACU,CAAA;AAEjB;AASA,SAAgBC,IAAqB;CACnC,IAAM,CAACC,GAAQC,KAAaC,EAAgB,GACtCC,IAAKC,EAAM,GACX,EAAE,qBAAkBC,EAAe,GAGnCC,IAAUC,EAAcC,EAAoB,qBAAqB,GACjEC,IAAcF,EAAcC,EAAoB,cAAc,GAE9DE,IAAkBC,QAChB;EACJ;GAAE,IAAI;GAAa,OAAOR,EAAG,qBAAqB,WAAW;EAAE;EAC/D;GAAE,IAAI;GAAO,OAAOA,EAAG,uBAAuB,cAAc;EAAE;EAC9D;GAAE,IAAI;GAAe,OAAOA,EAAG,uBAAuB,aAAa;EAAE;EACrE,GAAIG,IAAU,CAAC;GAAE,IAAI;GAAQ,OAAOH,EAAG,uBAAuB,cAAc;EAAE,CAAC,IAAI,CAAC;EACpF,GAAIM,IAAc,CAAC;GAAE,IAAI;GAAY,OAAON,EAAG,oBAAoB,UAAU;EAAE,CAAC,IAAI,CAAC;CACvF,GACA;EAACA;EAAIG;EAASG;CAAW,CAC3B,GAKMG,IAAcZ,EAAO,IAAI,KAAK,KAAKU,EAAK,EAAE,CAAC,IAC3CG,IAAWH,EAAK,MAAM,MAAQI,EAAI,OAAOF,CAAW,IAAIA,IAAcF,EAAK,EAAE,CAAC;CAYpF,OACE,kBAACK,GAAD;EAAiB,YAAYP,EAAoB;EAC/C,UAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IAA0C,aAAU;IACjE,UAAA,kBAACQ,GAAD;KACE,OAAON;KACP,aAAaG;KACb,cAjBe,MAAe;MACtC,IAAMI,IAAO,IAAI,gBAAgBjB,CAAM;MAMvC,AALIkB,MAAOR,EAAK,EAAE,CAAC,KACjBO,EAAK,OAAO,KAAK,IAEjBA,EAAK,IAAI,OAAOC,CAAE,GAEpBjB,EAAUgB,GAAM,EAAE,SAAS,GAAK,CAAC;KACnC;KAUU,SAAQ;IACT,CAAA;GACE,CAAA,GACL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACGJ,MAAa,eAAelB,EAAS,sBAAsB,kBAACwB,GAAD,CAAuB,CAAA,CAAC;KACnFN,MAAa,SAASlB,EAAS,iBAAiB,kBAACyB,GAAD,CAAmB,CAAA,CAAC;KACpEP,MAAa,iBACZlB,EAAS,6BAA6B,kBAAC0B,GAAD,CAAsB,CAAA,CAAC;KAC9DR,MAAa,UAAUlB,EAAS,iBAAiB,kBAAC2B,GAAD,CAAkB,CAAA,CAAC;KACpET,MAAa,cAAclB,EAAS,qBAAqB,kBAAC4B,GAAD,CAAsB,CAAA,CAAC;IAC9E;GACF,CAAA,CAAA;;CACU,CAAA;AAErB"}
@@ -1,6 +1,5 @@
1
1
  import { FC, PropsWithChildren } from 'react';
2
2
  import { ChannelRootProps } from '../types';
3
-
4
3
  /**
5
4
  * Hook to access Channel context
6
5
  */
@@ -13,4 +12,3 @@ export declare const useChannel: () => ChannelRootProps;
13
12
  * so we don't create another one. When standalone (no basePath), use MemoryRouter.
14
13
  */
15
14
  export declare const ChannelProvider: FC<PropsWithChildren<ChannelRootProps>>;
16
- //# sourceMappingURL=ChannelProvider.d.ts.map
@@ -2,7 +2,7 @@ import { createContext as e, use as t } from "react";
2
2
  import { MemoryRouter as n } from "react-router";
3
3
  import { PermissionProvider as r } from "@burdenoff/fe-libs/shared/providers/shell";
4
4
  import { jsx as i } from "react/jsx-runtime";
5
- //#region src/channel/providers/ChannelProvider.tsx
5
+ // src/channel/providers/ChannelProvider.tsx
6
6
  var a = e(null), o = () => {
7
7
  let e = t(a);
8
8
  if (!e) throw Error("useChannel must be used within ChannelProvider");
@@ -29,7 +29,6 @@ var a = e(null), o = () => {
29
29
  });
30
30
  return g ? _ : /* @__PURE__ */ i(n, { children: _ });
31
31
  };
32
- //#endregion
33
32
  export { s as ChannelProvider, o as useChannel };
34
33
 
35
34
  //# sourceMappingURL=ChannelProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelProvider.js","names":[],"sources":["../../../src/channel/providers/ChannelProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { ChannelRootProps } from '../types';\n\n/**\n * Context value for Channel microfrontend\n */\ntype ChannelContextValue = ChannelRootProps;\n\nconst ChannelContext = createContext<ChannelContextValue | null>(null);\n\n/**\n * Hook to access Channel context\n */\nexport const useChannel = () => {\n const context = use(ChannelContext);\n if (!context) {\n throw new Error('useChannel must be used within ChannelProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Channel microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const ChannelProvider: FC<PropsWithChildren<ChannelRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n tenantId,\n apiGatewayUrl,\n authToken,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: ChannelContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n tenantId,\n apiGatewayUrl,\n authToken,\n registerNavItems,\n registerFooterItems,\n };\n\n // PermissionProvider requires the shell's AuthProvider — only mount it in integrated mode.\n // In standalone/dev mode (MemoryRouter path), there's no AuthProvider, so we skip it.\n const isIntegrated = !!basePath;\n\n const content = (\n <ChannelContext.Provider value={contextValue}>\n {isIntegrated ? (\n <PermissionProvider scopeId={organizationId ?? null} gateway=\"global\">\n {children}\n </PermissionProvider>\n ) : (\n children\n )}\n </ChannelContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (isIntegrated) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAiB,EAA0C,KAAK,EAKzD,UAAmB;CAC9B,IAAM,IAAU,EAAI,EAAe;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,iDAAiD;AAEnE,QAAO;GAUI,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,mBACA,aACA,kBACA,cACA,qBACA,6BACI;CACJ,IAAM,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAIK,IAAe,CAAC,CAAC,GAEjB,IACJ,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC7B,IACC,kBAAC,GAAD;GAAoB,SAAS,KAAkB;GAAM,SAAQ;GAC1D;GACkB,CAAA,GAErB;EAEsB,CAAA;AAU5B,QALI,IACK,IAIF,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA"}
1
+ {"version":3,"file":"ChannelProvider.js","names":["ChannelContext","createContext","useChannel","context","use","ChannelProvider","contextValue","isIntegrated","basePath","content","PermissionProvider","organizationId","children","MemoryRouter"],"sources":["../../../src/channel/providers/ChannelProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, use } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { ChannelRootProps } from '../types';\n\n/**\n * Context value for Channel microfrontend\n */\ntype ChannelContextValue = ChannelRootProps;\n\nconst ChannelContext = createContext<ChannelContextValue | null>(null);\n\n/**\n * Hook to access Channel context\n */\nexport const useChannel = () => {\n const context = use(ChannelContext);\n if (!context) {\n throw new Error('useChannel must be used within ChannelProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Channel microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const ChannelProvider: FC<PropsWithChildren<ChannelRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n tenantId,\n apiGatewayUrl,\n authToken,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: ChannelContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n organizationId,\n tenantId,\n apiGatewayUrl,\n authToken,\n registerNavItems,\n registerFooterItems,\n };\n\n // PermissionProvider requires the shell's AuthProvider — only mount it in integrated mode.\n // In standalone/dev mode (MemoryRouter path), there's no AuthProvider, so we skip it.\n const isIntegrated = !!basePath;\n\n const content = (\n <ChannelContext.Provider value={contextValue}>\n {isIntegrated ? (\n <PermissionProvider scopeId={organizationId ?? null} gateway=\"global\">\n {children}\n </PermissionProvider>\n ) : (\n children\n )}\n </ChannelContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (isIntegrated) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAMA,IAAiBC,EAA0C,IAAI,GAKxDC,UAAmB;CAC9B,IAAMC,IAAUC,EAAIJ,CAAc;CAClC,IAAI,CAACG,GACH,MAAU,MAAM,gDAAgD;CAElE,OAAOA;AACT,GASaE,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,mBACA,aACA,kBACA,cACA,qBACA,6BACI;CACJ,IAAMC,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GAIMC,IAAe,CAAC,CAACC,GAEjBC,IACJ,kBAACT,EAAe,UAAhB;EAAyB,OAAOM;EAC7B,UAAAC,IACC,kBAACG,GAAD;GAAoB,SAASC,KAAkB;GAAM,SAAQ;GAC1D;EACiB,CAAA,IAEpBC;CAEqB,CAAA;CAU3B,OALIL,IACKE,IAIF,kBAACI,GAAD,EAAA,UAAeJ,EAAsB,CAAA;AAC9C"}
@@ -1,5 +1,4 @@
1
1
  import { ReactNode } from 'react';
2
-
3
2
  /**
4
3
  * Navigation item for shell integration
5
4
  */
@@ -86,4 +85,3 @@ export interface ChannelRootProps {
86
85
  */
87
86
  registerFooterItems?: (items: FooterItem | FooterItem[]) => () => void;
88
87
  }
89
- //# sourceMappingURL=index.d.ts.map
@@ -56,4 +56,3 @@ export declare function subscribeShellEvent(name: string, handler: (event: unkno
56
56
  */
57
57
  export declare function reportShellError(error: unknown, meta: ShellReportErrorMeta, fallbackLabel?: string): void;
58
58
  export declare function getGraphqlWsClient(): GraphqlWsLike | undefined;
59
- //# sourceMappingURL=shellBridge.d.ts.map
@@ -1,4 +1,4 @@
1
- //#region src/channel/utils/shellBridge.ts
1
+ // src/channel/utils/shellBridge.ts
2
2
  function e() {
3
3
  return globalThis.__burdenoffShell;
4
4
  }
@@ -30,7 +30,6 @@ function i(t, n, r) {
30
30
  function a() {
31
31
  return e()?.graphqlWsClient;
32
32
  }
33
- //#endregion
34
33
  export { n as emitShellEvent, a as getGraphqlWsClient, i as reportShellError, r as subscribeShellEvent };
35
34
 
36
35
  //# sourceMappingURL=shellBridge.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shellBridge.js","names":[],"sources":["../../../src/channel/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 * - `eventBus` — cross-MFE pub/sub bus per spec MFE-047\n * - `reportError` — central SRE error sink (Rybbit + backend forward)\n * - `graphqlWsClient` — graphql-ws Client instance (probe-only; subs falls\n * back to polling when absent)\n */\n\nexport interface ShellEventBus {\n /** Emit a domain event onto the shell bus. */\n emit?: (name: string, payload?: unknown, options?: Record<string, unknown>) => void;\n /** Subscribe; returns an unsubscribe function. */\n on?: (name: string, handler: (event: unknown) => void) => () => void;\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 eventBus?: ShellEventBus;\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\nfunction getEventBus(): ShellEventBus | undefined {\n return getShell()?.eventBus;\n}\n\n/**\n * Emit a domain event onto the shell bus. Safe no-op when the shell or bus\n * is absent. `name` follows `<domain>.<entity>.<verb>` (per spec 47).\n */\nexport function emitShellEvent(name: string, payload?: unknown): void {\n try {\n getEventBus()?.emit?.(name, payload, { component: 'microfe-channel' });\n } catch {\n /* sink failures must never break the emitter */\n }\n}\n\n/**\n * Subscribe to a shell-bus event; returns an unsubscribe function. Returns a\n * no-op unsubscribe when the bus is absent.\n */\nexport function subscribeShellEvent(name: string, handler: (event: unknown) => void): () => void {\n const bus = getEventBus();\n if (!bus?.on) return () => {};\n try {\n return bus.on(name, handler) ?? (() => {});\n } catch {\n return () => {};\n }\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 — mirrors the pattern in\n * `microfe-tags/src/tags/components/RouteHelpers.tsx`.\n */\nexport function reportShellError(\n error: unknown,\n meta: ShellReportErrorMeta,\n fallbackLabel?: string\n): 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 // Fallback path: shell missing or sink threw. Preserves observability in\n // standalone dev.\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":";AAmDA,SAAS,IAAuC;AAC9C,QAAQ,WAAgE;;AAG1E,SAAS,IAAyC;AAChD,QAAO,GAAU,EAAE;;AAOrB,SAAgB,EAAe,GAAc,GAAyB;AACpE,KAAI;AACF,KAAa,EAAE,OAAO,GAAM,GAAS,EAAE,WAAW,mBAAmB,CAAC;SAChE;;AASV,SAAgB,EAAoB,GAAc,GAA+C;CAC/F,IAAM,IAAM,GAAa;AACzB,KAAI,CAAC,GAAK,GAAI,cAAa;AAC3B,KAAI;AACF,SAAO,EAAI,GAAG,GAAM,EAAQ,WAAW;SACjC;AACN,eAAa;;;AASjB,SAAgB,EACd,GACA,GACA,GACM;CACN,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;AAMV,CACE,QAAQ,MADN,KAGY,IAAI,EAAK,OAAO,IAFD,GAAK,EAAK,WAAW,EAAE,CAEM;;AAI9D,SAAgB,IAAgD;AAC9D,QAAO,GAAU,EAAE"}
1
+ {"version":3,"file":"shellBridge.js","names":["getShell","getEventBus","emitShellEvent","name","payload","subscribeShellEvent","bus","handler","reportShellError","err","error","sink","meta","fallbackLabel","getGraphqlWsClient"],"sources":["../../../src/channel/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 * - `eventBus` — cross-MFE pub/sub bus per spec MFE-047\n * - `reportError` — central SRE error sink (Rybbit + backend forward)\n * - `graphqlWsClient` — graphql-ws Client instance (probe-only; subs falls\n * back to polling when absent)\n */\n\nexport interface ShellEventBus {\n /** Emit a domain event onto the shell bus. */\n emit?: (name: string, payload?: unknown, options?: Record<string, unknown>) => void;\n /** Subscribe; returns an unsubscribe function. */\n on?: (name: string, handler: (event: unknown) => void) => () => void;\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 eventBus?: ShellEventBus;\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\nfunction getEventBus(): ShellEventBus | undefined {\n return getShell()?.eventBus;\n}\n\n/**\n * Emit a domain event onto the shell bus. Safe no-op when the shell or bus\n * is absent. `name` follows `<domain>.<entity>.<verb>` (per spec 47).\n */\nexport function emitShellEvent(name: string, payload?: unknown): void {\n try {\n getEventBus()?.emit?.(name, payload, { component: 'microfe-channel' });\n } catch {\n /* sink failures must never break the emitter */\n }\n}\n\n/**\n * Subscribe to a shell-bus event; returns an unsubscribe function. Returns a\n * no-op unsubscribe when the bus is absent.\n */\nexport function subscribeShellEvent(name: string, handler: (event: unknown) => void): () => void {\n const bus = getEventBus();\n if (!bus?.on) return () => {};\n try {\n return bus.on(name, handler) ?? (() => {});\n } catch {\n return () => {};\n }\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 — mirrors the pattern in\n * `microfe-tags/src/tags/components/RouteHelpers.tsx`.\n */\nexport function reportShellError(\n error: unknown,\n meta: ShellReportErrorMeta,\n fallbackLabel?: string\n): 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 // Fallback path: shell missing or sink threw. Preserves observability in\n // standalone dev.\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":";AAmDA,SAASA,IAAuC;CAC9C,OAAQ,WAAgE;AAC1E;AAEA,SAASC,IAAyC;CAChD,OAAOD,EAAS,CAAC,EAAE;AACrB;AAMA,SAAgBE,EAAe,GAAc,GAAyB;CACpE,IAAI;EACF,EAAY,CAAC,EAAE,OAAOC,GAAMC,GAAS,EAAE,WAAW,kBAAkB,CAAC;CACvE,QAAQ,CAER;AACF;AAMA,SAAgBC,EAAoB,GAAc,GAA+C;CAC/F,IAAMC,IAAML,EAAY;CACxB,IAAI,CAACK,GAAK,IAAI,aAAa,CAAC;CAC5B,IAAI;EACF,OAAOA,EAAI,GAAGH,GAAMI,CAAO,YAAY,CAAC;CAC1C,QAAQ;EACN,aAAa,CAAC;CAChB;AACF;AAOA,SAAgBC,EACd,GACA,GACA,GACM;CACN,IAAMC,IAAMC,aAAiB,QAAQA,IAAY,MAAM,OAAOA,CAAK,CAAC,GAC9DC,IAAOX,EAAS;CACtB,IAAIW,GAAM,aACR,IAAI;EACF,EAAK,YAAYF,GAAKG,CAAI;EAC1B;CACF,QAAQ,CAER;CAIF,AACE,QAAQ,MADNC,KAGY,IAAID,EAAK,OAAO,IAFDH,GAAKG,EAAK,WAAW,CAAC,CAEM;AAE7D;AAEA,SAAgBE,IAAgD;CAC9D,OAAOd,EAAS,CAAC,EAAE;AACrB"}
package/dist/dev/App.d.ts CHANGED
@@ -2,5 +2,4 @@
2
2
  * Development app wrapper
3
3
  * Provides mock props and Apollo Client setup for local development
4
4
  */
5
- export declare function App(): import("react").JSX.Element;
6
- //# sourceMappingURL=App.d.ts.map
5
+ export declare function App(): import('react').JSX.Element;
@@ -1,2 +0,0 @@
1
-
2
- //# sourceMappingURL=main.d.ts.map