@burdenoff/microfe-channel 2026.527.1 → 2026.530.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 (25) hide show
  1. package/dist/channel/ChannelRoutes.js +5 -5
  2. package/dist/channel/ChannelRoutes.js.map +1 -1
  3. package/dist/channel/constants/permissions.js +3 -0
  4. package/dist/channel/constants/permissions.js.map +1 -1
  5. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +227 -221
  6. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  7. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +392 -391
  8. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  9. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +1 -1
  10. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  11. package/dist/channel/modules/channels/pages/ChannelsListPage.js +216 -212
  12. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  13. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +99 -98
  14. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  15. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +129 -126
  16. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  17. package/dist/channel/modules/channels/pages/CreateChannelPage.js +40 -50
  18. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  19. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +1 -1
  20. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  21. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +1 -1
  22. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  23. package/dist/channel/pages/ChannelLandingPage.js +50 -34
  24. package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
  25. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelDetailsPage.tsx"],"sourcesContent":["/**\n * Channel Details Page\n *\n * Displays channel details and provides connection/credentials management\n */\n\nimport { useState, useEffect } from 'react';\nimport { useParams, useNavigate, useLocation } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n Radio,\n ArrowLeft,\n Pencil,\n Loader2,\n AlertCircle,\n RefreshCw,\n Power,\n PowerOff,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Cable,\n Key,\n Trash2,\n Play,\n Plus,\n Save,\n X,\n Send,\n Mail,\n Phone,\n MessageSquare,\n Clock,\n Truck,\n Users,\n ArrowRight,\n} from 'lucide-react';\nimport {\n useChannel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n type ChannelAuthType,\n type ChannelDeliveryError,\n} from '../../../hooks/useChannels';\nimport {\n AdapterCredentialsForm,\n CredentialValidationStatus,\n} from '../../../components/AdapterCredentialsForm';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { AUTH_TYPE_I18N, HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\n\n/** Lightweight notification result for UI display (not the full GraphQL type) */\ninterface NotificationDisplayResult {\n success: boolean;\n messageId?: string | null;\n externalId?: string | null;\n status?: string | null;\n sentAt?: string | null;\n error?: Pick<ChannelDeliveryError, 'code' | 'message' | 'retryable'> | null;\n}\n\n// Platform display configuration\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\n// Status display configuration\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n INACTIVE: { label: 'Inactive', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n ERROR: {\n label: 'Error',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertTriangle,\n },\n ARCHIVED: { label: 'Archived', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n};\n\n// Health display configuration\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-muted-foreground', icon: HelpCircle },\n};\n\ninterface LocationState {\n pendingCredentials?: Record<string, unknown>;\n showCredentialsForm?: boolean;\n}\n\nexport function ChannelDetailsPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const location = useLocation();\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 locationState = location.state as LocationState | null;\n\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [showConnectionForm, setShowConnectionForm] = useState(\n locationState?.showCredentialsForm || false\n );\n const [credentials, setCredentials] = useState<Record<string, unknown>>(\n locationState?.pendingCredentials || {}\n );\n const [credentialsValid, setCredentialsValid] = useState(false);\n const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Test notification modal state\n const [showTestModal, setShowTestModal] = useState(false);\n const [testRecipient, setTestRecipient] = useState('');\n const [testSubject, setTestSubject] = useState(() =>\n tr('channel.testNotificationSubject', 'Test Notification')\n );\n const [testMessage, setTestMessage] = useState(() =>\n tr(\n 'channel.testNotificationBody',\n 'This is a test notification from Burdenoff Channel Service.'\n )\n );\n const [notificationResult, setNotificationResult] = useState<NotificationDisplayResult | null>(\n null\n );\n\n // Clear location state after using it to prevent re-triggering on refresh\n useEffect(() => {\n if (locationState?.pendingCredentials || locationState?.showCredentialsForm) {\n // Replace state without the pending data\n navigate(location.pathname, { replace: true, state: null });\n }\n }, []);\n\n const {\n channel,\n isLoading,\n error,\n updateChannel,\n runHealthCheck,\n createConnection,\n updateConnection,\n deleteConnection,\n testConnection,\n sendTestNotification,\n } = useChannel(channelId);\n\n const handleHealthCheck = async () => {\n setActionLoading('health');\n try {\n await runHealthCheck();\n } catch (err) {\n console.error('[channel] Failed to run health check', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async () => {\n if (!channel) return;\n setActionLoading('status');\n try {\n await updateChannel({\n status: channel.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE',\n });\n } catch (err) {\n console.error('[channel] Failed to toggle channel status', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleCreateConnection = async () => {\n if (!channel) return;\n setActionLoading('connection');\n try {\n // Determine auth type based on adapter type\n // Most adapters use API_KEY, some use BOT_TOKEN or WEBHOOK\n // Narrow generated enum may lag UI's widened AdapterType (e.g. 'SMTP'); compare as string.\n let authType: ChannelAuthType = 'API_KEY';\n const adapterTypeStr = channel.adapterType as string | null | undefined;\n if (adapterTypeStr?.includes('BOT')) {\n authType = 'BOT_TOKEN';\n } else if (adapterTypeStr?.includes('WEBHOOK')) {\n authType = 'WEBHOOK';\n } else if (adapterTypeStr === 'SMTP') {\n authType = 'SMTP_IMAP';\n }\n\n await createConnection({\n authType,\n credentials,\n });\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n } catch (err) {\n console.error('[channel] Failed to create connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleUpdateConnection = async (connectionId: string) => {\n setActionLoading('connection');\n try {\n await updateConnection(connectionId, {\n credentials,\n });\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n } catch (err) {\n console.error('[channel] Failed to update connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteConnection = async (connectionId: string) => {\n void nativeImpact('medium');\n setActionLoading('delete-connection');\n try {\n await deleteConnection(connectionId);\n void nativeNotify('success');\n } catch (err) {\n void nativeNotify('error');\n console.error('[channel] Failed to delete connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async (connectionId: string) => {\n setActionLoading('test');\n setTestResult(null);\n try {\n const result = await testConnection(connectionId);\n if (result) {\n setTestResult({\n success: result.success,\n message:\n result.message ||\n (result.success\n ? tr('channel.connectionTestSuccess', 'Connection successful!')\n : tr('channel.connectionTestFailed', 'Connection failed')),\n });\n }\n } catch (err) {\n console.error('[channel] Failed to test connection', err);\n setTestResult({\n success: false,\n message: tr('channel.connectionTestRequestFailed', 'Failed to test connection'),\n });\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleSendTestNotification = async () => {\n if (!testRecipient.trim()) return;\n\n setActionLoading('send-test');\n setNotificationResult(null);\n\n try {\n const options =\n channel?.platform === 'EMAIL'\n ? { subject: testSubject, html: `<p>${testMessage}</p>` }\n : undefined;\n\n const result = await sendTestNotification(testRecipient.trim(), testMessage, options);\n if (result) {\n setNotificationResult({\n success: result.success,\n messageId: result.messageId,\n externalId: result.externalId,\n status: result.status,\n sentAt: result.sentAt,\n error: result.error\n ? {\n code: result.error.code,\n message: result.error.message,\n retryable: result.error.retryable,\n }\n : null,\n });\n }\n } catch (err: unknown) {\n const errMsg =\n err instanceof Error\n ? err.message\n : tr('channel.sendTestNotificationFailed', 'Failed to send test notification');\n console.error('[channel] Failed to send test notification', err);\n setNotificationResult({\n success: false,\n error: {\n code: 'SEND_FAILED',\n message: errMsg,\n retryable: false,\n },\n });\n } finally {\n setActionLoading(null);\n }\n };\n\n const getRecipientPlaceholder = (platform: ChannelPlatform) => {\n switch (platform) {\n case 'EMAIL':\n return tr('channel.recipientPlaceholder.email', 'email@example.com');\n case 'SMS':\n return tr('channel.recipientPlaceholder.phone', '+1234567890');\n case 'PUSH':\n return tr('channel.recipientPlaceholder.deviceToken', 'Device token');\n case 'SLACK':\n return tr('channel.recipientPlaceholder.slack', 'Channel ID or User ID');\n case 'TEAMS':\n return tr('channel.recipientPlaceholder.channelId', 'Channel ID');\n case 'DISCORD':\n return tr('channel.recipientPlaceholder.channelId', 'Channel ID');\n case 'WHATSAPP':\n return tr('channel.recipientPlaceholder.phone', '+1234567890');\n default:\n return tr('channel.recipientPlaceholder.default', 'Recipient');\n }\n };\n\n const getRecipientLabel = (platform: ChannelPlatform) => {\n switch (platform) {\n case 'EMAIL':\n return tr('channel.recipientLabel.email', 'Email Address');\n case 'SMS':\n case 'WHATSAPP':\n return tr('channel.recipientLabel.phone', 'Phone Number');\n case 'PUSH':\n return tr('channel.recipientLabel.deviceToken', 'Device Token');\n case 'SLACK':\n case 'TEAMS':\n case 'DISCORD':\n return tr('channel.recipientLabel.channelOrUserId', 'Channel/User ID');\n default:\n return tr('channel.recipientLabel.default', 'Recipient');\n }\n };\n\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] || {\n label: platform,\n color: 'bg-bg-sunken text-text-secondary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] || STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] || HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || tr('channel.channelNotFound', 'Channel not found')}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\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 {tr('channel.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n const platformConfig = getPlatformConfig(channel.platform);\n const statusConfig = getStatusConfig(channel.status);\n const healthConfig = getHealthConfig(channel.connectionHealth);\n const StatusIcon = statusConfig.icon;\n const HealthIcon = healthConfig.icon;\n const connection = channel.connection;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_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(-1)}\n className=\"p-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <div className=\"flex items-center gap-2\">\n <Radio size={24} className=\"text-accent-primary\" />\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">{channel.name}</h1>\n </div>\n {channel.description && (\n <p className=\"text-sm text-muted-foreground mt-1\">{channel.description}</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={handleHealthCheck}\n disabled={actionLoading === 'health'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-status-info-text hover:bg-status-info-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'health' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={16} />\n )}\n {tr('channel.healthCheck', 'Health Check')}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleToggleStatus}\n disabled={actionLoading === 'status'}\n className={`flex items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors disabled:opacity-50 ${\n channel.status === 'ACTIVE'\n ? 'text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {actionLoading === 'status' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : channel.status === 'ACTIVE' ? (\n <PowerOff size={16} />\n ) : (\n <Power size={16} />\n )}\n {channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => {\n setShowTestModal(true);\n setNotificationResult(null);\n setTestRecipient('');\n }}\n disabled={!channel.connection}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-accent-primary hover:bg-accent-primary/10 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={\n !channel.connection\n ? tr('channel.configureCredentialsFirst', 'Configure credentials first')\n : tr('channel.sendTestNotification', 'Send test notification')\n }\n >\n <Send size={16} />\n {tr('channel.testChannel', 'Test Channel')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('./edit')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors shadow-sm\"\n >\n <Pencil size={16} />\n {tr('channel.edit', 'Edit')}\n </button>\n </div>\n </div>\n\n {/* Status Cards */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {/* Platform */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.platform', 'Platform')}\n </p>\n <span\n className={`inline-flex px-2 py-1 text-sm font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n\n {/* Status */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">{tr('channel.status', 'Status')}</p>\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={16} className={statusConfig.color.split(' ')[0]} />\n <span className={`px-2 py-1 text-sm font-medium rounded-full ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Health */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.connectionHealth', 'Connection Health')}\n </p>\n <div className=\"flex items-center gap-2\">\n <HealthIcon size={16} className={healthConfig.color} />\n <span className={`text-sm font-medium ${healthConfig.color}`}>\n {healthConfig.label}\n </span>\n </div>\n {channel.lastHealthCheckAt && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('channel.lastCheck', 'Last check')}:{' '}\n {new Date(channel.lastHealthCheckAt).toLocaleString()}\n </p>\n )}\n </div>\n\n {/* Connection Status */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.connection', 'Connection')}\n </p>\n <div className=\"flex items-center gap-2\">\n <Cable\n size={16}\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'\n }\n />\n <span\n className={`text-sm font-medium ${channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'}`}\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n </div>\n\n {/* Channel Details */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <h2 className=\"text-lg font-semibold text-foreground mb-4\">\n {tr('channel.channelDetails', 'Channel Details')}\n </h2>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.adapterType', 'Adapter Type')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {channel.adapterType?.replace(/_/g, ' ') ||\n tr('channel.notConfigured', 'Not configured')}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">{tr('channel.priority', 'Priority')}</p>\n <p className=\"text-sm font-medium text-foreground\">{channel.priority}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.consecutiveFailures', 'Consecutive Failures')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">{channel.consecutiveFailures}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.messages24h', 'Messages (24h)')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">{channel.messageCount24h}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.createdAt', 'Created At')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {new Date(channel.createdAt).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.updatedAt', 'Updated At')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {new Date(channel.updatedAt).toLocaleString()}\n </p>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"mt-4\">\n <p className=\"text-xs text-muted-foreground mb-1.5\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Channel tags\">\n {channel.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n\n {/* Health Check Error */}\n {channel.healthCheckError && (\n <div className=\"mt-4 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <p className=\"text-xs text-status-error-text font-medium\">\n {tr('channel.lastHealthCheckError', 'Last Health Check Error')}\n </p>\n <p className=\"text-sm text-status-error-text\">{channel.healthCheckError}</p>\n </div>\n )}\n </div>\n\n {/* Quick Links to Channel Sub-Pages */}\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`./deliveries`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-orange/10 rounded-lg\">\n <Truck size={18} className=\"text-accent-orange\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewMessageDeliveries', 'View message deliveries')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(`./webhooks`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-purple/10 rounded-lg\">\n <Clock size={18} className=\"text-accent-purple\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.webhookEvents', 'Webhook Events')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewIncomingWebhooks', 'View incoming webhooks')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(`./users`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-blue/10 rounded-lg\">\n <Users size={18} className=\"text-accent-blue\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.channelUsers', 'Channel Users')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewConnectedUsers', 'View connected users')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n </div>\n\n {/* Connection / Credentials Management */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Key size={20} className=\"text-accent-primary\" />\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('channel.connectionAndCredentials', 'Connection & Credentials')}\n </h2>\n </div>\n {!connection && !showConnectionForm && (\n <button\n type=\"button\"\n onClick={() => setShowConnectionForm(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.addCredentials', 'Add Credentials')}\n </button>\n )}\n </div>\n\n {/* Existing Connection */}\n {connection && !showConnectionForm && (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.authType', 'Auth Type')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {translateLabel(connection.authType, AUTH_TYPE_I18N, tr)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">{tr('channel.status', 'Status')}</p>\n <div className=\"flex items-center gap-2\">\n {connection.isHealthy ? (\n <CheckCircle2 size={14} className=\"text-status-success-text\" />\n ) : (\n <XCircle size={14} className=\"text-status-error-text\" />\n )}\n <span\n className={`text-sm font-medium ${connection.isHealthy ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {connection.isHealthy\n ? tr('channel.healthy', 'Healthy')\n : tr('channel.unhealthy', 'Unhealthy')}\n </span>\n </div>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.lastTested', 'Last Tested')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {connection.lastTestedAt\n ? new Date(connection.lastTestedAt).toLocaleString()\n : tr('channel.never', 'Never')}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.testStatus', 'Test Status')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {connection.testStatus || tr('channel.notTested', 'Not tested')}\n </p>\n </div>\n {connection.tokenExpiresAt && (\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.tokenExpires', 'Token Expires')}\n </p>\n <p\n className={`text-sm font-medium ${connection.needsTokenRefresh ? 'text-status-warning-text' : 'text-foreground'}`}\n >\n {new Date(connection.tokenExpiresAt).toLocaleString()}\n {connection.daysUntilExpiry !== undefined && (\n <span className=\"text-xs text-muted-foreground ml-1\">\n ({connection.daysUntilExpiry} {tr('channel.days', 'days')})\n </span>\n )}\n </p>\n </div>\n )}\n </div>\n\n {/* Test Result */}\n {testResult && (\n <div\n className={`p-3 rounded-lg ${testResult.success ? 'bg-status-success-bg-subtle border border-status-success-border' : 'bg-status-error-bg-subtle border border-status-error-border'}`}\n >\n <div className=\"flex items-center gap-2\">\n {testResult.success ? (\n <CheckCircle2 size={16} className=\"text-status-success-text\" />\n ) : (\n <XCircle size={16} className=\"text-status-error-text\" />\n )}\n <p\n className={`text-sm font-medium ${testResult.success ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {testResult.message}\n </p>\n </div>\n </div>\n )}\n\n {/* Connection Actions */}\n <div className=\"flex items-center gap-2 pt-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleTestConnection(connection.id)}\n disabled={actionLoading === 'test'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-status-info-text hover:bg-status-info-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'test' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Play size={16} />\n )}\n {tr('channel.testConnection', 'Test Connection')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => {\n setShowConnectionForm(true);\n // platformMetadata is the non-sensitive metadata the backend returns for\n // an existing connection (e.g. region, endpoint, accountId). The backend\n // intentionally omits or masks sensitive credential fields (access keys,\n // secrets, tokens) before returning this object, so pre-populating the\n // form is safe. The user will be prompted to re-enter any masked secrets\n // before submission because AdapterCredentialsForm marks them as required.\n setCredentials((connection?.platformMetadata as Record<string, unknown>) ?? {});\n }}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded-lg transition-colors\"\n >\n <Pencil size={16} />\n {tr('channel.updateCredentials', 'Update Credentials')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n type=\"button\"\n onClick={() => handleDeleteConnection(connection.id)}\n disabled={actionLoading === 'delete-connection'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'delete-connection' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Trash2 size={16} />\n )}\n {tr('channel.remove', 'Remove')}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* No Connection */}\n {!connection && !showConnectionForm && (\n <div className=\"text-center py-8\">\n <Cable size={32} className=\"mx-auto text-muted-foreground mb-3\" />\n <p className=\"text-sm text-muted-foreground mb-2\">\n {tr(\n 'channel.noCredentialsConfigured',\n 'No credentials configured for this channel'\n )}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.addCredentialsHint',\n 'Add credentials to enable message delivery through this channel'\n )}\n </p>\n </div>\n )}\n\n {/* Connection Form */}\n {showConnectionForm && (\n <div className=\"space-y-4\">\n {/* Show info if no adapter type selected */}\n {!channel.adapterType && (\n <div className=\"p-3 bg-accent-orange/10 border border-accent-orange/30 rounded-lg\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle size={16} className=\"text-accent-orange mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-accent-orange\">\n {tr('channel.noAdapterTypeConfigured', 'No adapter type configured')}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr(\n 'channel.noAdapterTypeConfiguredHint',\n 'You can still add credentials below. The system will attempt to validate them when the channel is configured with an adapter type.'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Adapter-specific credential form */}\n <AdapterCredentialsForm\n adapterType={channel.adapterType ?? undefined}\n credentials={credentials}\n onChange={setCredentials}\n onValidationChange={(valid) => setCredentialsValid(valid)}\n disabled={actionLoading === 'connection'}\n />\n\n {/* Validation status */}\n {channel.adapterType && (\n <div className=\"pt-2\">\n <CredentialValidationStatus\n adapterType={channel.adapterType}\n credentials={credentials}\n />\n </div>\n )}\n\n {/* Form Actions */}\n <div className=\"flex items-center gap-2 pt-2 border-t border-border\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <button\n type=\"button\"\n onClick={() => {\n if (connection) {\n handleUpdateConnection(connection.id);\n } else {\n handleCreateConnection();\n }\n }}\n disabled={\n actionLoading === 'connection' || (!credentialsValid && !!channel.adapterType)\n }\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {actionLoading === 'connection' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Save size={16} />\n )}\n {connection\n ? tr('channel.updateCredentials', 'Update Credentials')\n : tr('channel.saveCredentials', 'Save Credentials')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => {\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n }}\n className=\"flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <X size={16} />\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n )}\n </div>\n\n {/* Test Notification Modal */}\n {showTestModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"w-full max-w-md mx-4 p-6 bg-surface border border-border rounded-lg shadow-xl\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Send size={20} className=\"text-accent-primary\" />\n <h3 className=\"text-lg font-semibold text-foreground\">\n Test {PLATFORM_CONFIG[channel.platform]?.label || channel.platform} Channel\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowTestModal(false)}\n className=\"p-1 text-muted-foreground hover:text-foreground rounded\"\n >\n <X size={20} />\n </button>\n </div>\n\n <div className=\"space-y-4\">\n {/* Recipient */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n {getRecipientLabel(channel.platform)}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <div className=\"relative\">\n <input\n aria-label={getRecipientPlaceholder(channel.platform)}\n type=\"text\"\n value={testRecipient}\n onChange={(e) => setTestRecipient(e.target.value)}\n placeholder={getRecipientPlaceholder(channel.platform)}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\n {channel.platform === 'EMAIL' ? (\n <Mail size={16} />\n ) : channel.platform === 'SMS' || channel.platform === 'WHATSAPP' ? (\n <Phone size={16} />\n ) : (\n <MessageSquare size={16} />\n )}\n </div>\n </div>\n </div>\n\n {/* Subject (for email only) */}\n {channel.platform === 'EMAIL' && (\n <div>\n <label\n htmlFor=\"channel-details-test-subject\"\n className=\"block text-sm font-medium text-foreground mb-1.5\"\n >\n Subject\n </label>\n <input\n id=\"channel-details-test-subject\"\n aria-label=\"Email subject\"\n type=\"text\"\n value={testSubject}\n onChange={(e) => setTestSubject(e.target.value)}\n placeholder=\"Email subject\"\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n )}\n\n {/* Message */}\n <div>\n <label\n htmlFor=\"channel-details-test-message\"\n className=\"block text-sm font-medium text-foreground mb-1.5\"\n >\n Message\n </label>\n <textarea\n id=\"channel-details-test-message\"\n aria-label=\"Test message content\"\n value={testMessage}\n onChange={(e) => setTestMessage(e.target.value)}\n placeholder=\"Test message content\"\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n />\n </div>\n\n {/* Result */}\n {notificationResult && (\n <div\n className={`p-3 rounded-lg ${\n notificationResult.success\n ? 'bg-status-success-bg-subtle border border-status-success-border'\n : 'bg-status-error-bg-subtle border border-status-error-border'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n {notificationResult.success ? (\n <CheckCircle2 size={16} className=\"text-status-success-text mt-0.5\" />\n ) : (\n <XCircle size={16} className=\"text-status-error-text mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p\n className={`text-sm font-medium ${\n notificationResult.success\n ? 'text-status-success-text'\n : 'text-status-error-text'\n }`}\n >\n {notificationResult.success\n ? tr('channel.messageSentSuccess', 'Message sent successfully!')\n : tr('channel.messageSendFailed', 'Failed to send message')}\n </p>\n {notificationResult.messageId && (\n <p className=\"text-xs text-success-600 mt-1\">\n Message ID: {notificationResult.messageId}\n </p>\n )}\n {notificationResult.externalId && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n External ID: {notificationResult.externalId}\n </p>\n )}\n {notificationResult.status && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Status: {notificationResult.status}\n </p>\n )}\n {notificationResult.error && (\n <p className=\"text-xs text-status-error-text mt-1\">\n {notificationResult.error.message}\n {notificationResult.error.retryable && (\n <span className=\"text-warning-600 ml-1\">(will retry)</span>\n )}\n </p>\n )}\n {notificationResult.sentAt && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Sent at: {new Date(notificationResult.sentAt).toLocaleString()}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 pt-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_SEND}>\n <button\n type=\"button\"\n onClick={handleSendTestNotification}\n disabled={actionLoading === 'send-test' || !testRecipient.trim()}\n className=\"flex-1 flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {actionLoading === 'send-test' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Send size={16} />\n )}\n {tr('channel.sendTest', 'Send Test')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => setShowTestModal(false)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChannelDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelDetailsPage.tsx"],"sourcesContent":["/**\n * Channel Details Page\n *\n * Displays channel details and provides connection/credentials management\n */\n\nimport { useState, useEffect } from 'react';\nimport { useParams, useNavigate, useLocation } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n Radio,\n ArrowLeft,\n Pencil,\n Loader2,\n AlertCircle,\n RefreshCw,\n Power,\n PowerOff,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Cable,\n Key,\n Trash2,\n Play,\n Plus,\n Save,\n X,\n Send,\n Mail,\n Phone,\n MessageSquare,\n Clock,\n Truck,\n Users,\n ArrowRight,\n} from 'lucide-react';\nimport {\n useChannel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n type ChannelAuthType,\n type ChannelDeliveryError,\n} from '../../../hooks/useChannels';\nimport {\n AdapterCredentialsForm,\n CredentialValidationStatus,\n} from '../../../components/AdapterCredentialsForm';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { AUTH_TYPE_I18N, HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\n\n/** Lightweight notification result for UI display (not the full GraphQL type) */\ninterface NotificationDisplayResult {\n success: boolean;\n messageId?: string | null;\n externalId?: string | null;\n status?: string | null;\n sentAt?: string | null;\n error?: Pick<ChannelDeliveryError, 'code' | 'message' | 'retryable'> | null;\n}\n\n// Platform display configuration\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\n// Status display configuration\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n INACTIVE: { label: 'Inactive', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n ERROR: {\n label: 'Error',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertTriangle,\n },\n ARCHIVED: { label: 'Archived', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n};\n\n// Health display configuration\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-muted-foreground', icon: HelpCircle },\n};\n\ninterface LocationState {\n pendingCredentials?: Record<string, unknown>;\n showCredentialsForm?: boolean;\n}\n\nexport function ChannelDetailsPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const location = useLocation();\n const { hasPermission } = usePermissions();\n // Admin-only affordances — hide when the user lacks the permission.\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canConnectionCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE);\n const canConnectionUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE);\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 locationState = location.state as LocationState | null;\n\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [showConnectionForm, setShowConnectionForm] = useState(\n locationState?.showCredentialsForm || false\n );\n const [credentials, setCredentials] = useState<Record<string, unknown>>(\n locationState?.pendingCredentials || {}\n );\n const [credentialsValid, setCredentialsValid] = useState(false);\n const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Test notification modal state\n const [showTestModal, setShowTestModal] = useState(false);\n const [testRecipient, setTestRecipient] = useState('');\n const [testSubject, setTestSubject] = useState(() =>\n tr('channel.testNotificationSubject', 'Test Notification')\n );\n const [testMessage, setTestMessage] = useState(() =>\n tr(\n 'channel.testNotificationBody',\n 'This is a test notification from Burdenoff Channel Service.'\n )\n );\n const [notificationResult, setNotificationResult] = useState<NotificationDisplayResult | null>(\n null\n );\n\n // Clear location state after using it to prevent re-triggering on refresh\n useEffect(() => {\n if (locationState?.pendingCredentials || locationState?.showCredentialsForm) {\n // Replace state without the pending data\n navigate(location.pathname, { replace: true, state: null });\n }\n }, []);\n\n const {\n channel,\n isLoading,\n error,\n updateChannel,\n runHealthCheck,\n createConnection,\n updateConnection,\n deleteConnection,\n testConnection,\n sendTestNotification,\n } = useChannel(channelId);\n\n const handleHealthCheck = async () => {\n setActionLoading('health');\n try {\n await runHealthCheck();\n } catch (err) {\n console.error('[channel] Failed to run health check', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async () => {\n if (!channel) return;\n setActionLoading('status');\n try {\n await updateChannel({\n status: channel.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE',\n });\n } catch (err) {\n console.error('[channel] Failed to toggle channel status', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleCreateConnection = async () => {\n if (!channel) return;\n setActionLoading('connection');\n try {\n // Determine auth type based on adapter type\n // Most adapters use API_KEY, some use BOT_TOKEN or WEBHOOK\n // Narrow generated enum may lag UI's widened AdapterType (e.g. 'SMTP'); compare as string.\n let authType: ChannelAuthType = 'API_KEY';\n const adapterTypeStr = channel.adapterType as string | null | undefined;\n if (adapterTypeStr?.includes('BOT')) {\n authType = 'BOT_TOKEN';\n } else if (adapterTypeStr?.includes('WEBHOOK')) {\n authType = 'WEBHOOK';\n } else if (adapterTypeStr === 'SMTP') {\n authType = 'SMTP_IMAP';\n }\n\n await createConnection({\n authType,\n credentials,\n });\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n } catch (err) {\n console.error('[channel] Failed to create connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleUpdateConnection = async (connectionId: string) => {\n setActionLoading('connection');\n try {\n await updateConnection(connectionId, {\n credentials,\n });\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n } catch (err) {\n console.error('[channel] Failed to update connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteConnection = async (connectionId: string) => {\n void nativeImpact('medium');\n setActionLoading('delete-connection');\n try {\n await deleteConnection(connectionId);\n void nativeNotify('success');\n } catch (err) {\n void nativeNotify('error');\n console.error('[channel] Failed to delete connection', err);\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async (connectionId: string) => {\n setActionLoading('test');\n setTestResult(null);\n try {\n const result = await testConnection(connectionId);\n if (result) {\n setTestResult({\n success: result.success,\n message:\n result.message ||\n (result.success\n ? tr('channel.connectionTestSuccess', 'Connection successful!')\n : tr('channel.connectionTestFailed', 'Connection failed')),\n });\n }\n } catch (err) {\n console.error('[channel] Failed to test connection', err);\n setTestResult({\n success: false,\n message: tr('channel.connectionTestRequestFailed', 'Failed to test connection'),\n });\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleSendTestNotification = async () => {\n if (!testRecipient.trim()) return;\n\n setActionLoading('send-test');\n setNotificationResult(null);\n\n try {\n const options =\n channel?.platform === 'EMAIL'\n ? { subject: testSubject, html: `<p>${testMessage}</p>` }\n : undefined;\n\n const result = await sendTestNotification(testRecipient.trim(), testMessage, options);\n if (result) {\n setNotificationResult({\n success: result.success,\n messageId: result.messageId,\n externalId: result.externalId,\n status: result.status,\n sentAt: result.sentAt,\n error: result.error\n ? {\n code: result.error.code,\n message: result.error.message,\n retryable: result.error.retryable,\n }\n : null,\n });\n }\n } catch (err: unknown) {\n const errMsg =\n err instanceof Error\n ? err.message\n : tr('channel.sendTestNotificationFailed', 'Failed to send test notification');\n console.error('[channel] Failed to send test notification', err);\n setNotificationResult({\n success: false,\n error: {\n code: 'SEND_FAILED',\n message: errMsg,\n retryable: false,\n },\n });\n } finally {\n setActionLoading(null);\n }\n };\n\n const getRecipientPlaceholder = (platform: ChannelPlatform) => {\n switch (platform) {\n case 'EMAIL':\n return tr('channel.recipientPlaceholder.email', 'email@example.com');\n case 'SMS':\n return tr('channel.recipientPlaceholder.phone', '+1234567890');\n case 'PUSH':\n return tr('channel.recipientPlaceholder.deviceToken', 'Device token');\n case 'SLACK':\n return tr('channel.recipientPlaceholder.slack', 'Channel ID or User ID');\n case 'TEAMS':\n return tr('channel.recipientPlaceholder.channelId', 'Channel ID');\n case 'DISCORD':\n return tr('channel.recipientPlaceholder.channelId', 'Channel ID');\n case 'WHATSAPP':\n return tr('channel.recipientPlaceholder.phone', '+1234567890');\n default:\n return tr('channel.recipientPlaceholder.default', 'Recipient');\n }\n };\n\n const getRecipientLabel = (platform: ChannelPlatform) => {\n switch (platform) {\n case 'EMAIL':\n return tr('channel.recipientLabel.email', 'Email Address');\n case 'SMS':\n case 'WHATSAPP':\n return tr('channel.recipientLabel.phone', 'Phone Number');\n case 'PUSH':\n return tr('channel.recipientLabel.deviceToken', 'Device Token');\n case 'SLACK':\n case 'TEAMS':\n case 'DISCORD':\n return tr('channel.recipientLabel.channelOrUserId', 'Channel/User ID');\n default:\n return tr('channel.recipientLabel.default', 'Recipient');\n }\n };\n\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] || {\n label: platform,\n color: 'bg-bg-sunken text-text-secondary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] || STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] || HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || tr('channel.channelNotFound', 'Channel not found')}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\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 {tr('channel.goBack', 'Go Back')}\n </button>\n </div>\n </div>\n );\n }\n\n const platformConfig = getPlatformConfig(channel.platform);\n const statusConfig = getStatusConfig(channel.status);\n const healthConfig = getHealthConfig(channel.connectionHealth);\n const StatusIcon = statusConfig.icon;\n const HealthIcon = healthConfig.icon;\n const connection = channel.connection;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_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(-1)}\n className=\"p-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <div className=\"flex items-center gap-2\">\n <Radio size={24} className=\"text-accent-primary\" />\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">{channel.name}</h1>\n </div>\n {channel.description && (\n <p className=\"text-sm text-muted-foreground mt-1\">{channel.description}</p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={handleHealthCheck}\n disabled={actionLoading === 'health'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-status-info-text hover:bg-status-info-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'health' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={16} />\n )}\n {tr('channel.healthCheck', 'Health Check')}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleToggleStatus}\n disabled={actionLoading === 'status'}\n className={`flex items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors disabled:opacity-50 ${\n channel.status === 'ACTIVE'\n ? 'text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {actionLoading === 'status' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : channel.status === 'ACTIVE' ? (\n <PowerOff size={16} />\n ) : (\n <Power size={16} />\n )}\n {channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => {\n setShowTestModal(true);\n setNotificationResult(null);\n setTestRecipient('');\n }}\n disabled={!channel.connection}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-accent-primary hover:bg-accent-primary/10 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={\n !channel.connection\n ? tr('channel.configureCredentialsFirst', 'Configure credentials first')\n : tr('channel.sendTestNotification', 'Send test notification')\n }\n >\n <Send size={16} />\n {tr('channel.testChannel', 'Test Channel')}\n </button>\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => navigate('./edit')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors shadow-sm\"\n >\n <Pencil size={16} />\n {tr('channel.edit', 'Edit')}\n </button>\n )}\n </div>\n </div>\n\n {/* Status Cards */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {/* Platform */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.platform', 'Platform')}\n </p>\n <span\n className={`inline-flex px-2 py-1 text-sm font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n\n {/* Status */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">{tr('channel.status', 'Status')}</p>\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={16} className={statusConfig.color.split(' ')[0]} />\n <span className={`px-2 py-1 text-sm font-medium rounded-full ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Health */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.connectionHealth', 'Connection Health')}\n </p>\n <div className=\"flex items-center gap-2\">\n <HealthIcon size={16} className={healthConfig.color} />\n <span className={`text-sm font-medium ${healthConfig.color}`}>\n {healthConfig.label}\n </span>\n </div>\n {channel.lastHealthCheckAt && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('channel.lastCheck', 'Last check')}:{' '}\n {new Date(channel.lastHealthCheckAt).toLocaleString()}\n </p>\n )}\n </div>\n\n {/* Connection Status */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-xs text-muted-foreground mb-1\">\n {tr('channel.connection', 'Connection')}\n </p>\n <div className=\"flex items-center gap-2\">\n <Cable\n size={16}\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'\n }\n />\n <span\n className={`text-sm font-medium ${channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'}`}\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n </div>\n\n {/* Channel Details */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <h2 className=\"text-lg font-semibold text-foreground mb-4\">\n {tr('channel.channelDetails', 'Channel Details')}\n </h2>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.adapterType', 'Adapter Type')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {channel.adapterType?.replace(/_/g, ' ') ||\n tr('channel.notConfigured', 'Not configured')}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">{tr('channel.priority', 'Priority')}</p>\n <p className=\"text-sm font-medium text-foreground\">{channel.priority}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.consecutiveFailures', 'Consecutive Failures')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">{channel.consecutiveFailures}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.messages24h', 'Messages (24h)')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">{channel.messageCount24h}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.createdAt', 'Created At')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {new Date(channel.createdAt).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.updatedAt', 'Updated At')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {new Date(channel.updatedAt).toLocaleString()}\n </p>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"mt-4\">\n <p className=\"text-xs text-muted-foreground mb-1.5\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Channel tags\">\n {channel.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n\n {/* Health Check Error */}\n {channel.healthCheckError && (\n <div className=\"mt-4 p-3 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <p className=\"text-xs text-status-error-text font-medium\">\n {tr('channel.lastHealthCheckError', 'Last Health Check Error')}\n </p>\n <p className=\"text-sm text-status-error-text\">{channel.healthCheckError}</p>\n </div>\n )}\n </div>\n\n {/* Quick Links to Channel Sub-Pages */}\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`./deliveries`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-orange/10 rounded-lg\">\n <Truck size={18} className=\"text-accent-orange\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewMessageDeliveries', 'View message deliveries')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(`./webhooks`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-purple/10 rounded-lg\">\n <Clock size={18} className=\"text-accent-purple\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.webhookEvents', 'Webhook Events')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewIncomingWebhooks', 'View incoming webhooks')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(`./users`)}\n className=\"flex items-center gap-3 p-3 bg-surface border border-border rounded-lg hover:bg-action-primary-bg/10 transition-colors text-left\"\n >\n <div className=\"p-2 bg-accent-blue/10 rounded-lg\">\n <Users size={18} className=\"text-accent-blue\" />\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.channelUsers', 'Channel Users')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {tr('channel.viewConnectedUsers', 'View connected users')}\n </p>\n </div>\n <ArrowRight size={16} className=\"text-muted-foreground shrink-0\" />\n </button>\n </div>\n\n {/* Connection / Credentials Management */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Key size={20} className=\"text-accent-primary\" />\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('channel.connectionAndCredentials', 'Connection & Credentials')}\n </h2>\n </div>\n {canConnectionCreate && !connection && !showConnectionForm && (\n <button\n type=\"button\"\n onClick={() => setShowConnectionForm(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.addCredentials', 'Add Credentials')}\n </button>\n )}\n </div>\n\n {/* Existing Connection */}\n {connection && !showConnectionForm && (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.authType', 'Auth Type')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {translateLabel(connection.authType, AUTH_TYPE_I18N, tr)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">{tr('channel.status', 'Status')}</p>\n <div className=\"flex items-center gap-2\">\n {connection.isHealthy ? (\n <CheckCircle2 size={14} className=\"text-status-success-text\" />\n ) : (\n <XCircle size={14} className=\"text-status-error-text\" />\n )}\n <span\n className={`text-sm font-medium ${connection.isHealthy ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {connection.isHealthy\n ? tr('channel.healthy', 'Healthy')\n : tr('channel.unhealthy', 'Unhealthy')}\n </span>\n </div>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.lastTested', 'Last Tested')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {connection.lastTestedAt\n ? new Date(connection.lastTestedAt).toLocaleString()\n : tr('channel.never', 'Never')}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.testStatus', 'Test Status')}\n </p>\n <p className=\"text-sm font-medium text-foreground\">\n {connection.testStatus || tr('channel.notTested', 'Not tested')}\n </p>\n </div>\n {connection.tokenExpiresAt && (\n <div>\n <p className=\"text-xs text-muted-foreground\">\n {tr('channel.tokenExpires', 'Token Expires')}\n </p>\n <p\n className={`text-sm font-medium ${connection.needsTokenRefresh ? 'text-status-warning-text' : 'text-foreground'}`}\n >\n {new Date(connection.tokenExpiresAt).toLocaleString()}\n {connection.daysUntilExpiry !== undefined && (\n <span className=\"text-xs text-muted-foreground ml-1\">\n ({connection.daysUntilExpiry} {tr('channel.days', 'days')})\n </span>\n )}\n </p>\n </div>\n )}\n </div>\n\n {/* Test Result */}\n {testResult && (\n <div\n className={`p-3 rounded-lg ${testResult.success ? 'bg-status-success-bg-subtle border border-status-success-border' : 'bg-status-error-bg-subtle border border-status-error-border'}`}\n >\n <div className=\"flex items-center gap-2\">\n {testResult.success ? (\n <CheckCircle2 size={16} className=\"text-status-success-text\" />\n ) : (\n <XCircle size={16} className=\"text-status-error-text\" />\n )}\n <p\n className={`text-sm font-medium ${testResult.success ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {testResult.message}\n </p>\n </div>\n </div>\n )}\n\n {/* Connection Actions */}\n <div className=\"flex items-center gap-2 pt-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleTestConnection(connection.id)}\n disabled={actionLoading === 'test'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-status-info-text hover:bg-status-info-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'test' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Play size={16} />\n )}\n {tr('channel.testConnection', 'Test Connection')}\n </button>\n </PermissionGuard>\n {canConnectionUpdate && (\n <button\n type=\"button\"\n onClick={() => {\n setShowConnectionForm(true);\n // platformMetadata is the non-sensitive metadata the backend returns for\n // an existing connection (e.g. region, endpoint, accountId). The backend\n // intentionally omits or masks sensitive credential fields (access keys,\n // secrets, tokens) before returning this object, so pre-populating the\n // form is safe. The user will be prompted to re-enter any masked secrets\n // before submission because AdapterCredentialsForm marks them as required.\n setCredentials(\n (connection?.platformMetadata as Record<string, unknown>) ?? {}\n );\n }}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded-lg transition-colors\"\n >\n <Pencil size={16} />\n {tr('channel.updateCredentials', 'Update Credentials')}\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n type=\"button\"\n onClick={() => handleDeleteConnection(connection.id)}\n disabled={actionLoading === 'delete-connection'}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded-lg transition-colors disabled:opacity-50\"\n >\n {actionLoading === 'delete-connection' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Trash2 size={16} />\n )}\n {tr('channel.remove', 'Remove')}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* No Connection */}\n {!connection && !showConnectionForm && (\n <div className=\"text-center py-8\">\n <Cable size={32} className=\"mx-auto text-muted-foreground mb-3\" />\n <p className=\"text-sm text-muted-foreground mb-2\">\n {tr(\n 'channel.noCredentialsConfigured',\n 'No credentials configured for this channel'\n )}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.addCredentialsHint',\n 'Add credentials to enable message delivery through this channel'\n )}\n </p>\n </div>\n )}\n\n {/* Connection Form */}\n {showConnectionForm && (\n <div className=\"space-y-4\">\n {/* Show info if no adapter type selected */}\n {!channel.adapterType && (\n <div className=\"p-3 bg-accent-orange/10 border border-accent-orange/30 rounded-lg\">\n <div className=\"flex items-start gap-2\">\n <AlertCircle size={16} className=\"text-accent-orange mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-accent-orange\">\n {tr('channel.noAdapterTypeConfigured', 'No adapter type configured')}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr(\n 'channel.noAdapterTypeConfiguredHint',\n 'You can still add credentials below. The system will attempt to validate them when the channel is configured with an adapter type.'\n )}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Adapter-specific credential form */}\n <AdapterCredentialsForm\n adapterType={channel.adapterType ?? undefined}\n credentials={credentials}\n onChange={setCredentials}\n onValidationChange={(valid) => setCredentialsValid(valid)}\n disabled={actionLoading === 'connection'}\n />\n\n {/* Validation status */}\n {channel.adapterType && (\n <div className=\"pt-2\">\n <CredentialValidationStatus\n adapterType={channel.adapterType}\n credentials={credentials}\n />\n </div>\n )}\n\n {/* Form Actions */}\n <div className=\"flex items-center gap-2 pt-2 border-t border-border\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <button\n type=\"button\"\n onClick={() => {\n if (connection) {\n handleUpdateConnection(connection.id);\n } else {\n handleCreateConnection();\n }\n }}\n disabled={\n actionLoading === 'connection' || (!credentialsValid && !!channel.adapterType)\n }\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {actionLoading === 'connection' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Save size={16} />\n )}\n {connection\n ? tr('channel.updateCredentials', 'Update Credentials')\n : tr('channel.saveCredentials', 'Save Credentials')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => {\n setShowConnectionForm(false);\n setCredentials({});\n setCredentialsValid(false);\n }}\n className=\"flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <X size={16} />\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n </div>\n )}\n </div>\n\n {/* Test Notification Modal */}\n {showTestModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"w-full max-w-md mx-4 p-6 bg-surface border border-border rounded-lg shadow-xl\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Send size={20} className=\"text-accent-primary\" />\n <h3 className=\"text-lg font-semibold text-foreground\">\n Test {PLATFORM_CONFIG[channel.platform]?.label || channel.platform} Channel\n </h3>\n </div>\n <button\n type=\"button\"\n onClick={() => setShowTestModal(false)}\n className=\"p-1 text-muted-foreground hover:text-foreground rounded\"\n >\n <X size={20} />\n </button>\n </div>\n\n <div className=\"space-y-4\">\n {/* Recipient */}\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1.5\">\n {getRecipientLabel(channel.platform)}{' '}\n <span className=\"text-status-error-text\">*</span>\n </label>\n <div className=\"relative\">\n <input\n aria-label={getRecipientPlaceholder(channel.platform)}\n type=\"text\"\n value={testRecipient}\n onChange={(e) => setTestRecipient(e.target.value)}\n placeholder={getRecipientPlaceholder(channel.platform)}\n className=\"w-full pl-9 pr-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n <div className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\">\n {channel.platform === 'EMAIL' ? (\n <Mail size={16} />\n ) : channel.platform === 'SMS' || channel.platform === 'WHATSAPP' ? (\n <Phone size={16} />\n ) : (\n <MessageSquare size={16} />\n )}\n </div>\n </div>\n </div>\n\n {/* Subject (for email only) */}\n {channel.platform === 'EMAIL' && (\n <div>\n <label\n htmlFor=\"channel-details-test-subject\"\n className=\"block text-sm font-medium text-foreground mb-1.5\"\n >\n Subject\n </label>\n <input\n id=\"channel-details-test-subject\"\n aria-label=\"Email subject\"\n type=\"text\"\n value={testSubject}\n onChange={(e) => setTestSubject(e.target.value)}\n placeholder=\"Email subject\"\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n )}\n\n {/* Message */}\n <div>\n <label\n htmlFor=\"channel-details-test-message\"\n className=\"block text-sm font-medium text-foreground mb-1.5\"\n >\n Message\n </label>\n <textarea\n id=\"channel-details-test-message\"\n aria-label=\"Test message content\"\n value={testMessage}\n onChange={(e) => setTestMessage(e.target.value)}\n placeholder=\"Test message content\"\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg resize-none\"\n />\n </div>\n\n {/* Result */}\n {notificationResult && (\n <div\n className={`p-3 rounded-lg ${\n notificationResult.success\n ? 'bg-status-success-bg-subtle border border-status-success-border'\n : 'bg-status-error-bg-subtle border border-status-error-border'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n {notificationResult.success ? (\n <CheckCircle2 size={16} className=\"text-status-success-text mt-0.5\" />\n ) : (\n <XCircle size={16} className=\"text-status-error-text mt-0.5\" />\n )}\n <div className=\"flex-1\">\n <p\n className={`text-sm font-medium ${\n notificationResult.success\n ? 'text-status-success-text'\n : 'text-status-error-text'\n }`}\n >\n {notificationResult.success\n ? tr('channel.messageSentSuccess', 'Message sent successfully!')\n : tr('channel.messageSendFailed', 'Failed to send message')}\n </p>\n {notificationResult.messageId && (\n <p className=\"text-xs text-success-600 mt-1\">\n Message ID: {notificationResult.messageId}\n </p>\n )}\n {notificationResult.externalId && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n External ID: {notificationResult.externalId}\n </p>\n )}\n {notificationResult.status && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Status: {notificationResult.status}\n </p>\n )}\n {notificationResult.error && (\n <p className=\"text-xs text-status-error-text mt-1\">\n {notificationResult.error.message}\n {notificationResult.error.retryable && (\n <span className=\"text-warning-600 ml-1\">(will retry)</span>\n )}\n </p>\n )}\n {notificationResult.sentAt && (\n <p className=\"text-xs text-muted-foreground mt-1\">\n Sent at: {new Date(notificationResult.sentAt).toLocaleString()}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex items-center gap-2 pt-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_SEND}>\n <button\n type=\"button\"\n onClick={handleSendTestNotification}\n disabled={actionLoading === 'send-test' || !testRecipient.trim()}\n className=\"flex-1 flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {actionLoading === 'send-test' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Send size={16} />\n )}\n {tr('channel.sendTest', 'Send Test')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => setShowTestModal(false)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.close', 'Close')}\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -169,7 +169,7 @@ function A() {
169
169
  }), j("channel.refresh", "Refresh")]
