@burdenoff/microfe-channel 2026.1013.2 → 2026.1107.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/ChannelRoot.d.ts +0 -2
- package/dist/channel/ChannelRoot.js +9 -6
- package/dist/channel/ChannelRoot.js.map +1 -1
- package/dist/channel/ChannelRoutes.d.ts +1 -2
- package/dist/channel/ChannelRoutes.js +5 -6
- package/dist/channel/ChannelRoutes.js.map +1 -1
- package/dist/channel/components/AdapterCredentialsForm.d.ts +2 -4
- package/dist/channel/components/AdapterCredentialsForm.js +1 -2
- package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
- package/dist/channel/components/FeatureNotEnabled.d.ts +1 -2
- package/dist/channel/components/FeatureNotEnabled.js +1 -2
- package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
- package/dist/channel/components/MarkdownEditor.d.ts +1 -2
- package/dist/channel/components/MarkdownEditor.js +2 -5
- package/dist/channel/components/MarkdownEditor.js.map +1 -1
- package/dist/channel/components/PermissionGuard.d.ts +0 -2
- package/dist/channel/components/PermissionGuard.js +1 -2
- package/dist/channel/components/PermissionGuard.js.map +1 -1
- package/dist/channel/config/adapterCredentials.d.ts +0 -2
- package/dist/channel/config/adapterCredentials.js +3 -7
- package/dist/channel/config/adapterCredentials.js.map +1 -1
- package/dist/channel/constants/permissions.d.ts +0 -1
- package/dist/channel/constants/permissions.js +1 -2
- package/dist/channel/constants/permissions.js.map +1 -1
- package/dist/channel/graphql/global-client.d.ts +0 -2
- package/dist/channel/graphql/global-client.js +5 -6
- package/dist/channel/graphql/global-client.js.map +1 -1
- package/dist/channel/hooks/useChannelOrgSettings.d.ts +0 -2
- package/dist/channel/hooks/useChannelOrgSettings.js +20 -21
- package/dist/channel/hooks/useChannelOrgSettings.js.map +1 -1
- package/dist/channel/hooks/useChannelSubscription.d.ts +0 -1
- package/dist/channel/hooks/useChannelSubscription.js +7 -12
- package/dist/channel/hooks/useChannelSubscription.js.map +1 -1
- package/dist/channel/hooks/useChannels.d.ts +20 -30
- package/dist/channel/hooks/useChannels.js +100 -101
- package/dist/channel/hooks/useChannels.js.map +1 -1
- package/dist/channel/hooks/useTr.d.ts +0 -1
- package/dist/channel/hooks/useTr.js +1 -2
- package/dist/channel/hooks/useTr.js.map +1 -1
- package/dist/channel/index.d.ts +0 -1
- package/dist/channel/index.js +3 -2
- package/dist/channel/modules/channels/hooks/useExport.d.ts +0 -2
- package/dist/channel/modules/channels/hooks/useExport.js +77 -80
- package/dist/channel/modules/channels/hooks/useExport.js.map +1 -1
- package/dist/channel/modules/channels/i18n.d.ts +0 -2
- package/dist/channel/modules/channels/i18n.js +1 -2
- package/dist/channel/modules/channels/i18n.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +10 -11
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +400 -401
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +8 -7
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +5 -10
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +287 -288
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +11 -12
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +1 -2
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +4 -5
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +37 -38
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +7 -12
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/EditChannelPage.js +105 -103
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +22 -23
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/SendMessagePage.js +180 -187
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.d.ts +1 -2
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +144 -149
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/dist/channel/pages/ChannelLandingPage.d.ts +1 -2
- package/dist/channel/pages/ChannelLandingPage.js +20 -21
- package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
- package/dist/channel/providers/ChannelProvider.d.ts +0 -2
- package/dist/channel/providers/ChannelProvider.js +1 -2
- package/dist/channel/providers/ChannelProvider.js.map +1 -1
- package/dist/channel/types/index.d.ts +0 -2
- package/dist/channel/utils/shellBridge.d.ts +0 -1
- package/dist/channel/utils/shellBridge.js +1 -2
- package/dist/channel/utils/shellBridge.js.map +1 -1
- package/dist/dev/App.d.ts +1 -2
- package/dist/dev/main.d.ts +0 -2
- package/dist/generated/global-operations.d.ts +885 -336
- package/dist/generated/global-operations.js +2 -3
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.d.ts +2 -22
- package/dist/generated/global-types.js +1 -2
- package/dist/generated/global-types.js.map +1 -1
- package/dist/generated/wspace-operations.d.ts +4 -2
- package/dist/generated/wspace-types.d.ts +4 -24
- package/dist/index.d.ts +0 -1
- package/dist/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js +1 -2
- package/dist/node_modules/@apollo/client/errors/CombinedGraphQLErrors.js.map +1 -1
- package/dist/node_modules/@apollo/client/errors/utils.js +1 -2
- package/dist/node_modules/@apollo/client/errors/utils.js.map +1 -1
- package/dist/translations/en.d.ts +0 -1
- package/dist/utils/nativeBridge.d.ts +0 -1
- package/dist/utils/nativeBridge.js +1 -2
- package/package.json +65 -43
- package/dist/channel/ChannelRoot.d.ts.map +0 -1
- package/dist/channel/ChannelRoutes.d.ts.map +0 -1
- package/dist/channel/components/AdapterCredentialsForm.d.ts.map +0 -1
- package/dist/channel/components/FeatureNotEnabled.d.ts.map +0 -1
- package/dist/channel/components/MarkdownEditor.d.ts.map +0 -1
- package/dist/channel/components/PermissionGuard.d.ts.map +0 -1
- package/dist/channel/config/adapterCredentials.d.ts.map +0 -1
- package/dist/channel/constants/permissions.d.ts.map +0 -1
- package/dist/channel/graphql/global-client.d.ts.map +0 -1
- package/dist/channel/hooks/useChannelOrgSettings.d.ts.map +0 -1
- package/dist/channel/hooks/useChannelSubscription.d.ts.map +0 -1
- package/dist/channel/hooks/useChannels.d.ts.map +0 -1
- package/dist/channel/hooks/useTr.d.ts.map +0 -1
- package/dist/channel/index.d.ts.map +0 -1
- package/dist/channel/modules/channels/hooks/useExport.d.ts.map +0 -1
- package/dist/channel/modules/channels/i18n.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.d.ts.map +0 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.d.ts.map +0 -1
- package/dist/channel/pages/ChannelLandingPage.d.ts.map +0 -1
- package/dist/channel/providers/ChannelProvider.d.ts.map +0 -1
- package/dist/channel/types/index.d.ts.map +0 -1
- package/dist/channel/utils/shellBridge.d.ts.map +0 -1
- package/dist/dev/App.d.ts.map +0 -1
- package/dist/dev/main.d.ts.map +0 -1
- package/dist/generated/global-operations.d.ts.map +0 -1
- package/dist/generated/global-types.d.ts.map +0 -1
- package/dist/generated/wspace-operations.d.ts.map +0 -1
- package/dist/generated/wspace-types.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/translations/en.d.ts.map +0 -1
- package/dist/utils/nativeBridge.d.ts.map +0 -1
|
@@ -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';\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 { 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 {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\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-text-secondary bg-bg-sunken', 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-text-secondary bg-bg-sunken', 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-text-secondary', icon: HelpCircle },\n};\n\nexport function ChannelsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Channel creation is an admin-only action (channel:create). Hide the\n // affordance for users without it (per MICROFRONTEND-SPEC: {canCreateChannel && ...}).\n const canCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CREATE);\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [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-text-primary\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-text-secondary 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\" data-tour=\"channels-list-export-button\">\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: tr('channel.createChannel', 'Create Channel'),\n icon: <Plus size={16} aria-hidden=\"true\" />,\n onSelect: () => navigate('/channels/create'),\n }\n : undefined\n }\n actions={\n canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingChannels,\n onSelect: () => void exportChannels(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n\n {/* Page purpose */}\n <PagePurpose icon={<Radio size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.listPurpose',\n 'Every communication channel in your organization, in one browseable grid. Filter by platform or status, select several at once to activate, deactivate, health-check or delete them in bulk, and open any card to manage its connection and delivery history. Start here when you need to find a specific channel or tidy up the ones you no longer use.'\n )}\n </PagePurpose>\n\n {/* Emphasis — primary create call-to-action */}\n {canCreate && (\n <EmphasisPanel className=\"flex flex-col gap-3 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"rounded-lg bg-accent-primary/10 p-2\">\n <Plus size={18} className=\"text-accent-primary\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('channel.addAnotherChannel', 'Add another channel')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.addAnotherChannelHint',\n 'Connect a new platform — Email, SMS, Push, Slack and more — to start delivering messages through it.'\n )}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </EmphasisPanel>\n )}\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-text-secondary\"\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-subtle 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-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 <GlassCard\n treatment=\"plain\"\n className=\"flex flex-col gap-3 border-border-seam p-4 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"channels-list-bulk-actions\"\n >\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\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-subtle 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-text-secondary\">\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-text-secondary hover:text-text-primary\"\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-subtle rounded-md hover:bg-bg-sunken 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 </GlassCard>\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 <IllustratedEmptyState\n illustration={\n filter.search || filter.platform || filter.status ? 'empty-search' : 'empty-data'\n }\n title={tr('channel.noChannelsFound', 'No channels found')}\n description={\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 }\n action={\n canCreate && !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 ) : undefined\n }\n />\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\" data-tour=\"channels-list-grid\">\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 <GlassCard\n key={channel.id}\n treatment=\"insight\"\n className={`p-4 transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border-seam'}`}\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-subtle 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 channel.status === 'ACTIVE' ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\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-text-primary mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-text-secondary mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-text-secondary 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-text-secondary 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-text-secondary'\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-text-secondary\">\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-subtle 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-text-secondary 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 {canUpdate && (\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-text-secondary 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 )}\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-text-secondary 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-text-secondary hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-text-secondary 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-text-secondary\">\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-text-secondary hover:bg-bg-sunken 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-text-secondary 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 </GlassCard>\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-text-secondary\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-text-primary\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium text-text-primary\">{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-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\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-subtle rounded-lg hover:bg-bg-sunken 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":["useChannelContext","PLATFORM_CONFIG","STATUS_CONFIG","CheckCircle2","PowerOff","XCircle","AlertTriangle","HEALTH_CONFIG","HelpCircle","ChannelsListPage","navigate","useNavigate","usePermissions","canCreate","hasPermission","CHANNEL_PERMISSIONS","canUpdate","canExport","useI18n","tr","value","t","key","fallback","deleteConfirm","setDeleteConfirm","useState","actionLoading","setActionLoading","selectedChannelIds","setSelectedChannelIds","bulkActionLoading","setBulkActionLoading","useChannel","exportChannels","isExportingChannels","exportChannelsError","useExportChannels","organizationId","pageSize","currentPage","setCurrentPage","useChannels","totalPages","total","refetchRef","useRef","refetch","useEffect","filter","unsubs","subscribeShellEvent","name","u","handleDelete","nativeImpact","id","deleteChannel","nativeNotify","err","reportShellError","handleToggleStatus","channel","deactivateChannel","activateChannel","handleHealthCheck","runHealthCheck","selectedChannels","useMemo","channels","selectedActiveChannels","selectedInactiveChannels","allVisibleSelected","partiallySelected","toggleChannelSelection","next","prev","checked","channelId","toggleAllChannels","handleBulkDelete","message","native","nativeConfirm","handleBulkActivate","handleBulkDeactivate","handleBulkHealthCheck","getPlatformConfig","platform","translateLabel","PLATFORM_I18N","getStatusConfig","status","STATUS_I18N","getHealthConfig","health","HEALTH_I18N","PermissionGuard","CTAOverflowMenu","Plus","Loader2","Download","PagePurpose","Radio","EmphasisPanel","Search","updateFilter","config","AlertCircle","GlassCard","input","event","error","isLoading","IllustratedEmptyState","platformConfig","statusConfig","healthConfig","StatusIcon","HealthIcon","isActionLoading","e","TagBadge","a","Eye","Pencil","RefreshCw","Power","Trash2","p"],"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';\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 { 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 {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\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-text-secondary bg-bg-sunken', 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-text-secondary bg-bg-sunken', 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-text-secondary', icon: HelpCircle },\n};\n\nexport function ChannelsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Channel creation is an admin-only action (channel:create). Hide the\n // affordance for users without it (per MICROFRONTEND-SPEC: {canCreateChannel && ...}).\n const canCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CREATE);\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [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-text-primary\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-text-secondary 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\" data-tour=\"channels-list-export-button\">\n <CTAOverflowMenu\n primary={\n canCreate\n ? {\n label: tr('channel.createChannel', 'Create Channel'),\n icon: <Plus size={16} aria-hidden=\"true\" />,\n onSelect: () => navigate('/channels/create'),\n }\n : undefined\n }\n actions={\n canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingChannels,\n onSelect: () => void exportChannels(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n\n {/* Page purpose */}\n <PagePurpose icon={<Radio size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.listPurpose',\n 'Every communication channel in your organization, in one browseable grid. Filter by platform or status, select several at once to activate, deactivate, health-check or delete them in bulk, and open any card to manage its connection and delivery history. Start here when you need to find a specific channel or tidy up the ones you no longer use.'\n )}\n </PagePurpose>\n\n {/* Emphasis — primary create call-to-action */}\n {canCreate && (\n <EmphasisPanel className=\"flex flex-col gap-3 p-5 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <span className=\"rounded-lg bg-accent-primary/10 p-2\">\n <Plus size={18} className=\"text-accent-primary\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('channel.addAnotherChannel', 'Add another channel')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.addAnotherChannelHint',\n 'Connect a new platform — Email, SMS, Push, Slack and more — to start delivering messages through it.'\n )}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"inline-flex shrink-0 items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus size={16} aria-hidden=\"true\" />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </EmphasisPanel>\n )}\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-text-secondary\"\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-subtle 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-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 <GlassCard\n treatment=\"plain\"\n className=\"flex flex-col gap-3 border-border-seam p-4 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"channels-list-bulk-actions\"\n >\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\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-subtle 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-text-secondary\">\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-text-secondary hover:text-text-primary\"\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-subtle rounded-md hover:bg-bg-sunken 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 </GlassCard>\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 <IllustratedEmptyState\n illustration={\n filter.search || filter.platform || filter.status ? 'empty-search' : 'empty-data'\n }\n title={tr('channel.noChannelsFound', 'No channels found')}\n description={\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 }\n action={\n canCreate && !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 ) : undefined\n }\n />\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\" data-tour=\"channels-list-grid\">\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 <GlassCard\n key={channel.id}\n treatment=\"insight\"\n className={`p-4 transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border-seam'}`}\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-subtle 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 channel.status === 'ACTIVE' ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\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-text-primary mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-text-secondary mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-text-secondary 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-text-secondary 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-text-secondary'\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-text-secondary\">\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-subtle 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-text-secondary 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 {canUpdate && (\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-text-secondary 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 )}\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-text-secondary 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-text-secondary hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-text-secondary 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-text-secondary\">\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-text-secondary hover:bg-bg-sunken 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-text-secondary 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 </GlassCard>\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-text-secondary\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-text-primary\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium text-text-primary\">{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-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\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-subtle rounded-lg hover:bg-bg-sunken 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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"}
|
|
@@ -3,5 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* View and manage a specific connection within a channel.
|
|
5
5
|
*/
|
|
6
|
-
export declare function ConnectionDetailsPage(): import(
|
|
7
|
-
//# sourceMappingURL=ConnectionDetailsPage.d.ts.map
|
|
6
|
+
export declare function ConnectionDetailsPage(): import('react').JSX.Element;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useTr as e } from "../../../hooks/useTr.js";
|
|
2
|
-
import {
|
|
2
|
+
import { reportShellError as t } from "../../../utils/shellBridge.js";
|
|
3
3
|
import { nativeImpact as n, nativeNotify as r } from "../../../../utils/nativeBridge.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { useChannel as i } from "../../../hooks/useChannels.js";
|
|
5
|
+
import { useChannel as a } from "../../../providers/ChannelProvider.js";
|
|
6
|
+
import { getGlobalChannelClient as ee } from "../../../graphql/global-client.js";
|
|
7
|
+
import { GetConnectionDocument as te } from "../../../../generated/global-operations.js";
|
|
8
8
|
import { PermissionGuard as o } from "../../../components/PermissionGuard.js";
|
|
9
9
|
import { CHANNEL_PERMISSIONS as s } from "../../../constants/permissions.js";
|
|
10
10
|
import { useEffect as ne, useMemo as c, useState as l } from "react";
|
|
@@ -14,16 +14,16 @@ import { usePermissions as oe } from "@burdenoff/fe-libs/shared/providers/shell"
|
|
|
14
14
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
15
15
|
import { CTAOverflowMenu as se, EmphasisPanel as ce, GlassCard as x, NextSteps as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
import { TagBadge as w } from "@burdenoff/fe-libs/tag";
|
|
17
|
-
|
|
17
|
+
// src/channel/modules/channels/pages/ConnectionDetailsPage.tsx
|
|
18
18
|
function T() {
|
|
19
|
-
let T = v(), { hasPermission: E } = oe(), D = E(s.CHANNEL_UPDATE), O = E(s.CHANNEL_EXECUTE), k = ae(), { connectionId: A } = k, { authToken: j, apiGatewayUrl: M, organizationId: N } =
|
|
19
|
+
let T = v(), { hasPermission: E } = oe(), D = E(s.CHANNEL_UPDATE), O = E(s.CHANNEL_EXECUTE), k = ae(), { connectionId: A } = k, { authToken: j, apiGatewayUrl: M, organizationId: N } = a(), [P, F] = l(k.channelId), [I, L] = l(null);
|
|
20
20
|
ne(() => {
|
|
21
21
|
if (P || !A) return;
|
|
22
22
|
let e = !1;
|
|
23
23
|
return (async () => {
|
|
24
24
|
try {
|
|
25
|
-
let t = await
|
|
26
|
-
query:
|
|
25
|
+
let t = await ee(M, j, N).query({
|
|
26
|
+
query: te,
|
|
27
27
|
variables: { id: A },
|
|
28
28
|
fetchPolicy: "network-only"
|
|
29
29
|
});
|
|
@@ -43,7 +43,7 @@ function T() {
|
|
|
43
43
|
j,
|
|
44
44
|
N
|
|
45
45
|
]);
|
|
46
|
-
let R = P, { channel: z, isLoading: B, error: V, runHealthCheck: H, deleteConnection: U } =
|
|
46
|
+
let R = P, { channel: z, isLoading: B, error: V, runHealthCheck: H, deleteConnection: U } = i(R), W = e(), G = c(() => ({
|
|
47
47
|
true: {
|
|
48
48
|
icon: re,
|
|
49
49
|
color: "text-status-success-text",
|
|
@@ -71,7 +71,7 @@ function T() {
|
|
|
71
71
|
try {
|
|
72
72
|
await U(A), r("success"), T(`/channels/${R}`);
|
|
73
73
|
} catch (e) {
|
|
74
|
-
r("error"),
|
|
74
|
+
r("error"), t(e, {
|
|
75
75
|
module: "channel",
|
|
76
76
|
context: { action: "Failed to delete connection" }
|
|
77
77
|
}, "[channel] Failed to delete connection");
|
|
@@ -375,7 +375,6 @@ function T() {
|
|
|
375
375
|
})
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
|
-
//#endregion
|
|
379
378
|
export { T as ConnectionDetailsPage };
|
|
380
379
|
|
|
381
380
|
//# sourceMappingURL=ConnectionDetailsPage.js.map
|
|
@@ -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, useMemo } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\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 {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\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\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExecute = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXECUTE);\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 tr = useTr();\n\n const HEALTH_CONFIG: Record<\n string,\n {\n icon: typeof CheckCircle2;\n color: string;\n bgColor: string;\n labelKey: string;\n labelFallback: string;\n }\n > = useMemo(\n () => ({\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n labelKey: 'channel.healthLabel.healthy',\n labelFallback: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n labelKey: 'channel.healthLabel.unhealthy',\n labelFallback: 'Unhealthy',\n },\n }),\n []\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-text-secondary\" />\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-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n 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-text-secondary\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\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-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center 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-bg-sunken 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-text-secondary\">\n {channel.name} • {channel.platform}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={\n canUpdate\n ? {\n label: tr('channel.editChannel', 'Edit Channel'),\n icon: <Edit className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => navigate(`/channels/${channelId}/edit`),\n }\n : undefined\n }\n actions={\n canExecute\n ? [\n {\n label: tr('channel.runHealthCheck', 'Run Health Check'),\n icon: isRunningHealth ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n ),\n disabled: isRunningHealth,\n onSelect: () => void handleRunHealthCheck(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-5xl space-y-6\">\n {/* Page purpose */}\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.connectionDetailsPurpose',\n 'The authenticated link between this channel and its provider — its auth type, whether it is active, when it was last tested, and how soon its token expires. Use it to verify the channel can actually send, to spot a token that needs refreshing before it lapses, or to remove a connection you no longer need.'\n )}\n </PagePurpose>\n\n {/* Next-step guidance */}\n <NextSteps\n storageKey=\"channel-connection-detail\"\n steps={[\n ...(canExecute\n ? [\n {\n id: 'health',\n label: tr('channel.nextRunHealthCheck', 'Run a health check'),\n description: tr(\n 'channel.nextConnectionHealthDesc',\n 'Confirm the credentials still authenticate against the provider.'\n ),\n icon: <RefreshCw size={16} aria-hidden=\"true\" />,\n onClick: () => void handleRunHealthCheck(),\n },\n ]\n : []),\n ...(canUpdate\n ? [\n {\n id: 'edit',\n label: tr('channel.nextEditChannel', 'Edit the channel'),\n description: tr(\n 'channel.nextEditChannelDesc',\n 'Update credentials, status or platform metadata for this channel.'\n ),\n icon: <Edit size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}/edit`),\n },\n ]\n : []),\n {\n id: 'back',\n label: tr('channel.nextBackToChannel', 'Back to the channel'),\n description: tr(\n 'channel.nextBackToChannelDesc',\n 'Return to the channel overview to send a test or review deliveries.'\n ),\n icon: <ArrowLeft size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}`),\n },\n ]}\n />\n\n {/* Health Status */}\n <EmphasisPanel className=\"p-6\">\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\">\n {tr(health.labelKey, health.labelFallback)}\n </h2>\n <p className=\"text-sm text-text-secondary\">\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 </EmphasisPanel>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Connection Info */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-2\">\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-text-secondary\">\n {tr('connection.connectionId', 'Connection ID')}\n </p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.authType', 'Auth Type')}\n </p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"size-4 text-text-secondary\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">{tr('channel.status', 'Status')}</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-text-secondary'\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-text-secondary\">\n {tr('connection.tokenRefresh', 'Token Refresh')}\n </p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-text-secondary'\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-text-secondary\">\n {tr('channel.lastTested', 'Last Tested')}\n </p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null &&\n connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('connection.daysUntilTokenExpiry', 'Days Until Token Expiry')}\n </p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {tr('channel.duration.days', '{{count}} days', {\n count: connection.daysUntilExpiry,\n })}\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">\n {tr('connection.lastTestStatus', 'Last Test Status')}\n </p>\n <div className=\"p-3 rounded-lg bg-bg-sunken\">\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-text-secondary 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 </GlassCard>\n\n {/* Actions */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-1\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex flex-col gap-3\">\n <div>\n <p className=\"font-medium\">\n {tr('channel.deleteConnection', 'Delete Connection')}\n </p>\n <p className=\"text-sm text-text-secondary\">\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-status-error-text border border-status-error-border hover:bg-status-error-bg/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-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionDetailsPage.js","names":["useChannelContext","useChannel$1","ConnectionDetailsPage","navigate","useNavigate","usePermissions","canUpdate","hasPermission","CHANNEL_PERMISSIONS","canExecute","params","useParams","resolvedChannelId","setResolvedChannelId","useState","resolveError","setResolveError","connectionId","cancelled","result","getGlobalChannelClient","apiGatewayUrl","authToken","organizationId","GetConnectionDocument","conn","err","channelId","useChannel","tr","useTr","HEALTH_CONFIG","useMemo","CheckCircle2","XCircle","isDeleting","setIsDeleting","isRunningHealth","setIsRunningHealth","deleteConfirm","setDeleteConfirm","connection","channel","handleRunHealthCheck","runHealthCheck","handleDelete","nativeImpact","deleteConnection","nativeNotify","reportShellError","isLoading","Loader2","error","AlertCircle","health","HealthIcon","PermissionGuard","ArrowLeft","CTAOverflowMenu","Edit","RefreshCw","PagePurpose","Shield","NextSteps","EmphasisPanel","GlassCard","Clock","TagBadge","a","Trash2"],"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, useMemo } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\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 {\n CTAOverflowMenu,\n EmphasisPanel,\n GlassCard,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\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\nexport function ConnectionDetailsPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\n const canExecute = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_EXECUTE);\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 tr = useTr();\n\n const HEALTH_CONFIG: Record<\n string,\n {\n icon: typeof CheckCircle2;\n color: string;\n bgColor: string;\n labelKey: string;\n labelFallback: string;\n }\n > = useMemo(\n () => ({\n true: {\n icon: CheckCircle2,\n color: 'text-status-success-text',\n bgColor: 'bg-status-success-bg-subtle',\n labelKey: 'channel.healthLabel.healthy',\n labelFallback: 'Healthy',\n },\n false: {\n icon: XCircle,\n color: 'text-status-error-text',\n bgColor: 'bg-error-500/10',\n labelKey: 'channel.healthLabel.unhealthy',\n labelFallback: 'Unhealthy',\n },\n }),\n []\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-text-secondary\" />\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-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n 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-text-secondary\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.noConnectionFound', 'No connection found')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\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-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center 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-bg-sunken 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-text-secondary\">\n {channel.name} • {channel.platform}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={\n canUpdate\n ? {\n label: tr('channel.editChannel', 'Edit Channel'),\n icon: <Edit className=\"size-4\" aria-hidden=\"true\" />,\n onSelect: () => navigate(`/channels/${channelId}/edit`),\n }\n : undefined\n }\n actions={\n canExecute\n ? [\n {\n label: tr('channel.runHealthCheck', 'Run Health Check'),\n icon: isRunningHealth ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n ) : (\n <RefreshCw className=\"size-4\" aria-hidden=\"true\" />\n ),\n disabled: isRunningHealth,\n onSelect: () => void handleRunHealthCheck(),\n },\n ]\n : []\n }\n />\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-5xl space-y-6\">\n {/* Page purpose */}\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.connectionDetailsPurpose',\n 'The authenticated link between this channel and its provider — its auth type, whether it is active, when it was last tested, and how soon its token expires. Use it to verify the channel can actually send, to spot a token that needs refreshing before it lapses, or to remove a connection you no longer need.'\n )}\n </PagePurpose>\n\n {/* Next-step guidance */}\n <NextSteps\n storageKey=\"channel-connection-detail\"\n steps={[\n ...(canExecute\n ? [\n {\n id: 'health',\n label: tr('channel.nextRunHealthCheck', 'Run a health check'),\n description: tr(\n 'channel.nextConnectionHealthDesc',\n 'Confirm the credentials still authenticate against the provider.'\n ),\n icon: <RefreshCw size={16} aria-hidden=\"true\" />,\n onClick: () => void handleRunHealthCheck(),\n },\n ]\n : []),\n ...(canUpdate\n ? [\n {\n id: 'edit',\n label: tr('channel.nextEditChannel', 'Edit the channel'),\n description: tr(\n 'channel.nextEditChannelDesc',\n 'Update credentials, status or platform metadata for this channel.'\n ),\n icon: <Edit size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}/edit`),\n },\n ]\n : []),\n {\n id: 'back',\n label: tr('channel.nextBackToChannel', 'Back to the channel'),\n description: tr(\n 'channel.nextBackToChannelDesc',\n 'Return to the channel overview to send a test or review deliveries.'\n ),\n icon: <ArrowLeft size={16} aria-hidden=\"true\" />,\n onClick: () => navigate(`/channels/${channelId}`),\n },\n ]}\n />\n\n {/* Health Status */}\n <EmphasisPanel className=\"p-6\">\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\">\n {tr(health.labelKey, health.labelFallback)}\n </h2>\n <p className=\"text-sm text-text-secondary\">\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 </EmphasisPanel>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Connection Info */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-2\">\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-text-secondary\">\n {tr('connection.connectionId', 'Connection ID')}\n </p>\n <p className=\"font-mono text-sm\">{connection.id}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.authType', 'Auth Type')}\n </p>\n <p className=\"flex items-center gap-2\">\n <Shield className=\"size-4 text-text-secondary\" />\n {connection.authType}\n </p>\n </div>\n\n <div>\n <p className=\"text-sm text-text-secondary\">{tr('channel.status', 'Status')}</p>\n <p\n className={\n connection.isActive ? 'text-status-success-text' : 'text-text-secondary'\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-text-secondary\">\n {tr('connection.tokenRefresh', 'Token Refresh')}\n </p>\n <p\n className={\n connection.needsTokenRefresh\n ? 'text-status-warning-text'\n : 'text-text-secondary'\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-text-secondary\">\n {tr('channel.lastTested', 'Last Tested')}\n </p>\n <p className=\"flex items-center gap-2\">\n <Clock className=\"size-4 text-text-secondary\" />\n {new Date(connection.lastTestedAt).toLocaleString()}\n </p>\n </div>\n )}\n\n {connection.daysUntilExpiry !== null &&\n connection.daysUntilExpiry !== undefined && (\n <div>\n <p className=\"text-sm text-text-secondary\">\n {tr('connection.daysUntilTokenExpiry', 'Days Until Token Expiry')}\n </p>\n <p\n className={\n connection.daysUntilExpiry < 30\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n }\n >\n {tr('channel.duration.days', '{{count}} days', {\n count: connection.daysUntilExpiry,\n })}\n </p>\n </div>\n )}\n </div>\n\n {connection.testStatus && (\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-text-secondary mb-2\">\n {tr('connection.lastTestStatus', 'Last Test Status')}\n </p>\n <div className=\"p-3 rounded-lg bg-bg-sunken\">\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-text-secondary 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 </GlassCard>\n\n {/* Actions */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4 lg:col-span-1\">\n <h3 className=\"font-semibold\">{tr('channel.actions', 'Actions')}</h3>\n\n <div className=\"flex flex-col gap-3\">\n <div>\n <p className=\"font-medium\">\n {tr('channel.deleteConnection', 'Delete Connection')}\n </p>\n <p className=\"text-sm text-text-secondary\">\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-status-error-text border border-status-error-border hover:bg-status-error-bg/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-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 rounded-lg bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n </GlassCard>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,5 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Displays all connections across channels with their status.
|
|
5
5
|
*/
|
|
6
|
-
export declare function ConnectionsListPage(): import(
|
|
7
|
-
//# sourceMappingURL=ConnectionsListPage.d.ts.map
|
|
6
|
+
export declare function ConnectionsListPage(): import('react').JSX.Element;
|
|
@@ -9,7 +9,7 @@ import { usePermissions as g } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
|
9
9
|
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
10
|
import { EmphasisPanel as b, GlassCard as x, IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
11
11
|
import { TagBadge as w } from "@burdenoff/fe-libs/tag";
|
|
12
|
-
|
|
12
|
+
// src/channel/modules/channels/pages/ConnectionsListPage.tsx
|
|
13
13
|
var T = {
|
|
14
14
|
true: {
|
|
15
15
|
icon: c,
|
|
@@ -365,7 +365,6 @@ function E() {
|
|
|
365
365
|
})
|
|
366
366
|
});
|
|
367
367
|
}
|
|
368
|
-
//#endregion
|
|
369
368
|
export { E as ConnectionsListPage };
|
|
370
369
|
|
|
371
370
|
//# sourceMappingURL=ConnectionsListPage.js.map
|