@burdenoff/microfe-channel 2026.625.1 → 2026.625.2
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/channel/modules/channels/pages/ChannelDashboardPage.js +66 -76
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +117 -114
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +159 -171
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +88 -97
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +10 -22
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +131 -128
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeliveryHistoryPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/DeliveryHistoryPage.tsx"],"sourcesContent":["/**\n * Delivery History Page\n *\n * Displays message delivery attempts for a specific channel with retry capability\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\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 Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n GlassCard,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type DeliveryStatus, type DeliveryAttempt } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportDeliveryAttempts } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n DeliveryStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n QUEUED: { label: 'Queued', color: 'text-accent-primary bg-accent-primary-subtle', icon: Clock },\n SENDING: { label: 'Sending', color: 'text-accent-primary bg-accent-primary/10', icon: Send },\n SENT: {\n label: 'Sent',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n DELIVERED: {\n label: 'Delivered',\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 BOUNCED: {\n label: 'Bounced',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertCircle,\n },\n REJECTED: {\n label: 'Rejected',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n RATE_LIMITED: {\n label: 'Rate Limited',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n RETRYING: {\n label: 'Retrying',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n OPENED: {\n label: 'Opened',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n CLICKED: {\n label: 'Clicked',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n};\n\nexport function DeliveryHistoryPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_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: exportDeliveries,\n isExporting: isExportingDeliveries,\n exportError: exportDeliveriesError,\n } = useExportDeliveryAttempts(organizationId ?? undefined, channelId);\n const [isLoadingAttempts, setIsLoadingAttempts] = useState(true);\n const [attempts, setAttempts] = useState<DeliveryAttempt[]>([]);\n const [retryingId, setRetryingId] = useState<string | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n // Cursor stack: index 0 is always undefined (first page), subsequent entries are cursors\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchDeliveryAttempts, retryDelivery } = useChannel(channelId);\n\n const loadAttempts = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingAttempts(true);\n setRetryError(null);\n try {\n const result = await fetchDeliveryAttempts(limit, cursor);\n setAttempts(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingAttempts(false);\n }\n },\n [channelId, fetchDeliveryAttempts, limit]\n );\n\n const loadAttemptsRef = useRef(loadAttempts);\n const cursorStackRef = useRef(cursorStack);\n useEffect(() => {\n loadAttemptsRef.current = loadAttempts;\n cursorStackRef.current = cursorStack;\n });\n const isFirstPageRender = useRef(true);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadAttemptsRef.current(undefined);\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadAttemptsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n // Poll for delivery attempt updates every 60 s on first page only\n useEffect(() => {\n if (!channelId || currentPageIdx !== 0) return;\n const timer = setInterval(() => {\n void loadAttempts(undefined);\n }, 60_000);\n return () => clearInterval(timer);\n }, [channelId, currentPageIdx, loadAttempts]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadAttempts(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, loadAttempts]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadAttempts(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const hasNextPage = attempts.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n const handleRetry = async (attemptId: string) => {\n setRetryingId(attemptId);\n setRetryError(null);\n try {\n const result = await retryDelivery(attemptId);\n if (!result.success) {\n setRetryError(result.error || 'Retry failed');\n } else {\n await loadAttempts(cursorStack[currentPageIdx]);\n }\n } finally {\n setRetryingId(null);\n }\n };\n\n const getStatusConfig = (status: DeliveryStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n const deliveryColumns: ResponsiveTableColumn<DeliveryAttempt>[] = [\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n return (\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 );\n },\n },\n {\n key: 'content',\n header: tr('channel.content', 'Content'),\n priority: 'primary',\n cellClassName: 'text-sm text-text-primary truncate max-w-xs',\n cell: (delivery) => delivery.content,\n },\n {\n key: 'sentAt',\n header: tr('channel.sentAt', 'Sent At'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary',\n cell: (delivery) =>\n delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-',\n },\n {\n key: 'retries',\n header: tr('channel.retries', 'Retries'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary tabular-nums',\n cell: (delivery) => delivery.retryCount,\n },\n {\n key: 'error',\n header: tr('channel.error', 'Error'),\n priority: 'secondary',\n cellClassName: 'text-sm text-status-error-text max-w-xs truncate',\n cell: (delivery) => delivery.errorMessage || '-',\n },\n {\n key: 'actions',\n header: tr('channel.actions', 'Actions'),\n priority: 'secondary',\n cell: (delivery) => {\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n if (!canRetry) return null;\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleRetry(delivery.id)}\n disabled={retryingId === delivery.id}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium text-accent-primary hover:bg-accent-primary/10 rounded transition-colors disabled:opacity-50\"\n title={tr('channel.retryDelivery', 'Retry this delivery')}\n >\n <RotateCcw\n size={12}\n className={retryingId === delivery.id ? 'animate-spin' : ''}\n />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n );\n },\n },\n ];\n\n // Loading state\n if (isLoading) {\n return (\n <output\n className=\"flex items-center justify-center min-h-screen\"\n aria-label={tr('channel.loading', 'Loading')}\n >\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" aria-hidden=\"true\" />\n </output>\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_DELIVERY_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.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - Message deliveries\n </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\"\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={isLoadingAttempts ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingAttempts,\n onSelect: () => void loadAttempts(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingDeliveries,\n onSelect: () => void exportDeliveries(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportDeliveriesError && (\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\">{exportDeliveriesError}</p>\n </div>\n )}\n\n {/* Error Banner */}\n {retryError && (\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\">{retryError}</p>\n <button\n type=\"button\"\n onClick={() => setRetryError(null)}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n {tr('channel.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Info Banner */}\n <GlassCard treatment=\"insight\" className=\"p-4 border-border-seam\">\n <p className=\"text-sm text-text-secondary\">\n Delivery history shows all message attempts sent through this channel. Data is retained\n for 7 days. Click retry on a failed delivery to attempt sending again.\n </p>\n </GlassCard>\n\n {/* Deliveries List */}\n {isLoadingAttempts ? (\n <div className=\"bg-surface border border-border-seam rounded-lg px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-text-secondary mt-2\">Loading delivery attempts…</p>\n </div>\n ) : (\n <ResponsiveTable\n columns={deliveryColumns}\n data={attempts}\n rowKey={(delivery) => delivery.id}\n className=\"bg-surface border border-border-seam rounded-lg\"\n emptyState={\n <div className=\"flex flex-col items-center gap-2 py-8\">\n <Send size={32} className=\"text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n </p>\n </div>\n }\n />\n )}\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 || isLoadingAttempts}\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 || isLoadingAttempts}\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":"DeliveryHistoryPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/DeliveryHistoryPage.tsx"],"sourcesContent":["/**\n * Delivery History Page\n *\n * Displays message delivery attempts for a specific channel with retry capability\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\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 Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n GlassCard,\n ResponsiveTable,\n IllustratedEmptyState,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type DeliveryStatus, type DeliveryAttempt } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportDeliveryAttempts } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n DeliveryStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n QUEUED: { label: 'Queued', color: 'text-accent-primary bg-accent-primary-subtle', icon: Clock },\n SENDING: { label: 'Sending', color: 'text-accent-primary bg-accent-primary/10', icon: Send },\n SENT: {\n label: 'Sent',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n DELIVERED: {\n label: 'Delivered',\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 BOUNCED: {\n label: 'Bounced',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertCircle,\n },\n REJECTED: {\n label: 'Rejected',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n RATE_LIMITED: {\n label: 'Rate Limited',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n RETRYING: {\n label: 'Retrying',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n OPENED: {\n label: 'Opened',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n CLICKED: {\n label: 'Clicked',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n};\n\nexport function DeliveryHistoryPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_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: exportDeliveries,\n isExporting: isExportingDeliveries,\n exportError: exportDeliveriesError,\n } = useExportDeliveryAttempts(organizationId ?? undefined, channelId);\n const [isLoadingAttempts, setIsLoadingAttempts] = useState(true);\n const [attempts, setAttempts] = useState<DeliveryAttempt[]>([]);\n const [retryingId, setRetryingId] = useState<string | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n // Cursor stack: index 0 is always undefined (first page), subsequent entries are cursors\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchDeliveryAttempts, retryDelivery } = useChannel(channelId);\n\n const loadAttempts = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingAttempts(true);\n setRetryError(null);\n try {\n const result = await fetchDeliveryAttempts(limit, cursor);\n setAttempts(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingAttempts(false);\n }\n },\n [channelId, fetchDeliveryAttempts, limit]\n );\n\n const loadAttemptsRef = useRef(loadAttempts);\n const cursorStackRef = useRef(cursorStack);\n useEffect(() => {\n loadAttemptsRef.current = loadAttempts;\n cursorStackRef.current = cursorStack;\n });\n const isFirstPageRender = useRef(true);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadAttemptsRef.current(undefined);\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadAttemptsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n // Poll for delivery attempt updates every 60 s on first page only\n useEffect(() => {\n if (!channelId || currentPageIdx !== 0) return;\n const timer = setInterval(() => {\n void loadAttempts(undefined);\n }, 60_000);\n return () => clearInterval(timer);\n }, [channelId, currentPageIdx, loadAttempts]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadAttempts(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, loadAttempts]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadAttempts(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const hasNextPage = attempts.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n const handleRetry = async (attemptId: string) => {\n setRetryingId(attemptId);\n setRetryError(null);\n try {\n const result = await retryDelivery(attemptId);\n if (!result.success) {\n setRetryError(result.error || 'Retry failed');\n } else {\n await loadAttempts(cursorStack[currentPageIdx]);\n }\n } finally {\n setRetryingId(null);\n }\n };\n\n const getStatusConfig = (status: DeliveryStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n const deliveryColumns: ResponsiveTableColumn<DeliveryAttempt>[] = [\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n return (\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 );\n },\n },\n {\n key: 'content',\n header: tr('channel.content', 'Content'),\n priority: 'primary',\n cellClassName: 'text-sm text-text-primary truncate max-w-xs',\n cell: (delivery) => delivery.content,\n },\n {\n key: 'sentAt',\n header: tr('channel.sentAt', 'Sent At'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary',\n cell: (delivery) =>\n delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-',\n },\n {\n key: 'retries',\n header: tr('channel.retries', 'Retries'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary tabular-nums',\n cell: (delivery) => delivery.retryCount,\n },\n {\n key: 'error',\n header: tr('channel.error', 'Error'),\n priority: 'secondary',\n cellClassName: 'text-sm text-status-error-text max-w-xs truncate',\n cell: (delivery) => delivery.errorMessage || '-',\n },\n {\n key: 'actions',\n header: tr('channel.actions', 'Actions'),\n priority: 'secondary',\n cell: (delivery) => {\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n if (!canRetry) return null;\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleRetry(delivery.id)}\n disabled={retryingId === delivery.id}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium text-accent-primary hover:bg-accent-primary/10 rounded transition-colors disabled:opacity-50\"\n title={tr('channel.retryDelivery', 'Retry this delivery')}\n >\n <RotateCcw\n size={12}\n className={retryingId === delivery.id ? 'animate-spin' : ''}\n />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n );\n },\n },\n ];\n\n // Loading state\n if (isLoading) {\n return (\n <output\n className=\"flex items-center justify-center min-h-screen\"\n aria-label={tr('channel.loading', 'Loading')}\n >\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" aria-hidden=\"true\" />\n </output>\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_DELIVERY_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.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - Message deliveries\n </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\"\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={isLoadingAttempts ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingAttempts,\n onSelect: () => void loadAttempts(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingDeliveries,\n onSelect: () => void exportDeliveries(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportDeliveriesError && (\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\">{exportDeliveriesError}</p>\n </div>\n )}\n\n {/* Error Banner */}\n {retryError && (\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\">{retryError}</p>\n <button\n type=\"button\"\n onClick={() => setRetryError(null)}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n {tr('channel.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Info Banner */}\n <GlassCard treatment=\"insight\" className=\"p-4 border-border-seam\">\n <p className=\"text-sm text-text-secondary\">\n Delivery history shows all message attempts sent through this channel. Data is retained\n for 7 days. Click retry on a failed delivery to attempt sending again.\n </p>\n </GlassCard>\n\n {/* Deliveries List */}\n {isLoadingAttempts ? (\n <div className=\"bg-surface border border-border-seam rounded-lg px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-text-secondary mt-2\">Loading delivery attempts…</p>\n </div>\n ) : (\n <ResponsiveTable\n columns={deliveryColumns}\n data={attempts}\n rowKey={(delivery) => delivery.id}\n className=\"bg-surface border border-border-seam rounded-lg\"\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n description={tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n />\n }\n />\n )}\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 || isLoadingAttempts}\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 || isLoadingAttempts}\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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"}
|
|
@@ -4,48 +4,48 @@ import { WEBHOOK_RECEIVED_SUBSCRIPTION as n, useChannelSubscription as r } from
|
|
|
4
4
|
import { useChannel as ee } from "../../../hooks/useChannels.js";
|
|
5
5
|
import { PermissionGuard as i } from "../../../components/PermissionGuard.js";
|
|
6
6
|
import { CHANNEL_PERMISSIONS as a } from "../../../constants/permissions.js";
|
|
7
|
-
import { useExportWebhookEvents as
|
|
8
|
-
import { useCallback as
|
|
9
|
-
import { useI18n as
|
|
10
|
-
import { AlertCircle as
|
|
11
|
-
import { useNavigate as
|
|
12
|
-
import { usePermissions as
|
|
13
|
-
import { Fragment as
|
|
14
|
-
import { CTAOverflowMenu as
|
|
7
|
+
import { useExportWebhookEvents as te } from "../hooks/useExport.js";
|
|
8
|
+
import { useCallback as o, useEffect as s, useRef as c, useState as l } from "react";
|
|
9
|
+
import { useI18n as ne } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
|
+
import { AlertCircle as u, ArrowLeft as re, CheckCircle2 as d, ChevronLeft as f, ChevronRight as p, Clock as m, Download as h, Eye as ie, EyeOff as g, Loader2 as _, RefreshCw as v, XCircle as y } from "lucide-react";
|
|
11
|
+
import { useNavigate as ae, useParams as oe } from "react-router-dom";
|
|
12
|
+
import { usePermissions as b } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
13
|
+
import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
14
|
+
import { CTAOverflowMenu as w, GlassCard as T, IllustratedEmptyState as se } from "@burdenoff/fe-libs/ui";
|
|
15
15
|
//#region src/channel/modules/channels/pages/WebhookEventsPage.tsx
|
|
16
16
|
var E = {
|
|
17
17
|
PENDING: {
|
|
18
18
|
label: "Pending",
|
|
19
19
|
color: "text-text-secondary bg-bg-sunken",
|
|
20
|
-
icon:
|
|
20
|
+
icon: m
|
|
21
21
|
},
|
|
22
22
|
PROCESSING: {
|
|
23
23
|
label: "Processing",
|
|
24
24
|
color: "text-accent-primary bg-accent-primary/10",
|
|
25
|
-
icon:
|
|
25
|
+
icon: v
|
|
26
26
|
},
|
|
27
27
|
COMPLETED: {
|
|
28
28
|
label: "Completed",
|
|
29
29
|
color: "text-status-success-text bg-status-success-bg-subtle",
|
|
30
|
-
icon:
|
|
30
|
+
icon: d
|
|
31
31
|
},
|
|
32
32
|
FAILED: {
|
|
33
33
|
label: "Failed",
|
|
34
34
|
color: "text-status-error-text bg-status-error-bg-subtle",
|
|
35
|
-
icon:
|
|
35
|
+
icon: y
|
|
36
36
|
},
|
|
37
37
|
IGNORED: {
|
|
38
38
|
label: "Ignored",
|
|
39
39
|
color: "text-status-warning-text bg-status-warning-bg-subtle",
|
|
40
|
-
icon:
|
|
40
|
+
icon: g
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
function D() {
|
|
44
|
-
let { channelId:
|
|
45
|
-
let n =
|
|
44
|
+
let { channelId: d } = oe(), m = ae(), { hasPermission: y } = b(), D = y(a.CHANNEL_WEBHOOK_EXPORT), { t: ce } = ne(), O = (e, t) => {
|
|
45
|
+
let n = ce(e);
|
|
46
46
|
return n === e ? t : n;
|
|
47
|
-
}, { organizationId:
|
|
48
|
-
if (
|
|
47
|
+
}, { organizationId: le } = e(), { triggerExport: k, isExporting: A, exportError: j } = te(le ?? void 0, d), [M, N] = l(!0), [P, F] = l([]), [I, L] = l(null), [R, z] = l(25), [B, V] = l(0), [H, U] = l([void 0]), [W, G] = l(0), { channel: K, isLoading: ue, error: q, fetchWebhookEvents: J } = ee(d), Y = o(async (e) => {
|
|
48
|
+
if (d) {
|
|
49
49
|
N(!0);
|
|
50
50
|
try {
|
|
51
51
|
let t = await J(R, e);
|
|
@@ -55,26 +55,26 @@ function D() {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}, [
|
|
58
|
-
|
|
58
|
+
d,
|
|
59
59
|
J,
|
|
60
60
|
R
|
|
61
|
-
]), X =
|
|
62
|
-
|
|
61
|
+
]), X = c(Y), Z = c(H), Q = c(W);
|
|
62
|
+
s(() => {
|
|
63
63
|
X.current = Y, Z.current = H, Q.current = W;
|
|
64
64
|
});
|
|
65
|
-
let $ =
|
|
66
|
-
|
|
65
|
+
let $ = c(!0);
|
|
66
|
+
s(() => {
|
|
67
67
|
Promise.resolve().then(() => {
|
|
68
68
|
U([void 0]), G(0), X.current(void 0);
|
|
69
69
|
});
|
|
70
|
-
}, [R]),
|
|
70
|
+
}, [R]), s(() => {
|
|
71
71
|
if ($.current) {
|
|
72
72
|
$.current = !1;
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
Promise.resolve().then(() => X.current(Z.current[W]));
|
|
76
76
|
}, [W]);
|
|
77
|
-
let
|
|
77
|
+
let de = o(async () => {
|
|
78
78
|
let e = await Y(H[W]);
|
|
79
79
|
e && U((t) => {
|
|
80
80
|
let n = [...t];
|
|
@@ -84,107 +84,107 @@ function D() {
|
|
|
84
84
|
H,
|
|
85
85
|
W,
|
|
86
86
|
Y
|
|
87
|
-
]),
|
|
87
|
+
]), fe = o(() => {
|
|
88
88
|
let e = W - 1;
|
|
89
89
|
G(e), Y(H[e]);
|
|
90
90
|
}, [
|
|
91
91
|
H,
|
|
92
92
|
W,
|
|
93
93
|
Y
|
|
94
|
-
]),
|
|
94
|
+
]), pe = P.length === R, me = W > 0;
|
|
95
95
|
r({
|
|
96
96
|
query: n,
|
|
97
|
-
variables: { channelId:
|
|
97
|
+
variables: { channelId: d },
|
|
98
98
|
operationName: "WebhookReceived",
|
|
99
|
-
enabled: !!
|
|
100
|
-
onData:
|
|
101
|
-
t("webhook.received", { channelId:
|
|
102
|
-
}, [
|
|
103
|
-
pollFn:
|
|
99
|
+
enabled: !!d,
|
|
100
|
+
onData: o(() => {
|
|
101
|
+
t("webhook.received", { channelId: d }), X.current(Z.current[Q.current]);
|
|
102
|
+
}, [d]),
|
|
103
|
+
pollFn: o(() => {
|
|
104
104
|
X.current(Z.current[Q.current]);
|
|
105
105
|
}, []),
|
|
106
106
|
pollIntervalMs: 6e4
|
|
107
107
|
});
|
|
108
|
-
let
|
|
109
|
-
return
|
|
108
|
+
let he = (e) => E[e] || E.PENDING;
|
|
109
|
+
return ue ? /* @__PURE__ */ S("div", {
|
|
110
110
|
className: "flex items-center justify-center min-h-screen",
|
|
111
|
-
children: /* @__PURE__ */
|
|
111
|
+
children: /* @__PURE__ */ S(_, {
|
|
112
112
|
size: 32,
|
|
113
113
|
className: "animate-spin text-accent-primary"
|
|
114
114
|
})
|
|
115
|
-
}) : q || !K ? /* @__PURE__ */
|
|
115
|
+
}) : q || !K ? /* @__PURE__ */ S("div", {
|
|
116
116
|
className: "p-6",
|
|
117
|
-
children: /* @__PURE__ */
|
|
117
|
+
children: /* @__PURE__ */ C("div", {
|
|
118
118
|
className: "flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg",
|
|
119
119
|
children: [
|
|
120
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ S(u, {
|
|
121
121
|
size: 20,
|
|
122
122
|
className: "text-status-error-text"
|
|
123
123
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ S("div", { children: /* @__PURE__ */ S("p", {
|
|
125
125
|
className: "text-sm font-medium text-status-error-text",
|
|
126
126
|
children: q || "Channel not found"
|
|
127
127
|
}) }),
|
|
128
|
-
/* @__PURE__ */
|
|
128
|
+
/* @__PURE__ */ S("button", {
|
|
129
129
|
type: "button",
|
|
130
|
-
onClick: () =>
|
|
130
|
+
onClick: () => m(`/channels/${d}`),
|
|
131
131
|
className: "ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
|
|
132
132
|
children: "Go Back"
|
|
133
133
|
})
|
|
134
134
|
]
|
|
135
135
|
})
|
|
136
|
-
}) : /* @__PURE__ */
|
|
136
|
+
}) : /* @__PURE__ */ S(i, {
|
|
137
137
|
permission: a.CHANNEL_WEBHOOK_READ,
|
|
138
|
-
children: /* @__PURE__ */
|
|
138
|
+
children: /* @__PURE__ */ C("div", {
|
|
139
139
|
className: "space-y-6 p-3 sm:p-6",
|
|
140
140
|
children: [
|
|
141
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ C("div", {
|
|
142
142
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
143
|
-
children: [/* @__PURE__ */
|
|
143
|
+
children: [/* @__PURE__ */ C("div", {
|
|
144
144
|
className: "flex items-center gap-3",
|
|
145
|
-
children: [/* @__PURE__ */
|
|
145
|
+
children: [/* @__PURE__ */ S("button", {
|
|
146
146
|
type: "button",
|
|
147
|
-
onClick: () =>
|
|
148
|
-
"aria-label":
|
|
147
|
+
onClick: () => m(`/channels/${d}`),
|
|
148
|
+
"aria-label": O("channel.backToChannel", "Back to channel"),
|
|
149
149
|
className: "p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors",
|
|
150
|
-
children: /* @__PURE__ */
|
|
150
|
+
children: /* @__PURE__ */ S(re, {
|
|
151
151
|
size: 20,
|
|
152
152
|
"aria-hidden": "true"
|
|
153
153
|
})
|
|
154
|
-
}), /* @__PURE__ */
|
|
154
|
+
}), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h1", {
|
|
155
155
|
className: "text-xl sm:text-2xl font-bold text-text-primary",
|
|
156
|
-
children:
|
|
157
|
-
}), /* @__PURE__ */
|
|
156
|
+
children: O("channel.webhookEvents", "Webhook Events")
|
|
157
|
+
}), /* @__PURE__ */ C("p", {
|
|
158
158
|
className: "text-sm text-text-secondary mt-1",
|
|
159
159
|
children: [K.name, " - Event history"]
|
|
160
160
|
})] })]
|
|
161
|
-
}), /* @__PURE__ */
|
|
161
|
+
}), /* @__PURE__ */ C("div", {
|
|
162
162
|
className: "flex items-center gap-3",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ C("select", {
|
|
164
164
|
value: R,
|
|
165
165
|
onChange: (e) => z(Number(e.target.value)),
|
|
166
166
|
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",
|
|
167
167
|
children: [
|
|
168
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ S("option", {
|
|
169
169
|
value: 10,
|
|
170
170
|
children: "10 results"
|
|
171
171
|
}),
|
|
172
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ S("option", {
|
|
173
173
|
value: 25,
|
|
174
174
|
children: "25 results"
|
|
175
175
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ S("option", {
|
|
177
177
|
value: 50,
|
|
178
178
|
children: "50 results"
|
|
179
179
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ S("option", {
|
|
181
181
|
value: 100,
|
|
182
182
|
children: "100 results"
|
|
183
183
|
})
|
|
184
184
|
]
|
|
185
|
-
}), /* @__PURE__ */
|
|
186
|
-
label:
|
|
187
|
-
icon: /* @__PURE__ */
|
|
185
|
+
}), /* @__PURE__ */ S(w, { actions: [{
|
|
186
|
+
label: O("channel.refresh", "Refresh"),
|
|
187
|
+
icon: /* @__PURE__ */ S(v, {
|
|
188
188
|
size: 16,
|
|
189
189
|
className: M ? "animate-spin" : "",
|
|
190
190
|
"aria-hidden": "true"
|
|
@@ -192,143 +192,146 @@ function D() {
|
|
|
192
192
|
disabled: M,
|
|
193
193
|
onSelect: () => void Y(H[W])
|
|
194
194
|
}, ...D ? [{
|
|
195
|
-
label:
|
|
196
|
-
icon: A ? /* @__PURE__ */
|
|
195
|
+
label: O("channel.export", "Export"),
|
|
196
|
+
icon: A ? /* @__PURE__ */ S(_, {
|
|
197
197
|
size: 16,
|
|
198
198
|
className: "animate-spin",
|
|
199
199
|
"aria-hidden": "true"
|
|
200
|
-
}) : /* @__PURE__ */
|
|
200
|
+
}) : /* @__PURE__ */ S(h, {
|
|
201
201
|
size: 16,
|
|
202
202
|
"aria-hidden": "true"
|
|
203
203
|
}),
|
|
204
204
|
disabled: A,
|
|
205
|
-
onSelect: () => void
|
|
205
|
+
onSelect: () => void k()
|
|
206
206
|
}] : []] })]
|
|
207
207
|
})]
|
|
208
208
|
}),
|
|
209
|
-
j && /* @__PURE__ */
|
|
209
|
+
j && /* @__PURE__ */ C("div", {
|
|
210
210
|
className: "p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3",
|
|
211
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ S(u, {
|
|
212
212
|
size: 20,
|
|
213
213
|
className: "text-status-error-text"
|
|
214
|
-
}), /* @__PURE__ */
|
|
214
|
+
}), /* @__PURE__ */ S("p", {
|
|
215
215
|
className: "text-sm text-status-error-text flex-1",
|
|
216
216
|
children: j
|
|
217
217
|
})]
|
|
218
218
|
}),
|
|
219
|
-
/* @__PURE__ */
|
|
219
|
+
/* @__PURE__ */ S(T, {
|
|
220
220
|
treatment: "insight",
|
|
221
221
|
className: "p-4 border-border-seam",
|
|
222
|
-
children: /* @__PURE__ */
|
|
222
|
+
children: /* @__PURE__ */ S("p", {
|
|
223
223
|
className: "text-sm text-text-secondary",
|
|
224
224
|
children: "Webhook events show all incoming webhooks received by this channel. Data is retained for 7 days."
|
|
225
225
|
})
|
|
226
226
|
}),
|
|
227
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ S(T, {
|
|
228
228
|
treatment: "plain",
|
|
229
229
|
className: "border-border-seam rounded-lg overflow-hidden p-0",
|
|
230
|
-
children: /* @__PURE__ */
|
|
230
|
+
children: /* @__PURE__ */ S("div", {
|
|
231
231
|
className: "overflow-x-auto min-w-full",
|
|
232
|
-
children: /* @__PURE__ */
|
|
232
|
+
children: /* @__PURE__ */ C("table", {
|
|
233
233
|
className: "w-full min-w-[600px]",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
234
|
+
children: [/* @__PURE__ */ S("thead", {
|
|
235
235
|
className: "bg-bg-sunken/50 border-b border-border-subtle",
|
|
236
|
-
children: /* @__PURE__ */
|
|
237
|
-
/* @__PURE__ */
|
|
236
|
+
children: /* @__PURE__ */ C("tr", { children: [
|
|
237
|
+
/* @__PURE__ */ S("th", {
|
|
238
238
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
239
|
-
children:
|
|
239
|
+
children: O("channel.status", "Status")
|
|
240
240
|
}),
|
|
241
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ S("th", {
|
|
242
242
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
243
|
-
children:
|
|
243
|
+
children: O("channel.eventType", "Event Type")
|
|
244
244
|
}),
|
|
245
|
-
/* @__PURE__ */
|
|
245
|
+
/* @__PURE__ */ S("th", {
|
|
246
246
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
247
|
-
children:
|
|
247
|
+
children: O("channel.duplicate", "Duplicate")
|
|
248
248
|
}),
|
|
249
|
-
/* @__PURE__ */
|
|
249
|
+
/* @__PURE__ */ S("th", {
|
|
250
250
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
251
|
-
children:
|
|
251
|
+
children: O("channel.processedAt", "Processed At")
|
|
252
252
|
}),
|
|
253
|
-
/* @__PURE__ */
|
|
253
|
+
/* @__PURE__ */ S("th", {
|
|
254
254
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
255
|
-
children:
|
|
255
|
+
children: O("channel.error", "Error")
|
|
256
256
|
}),
|
|
257
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ S("th", {
|
|
258
258
|
className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
|
|
259
|
-
children:
|
|
259
|
+
children: O("channel.actions", "Actions")
|
|
260
260
|
})
|
|
261
261
|
] })
|
|
262
|
-
}), /* @__PURE__ */
|
|
262
|
+
}), /* @__PURE__ */ S("tbody", {
|
|
263
263
|
className: "divide-y divide-border",
|
|
264
|
-
children: P.length === 0 ? /* @__PURE__ */
|
|
264
|
+
children: P.length === 0 ? /* @__PURE__ */ S("tr", { children: /* @__PURE__ */ S("td", {
|
|
265
265
|
colSpan: 6,
|
|
266
|
-
className: "px-4 py-8
|
|
267
|
-
children:
|
|
266
|
+
className: "px-4 py-8",
|
|
267
|
+
children: /* @__PURE__ */ S(se, {
|
|
268
|
+
illustration: "empty-notifications",
|
|
269
|
+
title: O("channel.noWebhookEventsFound", "No webhook events found")
|
|
270
|
+
})
|
|
268
271
|
}) }) : P.map((e) => {
|
|
269
|
-
let t =
|
|
270
|
-
return /* @__PURE__ */
|
|
272
|
+
let t = he(e.status), n = t.icon, r = I === e.id;
|
|
273
|
+
return /* @__PURE__ */ C(x, { children: [/* @__PURE__ */ C("tr", {
|
|
271
274
|
className: "hover:bg-bg-sunken/30 transition-colors",
|
|
272
275
|
children: [
|
|
273
|
-
/* @__PURE__ */
|
|
276
|
+
/* @__PURE__ */ S("td", {
|
|
274
277
|
className: "px-4 py-3",
|
|
275
|
-
children: /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ C("div", {
|
|
276
279
|
className: "flex items-center gap-2",
|
|
277
|
-
children: [/* @__PURE__ */
|
|
280
|
+
children: [/* @__PURE__ */ S(n, {
|
|
278
281
|
size: 14,
|
|
279
282
|
className: t.color.split(" ")[0]
|
|
280
|
-
}), /* @__PURE__ */
|
|
283
|
+
}), /* @__PURE__ */ S("span", {
|
|
281
284
|
className: `text-sm font-medium ${t.color}`,
|
|
282
285
|
children: t.label
|
|
283
286
|
})]
|
|
284
287
|
})
|
|
285
288
|
}),
|
|
286
|
-
/* @__PURE__ */
|
|
289
|
+
/* @__PURE__ */ S("td", {
|
|
287
290
|
className: "px-4 py-3 text-sm font-mono text-text-primary",
|
|
288
291
|
children: e.eventType
|
|
289
292
|
}),
|
|
290
|
-
/* @__PURE__ */
|
|
293
|
+
/* @__PURE__ */ S("td", {
|
|
291
294
|
className: "px-4 py-3",
|
|
292
|
-
children: e.isDuplicate ? /* @__PURE__ */
|
|
295
|
+
children: e.isDuplicate ? /* @__PURE__ */ S("span", {
|
|
293
296
|
className: "text-xs text-status-warning-text bg-status-warning-bg-subtle px-2 py-1 rounded",
|
|
294
297
|
children: "Duplicate"
|
|
295
|
-
}) : /* @__PURE__ */
|
|
298
|
+
}) : /* @__PURE__ */ S("span", {
|
|
296
299
|
className: "text-xs text-text-secondary",
|
|
297
300
|
children: "No"
|
|
298
301
|
})
|
|
299
302
|
}),
|
|
300
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ S("td", {
|
|
301
304
|
className: "px-4 py-3 text-sm text-text-secondary",
|
|
302
305
|
children: e.processedAt ? new Date(e.processedAt).toLocaleString() : "-"
|
|
303
306
|
}),
|
|
304
|
-
/* @__PURE__ */
|
|
307
|
+
/* @__PURE__ */ S("td", {
|
|
305
308
|
className: "px-4 py-3 text-sm text-status-error-text max-w-xs truncate",
|
|
306
309
|
children: e.errorMessage || "-"
|
|
307
310
|
}),
|
|
308
|
-
/* @__PURE__ */
|
|
311
|
+
/* @__PURE__ */ S("td", {
|
|
309
312
|
className: "px-4 py-3",
|
|
310
|
-
children: /* @__PURE__ */
|
|
313
|
+
children: /* @__PURE__ */ S("button", {
|
|
311
314
|
type: "button",
|
|
312
315
|
onClick: () => L(r ? null : e.id),
|
|
313
|
-
"aria-label": r ?
|
|
316
|
+
"aria-label": r ? O("channel.hidePayload", "Hide payload") : O("channel.viewPayload", "View payload"),
|
|
314
317
|
"aria-expanded": r,
|
|
315
318
|
className: "p-1 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded transition-colors",
|
|
316
|
-
children:
|
|
319
|
+
children: S(r ? g : ie, {
|
|
317
320
|
size: 16,
|
|
318
321
|
"aria-hidden": "true"
|
|
319
322
|
})
|
|
320
323
|
})
|
|
321
324
|
})
|
|
322
325
|
]
|
|
323
|
-
}, e.id), r && /* @__PURE__ */
|
|
326
|
+
}, e.id), r && /* @__PURE__ */ S("tr", { children: /* @__PURE__ */ S("td", {
|
|
324
327
|
colSpan: 6,
|
|
325
328
|
className: "px-4 py-3 bg-bg-sunken/30",
|
|
326
|
-
children: /* @__PURE__ */
|
|
329
|
+
children: /* @__PURE__ */ C("div", {
|
|
327
330
|
className: "text-xs",
|
|
328
|
-
children: [/* @__PURE__ */
|
|
331
|
+
children: [/* @__PURE__ */ C("p", {
|
|
329
332
|
className: "text-text-secondary mb-1",
|
|
330
|
-
children: [
|
|
331
|
-
}), /* @__PURE__ */
|
|
333
|
+
children: [O("channel.payload", "Payload"), ":"]
|
|
334
|
+
}), /* @__PURE__ */ S("pre", {
|
|
332
335
|
className: "bg-surface p-2 rounded border border-border-subtle overflow-auto",
|
|
333
336
|
children: JSON.stringify(e.payload, null, 2)
|
|
334
337
|
})]
|
|
@@ -339,39 +342,39 @@ function D() {
|
|
|
339
342
|
})
|
|
340
343
|
})
|
|
341
344
|
}),
|
|
342
|
-
/* @__PURE__ */
|
|
345
|
+
/* @__PURE__ */ C("div", {
|
|
343
346
|
className: "flex items-center justify-between",
|
|
344
|
-
children: [/* @__PURE__ */
|
|
347
|
+
children: [/* @__PURE__ */ S("p", {
|
|
345
348
|
className: "text-sm text-text-secondary",
|
|
346
|
-
children: B > 0 && /* @__PURE__ */
|
|
347
|
-
|
|
349
|
+
children: B > 0 && /* @__PURE__ */ C(x, { children: [
|
|
350
|
+
O("channel.page", "Page"),
|
|
348
351
|
" ",
|
|
349
|
-
/* @__PURE__ */
|
|
352
|
+
/* @__PURE__ */ S("span", {
|
|
350
353
|
className: "font-medium",
|
|
351
354
|
children: W + 1
|
|
352
355
|
}),
|
|
353
356
|
" · ",
|
|
354
|
-
|
|
357
|
+
O("channel.totalRecords", "Total"),
|
|
355
358
|
": ",
|
|
356
|
-
/* @__PURE__ */
|
|
359
|
+
/* @__PURE__ */ S("span", {
|
|
357
360
|
className: "font-medium",
|
|
358
361
|
children: B
|
|
359
362
|
})
|
|
360
363
|
] })
|
|
361
|
-
}), /* @__PURE__ */
|
|
364
|
+
}), /* @__PURE__ */ C("div", {
|
|
362
365
|
className: "flex items-center gap-2",
|
|
363
|
-
children: [/* @__PURE__ */
|
|
366
|
+
children: [/* @__PURE__ */ C("button", {
|
|
364
367
|
type: "button",
|
|
365
|
-
onClick:
|
|
366
|
-
disabled: !
|
|
368
|
+
onClick: fe,
|
|
369
|
+
disabled: !me || M,
|
|
367
370
|
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",
|
|
368
|
-
children: [/* @__PURE__ */
|
|
369
|
-
}), /* @__PURE__ */
|
|
371
|
+
children: [/* @__PURE__ */ S(f, { size: 16 }), O("channel.previous", "Previous")]
|
|
372
|
+
}), /* @__PURE__ */ C("button", {
|
|
370
373
|
type: "button",
|
|
371
|
-
onClick: () => void
|
|
372
|
-
disabled: !
|
|
374
|
+
onClick: () => void de(),
|
|
375
|
+
disabled: !pe || M,
|
|
373
376
|
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",
|
|
374
|
-
children: [
|
|
377
|
+
children: [O("channel.next", "Next"), /* @__PURE__ */ S(p, { size: 16 })]
|
|
375
378
|
})]
|
|
376
379
|
})]
|
|
377
380
|
})
|