170
170
  }),
171
171
  /* @__PURE__ */ E(n, {
172
- permission: r.CHANNEL_ADMIN,
172
+ permission: r.CHANNEL_USER_EXPORT,
173
173
  children: /* @__PURE__ */ D("button", {
174
174
  type: "button",
175
175
  onClick: () => void te(),
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelUsersPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelUsersPage.tsx"],"sourcesContent":["/**\n * Channel Users Page\n *\n * Displays user platform connections for a specific channel\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 User,\n UserCheck,\n Bell,\n BellOff,\n RefreshCw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n useChannel,\n type UserPlatformConnection,\n type UserPresenceStatus,\n type PairingStatus,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportChannelUsers } from '../hooks/useExport';\n\nconst PRESENCE_CONFIG: Record<UserPresenceStatus, { label: string; color: string }> = {\n ONLINE: { label: 'Online', color: 'text-status-success-text' },\n OFFLINE: { label: 'Offline', color: 'text-muted-foreground' },\n AWAY: { label: 'Away', color: 'text-status-warning-text' },\n DND: { label: 'Do Not Disturb', color: 'text-status-error-text' },\n};\n\nconst PAIRING_CONFIG: Record<\n PairingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: {\n label: 'Pending',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n APPROVED: {\n label: 'Approved',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n EXPIRED: { label: 'Expired', color: 'text-muted-foreground bg-muted', icon: XCircle },\n};\n\nexport function ChannelUsersPage() {\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: exportUsers,\n isExporting: isExportingUsers,\n exportError: exportUsersError,\n } = useExportChannelUsers(organizationId ?? undefined, channelId);\n const [isLoadingUsers, setIsLoadingUsers] = useState(true);\n const [users, setUsers] = useState<UserPlatformConnection[]>([]);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [currentPage, setCurrentPage] = useState(0);\n\n const { channel, isLoading, error, fetchUserPlatformConnections } = useChannel(channelId);\n\n const loadUsers = useCallback(\n async (page: number = 0) => {\n if (!channelId) return;\n setIsLoadingUsers(true);\n try {\n const result = await fetchUserPlatformConnections(limit, page * limit);\n setUsers(result.items);\n setTotal(result.total);\n } finally {\n setIsLoadingUsers(false);\n }\n },\n [channelId, fetchUserPlatformConnections, limit]\n );\n\n const loadUsersRef = useRef(loadUsers);\n useEffect(() => {\n loadUsersRef.current = loadUsers;\n });\n const isFirstPageRender = useRef(true);\n // Prevents the [currentPage] effect from double-loading when limit changes\n // reset the page back to 0 (the [limit] effect already handles that load).\n const limitChangingRef = useRef(false);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n // Set the guard before resetting page so the [currentPage] effect skips\n // the redundant load that would otherwise fire when page resets to 0.\n limitChangingRef.current = true;\n setCurrentPage(0);\n void loadUsersRef.current(0).finally(() => {\n limitChangingRef.current = false;\n });\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n if (limitChangingRef.current) return;\n void Promise.resolve().then(() => loadUsersRef.current(currentPage));\n }, [currentPage]);\n\n const totalPages = Math.max(1, Math.ceil(total / limit));\n const hasPrevPage = currentPage > 0;\n const hasNextPage = currentPage < totalPages - 1;\n\n const getPresenceConfig = (status: UserPresenceStatus) => {\n return PRESENCE_CONFIG[status] || PRESENCE_CONFIG.OFFLINE;\n };\n\n const getPairingConfig = (status: PairingStatus) => {\n return PAIRING_CONFIG[status] || PAIRING_CONFIG.EXPIRED;\n };\n\n // Loading state (only full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.USER_PLATFORM_CONNECTION_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.channelUsers', 'Channel Users')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {channel.name} - User connections\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-accent-primary/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 loadUsers(currentPage)}\n disabled={isLoadingUsers}\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={isLoadingUsers ? 'animate-spin' : ''} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_ADMIN}>\n <button\n type=\"button\"\n onClick={() => void exportUsers()}\n disabled={isExportingUsers}\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 {isExportingUsers ? (\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 {exportUsersError && (\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\">{exportUsersError}</p>\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 Users connected to this channel through OAuth or pairing. Manage notification\n preferences and DM authorization here.\n </p>\n </div>\n\n {/* Users 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-[800px]\">\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.user', 'User')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.platformId', 'Platform ID')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.source', 'Source')}\n </th>\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.pairing', 'Pairing')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.notifications', 'Notifications')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.dmAllowed', 'DM Allowed')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {users.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"px-4 py-8 text-center text-muted-foreground\">\n {tr('channel.noUsersConnected', 'No users connected to this channel')}\n </td>\n </tr>\n ) : (\n users.map((user) => {\n const presenceConfig = getPresenceConfig(\n user.presenceStatus as UserPresenceStatus\n );\n const pairingConfig = user.pairingStatus\n ? getPairingConfig(user.pairingStatus as PairingStatus)\n : null;\n const PairingIcon = pairingConfig?.icon ?? XCircle;\n return (\n <tr key={user.id} className=\"hover:bg-muted/30 transition-colors\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 bg-accent-primary/10 rounded-full flex items-center justify-center\">\n <User size={16} className=\"text-accent-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n {user.externalDisplayName || 'Unknown'}\n </p>\n {user.externalUsername && (\n <p className=\"text-xs text-muted-foreground\">\n {user.externalUsername}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-muted-foreground\">\n {user.externalUserId}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-muted-foreground bg-muted px-2 py-1 rounded\">\n {user.source}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-2 h-2 rounded-full ${presenceConfig.color.replace('text-', 'bg-')}`}\n />\n <span className={`text-sm ${presenceConfig.color}`}>\n {presenceConfig.label}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n {user.pairingStatus && pairingConfig ? (\n <span\n className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded ${pairingConfig.color}`}\n >\n <PairingIcon size={12} />\n {pairingConfig.label}\n </span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n {user.notificationsEnabled ? (\n <span className=\"inline-flex items-center gap-1\">\n <Bell\n size={16}\n className=\"text-status-success-text\"\n aria-hidden=\"true\"\n />\n <span className=\"sr-only\">{tr('channel.enabled', 'Enabled')}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1\">\n <BellOff\n size={16}\n className=\"text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <span className=\"sr-only\">{tr('channel.disabled', 'Disabled')}</span>\n </span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n {user.dmAllowed ? (\n <span className=\"inline-flex items-center gap-1 text-sm text-status-success-text\">\n <UserCheck size={16} />\n {tr('channel.allowed', 'Allowed')}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-sm text-muted-foreground\">\n <XCircle size={16} />\n {tr('channel.blocked', 'Blocked')}\n </span>\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.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * limit + 1}–{Math.min((currentPage + 1) * limit, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <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={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={!hasPrevPage || isLoadingUsers}\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 <span className=\"text-sm text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={!hasNextPage || isLoadingUsers}\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":"ChannelUsersPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelUsersPage.tsx"],"sourcesContent":["/**\n * Channel Users Page\n *\n * Displays user platform connections for a specific channel\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 User,\n UserCheck,\n Bell,\n BellOff,\n RefreshCw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n useChannel,\n type UserPlatformConnection,\n type UserPresenceStatus,\n type PairingStatus,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportChannelUsers } from '../hooks/useExport';\n\nconst PRESENCE_CONFIG: Record<UserPresenceStatus, { label: string; color: string }> = {\n ONLINE: { label: 'Online', color: 'text-status-success-text' },\n OFFLINE: { label: 'Offline', color: 'text-muted-foreground' },\n AWAY: { label: 'Away', color: 'text-status-warning-text' },\n DND: { label: 'Do Not Disturb', color: 'text-status-error-text' },\n};\n\nconst PAIRING_CONFIG: Record<\n PairingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: {\n label: 'Pending',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n APPROVED: {\n label: 'Approved',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n EXPIRED: { label: 'Expired', color: 'text-muted-foreground bg-muted', icon: XCircle },\n};\n\nexport function ChannelUsersPage() {\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: exportUsers,\n isExporting: isExportingUsers,\n exportError: exportUsersError,\n } = useExportChannelUsers(organizationId ?? undefined, channelId);\n const [isLoadingUsers, setIsLoadingUsers] = useState(true);\n const [users, setUsers] = useState<UserPlatformConnection[]>([]);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [currentPage, setCurrentPage] = useState(0);\n\n const { channel, isLoading, error, fetchUserPlatformConnections } = useChannel(channelId);\n\n const loadUsers = useCallback(\n async (page: number = 0) => {\n if (!channelId) return;\n setIsLoadingUsers(true);\n try {\n const result = await fetchUserPlatformConnections(limit, page * limit);\n setUsers(result.items);\n setTotal(result.total);\n } finally {\n setIsLoadingUsers(false);\n }\n },\n [channelId, fetchUserPlatformConnections, limit]\n );\n\n const loadUsersRef = useRef(loadUsers);\n useEffect(() => {\n loadUsersRef.current = loadUsers;\n });\n const isFirstPageRender = useRef(true);\n // Prevents the [currentPage] effect from double-loading when limit changes\n // reset the page back to 0 (the [limit] effect already handles that load).\n const limitChangingRef = useRef(false);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n // Set the guard before resetting page so the [currentPage] effect skips\n // the redundant load that would otherwise fire when page resets to 0.\n limitChangingRef.current = true;\n setCurrentPage(0);\n void loadUsersRef.current(0).finally(() => {\n limitChangingRef.current = false;\n });\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n if (limitChangingRef.current) return;\n void Promise.resolve().then(() => loadUsersRef.current(currentPage));\n }, [currentPage]);\n\n const totalPages = Math.max(1, Math.ceil(total / limit));\n const hasPrevPage = currentPage > 0;\n const hasNextPage = currentPage < totalPages - 1;\n\n const getPresenceConfig = (status: UserPresenceStatus) => {\n return PRESENCE_CONFIG[status] || PRESENCE_CONFIG.OFFLINE;\n };\n\n const getPairingConfig = (status: PairingStatus) => {\n return PAIRING_CONFIG[status] || PAIRING_CONFIG.EXPIRED;\n };\n\n // Loading state (only full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.USER_PLATFORM_CONNECTION_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.channelUsers', 'Channel Users')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {channel.name} - User connections\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-accent-primary/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 loadUsers(currentPage)}\n disabled={isLoadingUsers}\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={isLoadingUsers ? 'animate-spin' : ''} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_USER_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportUsers()}\n disabled={isExportingUsers}\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 {isExportingUsers ? (\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 {exportUsersError && (\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\">{exportUsersError}</p>\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 Users connected to this channel through OAuth or pairing. Manage notification\n preferences and DM authorization here.\n </p>\n </div>\n\n {/* Users 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-[800px]\">\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.user', 'User')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.platformId', 'Platform ID')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.source', 'Source')}\n </th>\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.pairing', 'Pairing')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.notifications', 'Notifications')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.dmAllowed', 'DM Allowed')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {users.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"px-4 py-8 text-center text-muted-foreground\">\n {tr('channel.noUsersConnected', 'No users connected to this channel')}\n </td>\n </tr>\n ) : (\n users.map((user) => {\n const presenceConfig = getPresenceConfig(\n user.presenceStatus as UserPresenceStatus\n );\n const pairingConfig = user.pairingStatus\n ? getPairingConfig(user.pairingStatus as PairingStatus)\n : null;\n const PairingIcon = pairingConfig?.icon ?? XCircle;\n return (\n <tr key={user.id} className=\"hover:bg-muted/30 transition-colors\">\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 bg-accent-primary/10 rounded-full flex items-center justify-center\">\n <User size={16} className=\"text-accent-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n {user.externalDisplayName || 'Unknown'}\n </p>\n {user.externalUsername && (\n <p className=\"text-xs text-muted-foreground\">\n {user.externalUsername}\n </p>\n )}\n </div>\n </div>\n </td>\n <td className=\"px-4 py-3 text-sm font-mono text-muted-foreground\">\n {user.externalUserId}\n </td>\n <td className=\"px-4 py-3\">\n <span className=\"text-xs text-muted-foreground bg-muted px-2 py-1 rounded\">\n {user.source}\n </span>\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-2 h-2 rounded-full ${presenceConfig.color.replace('text-', 'bg-')}`}\n />\n <span className={`text-sm ${presenceConfig.color}`}>\n {presenceConfig.label}\n </span>\n </div>\n </td>\n <td className=\"px-4 py-3\">\n {user.pairingStatus && pairingConfig ? (\n <span\n className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded ${pairingConfig.color}`}\n >\n <PairingIcon size={12} />\n {pairingConfig.label}\n </span>\n ) : (\n <span className=\"text-xs text-muted-foreground\">-</span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n {user.notificationsEnabled ? (\n <span className=\"inline-flex items-center gap-1\">\n <Bell\n size={16}\n className=\"text-status-success-text\"\n aria-hidden=\"true\"\n />\n <span className=\"sr-only\">{tr('channel.enabled', 'Enabled')}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1\">\n <BellOff\n size={16}\n className=\"text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <span className=\"sr-only\">{tr('channel.disabled', 'Disabled')}</span>\n </span>\n )}\n </td>\n <td className=\"px-4 py-3\">\n {user.dmAllowed ? (\n <span className=\"inline-flex items-center gap-1 text-sm text-status-success-text\">\n <UserCheck size={16} />\n {tr('channel.allowed', 'Allowed')}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-sm text-muted-foreground\">\n <XCircle size={16} />\n {tr('channel.blocked', 'Blocked')}\n </span>\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.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * limit + 1}–{Math.min((currentPage + 1) * limit, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <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={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={!hasPrevPage || isLoadingUsers}\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 <span className=\"text-sm text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={!hasNextPage || isLoadingUsers}\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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"}