@burdenoff/microfe-channel 2026.624.3 → 2026.625.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +108 -98
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +287 -277
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +140 -131
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +170 -173
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +127 -120
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +161 -155
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +70 -68
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +27 -24
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +161 -156
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +179 -182
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +130 -122
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +137 -132
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +87 -84
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +193 -194
- 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 ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\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 { 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 <button\n type=\"button\"\n onClick={() => void loadAttempts(cursorStack[currentPageIdx])}\n disabled={isLoadingAttempts}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50\"\n >\n <RefreshCw size={16} className={isLoadingAttempts ? 'animate-spin' : ''} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportDeliveries()}\n disabled={isExportingDeliveries}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border-subtle hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tr('channel.exportCSV', 'Export to CSV')}\n >\n {isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\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 <div className=\"p-4 bg-surface border border-border-subtle rounded-lg\">\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 </div>\n\n {/* Deliveries List */}\n {isLoadingAttempts ? (\n <div className=\"bg-surface border border-border-subtle 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-subtle 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 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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"}
|
|
@@ -7,8 +7,9 @@ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
7
7
|
import { AlertCircle as u, ArrowLeft as d, Bell as f, Hash as p, Inbox as m, Loader2 as h, Mail as g, MessageCircle as _, MessageSquare as v, Save as y, Send as b, Smartphone as x, Trash2 as S, Users as C } from "lucide-react";
|
|
8
8
|
import { useNavigate as w, useParams as T } from "react-router-dom";
|
|
9
9
|
import { Fragment as E, jsx as D, jsxs as O } from "react/jsx-runtime";
|
|
10
|
+
import { GlassCard as k } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/channel/modules/channels/pages/EditChannelPage.tsx
|
|
11
|
-
var
|
|
12
|
+
var A = [
|
|
12
13
|
{
|
|
13
14
|
value: "EMAIL",
|
|
14
15
|
label: "Email",
|
|
@@ -184,7 +185,7 @@ var k = [
|
|
|
184
185
|
label: "HTTP Webhook"
|
|
185
186
|
}]
|
|
186
187
|
}
|
|
187
|
-
],
|
|
188
|
+
], j = [
|
|
188
189
|
{
|
|
189
190
|
value: 0,
|
|
190
191
|
label: "0 - Lowest"
|
|
@@ -206,17 +207,17 @@ var k = [
|
|
|
206
207
|
label: "4 - Highest"
|
|
207
208
|
}
|
|
208
209
|
];
|
|
209
|
-
function
|
|
210
|
+
function M() {
|
|
210
211
|
let f = w(), { channelId: p } = T(), { channel: m, isLoading: g, error: _ } = n(p), { updateChannel: v, deleteChannel: b } = r(), { t: x } = l(), C = (e, t) => {
|
|
211
212
|
let n = x(e);
|
|
212
213
|
return n === e ? t : n;
|
|
213
|
-
}, [
|
|
214
|
+
}, [M, N] = c(""), [P, F] = c(""), [I, L] = c("AWS_SES"), [R, z] = c(2), [B, V] = c("INACTIVE"), [H, U] = c(!1), [W, G] = c(!1), [K, q] = c(null), [J, Y] = c(!1), X = s(null);
|
|
214
215
|
o(() => {
|
|
215
|
-
m && m.id !==
|
|
216
|
-
|
|
216
|
+
m && m.id !== X.current && (X.current = m.id, Promise.resolve().then(() => {
|
|
217
|
+
N(m.name || ""), F(m.description || ""), L(m.adapterType || "AWS_SES"), z(m.priority || 2), V(m.status || "INACTIVE");
|
|
217
218
|
}));
|
|
218
219
|
}, [m]);
|
|
219
|
-
let
|
|
220
|
+
let Z = m?.platform || "EMAIL", Q = A.find((e) => e.value === Z);
|
|
220
221
|
return g ? /* @__PURE__ */ D("div", {
|
|
221
222
|
className: "flex items-center justify-center min-h-screen",
|
|
222
223
|
children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-text-secondary" })
|
|
@@ -264,124 +265,131 @@ function j() {
|
|
|
264
265
|
children: /* @__PURE__ */ O("div", {
|
|
265
266
|
className: "space-y-6",
|
|
266
267
|
children: [
|
|
267
|
-
|
|
268
|
+
K && /* @__PURE__ */ O("div", {
|
|
268
269
|
className: "p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3",
|
|
269
270
|
children: [/* @__PURE__ */ D(u, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ D("p", {
|
|
270
271
|
className: "text-sm text-status-error-text",
|
|
271
|
-
children:
|
|
272
|
+
children: K
|
|
272
273
|
})]
|
|
273
274
|
}),
|
|
274
|
-
/* @__PURE__ */ O(
|
|
275
|
-
|
|
275
|
+
/* @__PURE__ */ O(k, {
|
|
276
|
+
treatment: "insight",
|
|
277
|
+
className: "p-4 border-border-seam space-y-2",
|
|
276
278
|
children: [/* @__PURE__ */ D("div", {
|
|
277
279
|
className: "flex items-center gap-2",
|
|
278
|
-
children:
|
|
279
|
-
let e =
|
|
280
|
+
children: Q && /* @__PURE__ */ O(E, { children: [(() => {
|
|
281
|
+
let e = Q.icon;
|
|
280
282
|
return /* @__PURE__ */ D(e, { className: "size-5 text-text-secondary" });
|
|
281
283
|
})(), /* @__PURE__ */ D("span", {
|
|
282
284
|
className: "font-medium",
|
|
283
|
-
children:
|
|
285
|
+
children: Z
|
|
284
286
|
})] })
|
|
285
287
|
}), /* @__PURE__ */ D("p", {
|
|
286
288
|
className: "text-sm text-text-secondary",
|
|
287
289
|
children: C("channel.platformLockedHint", "Platform cannot be changed after channel creation. Create a new channel to use a different platform.")
|
|
288
290
|
})]
|
|
289
291
|
}),
|
|
290
|
-
/* @__PURE__ */ O(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
type: "text",
|
|
298
|
-
value: j,
|
|
299
|
-
onChange: (e) => M(e.target.value),
|
|
300
|
-
placeholder: C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
|
|
301
|
-
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
302
|
-
})]
|
|
303
|
-
}),
|
|
304
|
-
/* @__PURE__ */ O("div", {
|
|
305
|
-
className: "space-y-2",
|
|
306
|
-
children: [/* @__PURE__ */ D("label", {
|
|
307
|
-
className: "text-sm font-medium",
|
|
308
|
-
children: C("channel.description", "Description")
|
|
309
|
-
}), /* @__PURE__ */ D("textarea", {
|
|
310
|
-
"aria-label": C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
|
|
311
|
-
value: N,
|
|
312
|
-
onChange: (e) => P(e.target.value),
|
|
313
|
-
placeholder: C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
|
|
314
|
-
rows: 3,
|
|
315
|
-
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
316
|
-
})]
|
|
317
|
-
}),
|
|
318
|
-
Z && /* @__PURE__ */ O("div", {
|
|
319
|
-
className: "space-y-2",
|
|
320
|
-
children: [/* @__PURE__ */ D("label", {
|
|
321
|
-
className: "text-sm font-medium",
|
|
322
|
-
children: C("channel.adapterProvider", "Adapter / Provider")
|
|
323
|
-
}), /* @__PURE__ */ D("select", {
|
|
324
|
-
value: F,
|
|
325
|
-
onChange: (e) => I(e.target.value),
|
|
326
|
-
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
327
|
-
children: Z.adapters.map((e) => /* @__PURE__ */ D("option", {
|
|
328
|
-
value: e.value,
|
|
329
|
-
children: e.label
|
|
330
|
-
}, e.value))
|
|
331
|
-
})]
|
|
332
|
-
}),
|
|
333
|
-
/* @__PURE__ */ O("div", {
|
|
334
|
-
className: "grid grid-cols-2 gap-4",
|
|
335
|
-
children: [/* @__PURE__ */ O("div", {
|
|
336
|
-
className: "space-y-2",
|
|
337
|
-
children: [
|
|
338
|
-
/* @__PURE__ */ D("label", {
|
|
292
|
+
/* @__PURE__ */ O(k, {
|
|
293
|
+
treatment: "plain",
|
|
294
|
+
className: "p-5 border-border-seam space-y-6",
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ O("div", {
|
|
297
|
+
className: "space-y-2",
|
|
298
|
+
children: [/* @__PURE__ */ O("label", {
|
|
339
299
|
className: "text-sm font-medium",
|
|
340
|
-
children: C("channel.
|
|
341
|
-
}),
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
300
|
+
children: [C("channel.channelName", "Channel Name"), " *"]
|
|
301
|
+
}), /* @__PURE__ */ D("input", {
|
|
302
|
+
"aria-label": C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
|
|
303
|
+
type: "text",
|
|
304
|
+
value: M,
|
|
305
|
+
onChange: (e) => N(e.target.value),
|
|
306
|
+
placeholder: C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
|
|
307
|
+
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
308
|
+
})]
|
|
309
|
+
}),
|
|
310
|
+
/* @__PURE__ */ O("div", {
|
|
311
|
+
className: "space-y-2",
|
|
312
|
+
children: [/* @__PURE__ */ D("label", {
|
|
313
|
+
className: "text-sm font-medium",
|
|
314
|
+
children: C("channel.description", "Description")
|
|
315
|
+
}), /* @__PURE__ */ D("textarea", {
|
|
316
|
+
"aria-label": C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
|
|
317
|
+
value: P,
|
|
318
|
+
onChange: (e) => F(e.target.value),
|
|
319
|
+
placeholder: C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
|
|
320
|
+
rows: 3,
|
|
321
|
+
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
322
|
+
})]
|
|
323
|
+
}),
|
|
324
|
+
Q && /* @__PURE__ */ O("div", {
|
|
325
|
+
className: "space-y-2",
|
|
326
|
+
children: [/* @__PURE__ */ D("label", {
|
|
327
|
+
className: "text-sm font-medium",
|
|
328
|
+
children: C("channel.adapterProvider", "Adapter / Provider")
|
|
329
|
+
}), /* @__PURE__ */ D("select", {
|
|
330
|
+
value: I,
|
|
331
|
+
onChange: (e) => L(e.target.value),
|
|
345
332
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
346
|
-
children:
|
|
333
|
+
children: Q.adapters.map((e) => /* @__PURE__ */ D("option", {
|
|
347
334
|
value: e.value,
|
|
348
335
|
children: e.label
|
|
349
336
|
}, e.value))
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
337
|
+
})]
|
|
338
|
+
}),
|
|
339
|
+
/* @__PURE__ */ O("div", {
|
|
340
|
+
className: "grid grid-cols-2 gap-4",
|
|
341
|
+
children: [/* @__PURE__ */ O("div", {
|
|
342
|
+
className: "space-y-2",
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ D("label", {
|
|
345
|
+
className: "text-sm font-medium",
|
|
346
|
+
children: C("channel.priority", "Priority")
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ D("select", {
|
|
349
|
+
value: R,
|
|
350
|
+
onChange: (e) => z(Number(e.target.value)),
|
|
351
|
+
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
352
|
+
children: j.map((e) => /* @__PURE__ */ D("option", {
|
|
353
|
+
value: e.value,
|
|
354
|
+
children: e.label
|
|
355
|
+
}, e.value))
|
|
356
|
+
}),
|
|
357
|
+
/* @__PURE__ */ D("p", {
|
|
358
|
+
className: "text-xs text-text-secondary",
|
|
359
|
+
children: C("channel.priorityFailoverHint", "Higher priority channels are used first in failover")
|
|
360
|
+
})
|
|
361
|
+
]
|
|
362
|
+
}), /* @__PURE__ */ O("div", {
|
|
363
|
+
className: "space-y-2",
|
|
364
|
+
children: [/* @__PURE__ */ D("label", {
|
|
365
|
+
className: "text-sm font-medium",
|
|
366
|
+
children: C("channel.status", "Status")
|
|
367
|
+
}), /* @__PURE__ */ O("select", {
|
|
368
|
+
value: B,
|
|
369
|
+
onChange: (e) => V(e.target.value),
|
|
370
|
+
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ D("option", {
|
|
373
|
+
value: "INACTIVE",
|
|
374
|
+
children: C("channel.status.inactive", "Inactive")
|
|
375
|
+
}),
|
|
376
|
+
/* @__PURE__ */ D("option", {
|
|
377
|
+
value: "ACTIVE",
|
|
378
|
+
children: C("channel.status.active", "Active")
|
|
379
|
+
}),
|
|
380
|
+
/* @__PURE__ */ D("option", {
|
|
381
|
+
value: "SUSPENDED",
|
|
382
|
+
children: C("channel.status.suspended", "Suspended")
|
|
383
|
+
})
|
|
384
|
+
]
|
|
385
|
+
})]
|
|
386
|
+
})]
|
|
387
|
+
})
|
|
388
|
+
]
|
|
381
389
|
}),
|
|
382
390
|
/* @__PURE__ */ O("div", {
|
|
383
391
|
className: "flex items-center justify-between pt-6 border-t",
|
|
384
|
-
children: [/* @__PURE__ */ D("div", { children:
|
|
392
|
+
children: [/* @__PURE__ */ D("div", { children: J ? /* @__PURE__ */ O("div", {
|
|
385
393
|
className: "flex items-center gap-2",
|
|
386
394
|
children: [
|
|
387
395
|
/* @__PURE__ */ D("span", {
|
|
@@ -391,22 +399,22 @@ function j() {
|
|
|
391
399
|
/* @__PURE__ */ O("button", {
|
|
392
400
|
type: "button",
|
|
393
401
|
onClick: async () => {
|
|
394
|
-
if (!(!p || !
|
|
395
|
-
e("medium"),
|
|
402
|
+
if (!(!p || !J)) {
|
|
403
|
+
e("medium"), G(!0);
|
|
396
404
|
try {
|
|
397
405
|
await b(p) ? (t("success"), f("../list")) : t("error");
|
|
398
406
|
} catch (e) {
|
|
399
|
-
t("error"),
|
|
407
|
+
t("error"), q(e instanceof Error ? e.message : C("channel.deleteFailed", "Failed to delete channel")), G(!1);
|
|
400
408
|
}
|
|
401
409
|
}
|
|
402
410
|
},
|
|
403
|
-
disabled:
|
|
411
|
+
disabled: W,
|
|
404
412
|
className: "px-4 py-2 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors flex items-center gap-2",
|
|
405
|
-
children: [
|
|
413
|
+
children: [W ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.delete", "Delete")]
|
|
406
414
|
}),
|
|
407
415
|
/* @__PURE__ */ D("button", {
|
|
408
416
|
type: "button",
|
|
409
|
-
onClick: () =>
|
|
417
|
+
onClick: () => Y(!1),
|
|
410
418
|
className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors",
|
|
411
419
|
children: C("channel.cancel", "Cancel")
|
|
412
420
|
})
|
|
@@ -415,7 +423,7 @@ function j() {
|
|
|
415
423
|
permission: a.CHANNEL_DELETE,
|
|
416
424
|
children: /* @__PURE__ */ O("button", {
|
|
417
425
|
type: "button",
|
|
418
|
-
onClick: () =>
|
|
426
|
+
onClick: () => Y(!0),
|
|
419
427
|
className: "px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg/10 rounded-lg transition-colors flex items-center gap-2",
|
|
420
428
|
children: [/* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.deleteChannel", "Delete Channel")]
|
|
421
429
|
})
|
|
@@ -430,29 +438,29 @@ function j() {
|
|
|
430
438
|
type: "button",
|
|
431
439
|
onClick: async () => {
|
|
432
440
|
if (p) {
|
|
433
|
-
if (!
|
|
434
|
-
|
|
441
|
+
if (!M.trim()) {
|
|
442
|
+
q(C("channel.validation.nameRequired", "Channel name is required"));
|
|
435
443
|
return;
|
|
436
444
|
}
|
|
437
|
-
|
|
445
|
+
U(!0), q(null);
|
|
438
446
|
try {
|
|
439
447
|
await v(p, {
|
|
440
|
-
name:
|
|
441
|
-
description:
|
|
442
|
-
adapterType:
|
|
443
|
-
priority:
|
|
444
|
-
status:
|
|
445
|
-
}) ? f(`/channels/${p}`) :
|
|
448
|
+
name: M.trim(),
|
|
449
|
+
description: P.trim() || void 0,
|
|
450
|
+
adapterType: I,
|
|
451
|
+
priority: R,
|
|
452
|
+
status: B
|
|
453
|
+
}) ? f(`/channels/${p}`) : q(C("channel.updateFailed", "Failed to update channel"));
|
|
446
454
|
} catch (e) {
|
|
447
|
-
|
|
455
|
+
q(e instanceof Error ? e.message : C("channel.updateFailed", "Failed to update channel"));
|
|
448
456
|
} finally {
|
|
449
|
-
|
|
457
|
+
U(!1);
|
|
450
458
|
}
|
|
451
459
|
}
|
|
452
460
|
},
|
|
453
|
-
disabled:
|
|
461
|
+
disabled: H || !M.trim(),
|
|
454
462
|
className: "px-6 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2 disabled:opacity-50",
|
|
455
|
-
children: [
|
|
463
|
+
children: [H ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(y, { className: "size-4 " }), C("channel.saveChanges", "Save Changes")]
|
|
456
464
|
})]
|
|
457
465
|
})]
|
|
458
466
|
})
|
|
@@ -463,6 +471,6 @@ function j() {
|
|
|
463
471
|
});
|
|
464
472
|
}
|
|
465
473
|
//#endregion
|
|
466
|
-
export {
|
|
474
|
+
export { M as EditChannelPage };
|
|
467
475
|
|
|
468
476
|
//# sourceMappingURL=EditChannelPage.js.map
|