@burdenoff/microfe-channel 2026.525.1 → 2026.527.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/ChannelRoutes.js +0 -1
- package/dist/channel/ChannelRoutes.js.map +1 -1
- package/dist/channel/components/AdapterCredentialsForm.js +14 -2
- package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
- package/dist/channel/components/FeatureNotEnabled.js +1 -1
- package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
- package/dist/channel/components/PermissionGuard.js +1 -1
- package/dist/channel/components/PermissionGuard.js.map +1 -1
- package/dist/channel/config/adapterCredentials.js +19 -11
- package/dist/channel/config/adapterCredentials.js.map +1 -1
- package/dist/channel/hooks/useChannelSubscription.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +18 -0
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +411 -386
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +30 -15
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +7 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +26 -2
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +19 -11
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +39 -22
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +7 -0
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +16 -5
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +10 -3
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +17 -8
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +11 -6
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +21 -9
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +7 -0
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/dist/channel/pages/ChannelLandingPage.js +0 -1
- package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
- package/dist/channel/providers/ChannelProvider.js +1 -1
- package/dist/channel/providers/ChannelProvider.js.map +1 -1
- package/package.json +1 -1
- package/dist/channel/modules/channels/pages/index.js +0 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelsListPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelsListPage.tsx"],"sourcesContent":["/**\n * Channels List Page\n *\n * Displays a list of all channels with CRUD operations\n */\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError, subscribeShellEvent } from '../../../utils/shellBridge';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n Trash2,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Pencil,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Download,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\nimport { useExportChannels } from '../hooks/useExport';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\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\nexport function ChannelsListPage() {\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 [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [selectedChannelIds, setSelectedChannelIds] = useState<Set<string>>(new Set());\n const [bulkActionLoading, setBulkActionLoading] = useState<string | null>(null);\n\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportChannels,\n isExporting: isExportingChannels,\n exportError: exportChannelsError,\n } = useExportChannels(organizationId ?? undefined);\n const [pageSize] = useState(12);\n const [currentPage, setCurrentPage] = useState(0);\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n // Fetch the current page whenever page or page size changes\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n // Reset to page 0 when filter changes\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n // Cross-MFE refresh: refetch the list whenever channel/connection events\n // are emitted by other tabs or microfrontends.\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n const handleDelete = async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete channel' } },\n '[channel] Failed to delete channel'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async (channel: Channel) => {\n setActionLoading(channel.id);\n try {\n if (channel.status === 'ACTIVE') {\n await deactivateChannel(channel.id);\n } else {\n await activateChannel(channel.id);\n }\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to toggle channel status' } },\n '[channel] Failed to toggle channel status'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleHealthCheck = async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to run health check' } },\n '[channel] Failed to run health check'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const selectedChannels = useMemo(\n () => channels.filter((channel) => selectedChannelIds.has(channel.id)),\n [channels, selectedChannelIds]\n );\n const selectedActiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status === 'ACTIVE'),\n [selectedChannels]\n );\n const selectedInactiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status !== 'ACTIVE'),\n [selectedChannels]\n );\n const allVisibleSelected =\n channels.length > 0 && channels.every((channel) => selectedChannelIds.has(channel.id));\n const partiallySelected =\n channels.some((channel) => selectedChannelIds.has(channel.id)) && !allVisibleSelected;\n\n const toggleChannelSelection = (channelId: string, checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(channelId);\n } else {\n next.delete(channelId);\n }\n return next;\n });\n };\n\n const toggleAllChannels = (checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n channels.forEach((channel) => {\n if (checked) {\n next.add(channel.id);\n } else {\n next.delete(channel.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkDelete = async () => {\n if (selectedChannels.length === 0) return;\n void nativeImpact('medium');\n const message = tr(\n selectedChannels.length === 1\n ? 'channel.bulkDeleteConfirmSingle'\n : 'channel.bulkDeleteConfirmMultiple',\n selectedChannels.length === 1\n ? 'Delete 1 selected channel?'\n : `Delete ${selectedChannels.length} selected channels?`\n );\n const native = await nativeConfirm({\n title: 'Delete channels',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n setBulkActionLoading('delete');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => deleteChannel(channel.id)));\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkActivate = async () => {\n if (selectedInactiveChannels.length === 0) return;\n setBulkActionLoading('activate');\n try {\n await Promise.allSettled(\n selectedInactiveChannels.map((channel) => activateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkDeactivate = async () => {\n if (selectedActiveChannels.length === 0) return;\n setBulkActionLoading('deactivate');\n try {\n await Promise.allSettled(\n selectedActiveChannels.map((channel) => deactivateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkHealthCheck = async () => {\n if (selectedChannels.length === 0) return;\n setBulkActionLoading('health');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => runHealthCheck(channel.id)));\n } finally {\n setBulkActionLoading(null);\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 return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-4 sm: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>\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('channel.manageChannels', 'Manage your communication channels')} ({total} total)\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_ADMIN}>\n <button\n onClick={() => void exportChannels()}\n disabled={isExportingChannels}\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 {isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n <button\n onClick={() => navigate('/channels/create')}\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\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n type=\"text\"\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 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 <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Export Error */}\n {exportChannelsError && (\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 <p className=\"text-sm text-status-error-text flex-1\">{exportChannelsError}</p>\n </div>\n )}\n\n {channels.length > 0 && (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllChannels(event.target.checked)}\n aria-label=\"Select all channels\"\n className=\"h-4 w-4 rounded border-border text-primary\"\n />\n <span>{tr('channel.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {selectedChannelIds.size} {tr('channel.selectedCountLabel', 'selected')}\n </span>\n {selectedChannelIds.size > 0 && (\n <button\n onClick={() => setSelectedChannelIds(new Set())}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n {tr('channel.clearSelection', 'Clear selection')}\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n onClick={handleBulkHealthCheck}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-border rounded-md hover:bg-muted transition-colors disabled:opacity-50\"\n >\n {tr('channel.healthCheck', 'Health Check')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n onClick={handleBulkActivate}\n disabled={selectedInactiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-success-border/30 text-status-success-text rounded-md hover:bg-status-success-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.activateSelected', 'Activate Selected')}\n {selectedInactiveChannels.length > 0\n ? ` (${selectedInactiveChannels.length})`\n : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n onClick={handleBulkDeactivate}\n disabled={selectedActiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-warning-border text-status-warning-text rounded-md hover:bg-status-warning-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deactivateSelected', 'Deactivate Selected')}\n {selectedActiveChannels.length > 0 ? ` (${selectedActiveChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n onClick={handleBulkDelete}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-error-border/30 text-status-error-text rounded-md hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deleteSelected', 'Delete Selected')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\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 {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n <button\n onClick={() => refetch()}\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.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Radio size={48} className=\"text-muted-foreground mb-4\" />\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('channel.noChannelsFound', 'No channels found')}\n </h3>\n <p className=\"text-sm text-muted-foreground max-w-md mb-4\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your filters to see more channels.')\n : tr(\n 'channel.createFirstChannelPrompt',\n 'Create your first communication channel to get started.'\n )}\n </p>\n {!filter.search && !filter.platform && !filter.status && (\n <button\n onClick={() => navigate('/channels/create')}\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\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {channels.map((channel: Channel) => {\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 isActionLoading = actionLoading === channel.id;\n\n return (\n <div\n key={channel.id}\n className={`p-4 bg-surface border rounded-lg transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border hover:border-action-primary-bg/40'}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={selectedChannelIds.has(channel.id)}\n onChange={(event) =>\n toggleChannelSelection(channel.id, event.target.checked)\n }\n aria-label={`Select channel ${channel.name}`}\n className=\"h-4 w-4 rounded border-border text-primary\"\n />\n <Radio size={20} className=\"text-accent-primary\" />\n <span\n className={`px-2 py-0.5 text-xs font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusConfig.color}`}\n >\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Content - Clickable area */}\n <div\n onClick={() => navigate(`/channels/${channel.id}`)}\n className=\"cursor-pointer\"\n >\n <h3 className=\"text-sm font-semibold text-foreground mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-muted-foreground mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-muted-foreground mb-2 font-mono\">\n {channel.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n\n {/* Stats Row */}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground mb-2\">\n <span>\n {tr('channel.priority', 'Priority')}: {channel.priority}\n </span>\n </div>\n\n {/* Health & Connection Status */}\n <div className=\"flex items-center gap-3 text-xs\">\n <div className=\"flex items-center gap-1\">\n <HealthIcon size={12} className={healthConfig.color} />\n <span className={healthConfig.color}>{healthConfig.label}</span>\n </div>\n <span>•</span>\n <span\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'\n }\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\" aria-label=\"Channel tags\">\n {channel.tagAssignments.slice(0, 4).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {channel.tagAssignments.length > 4 && (\n <span className=\"text-xs text-muted-foreground\">\n +{channel.tagAssignments.length - 4}\n </span>\n )}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"mt-3 pt-3 border-t border-border flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <div className=\"flex items-center gap-1\">\n <button\n onClick={() => navigate(`/channels/${channel.id}`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.viewDetails', 'View Details')}\n >\n <Eye size={14} />\n </button>\n <button\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.editChannel', 'Edit Channel')}\n >\n <Pencil size={14} />\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n onClick={() => handleHealthCheck(channel.id)}\n disabled={isActionLoading}\n className=\"p-1.5 text-muted-foreground hover:text-status-info-text hover:bg-status-info-bg-subtle rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n title={tr('channel.runHealthCheck', 'Run Health Check')}\n >\n {isActionLoading ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n onClick={() => handleToggleStatus(channel)}\n disabled={isActionLoading}\n className={`p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${\n channel.status === 'ACTIVE'\n ? 'text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {channel.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n </div>\n\n {/* Delete */}\n {deleteConfirm === channel.id ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('channel.deleteQuestion', 'Delete?')}\n </span>\n <button\n onClick={() => handleDelete(channel.id)}\n disabled={isActionLoading}\n className=\"px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer disabled:cursor-not-allowed\"\n >\n {isActionLoading ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n onClick={() => setDeleteConfirm(null)}\n className=\"px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded transition-colors cursor-pointer\"\n >\n {tr('channel.no', 'No')}\n </button>\n </div>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n onClick={() => setDeleteConfirm(channel.id)}\n className=\"p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer\"\n title={tr('channel.deleteChannel', 'Delete Channel')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Pagination */}\n {total > pageSize && (\n <div className=\"flex items-center justify-between px-1 py-2\">\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-foreground\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium text-foreground\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={currentPage === 0}\n className=\"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.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={currentPage >= totalPages - 1}\n className=\"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 </button>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChannelsListPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelsListPage.tsx"],"sourcesContent":["/**\n * Channels List Page\n *\n * Displays a list of all channels with CRUD operations\n */\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError, subscribeShellEvent } from '../../../utils/shellBridge';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n Trash2,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Pencil,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Download,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\nimport { useExportChannels } from '../hooks/useExport';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\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\nexport function ChannelsListPage() {\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 [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [selectedChannelIds, setSelectedChannelIds] = useState<Set<string>>(new Set());\n const [bulkActionLoading, setBulkActionLoading] = useState<string | null>(null);\n\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportChannels,\n isExporting: isExportingChannels,\n exportError: exportChannelsError,\n } = useExportChannels(organizationId ?? undefined);\n const [pageSize] = useState(12);\n const [currentPage, setCurrentPage] = useState(0);\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n // Fetch the current page whenever page or page size changes\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n // Reset to page 0 when filter changes\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n // Cross-MFE refresh: refetch the list whenever channel/connection events\n // are emitted by other tabs or microfrontends.\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n const handleDelete = async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete channel' } },\n '[channel] Failed to delete channel'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async (channel: Channel) => {\n setActionLoading(channel.id);\n try {\n if (channel.status === 'ACTIVE') {\n await deactivateChannel(channel.id);\n } else {\n await activateChannel(channel.id);\n }\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to toggle channel status' } },\n '[channel] Failed to toggle channel status'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleHealthCheck = async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to run health check' } },\n '[channel] Failed to run health check'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const selectedChannels = useMemo(\n () => channels.filter((channel) => selectedChannelIds.has(channel.id)),\n [channels, selectedChannelIds]\n );\n const selectedActiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status === 'ACTIVE'),\n [selectedChannels]\n );\n const selectedInactiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status !== 'ACTIVE'),\n [selectedChannels]\n );\n const allVisibleSelected =\n channels.length > 0 && channels.every((channel) => selectedChannelIds.has(channel.id));\n const partiallySelected =\n channels.some((channel) => selectedChannelIds.has(channel.id)) && !allVisibleSelected;\n\n const toggleChannelSelection = (channelId: string, checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(channelId);\n } else {\n next.delete(channelId);\n }\n return next;\n });\n };\n\n const toggleAllChannels = (checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n channels.forEach((channel) => {\n if (checked) {\n next.add(channel.id);\n } else {\n next.delete(channel.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkDelete = async () => {\n if (selectedChannels.length === 0) return;\n void nativeImpact('medium');\n const message = tr(\n selectedChannels.length === 1\n ? 'channel.bulkDeleteConfirmSingle'\n : 'channel.bulkDeleteConfirmMultiple',\n selectedChannels.length === 1\n ? 'Delete 1 selected channel?'\n : `Delete ${selectedChannels.length} selected channels?`\n );\n const native = await nativeConfirm({\n title: 'Delete channels',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n setBulkActionLoading('delete');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => deleteChannel(channel.id)));\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkActivate = async () => {\n if (selectedInactiveChannels.length === 0) return;\n setBulkActionLoading('activate');\n try {\n await Promise.allSettled(\n selectedInactiveChannels.map((channel) => activateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkDeactivate = async () => {\n if (selectedActiveChannels.length === 0) return;\n setBulkActionLoading('deactivate');\n try {\n await Promise.allSettled(\n selectedActiveChannels.map((channel) => deactivateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkHealthCheck = async () => {\n if (selectedChannels.length === 0) return;\n setBulkActionLoading('health');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => runHealthCheck(channel.id)));\n } finally {\n setBulkActionLoading(null);\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 return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-4 sm: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>\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('channel.manageChannels', 'Manage your communication channels')} ({total} total)\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_ADMIN}>\n <button\n type=\"button\"\n onClick={() => void exportChannels()}\n disabled={isExportingChannels}\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 {isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\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\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n aria-label={tr('channel.searchChannels', 'Search channels...')}\n type=\"text\"\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 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 <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Export Error */}\n {exportChannelsError && (\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 <p className=\"text-sm text-status-error-text flex-1\">{exportChannelsError}</p>\n </div>\n )}\n\n {channels.length > 0 && (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllChannels(event.target.checked)}\n aria-label=\"Select all channels\"\n className=\"size-4 rounded border-border text-primary\"\n />\n <span>{tr('channel.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {selectedChannelIds.size} {tr('channel.selectedCountLabel', 'selected')}\n </span>\n {selectedChannelIds.size > 0 && (\n <button\n type=\"button\"\n onClick={() => setSelectedChannelIds(new Set())}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n {tr('channel.clearSelection', 'Clear selection')}\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={handleBulkHealthCheck}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-border rounded-md hover:bg-muted transition-colors disabled:opacity-50\"\n >\n {tr('channel.healthCheck', 'Health Check')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkActivate}\n disabled={selectedInactiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-success-border/30 text-status-success-text rounded-md hover:bg-status-success-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.activateSelected', 'Activate Selected')}\n {selectedInactiveChannels.length > 0\n ? ` (${selectedInactiveChannels.length})`\n : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkDeactivate}\n disabled={selectedActiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-warning-border text-status-warning-text rounded-md hover:bg-status-warning-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deactivateSelected', 'Deactivate Selected')}\n {selectedActiveChannels.length > 0 ? ` (${selectedActiveChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={handleBulkDelete}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-error-border/30 text-status-error-text rounded-md hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deleteSelected', 'Delete Selected')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\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 {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => refetch()}\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.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Radio size={48} className=\"text-muted-foreground mb-4\" />\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('channel.noChannelsFound', 'No channels found')}\n </h3>\n <p className=\"text-sm text-muted-foreground max-w-md mb-4\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your filters to see more channels.')\n : tr(\n 'channel.createFirstChannelPrompt',\n 'Create your first communication channel to get started.'\n )}\n </p>\n {!filter.search && !filter.platform && !filter.status && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\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\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {channels.map((channel: Channel) => {\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 isActionLoading = actionLoading === channel.id;\n\n return (\n <div\n key={channel.id}\n className={`p-4 bg-surface border rounded-lg transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border hover:border-action-primary-bg/40'}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={selectedChannelIds.has(channel.id)}\n onChange={(event) =>\n toggleChannelSelection(channel.id, event.target.checked)\n }\n aria-label={`Select channel ${channel.name}`}\n className=\"size-4 rounded border-border text-primary\"\n />\n <Radio size={20} className=\"text-accent-primary\" />\n <span\n className={`px-2 py-0.5 text-xs font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusConfig.color}`}\n >\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Content - Clickable area */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/channels/${channel.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`/channels/${channel.id}`);\n }\n }}\n className=\"cursor-pointer\"\n >\n <h3 className=\"text-sm font-semibold text-foreground mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-muted-foreground mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-muted-foreground mb-2 font-mono\">\n {channel.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n\n {/* Stats Row */}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground mb-2\">\n <span>\n {tr('channel.priority', 'Priority')}: {channel.priority}\n </span>\n </div>\n\n {/* Health & Connection Status */}\n <div className=\"flex items-center gap-3 text-xs\">\n <div className=\"flex items-center gap-1\">\n <HealthIcon size={12} className={healthConfig.color} />\n <span className={healthConfig.color}>{healthConfig.label}</span>\n </div>\n <span>•</span>\n <span\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'\n }\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\" aria-label=\"Channel tags\">\n {channel.tagAssignments.slice(0, 4).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {channel.tagAssignments.length > 4 && (\n <span className=\"text-xs text-muted-foreground\">\n +{channel.tagAssignments.length - 4}\n </span>\n )}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"mt-3 pt-3 border-t border-border flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.viewDetails', 'View Details')}\n >\n <Eye size={14} />\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.editChannel', 'Edit Channel')}\n >\n <Pencil size={14} />\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleHealthCheck(channel.id)}\n disabled={isActionLoading}\n className=\"p-1.5 text-muted-foreground hover:text-status-info-text hover:bg-status-info-bg-subtle rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n title={tr('channel.runHealthCheck', 'Run Health Check')}\n >\n {isActionLoading ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(channel)}\n disabled={isActionLoading}\n className={`p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${\n channel.status === 'ACTIVE'\n ? 'text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {channel.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n </div>\n\n {/* Delete */}\n {deleteConfirm === channel.id ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('channel.deleteQuestion', 'Delete?')}\n </span>\n <button\n type=\"button\"\n onClick={() => handleDelete(channel.id)}\n disabled={isActionLoading}\n className=\"px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer disabled:cursor-not-allowed\"\n >\n {isActionLoading ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(null)}\n className=\"px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded transition-colors cursor-pointer\"\n >\n {tr('channel.no', 'No')}\n </button>\n </div>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(channel.id)}\n className=\"p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer\"\n title={tr('channel.deleteChannel', 'Delete Channel')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Pagination */}\n {total > pageSize && (\n <div className=\"flex items-center justify-between px-1 py-2\">\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-foreground\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium text-foreground\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\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={currentPage === 0}\n className=\"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.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={currentPage >= totalPages - 1}\n className=\"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 </button>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -82,17 +82,18 @@ function O() {
|
|
|
82
82
|
};
|
|
83
83
|
if (R || !M && !P) return /* @__PURE__ */ w("div", {
|
|
84
84
|
className: "flex items-center justify-center min-h-screen",
|
|
85
|
-
children: /* @__PURE__ */ w(_, { className: "
|
|
85
|
+
children: /* @__PURE__ */ w(_, { className: "size-8 animate-spin text-muted-foreground" })
|
|
86
86
|
});
|
|
87
87
|
if (P || z || !L) return /* @__PURE__ */ T("div", {
|
|
88
88
|
className: "flex flex-col items-center justify-center min-h-screen gap-4",
|
|
89
89
|
children: [
|
|
90
|
-
/* @__PURE__ */ w(f, { className: "
|
|
90
|
+
/* @__PURE__ */ w(f, { className: "size-12 text-destructive" }),
|
|
91
91
|
/* @__PURE__ */ w("h1", {
|
|
92
92
|
className: "text-xl font-semibold",
|
|
93
93
|
children: U("channel.channelNotFound", "Channel not found")
|
|
94
94
|
}),
|
|
95
95
|
/* @__PURE__ */ w("button", {
|
|
96
|
+
type: "button",
|
|
96
97
|
onClick: () => m("../../list"),
|
|
97
98
|
className: "text-primary hover:underline",
|
|
98
99
|
children: U("channel.backToChannels", "Back to channels")
|
|
@@ -102,7 +103,7 @@ function O() {
|
|
|
102
103
|
if (!X) return /* @__PURE__ */ T("div", {
|
|
103
104
|
className: "flex flex-col items-center justify-center min-h-screen gap-4",
|
|
104
105
|
children: [
|
|
105
|
-
/* @__PURE__ */ w(f, { className: "
|
|
106
|
+
/* @__PURE__ */ w(f, { className: "size-12 text-muted-foreground" }),
|
|
106
107
|
/* @__PURE__ */ w("h1", {
|
|
107
108
|
className: "text-xl font-semibold",
|
|
108
109
|
children: U("channel.noConnectionFound", "No connection found")
|
|
@@ -112,6 +113,7 @@ function O() {
|
|
|
112
113
|
children: U("channel.noConnectionConfigured", "This channel does not have a connection configured.")
|
|
113
114
|
}),
|
|
114
115
|
/* @__PURE__ */ w("button", {
|
|
116
|
+
type: "button",
|
|
115
117
|
onClick: () => m(`/channels/${I}`),
|
|
116
118
|
className: "text-primary hover:underline",
|
|
117
119
|
children: U("channel.backToChannel", "Back to channel")
|
|
@@ -132,9 +134,10 @@ function O() {
|
|
|
132
134
|
children: [/* @__PURE__ */ T("div", {
|
|
133
135
|
className: "flex items-center gap-4",
|
|
134
136
|
children: [/* @__PURE__ */ w("button", {
|
|
137
|
+
type: "button",
|
|
135
138
|
onClick: () => m(`/channels/${I}`),
|
|
136
139
|
className: "p-2 hover:bg-muted rounded-lg transition-colors",
|
|
137
|
-
children: /* @__PURE__ */ w(p, { className: "
|
|
140
|
+
children: /* @__PURE__ */ w(p, { className: "size-5 " })
|
|
138
141
|
}), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("h1", {
|
|
139
142
|
className: "text-2xl font-bold",
|
|
140
143
|
children: U("channel.connectionDetails", "Connection Details")
|
|
@@ -151,15 +154,17 @@ function O() {
|
|
|
151
154
|
children: [/* @__PURE__ */ w(s, {
|
|
152
155
|
permission: c.CHANNEL_EXECUTE,
|
|
153
156
|
children: /* @__PURE__ */ T("button", {
|
|
157
|
+
type: "button",
|
|
154
158
|
onClick: Z,
|
|
155
159
|
disabled: K,
|
|
156
160
|
className: "px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2",
|
|
157
|
-
children: [K ? /* @__PURE__ */ w(_, { className: "
|
|
161
|
+
children: [K ? /* @__PURE__ */ w(_, { className: "size-4 animate-spin" }) : /* @__PURE__ */ w(v, { className: "size-4 " }), U("channel.runHealthCheck", "Run Health Check")]
|
|
158
162
|
})
|
|
159
163
|
}), /* @__PURE__ */ T("button", {
|
|
164
|
+
type: "button",
|
|
160
165
|
onClick: () => m(`/channels/${I}/edit`),
|
|
161
166
|
className: "px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2",
|
|
162
|
-
children: [/* @__PURE__ */ w(g, { className: "
|
|
167
|
+
children: [/* @__PURE__ */ w(g, { className: "size-4 " }), U("channel.editChannel", "Edit Channel")]
|
|
163
168
|
})]
|
|
164
169
|
})]
|
|
165
170
|
})
|
|
@@ -175,7 +180,7 @@ function O() {
|
|
|
175
180
|
className: "flex items-center gap-4",
|
|
176
181
|
children: [/* @__PURE__ */ w("div", {
|
|
177
182
|
className: $.color,
|
|
178
|
-
children: /* @__PURE__ */ w(ee, { className: "
|
|
183
|
+
children: /* @__PURE__ */ w(ee, { className: "size-10 " })
|
|
179
184
|
}), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("h2", {
|
|
180
185
|
className: "text-xl font-semibold",
|
|
181
186
|
children: $.label
|
|
@@ -208,7 +213,7 @@ function O() {
|
|
|
208
213
|
children: "Auth Type"
|
|
209
214
|
}), /* @__PURE__ */ T("p", {
|
|
210
215
|
className: "flex items-center gap-2",
|
|
211
|
-
children: [/* @__PURE__ */ w(y, { className: "
|
|
216
|
+
children: [/* @__PURE__ */ w(y, { className: "size-4 text-muted-foreground" }), X.authType]
|
|
212
217
|
})] }),
|
|
213
218
|
/* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
|
|
214
219
|
className: "text-sm text-muted-foreground",
|
|
@@ -229,7 +234,7 @@ function O() {
|
|
|
229
234
|
children: "Last Tested"
|
|
230
235
|
}), /* @__PURE__ */ T("p", {
|
|
231
236
|
className: "flex items-center gap-2",
|
|
232
|
-
children: [/* @__PURE__ */ w(h, { className: "
|
|
237
|
+
children: [/* @__PURE__ */ w(h, { className: "size-4 text-muted-foreground" }), new Date(X.lastTestedAt).toLocaleString()]
|
|
233
238
|
})] }),
|
|
234
239
|
X.daysUntilExpiry !== null && X.daysUntilExpiry !== void 0 && /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
|
|
235
240
|
className: "text-sm text-muted-foreground",
|
|
@@ -291,12 +296,14 @@ function O() {
|
|
|
291
296
|
children: U("channel.confirmDelete", "Confirm delete?")
|
|
292
297
|
}),
|
|
293
298
|
/* @__PURE__ */ T("button", {
|
|
299
|
+
type: "button",
|
|
294
300
|
onClick: Q,
|
|
295
301
|
disabled: W,
|
|
296
302
|
className: "px-4 py-2 rounded-lg bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors flex items-center gap-2",
|
|
297
|
-
children: [W ? /* @__PURE__ */ w(_, { className: "
|
|
303
|
+
children: [W ? /* @__PURE__ */ w(_, { className: "size-4 animate-spin" }) : /* @__PURE__ */ w(b, { className: "size-4 " }), U("channel.delete", "Delete")]
|
|
298
304
|
}),
|
|
299
305
|
/* @__PURE__ */ w("button", {
|
|
306
|
+
type: "button",
|
|
300
307
|
onClick: () => Y(!1),
|
|
301
308
|
className: "px-4 py-2 rounded-lg border hover:bg-muted transition-colors",
|
|
302
309
|
children: U("channel.cancel", "Cancel")
|
|
@@ -305,9 +312,10 @@ function O() {
|
|
|
305
312
|
}) : /* @__PURE__ */ w(s, {
|
|
306
313
|
permission: c.CHANNEL_CONNECTION_DELETE,
|
|
307
314
|
children: /* @__PURE__ */ T("button", {
|
|
315
|
+
type: "button",
|
|
308
316
|
onClick: () => Y(!0),
|
|
309
317
|
className: "px-4 py-2 rounded-lg text-destructive border border-destructive hover:bg-destructive/10 transition-colors flex items-center gap-2",
|
|
310
|
-
children: [/* @__PURE__ */ w(b, { className: "
|
|
318
|
+
children: [/* @__PURE__ */ w(b, { className: "size-4 " }), U("channel.delete", "Delete")]
|
|
311
319
|
})
|
|
312
320
|
})]
|
|
313
321
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionDetailsPage.tsx"],"sourcesContent":["/**\n * Connection Details Page\n *\n * View and manage a specific connection within a channel.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError } from '../../../utils/shellBridge';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Trash2,\n Edit,\n Shield,\n Clock,\n} from 'lucide-react';\nimport { useChannel } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n type GetConnectionQuery,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<\n string,\n { icon: typeof CheckCircle2; color: string; bgColor: string; label: string }\n> = {\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n label: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n label: 'Unhealthy',\n },\n};\n\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const params = useParams<{ channelId?: string; connectionId: string }>();\n const { connectionId } = params;\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n\n // Route may be /channels/connections/:connectionId (no channelId in URL).\n // Resolve channelId from getConnection when absent so downstream channel hooks work.\n const [resolvedChannelId, setResolvedChannelId] = useState<string | undefined>(params.channelId);\n const [resolveError, setResolveError] = useState<string | null>(null);\n\n useEffect(() => {\n if (resolvedChannelId || !connectionId) return;\n let cancelled = false;\n void (async () => {\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection;\n if (conn?.channelId) setResolvedChannelId(conn.channelId);\n else setResolveError('Connection not found');\n } catch (err) {\n if (!cancelled)\n setResolveError(err instanceof Error ? err.message : 'Failed to resolve connection');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, resolvedChannelId, apiGatewayUrl, authToken, organizationId]);\n\n const channelId = resolvedChannelId;\n const { channel, isLoading, error, runHealthCheck, deleteConnection } = useChannel(channelId);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [isDeleting, setIsDeleting] = useState(false);\n const [isRunningHealth, setIsRunningHealth] = useState(false);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n const connection = channel?.connection;\n\n // Run health check\n const handleRunHealthCheck = async () => {\n setIsRunningHealth(true);\n try {\n await runHealthCheck();\n } finally {\n setIsRunningHealth(false);\n }\n };\n\n // Delete connection\n const handleDelete = async () => {\n if (!connectionId) return;\n void nativeImpact('medium');\n setIsDeleting(true);\n try {\n // deleteConnection throws on failure; success is guaranteed if it returns.\n await deleteConnection(connectionId);\n void nativeNotify('success');\n navigate(`/channels/${channelId}`);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete connection' } },\n '[channel] Failed to delete connection'\n );\n } finally {\n setIsDeleting(false);\n }\n };\n\n if (isLoading || (!resolvedChannelId && !resolveError)) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"w-8 h-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (resolveError || error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"w-12 h-12 text-destructive\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button onClick={() => navigate('../../list')} className=\"text-primary hover:underline\">\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"w-12 h-12 text-muted-foreground\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.noConnectionConfigured',\n 'This channel does not have a connection configured.'\n )}\n </p>\n <button\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannel', 'Back to channel')}\n </button>\n </div>\n );\n }\n\n const health = HEALTH_CONFIG[String(connection.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_READ}>\n <div className=\"min-h-screen bg-background\">\n {/* Header */}\n <div className=\"border-b bg-card\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"w-5 h-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.connectionDetails', 'Connection Details')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {channel.name} • {channel.platform}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n onClick={handleRunHealthCheck}\n disabled={isRunningHealth}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2\"\n >\n {isRunningHealth ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"w-4 h-4\" />\n )}\n {tr('channel.runHealthCheck', 'Run Health Check')}\n </button>\n </PermissionGuard>\n <button\n onClick={() => navigate(`/channels/${channelId}/edit`)}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2\"\n >\n <Edit className=\"w-4 h-4\" />\n {tr('channel.editChannel', 'Edit Channel')}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-3xl space-y-6\">\n {/* Health Status */}\n <div className={`p-6 rounded-lg border ${health.bgColor}`}>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className={health.color}>\n <HealthIcon className=\"w-10 h-10\" />\n </div>\n <div>\n <h2 className=\"text-xl font-semibold\">{health.label}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {connection.isHealthy\n ? tr('channel.connectionWorking', 'Connection is working properly')\n : tr('channel.connectionNotWorking', 'Connection is not working properly')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Connection Info */}\n <div className=\"border rounded-lg p-6 space-y-4\">\n <h3 className=\"font-semibold\">\n {tr('channel.connectionInformation', 'Connection Information')}\n </h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Connection ID</p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Auth Type</p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"w-4 h-4 text-muted-foreground\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Status</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-muted-foreground'\n }\n >\n {connection.isActive\n ? tr('channel.activeStatus', 'Active')\n : tr('channel.inactiveStatus', 'Inactive')}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Token Refresh</p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-muted-foreground'\n }\n >\n {connection.needsTokenRefresh\n ? tr('channel.tokenRefreshRequired', 'Required soon')\n : tr('channel.tokenRefreshNotNeeded', 'Not needed')}\n </p>\n </div>\n\n {connection.lastTestedAt && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Last Tested</p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"w-4 h-4 text-muted-foreground\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null && connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Days Until Token Expiry</p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-muted-foreground'\n }\n >\n {connection.daysUntilExpiry} days\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-2\">Last Test Status</p>\n <div className=\"p-3 rounded-lg bg-muted\">\n <p className=\"font-mono text-sm\">{connection.testStatus}</p>\n </div>\n </div>\n )}\n\n {connection.tagAssignments && connection.tagAssignments.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-2\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Connection tags\">\n {connection.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"border rounded-lg p-6 space-y-4\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"font-medium\">{tr('channel.deleteConnection', 'Delete Connection')}</p>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.deleteConnectionWarning',\n 'Permanently remove this connection. The channel will become inactive.'\n )}\n </p>\n </div>\n\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 rounded-lg text-destructive border border-destructive hover:bg-destructive/10 transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"w-4 h-4\" />\n {tr('channel.delete', 'Delete')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionDetailsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionDetailsPage.tsx"],"sourcesContent":["/**\n * Connection Details Page\n *\n * View and manage a specific connection within a channel.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError } from '../../../utils/shellBridge';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Trash2,\n Edit,\n Shield,\n Clock,\n} from 'lucide-react';\nimport { useChannel } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n type GetConnectionQuery,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<\n string,\n { icon: typeof CheckCircle2; color: string; bgColor: string; label: string }\n> = {\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n label: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n label: 'Unhealthy',\n },\n};\n\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const params = useParams<{ channelId?: string; connectionId: string }>();\n const { connectionId } = params;\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n\n // Route may be /channels/connections/:connectionId (no channelId in URL).\n // Resolve channelId from getConnection when absent so downstream channel hooks work.\n const [resolvedChannelId, setResolvedChannelId] = useState<string | undefined>(params.channelId);\n const [resolveError, setResolveError] = useState<string | null>(null);\n\n useEffect(() => {\n if (resolvedChannelId || !connectionId) return;\n let cancelled = false;\n void (async () => {\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection;\n if (conn?.channelId) setResolvedChannelId(conn.channelId);\n else setResolveError('Connection not found');\n } catch (err) {\n if (!cancelled)\n setResolveError(err instanceof Error ? err.message : 'Failed to resolve connection');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, resolvedChannelId, apiGatewayUrl, authToken, organizationId]);\n\n const channelId = resolvedChannelId;\n const { channel, isLoading, error, runHealthCheck, deleteConnection } = useChannel(channelId);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [isDeleting, setIsDeleting] = useState(false);\n const [isRunningHealth, setIsRunningHealth] = useState(false);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n const connection = channel?.connection;\n\n // Run health check\n const handleRunHealthCheck = async () => {\n setIsRunningHealth(true);\n try {\n await runHealthCheck();\n } finally {\n setIsRunningHealth(false);\n }\n };\n\n // Delete connection\n const handleDelete = async () => {\n if (!connectionId) return;\n void nativeImpact('medium');\n setIsDeleting(true);\n try {\n // deleteConnection throws on failure; success is guaranteed if it returns.\n await deleteConnection(connectionId);\n void nativeNotify('success');\n navigate(`/channels/${channelId}`);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete connection' } },\n '[channel] Failed to delete connection'\n );\n } finally {\n setIsDeleting(false);\n }\n };\n\n if (isLoading || (!resolvedChannelId && !resolveError)) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (resolveError || error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-destructive\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-muted-foreground\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.noConnectionConfigured',\n 'This channel does not have a connection configured.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannel', 'Back to channel')}\n </button>\n </div>\n );\n }\n\n const health = HEALTH_CONFIG[String(connection.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_READ}>\n <div className=\"min-h-screen bg-background\">\n {/* Header */}\n <div className=\"border-b bg-card\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.connectionDetails', 'Connection Details')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {channel.name} • {channel.platform}\n </p>\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={handleRunHealthCheck}\n disabled={isRunningHealth}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2\"\n >\n {isRunningHealth ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"size-4 \" />\n )}\n {tr('channel.runHealthCheck', 'Run Health Check')}\n </button>\n </PermissionGuard>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}/edit`)}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors flex items-center gap-2\"\n >\n <Edit className=\"size-4 \" />\n {tr('channel.editChannel', 'Edit Channel')}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-3xl space-y-6\">\n {/* Health Status */}\n <div className={`p-6 rounded-lg border ${health.bgColor}`}>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className={health.color}>\n <HealthIcon className=\"size-10 \" />\n </div>\n <div>\n <h2 className=\"text-xl font-semibold\">{health.label}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {connection.isHealthy\n ? tr('channel.connectionWorking', 'Connection is working properly')\n : tr('channel.connectionNotWorking', 'Connection is not working properly')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Connection Info */}\n <div className=\"border rounded-lg p-6 space-y-4\">\n <h3 className=\"font-semibold\">\n {tr('channel.connectionInformation', 'Connection Information')}\n </h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-sm text-muted-foreground\">Connection ID</p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Auth Type</p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"size-4 text-muted-foreground\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Status</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-muted-foreground'\n }\n >\n {connection.isActive\n ? tr('channel.activeStatus', 'Active')\n : tr('channel.inactiveStatus', 'Inactive')}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-muted-foreground\">Token Refresh</p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-muted-foreground'\n }\n >\n {connection.needsTokenRefresh\n ? tr('channel.tokenRefreshRequired', 'Required soon')\n : tr('channel.tokenRefreshNotNeeded', 'Not needed')}\n </p>\n </div>\n\n {connection.lastTestedAt && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Last Tested</p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"size-4 text-muted-foreground\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null && connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-muted-foreground\">Days Until Token Expiry</p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-muted-foreground'\n }\n >\n {connection.daysUntilExpiry} days\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-2\">Last Test Status</p>\n <div className=\"p-3 rounded-lg bg-muted\">\n <p className=\"font-mono text-sm\">{connection.testStatus}</p>\n </div>\n </div>\n )}\n\n {connection.tagAssignments && connection.tagAssignments.length > 0 && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground mb-2\">{tr('channel.tags', 'Tags')}</p>\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Connection tags\">\n {connection.tagAssignments.map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"md\" />\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Actions */}\n <div className=\"border rounded-lg p-6 space-y-4\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"font-medium\">{tr('channel.deleteConnection', 'Delete Connection')}</p>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.deleteConnectionWarning',\n 'Permanently remove this connection. The channel will become inactive.'\n )}\n </p>\n </div>\n\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 rounded-lg text-destructive border border-destructive hover:bg-destructive/10 transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.delete', 'Delete')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-muted transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|