@burdenoff/microfe-channel 2026.608.1 → 2026.624.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.
Files changed (31) hide show
  1. package/dist/channel/components/AdapterCredentialsForm.js +11 -11
  2. package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
  3. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +54 -54
  4. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  5. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +456 -462
  6. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  7. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +26 -26
  8. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  9. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +226 -252
  10. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  11. package/dist/channel/modules/channels/pages/ChannelsListPage.js +41 -41
  12. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  13. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +30 -30
  14. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  15. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +29 -29
  16. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  17. package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -28
  18. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  19. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +8 -8
  20. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  21. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +208 -233
  22. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  23. package/dist/channel/modules/channels/pages/EditChannelPage.js +25 -25
  24. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  25. package/dist/channel/modules/channels/pages/EditConnectionPage.js +4 -4
  26. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  27. package/dist/channel/modules/channels/pages/SendMessagePage.js +27 -27
  28. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  29. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +29 -29
  30. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  31. package/package.json +1 -1
@@ -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 { 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-muted-foreground bg-muted', 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 // 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-muted-foreground hover:text-foreground hover:bg-muted 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-foreground\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-muted-foreground 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 rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-foreground\"\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-muted-foreground hover:text-foreground hover:bg-muted 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 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 rounded-lg\">\n <p className=\"text-sm text-muted-foreground\">\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 <div className=\"bg-surface border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full min-w-[700px]\">\n <thead className=\"bg-muted/50 border-b border-border\">\n <tr>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.content', 'Content')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.sentAt', 'Sent At')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.retries', 'Retries')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.error', 'Error')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {isLoadingAttempts ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-muted-foreground mt-2\">\n Loading delivery attempts…\n </p>\n </td>\n </tr>\n ) : attempts.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-12 text-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Send size={32} className=\"text-muted-foreground\" />\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n </p>\n </div>\n </td>\n </tr>\n ) : (\n attempts.map((delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n return (\n <tr key={delivery.id} className=\"hover:bg-muted/30 transition-colors\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span className={`text-sm font-medium ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm text-foreground truncate max-w-xs\">\n {delivery.content}\n </td>\n <td className=\"px-4 py-3 text-sm text-muted-foreground\">\n {delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-'}\n </td>\n <td className=\"px-4 py-3 text-sm text-muted-foreground\">\n {delivery.retryCount}\n </td>\n <td className=\"px-4 py-3 text-sm text-status-error-text max-w-xs truncate\">\n {delivery.errorMessage || '-'}\n </td>\n <td className=\"px-4 py-3\">\n {canRetry && (\n <PermissionGuard\n permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}\n >\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 </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\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 rounded-lg hover:bg-muted 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 rounded-lg hover:bg-muted 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 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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"}
@@ -178,7 +178,7 @@ var k = [
178
178
  label: "Webhook",
179
179
  description: "Generic HTTP webhook",
180
180
  icon: b,
181
- color: "text-muted-foreground bg-muted/50 border-border",
181
+ color: "text-text-secondary bg-bg-sunken/50 border-border-subtle",
182
182
  adapters: [{
183
183
  value: "WEBHOOK_HTTP",
184
184
  label: "HTTP Webhook"
@@ -219,11 +219,11 @@ function j() {
219
219
  let X = m?.platform || "EMAIL", Z = k.find((e) => e.value === X);
220
220
  return g ? /* @__PURE__ */ D("div", {
221
221
  className: "flex items-center justify-center min-h-screen",
222
- children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-muted-foreground" })
222
+ children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-text-secondary" })
223
223
  }) : _ || !m ? /* @__PURE__ */ O("div", {
224
224
  className: "flex flex-col items-center justify-center min-h-screen gap-4",
225
225
  children: [
226
- /* @__PURE__ */ D(u, { className: "size-12 text-destructive" }),
226
+ /* @__PURE__ */ D(u, { className: "size-12 text-status-error-text" }),
227
227
  /* @__PURE__ */ D("h1", {
228
228
  className: "text-xl font-semibold",
229
229
  children: C("channel.channelNotFound", "Channel not found")
@@ -238,9 +238,9 @@ function j() {
238
238
  }) : /* @__PURE__ */ D(i, {
239
239
  permission: a.CHANNEL_UPDATE,
240
240
  children: /* @__PURE__ */ O("div", {
241
- className: "min-h-screen bg-background",
241
+ className: "min-h-screen bg-bg-surface",
242
242
  children: [/* @__PURE__ */ D("div", {
243
- className: "border-b bg-card",
243
+ className: "border-b bg-bg-surface",
244
244
  children: /* @__PURE__ */ D("div", {
245
245
  className: "container mx-auto px-6 py-4",
246
246
  children: /* @__PURE__ */ O("div", {
@@ -248,13 +248,13 @@ function j() {
248
248
  children: [/* @__PURE__ */ D("button", {
249
249
  type: "button",
250
250
  onClick: () => f(`/channels/${p}`),
251
- className: "p-2 hover:bg-muted rounded-lg transition-colors",
251
+ className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
252
252
  children: /* @__PURE__ */ D(d, { className: "size-5 " })
253
253
  }), /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("h1", {
254
254
  className: "text-2xl font-bold",
255
255
  children: C("channel.editChannel", "Edit Channel")
256
256
  }), /* @__PURE__ */ D("p", {
257
- className: "text-sm text-muted-foreground",
257
+ className: "text-sm text-text-secondary",
258
258
  children: C("channel.updateChannelConfig", "Update channel configuration")
259
259
  })] })]
260
260
  })
@@ -265,25 +265,25 @@ function j() {
265
265
  className: "space-y-6",
266
266
  children: [
267
267
  G && /* @__PURE__ */ O("div", {
268
- className: "p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3",
269
- children: [/* @__PURE__ */ D(u, { className: "size-5 text-destructive shrink-0" }), /* @__PURE__ */ D("p", {
270
- className: "text-sm text-destructive",
268
+ className: "p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3",
269
+ children: [/* @__PURE__ */ D(u, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ D("p", {
270
+ className: "text-sm text-status-error-text",
271
271
  children: G
272
272
  })]
273
273
  }),
274
274
  /* @__PURE__ */ O("div", {
275
- className: "p-4 rounded-lg bg-muted/50 border space-y-2",
275
+ className: "p-4 rounded-lg bg-bg-sunken/50 border space-y-2",
276
276
  children: [/* @__PURE__ */ D("div", {
277
277
  className: "flex items-center gap-2",
278
278
  children: Z && /* @__PURE__ */ O(E, { children: [(() => {
279
279
  let e = Z.icon;
280
- return /* @__PURE__ */ D(e, { className: "size-5 text-muted-foreground" });
280
+ return /* @__PURE__ */ D(e, { className: "size-5 text-text-secondary" });
281
281
  })(), /* @__PURE__ */ D("span", {
282
282
  className: "font-medium",
283
283
  children: X
284
284
  })] })
285
285
  }), /* @__PURE__ */ D("p", {
286
- className: "text-sm text-muted-foreground",
286
+ className: "text-sm text-text-secondary",
287
287
  children: C("channel.platformLockedHint", "Platform cannot be changed after channel creation. Create a new channel to use a different platform.")
288
288
  })]
289
289
  }),
@@ -298,7 +298,7 @@ function j() {
298
298
  value: j,
299
299
  onChange: (e) => M(e.target.value),
300
300
  placeholder: C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
301
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
301
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
302
302
  })]
303
303
  }),
304
304
  /* @__PURE__ */ O("div", {
@@ -312,7 +312,7 @@ function j() {
312
312
  onChange: (e) => P(e.target.value),
313
313
  placeholder: C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
314
314
  rows: 3,
315
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none"
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
316
  })]
317
317
  }),
318
318
  Z && /* @__PURE__ */ O("div", {
@@ -323,7 +323,7 @@ function j() {
323
323
  }), /* @__PURE__ */ D("select", {
324
324
  value: F,
325
325
  onChange: (e) => I(e.target.value),
326
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
326
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
327
327
  children: Z.adapters.map((e) => /* @__PURE__ */ D("option", {
328
328
  value: e.value,
329
329
  children: e.label
@@ -342,14 +342,14 @@ function j() {
342
342
  /* @__PURE__ */ D("select", {
343
343
  value: L,
344
344
  onChange: (e) => R(Number(e.target.value)),
345
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
345
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
346
346
  children: A.map((e) => /* @__PURE__ */ D("option", {
347
347
  value: e.value,
348
348
  children: e.label
349
349
  }, e.value))
350
350
  }),
351
351
  /* @__PURE__ */ D("p", {
352
- className: "text-xs text-muted-foreground",
352
+ className: "text-xs text-text-secondary",
353
353
  children: C("channel.priorityFailoverHint", "Higher priority channels are used first in failover")
354
354
  })
355
355
  ]
@@ -361,7 +361,7 @@ function j() {
361
361
  }), /* @__PURE__ */ O("select", {
362
362
  value: z,
363
363
  onChange: (e) => B(e.target.value),
364
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
364
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
365
365
  children: [
366
366
  /* @__PURE__ */ D("option", {
367
367
  value: "INACTIVE",
@@ -385,7 +385,7 @@ function j() {
385
385
  className: "flex items-center gap-2",
386
386
  children: [
387
387
  /* @__PURE__ */ D("span", {
388
- className: "text-sm text-muted-foreground",
388
+ className: "text-sm text-text-secondary",
389
389
  children: C("channel.confirmDelete", "Confirm delete?")
390
390
  }),
391
391
  /* @__PURE__ */ O("button", {
@@ -401,13 +401,13 @@ function j() {
401
401
  }
402
402
  },
403
403
  disabled: U,
404
- className: "px-4 py-2 text-sm bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive/90 transition-colors flex items-center gap-2",
404
+ 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
405
  children: [U ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.delete", "Delete")]
406
406
  }),
407
407
  /* @__PURE__ */ D("button", {
408
408
  type: "button",
409
409
  onClick: () => J(!1),
410
- className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
410
+ className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors",
411
411
  children: C("channel.cancel", "Cancel")
412
412
  })
413
413
  ]
@@ -416,7 +416,7 @@ function j() {
416
416
  children: /* @__PURE__ */ O("button", {
417
417
  type: "button",
418
418
  onClick: () => J(!0),
419
- className: "px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2",
419
+ 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
420
  children: [/* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.deleteChannel", "Delete Channel")]
421
421
  })
422
422
  }) }), /* @__PURE__ */ O("div", {
@@ -424,7 +424,7 @@ function j() {
424
424
  children: [/* @__PURE__ */ D("button", {
425
425
  type: "button",
426
426
  onClick: () => f(`/channels/${p}`),
427
- className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
427
+ className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors",
428
428
  children: C("channel.cancel", "Cancel")
429
429
  }), /* @__PURE__ */ O("button", {
430
430
  type: "button",
@@ -451,7 +451,7 @@ function j() {
451
451
  }
452
452
  },
453
453
  disabled: V || !j.trim(),
454
- className: "px-6 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-2 disabled:opacity-50",
454
+ 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
455
  children: [V ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(y, { className: "size-4 " }), C("channel.saveChanges", "Save Changes")]
456
456
  })]
457
457
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-muted-foreground bg-muted/50 border-border',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = useChannels();\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\n // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-destructive\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-background\">\n {/* Header */}\n <div className=\"border-b bg-card\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-destructive shrink-0\" />\n <p className=\"text-sm text-destructive\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <div className=\"p-4 rounded-lg bg-muted/50 border space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"size-5 text-muted-foreground\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </div>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n aria-label={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 text-sm bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n className=\"px-6 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-2 disabled:opacity-50\"\n >\n {isSaving ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-text-secondary bg-bg-sunken/50 border-border-subtle',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = useChannels();\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\n // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <div className=\"p-4 rounded-lg bg-bg-sunken/50 border space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"size-5 text-text-secondary\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </div>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n aria-label={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n 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\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n 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\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n 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\"\n >\n {isSaving ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -118,7 +118,7 @@ function b() {
118
118
  B(!1);
119
119
  }
120
120
  },
121
- className: "space-y-6 bg-bg-elevated rounded-lg p-6 border border-border",
121
+ className: "space-y-6 bg-bg-elevated rounded-lg p-6 border border-border-subtle",
122
122
  children: [
123
123
  /* @__PURE__ */ y("div", {
124
124
  className: "text-sm text-fg-muted",
@@ -154,7 +154,7 @@ function b() {
154
154
  value: F,
155
155
  onChange: (e) => I(e.target.value),
156
156
  rows: 6,
157
- className: "w-full px-3 py-2 bg-bg border border-border rounded-md font-mono text-sm text-fg",
157
+ className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg",
158
158
  placeholder: E("connection.field.credentialsLeaveBlank", "Leave blank to keep existing credentials")
159
159
  })] }),
160
160
  /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
@@ -167,7 +167,7 @@ function b() {
167
167
  value: L,
168
168
  onChange: (e) => R(e.target.value),
169
169
  rows: 4,
170
- className: "w-full px-3 py-2 bg-bg border border-border rounded-md font-mono text-sm text-fg"
170
+ className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg"
171
171
  })] }),
172
172
  /* @__PURE__ */ y("div", {
173
173
  className: "flex gap-3 pt-2",
@@ -179,7 +179,7 @@ function b() {
179
179
  }), /* @__PURE__ */ v("button", {
180
180
  type: "button",
181
181
  onClick: () => b(-1),
182
- className: "px-4 py-2 text-fg border border-border rounded-md",
182
+ className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
183
183
  children: E("common.cancel", "Cancel")
184
184
  })]
185
185
  })
@@ -1 +1 @@
1
- {"version":3,"file":"EditConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditConnectionPage.tsx"],"sourcesContent":["/**\n * Edit Connection Page\n *\n * Standalone page mounted at /channels/connections/:connectionId/edit.\n * Fetches the connection directly via getConnection (no channelId required in URL).\n *\n * Form fields match UpdateConnectionInput exactly.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n UpdateConnectionDocument,\n type GetConnectionQuery,\n type UpdateConnectionMutation,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent, reportShellError } from '../../../utils/shellBridge';\n\ntype Connection = NonNullable<GetConnectionQuery['getConnection']>;\n\nexport function EditConnectionPage() {\n const navigate = useNavigate();\n const { connectionId } = useParams<{ connectionId: string }>();\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\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\n const [connection, setConnection] = useState<Connection | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const [isActive, setIsActive] = useState(true);\n const [credentials, setCredentials] = useState('');\n const [platformMetadata, setPlatformMetadata] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n useEffect(() => {\n if (!connectionId) return;\n let cancelled = false;\n void (async () => {\n setIsLoading(true);\n setError(null);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection ?? null;\n setConnection(conn);\n setIsActive(conn?.isActive ?? true);\n } catch (err: unknown) {\n if (cancelled) return;\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.loadFailed', 'Failed to load connection')\n );\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, apiGatewayUrl, authToken, organizationId, tr]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!connectionId) return;\n setError(null);\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n let parsedMetadata: Record<string, unknown> | undefined;\n if (platformMetadata.trim()) {\n try {\n parsedMetadata = JSON.parse(platformMetadata);\n } catch {\n setError(tr('connection.validation.metadataJson', 'Platform metadata must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n await client.mutate<UpdateConnectionMutation>({\n mutation: UpdateConnectionDocument,\n variables: {\n id: connectionId,\n input: {\n isActive,\n credentials: parsedCredentials,\n platformMetadata: parsedMetadata,\n },\n },\n });\n emitShellEvent('connection.updated', { id: connectionId });\n navigate(`../${connectionId}`);\n } catch (err: unknown) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'EditConnectionPage.submit', connectionId } },\n '[channel] Failed to update connection'\n );\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.updateFailed', 'Failed to update connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"container mx-auto p-6 flex items-center justify-center min-h-64\">\n <Loader2 className=\"size-6 animate-spin text-fg-muted\" />\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr('connection.notFound', 'Connection not found')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.edit.title', 'Edit Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr('connection.edit.subtitle', 'Update credentials, status, or platform metadata.')}\n </p>\n </div>\n </div>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <form\n onSubmit={handleSubmit}\n className=\"space-y-6 bg-bg-elevated rounded-lg p-6 border border-border\"\n >\n <div className=\"text-sm text-fg-muted\">\n <span className=\"font-medium text-fg\">ID:</span> {connection.id}\n <br />\n <span className=\"font-medium text-fg\">\n {tr('connection.field.authType', 'Auth Type')}:\n </span>{' '}\n {connection.authType}\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-fg\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n />\n {tr('connection.field.isActive', 'Active')}\n </label>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-conn-platform-metadata\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n </label>\n <textarea\n id=\"edit-conn-platform-metadata\"\n aria-label={tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n value={platformMetadata}\n onChange={(e) => setPlatformMetadata(e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 bg-bg border border-border rounded-md font-mono text-sm text-fg\"\n />\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('common.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditConnectionPage.tsx"],"sourcesContent":["/**\n * Edit Connection Page\n *\n * Standalone page mounted at /channels/connections/:connectionId/edit.\n * Fetches the connection directly via getConnection (no channelId required in URL).\n *\n * Form fields match UpdateConnectionInput exactly.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n UpdateConnectionDocument,\n type GetConnectionQuery,\n type UpdateConnectionMutation,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent, reportShellError } from '../../../utils/shellBridge';\n\ntype Connection = NonNullable<GetConnectionQuery['getConnection']>;\n\nexport function EditConnectionPage() {\n const navigate = useNavigate();\n const { connectionId } = useParams<{ connectionId: string }>();\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\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\n const [connection, setConnection] = useState<Connection | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const [isActive, setIsActive] = useState(true);\n const [credentials, setCredentials] = useState('');\n const [platformMetadata, setPlatformMetadata] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n useEffect(() => {\n if (!connectionId) return;\n let cancelled = false;\n void (async () => {\n setIsLoading(true);\n setError(null);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection ?? null;\n setConnection(conn);\n setIsActive(conn?.isActive ?? true);\n } catch (err: unknown) {\n if (cancelled) return;\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.loadFailed', 'Failed to load connection')\n );\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, apiGatewayUrl, authToken, organizationId, tr]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!connectionId) return;\n setError(null);\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n let parsedMetadata: Record<string, unknown> | undefined;\n if (platformMetadata.trim()) {\n try {\n parsedMetadata = JSON.parse(platformMetadata);\n } catch {\n setError(tr('connection.validation.metadataJson', 'Platform metadata must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n await client.mutate<UpdateConnectionMutation>({\n mutation: UpdateConnectionDocument,\n variables: {\n id: connectionId,\n input: {\n isActive,\n credentials: parsedCredentials,\n platformMetadata: parsedMetadata,\n },\n },\n });\n emitShellEvent('connection.updated', { id: connectionId });\n navigate(`../${connectionId}`);\n } catch (err: unknown) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'EditConnectionPage.submit', connectionId } },\n '[channel] Failed to update connection'\n );\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.updateFailed', 'Failed to update connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"container mx-auto p-6 flex items-center justify-center min-h-64\">\n <Loader2 className=\"size-6 animate-spin text-fg-muted\" />\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr('connection.notFound', 'Connection not found')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.edit.title', 'Edit Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr('connection.edit.subtitle', 'Update credentials, status, or platform metadata.')}\n </p>\n </div>\n </div>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <form\n onSubmit={handleSubmit}\n className=\"space-y-6 bg-bg-elevated rounded-lg p-6 border border-border-subtle\"\n >\n <div className=\"text-sm text-fg-muted\">\n <span className=\"font-medium text-fg\">ID:</span> {connection.id}\n <br />\n <span className=\"font-medium text-fg\">\n {tr('connection.field.authType', 'Auth Type')}:\n </span>{' '}\n {connection.authType}\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-fg\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n />\n {tr('connection.field.isActive', 'Active')}\n </label>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-conn-platform-metadata\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n </label>\n <textarea\n id=\"edit-conn-platform-metadata\"\n aria-label={tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n value={platformMetadata}\n onChange={(e) => setPlatformMetadata(e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n />\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('common.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border-subtle rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}