@burdenoff/microfe-channel 2026.1013.1 → 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 +2 -3
- 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 +12 -13
- 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 +11 -10
- 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 +6 -11
- 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 +289 -290
- 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 +3 -4
- 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 +6 -7
- 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 +39 -40
- 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 +8 -13
- 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 +108 -106
- 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 +181 -188
- 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 +145 -150
- 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":"ConnectionsListPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionsListPage.tsx"],"sourcesContent":["/**\n * Connections List Page\n *\n * Displays all connections across channels with their status.\n */\n\nimport { useState, useEffect } 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 {\n ArrowLeft,\n Plus,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Settings,\n Shield,\n} from 'lucide-react';\nimport {\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useChannels } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<string, { icon: typeof CheckCircle2; color: string; label: string }> = {\n true: { icon: CheckCircle2, color: 'text-status-success-text', label: 'Healthy' },\n false: { icon: XCircle, color: 'text-status-error-text', label: 'Unhealthy' },\n};\n\nexport function ConnectionsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Creating connections is admin-only (channel-connection:create) — hide otherwise.\n const canConnectionCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE);\n const { channels, isLoading, error, refetch } = useChannels();\n\n // Poll for connection status updates every 60 s.\n // connectionStatusChanged subscription is scoped to a single channelId;\n // this page shows all connections, so polling is used instead.\n useEffect(() => {\n const timer = setInterval(() => {\n void refetch();\n }, 60_000);\n return () => clearInterval(timer);\n }, [refetch]);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Filter state\n const [healthFilter, setHealthFilter] = useState<string>('all');\n const [platformFilter, setPlatformFilter] = useState<string>('all');\n const pageSize = 10;\n const [currentPage, setCurrentPage] = useState(0);\n\n // Get all connections from channels (single-pass; avoids filter().map() double traversal)\n const connections = channels.reduce<\n Array<{\n channelId: string;\n channelName: string;\n platform: (typeof channels)[number]['platform'];\n adapterType: (typeof channels)[number]['adapterType'];\n channelStatus: (typeof channels)[number]['status'];\n connectionId: string;\n isActive: boolean;\n isHealthy: boolean;\n needsTokenRefresh: boolean;\n lastTestedAt: string | null | undefined;\n authType: NonNullable<(typeof channels)[number]['connection']>['authType'];\n tagAssignments: NonNullable<\n NonNullable<(typeof channels)[number]['connection']>['tagAssignments']\n >;\n }>\n >((acc, ch) => {\n const conn = ch.connection;\n if (!conn) return acc;\n acc.push({\n channelId: ch.id,\n channelName: ch.name,\n platform: ch.platform,\n adapterType: ch.adapterType,\n channelStatus: ch.status,\n connectionId: conn.id,\n isActive: conn.isActive,\n isHealthy: conn.isHealthy,\n needsTokenRefresh: conn.needsTokenRefresh,\n lastTestedAt: conn.lastTestedAt,\n authType: conn.authType,\n tagAssignments: conn.tagAssignments ?? [],\n });\n return acc;\n }, []);\n\n // Get unique platforms\n const platforms = [...new Set(connections.map((c) => c.platform))];\n\n // Connections that are unhealthy or have a token expiring soon — the single\n // most important thing to act on, surfaced in the page's emphasis zone.\n const connectionsNeedingAttention = connections.filter(\n (c) => !c.isHealthy || c.needsTokenRefresh\n ).length;\n\n // Apply filters\n const filteredConnections = connections.filter((conn) => {\n if (healthFilter === 'healthy' && !conn.isHealthy) return false;\n if (healthFilter === 'unhealthy' && conn.isHealthy) return false;\n if (platformFilter !== 'all' && conn.platform !== platformFilter) return false;\n return true;\n });\n\n const totalPages = Math.max(1, Math.ceil(filteredConnections.length / pageSize));\n const pagedConnections = filteredConnections.slice(\n currentPage * pageSize,\n (currentPage + 1) * pageSize\n );\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (error) {\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.loadConnectionsFailed', 'Failed to load connections')}\n </h1>\n <p className=\"text-sm text-text-secondary\">{error}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n );\n }\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\" data-tour=\"channels-connections-header\">\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('./list')}\n aria-label={tr('channel.backToChannels', 'Back to channels')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.connections', 'Connections')}</h1>\n <p className=\"text-sm text-text-secondary\">\n {filteredConnections.length} connection\n {filteredConnections.length !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n {canConnectionCreate && (\n <button\n type=\"button\"\n onClick={() => navigate('./create')}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2\"\n >\n <Plus className=\"size-4 \" />\n {tr('channel.newConnection', 'New Connection')}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Page purpose + emphasis */}\n <div className=\"container mx-auto space-y-4 px-6 pt-4\">\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.connectionsPurpose',\n \"A connection is the authenticated link — the OAuth grant, API key or credentials — that lets a channel actually reach its provider. This page lists every channel's connection with its live health and token status. Use it to confirm channels are authenticated and to catch tokens that are about to expire before deliveries start failing.\"\n )}\n </PagePurpose>\n {connectionsNeedingAttention > 0 && (\n <EmphasisPanel className=\"flex items-center gap-3 p-4 shadow-[var(--shadow-glow)]\">\n <span className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <AlertCircle size={18} className=\"text-status-warning-text\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {connectionsNeedingAttention}{' '}\n {connectionsNeedingAttention === 1\n ? tr('channel.connectionNeedsAttention', 'connection needs attention')\n : tr('channel.connectionsNeedAttention', 'connections need attention')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.connectionsNeedAttentionHint',\n 'These connections are unhealthy or have tokens expiring soon. Refresh or re-authenticate them to keep deliveries flowing.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"container mx-auto px-6 py-4\" data-tour=\"channels-connections-filters\">\n <div className=\"flex items-center gap-4\">\n <select\n value={platformFilter}\n onChange={(e) => setPlatformFilter(e.target.value)}\n className=\"px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {platforms.map((p) => (\n <option key={p} value={p}>\n {p}\n </option>\n ))}\n </select>\n\n <select\n value={healthFilter}\n onChange={(e) => setHealthFilter(e.target.value)}\n className=\"px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">{tr('channel.allStatus', 'All Status')}</option>\n <option value=\"healthy\">{tr('channel.healthy', 'Healthy')}</option>\n <option value=\"unhealthy\">{tr('channel.unhealthy', 'Unhealthy')}</option>\n </select>\n\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('channel.refresh', 'Refresh')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <RefreshCw className=\"size-5 \" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n\n {/* Connections List */}\n <div className=\"container mx-auto px-6 pb-8\">\n {filteredConnections.length === 0 ? (\n <IllustratedEmptyState\n illustration={channels.length === 0 ? 'empty-data' : 'empty-search'}\n title={tr('channel.noConnectionsFound', 'No connections found')}\n description={\n channels.length === 0\n ? tr('channel.createChannelFirst', 'Create a channel first to add connections')\n : tr('channel.noConnectionsMatchFilter', 'No connections match your filters')\n }\n action={\n channels.length === 0 ? (\n canConnectionCreate ? (\n <button\n type=\"button\"\n onClick={() => navigate('./create')}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr('channel.createConnection', 'Create Connection')}\n </button>\n ) : undefined\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n setHealthFilter('all');\n setPlatformFilter('all');\n }}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken\"\n >\n {tr('channel.clearFilters', 'Clear Filters')}\n </button>\n )\n }\n />\n ) : (\n <>\n <GlassCard\n treatment=\"plain\"\n className=\"border-border-seam rounded-lg p-0 divide-y divide-[var(--color-border-seam)] overflow-hidden\"\n >\n {pagedConnections.map((conn) => {\n const health = HEALTH_CONFIG[String(conn.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <div\n key={conn.connectionId}\n role=\"button\"\n tabIndex={0}\n className=\"p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer\"\n onClick={() => navigate(`./${conn.channelId}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`./${conn.channelId}`);\n }\n }}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div\n className={`${health.color} ${\n conn.isHealthy ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n aria-label={health.label}\n >\n <HealthIcon className=\"size-5 \" aria-hidden=\"true\" />\n </div>\n <div>\n <p className=\"font-medium\">{conn.channelName}</p>\n <p className=\"text-sm text-text-secondary\">\n {conn.platform} • {conn.adapterType} • {conn.authType}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right\">\n <p className=\"text-sm\">\n {conn.channelStatus === 'ACTIVE' ? (\n <span className=\"text-status-success-text\">\n {tr('channel.activeStatus', 'Active')}\n </span>\n ) : conn.channelStatus === 'INACTIVE' ? (\n <span className=\"text-text-secondary\">\n {tr('channel.inactiveStatus', 'Inactive')}\n </span>\n ) : conn.channelStatus === 'ERROR' ? (\n <span className=\"text-status-error-text\">\n {tr('channel.errorStatus', 'Error')}\n </span>\n ) : conn.channelStatus === 'SUSPENDED' ? (\n <span className=\"text-status-warning-text\">\n {tr('channel.suspendedStatus', 'Suspended')}\n </span>\n ) : conn.channelStatus === 'ARCHIVED' ? (\n <span className=\"text-text-secondary\">\n {tr('channel.archivedStatus', 'Archived')}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr('channel.inactiveStatus', 'Inactive')}\n </span>\n )}\n </p>\n {conn.lastTestedAt && (\n <p className=\"text-xs text-text-secondary\">\n Last tested: {new Date(conn.lastTestedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n\n {conn.needsTokenRefresh && (\n <span className=\"px-2 py-1 text-xs bg-status-warning-bg-subtle text-status-warning-text rounded\">\n Token Expires\n </span>\n )}\n\n {conn.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1\" aria-label=\"Connection tags\">\n {conn.tagAssignments.slice(0, 3).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {conn.tagAssignments.length > 3 && (\n <span className=\"text-xs text-text-secondary\">\n +{conn.tagAssignments.length - 3}\n </span>\n )}\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n navigate(`/channels/${conn.channelId}`);\n }}\n aria-label={tr('channel.viewChannelSettings', 'View channel settings')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <Settings className=\"size-4 \" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </GlassCard>\n {/* Pagination */}\n {filteredConnections.length > pageSize && (\n <div className=\"flex items-center justify-between px-4 py-3 border border-border-seam rounded-lg mt-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * pageSize + 1}–\n {Math.min((currentPage + 1) * pageSize, filteredConnections.length)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium\">{filteredConnections.length}</span>\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 </>\n )}\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionsListPage.js","names":["_Fragment","HEALTH_CONFIG","CheckCircle2","XCircle","ConnectionsListPage","navigate","useNavigate","usePermissions","canConnectionCreate","hasPermission","CHANNEL_PERMISSIONS","useChannels","timer","refetch","useI18n","tr","value","t","key","fallback","healthFilter","setHealthFilter","useState","platformFilter","setPlatformFilter","currentPage","setCurrentPage","connections","channels","conn","ch","acc","platforms","c","connectionsNeedingAttention","filteredConnections","totalPages","pagedConnections","isLoading","Loader2","error","AlertCircle","PermissionGuard","ArrowLeft","Plus","PagePurpose","Shield","EmphasisPanel","p","RefreshCw","IllustratedEmptyState","Fragment","GlassCard","health","HealthIcon","e","TagBadge","a","Settings"],"sources":["../../../../../src/channel/modules/channels/pages/ConnectionsListPage.tsx"],"sourcesContent":["/**\n * Connections List Page\n *\n * Displays all connections across channels with their status.\n */\n\nimport { useState, useEffect } 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 {\n ArrowLeft,\n Plus,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n RefreshCw,\n Settings,\n Shield,\n} from 'lucide-react';\nimport {\n EmphasisPanel,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useChannels } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst HEALTH_CONFIG: Record<string, { icon: typeof CheckCircle2; color: string; label: string }> = {\n true: { icon: CheckCircle2, color: 'text-status-success-text', label: 'Healthy' },\n false: { icon: XCircle, color: 'text-status-error-text', label: 'Unhealthy' },\n};\n\nexport function ConnectionsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Creating connections is admin-only (channel-connection:create) — hide otherwise.\n const canConnectionCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE);\n const { channels, isLoading, error, refetch } = useChannels();\n\n // Poll for connection status updates every 60 s.\n // connectionStatusChanged subscription is scoped to a single channelId;\n // this page shows all connections, so polling is used instead.\n useEffect(() => {\n const timer = setInterval(() => {\n void refetch();\n }, 60_000);\n return () => clearInterval(timer);\n }, [refetch]);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Filter state\n const [healthFilter, setHealthFilter] = useState<string>('all');\n const [platformFilter, setPlatformFilter] = useState<string>('all');\n const pageSize = 10;\n const [currentPage, setCurrentPage] = useState(0);\n\n // Get all connections from channels (single-pass; avoids filter().map() double traversal)\n const connections = channels.reduce<\n Array<{\n channelId: string;\n channelName: string;\n platform: (typeof channels)[number]['platform'];\n adapterType: (typeof channels)[number]['adapterType'];\n channelStatus: (typeof channels)[number]['status'];\n connectionId: string;\n isActive: boolean;\n isHealthy: boolean;\n needsTokenRefresh: boolean;\n lastTestedAt: string | null | undefined;\n authType: NonNullable<(typeof channels)[number]['connection']>['authType'];\n tagAssignments: NonNullable<\n NonNullable<(typeof channels)[number]['connection']>['tagAssignments']\n >;\n }>\n >((acc, ch) => {\n const conn = ch.connection;\n if (!conn) return acc;\n acc.push({\n channelId: ch.id,\n channelName: ch.name,\n platform: ch.platform,\n adapterType: ch.adapterType,\n channelStatus: ch.status,\n connectionId: conn.id,\n isActive: conn.isActive,\n isHealthy: conn.isHealthy,\n needsTokenRefresh: conn.needsTokenRefresh,\n lastTestedAt: conn.lastTestedAt,\n authType: conn.authType,\n tagAssignments: conn.tagAssignments ?? [],\n });\n return acc;\n }, []);\n\n // Get unique platforms\n const platforms = [...new Set(connections.map((c) => c.platform))];\n\n // Connections that are unhealthy or have a token expiring soon — the single\n // most important thing to act on, surfaced in the page's emphasis zone.\n const connectionsNeedingAttention = connections.filter(\n (c) => !c.isHealthy || c.needsTokenRefresh\n ).length;\n\n // Apply filters\n const filteredConnections = connections.filter((conn) => {\n if (healthFilter === 'healthy' && !conn.isHealthy) return false;\n if (healthFilter === 'unhealthy' && conn.isHealthy) return false;\n if (platformFilter !== 'all' && conn.platform !== platformFilter) return false;\n return true;\n });\n\n const totalPages = Math.max(1, Math.ceil(filteredConnections.length / pageSize));\n const pagedConnections = filteredConnections.slice(\n currentPage * pageSize,\n (currentPage + 1) * pageSize\n );\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (error) {\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.loadConnectionsFailed', 'Failed to load connections')}\n </h1>\n <p className=\"text-sm text-text-secondary\">{error}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n );\n }\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\" data-tour=\"channels-connections-header\">\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('./list')}\n aria-label={tr('channel.backToChannels', 'Back to channels')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.connections', 'Connections')}</h1>\n <p className=\"text-sm text-text-secondary\">\n {filteredConnections.length} connection\n {filteredConnections.length !== 1 ? 's' : ''}\n </p>\n </div>\n </div>\n {canConnectionCreate && (\n <button\n type=\"button\"\n onClick={() => navigate('./create')}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2\"\n >\n <Plus className=\"size-4 \" />\n {tr('channel.newConnection', 'New Connection')}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {/* Page purpose + emphasis */}\n <div className=\"container mx-auto space-y-4 px-6 pt-4\">\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.connectionsPurpose',\n \"A connection is the authenticated link — the OAuth grant, API key or credentials — that lets a channel actually reach its provider. This page lists every channel's connection with its live health and token status. Use it to confirm channels are authenticated and to catch tokens that are about to expire before deliveries start failing.\"\n )}\n </PagePurpose>\n {connectionsNeedingAttention > 0 && (\n <EmphasisPanel className=\"flex items-center gap-3 p-4 shadow-[var(--shadow-glow)]\">\n <span className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <AlertCircle size={18} className=\"text-status-warning-text\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {connectionsNeedingAttention}{' '}\n {connectionsNeedingAttention === 1\n ? tr('channel.connectionNeedsAttention', 'connection needs attention')\n : tr('channel.connectionsNeedAttention', 'connections need attention')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.connectionsNeedAttentionHint',\n 'These connections are unhealthy or have tokens expiring soon. Refresh or re-authenticate them to keep deliveries flowing.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"container mx-auto px-6 py-4\" data-tour=\"channels-connections-filters\">\n <div className=\"flex items-center gap-4\">\n <select\n value={platformFilter}\n onChange={(e) => setPlatformFilter(e.target.value)}\n className=\"px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {platforms.map((p) => (\n <option key={p} value={p}>\n {p}\n </option>\n ))}\n </select>\n\n <select\n value={healthFilter}\n onChange={(e) => setHealthFilter(e.target.value)}\n className=\"px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">{tr('channel.allStatus', 'All Status')}</option>\n <option value=\"healthy\">{tr('channel.healthy', 'Healthy')}</option>\n <option value=\"unhealthy\">{tr('channel.unhealthy', 'Unhealthy')}</option>\n </select>\n\n <button\n type=\"button\"\n onClick={() => refetch()}\n aria-label={tr('channel.refresh', 'Refresh')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <RefreshCw className=\"size-5 \" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n\n {/* Connections List */}\n <div className=\"container mx-auto px-6 pb-8\">\n {filteredConnections.length === 0 ? (\n <IllustratedEmptyState\n illustration={channels.length === 0 ? 'empty-data' : 'empty-search'}\n title={tr('channel.noConnectionsFound', 'No connections found')}\n description={\n channels.length === 0\n ? tr('channel.createChannelFirst', 'Create a channel first to add connections')\n : tr('channel.noConnectionsMatchFilter', 'No connections match your filters')\n }\n action={\n channels.length === 0 ? (\n canConnectionCreate ? (\n <button\n type=\"button\"\n onClick={() => navigate('./create')}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr('channel.createConnection', 'Create Connection')}\n </button>\n ) : undefined\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n setHealthFilter('all');\n setPlatformFilter('all');\n }}\n className=\"px-4 py-2 rounded-lg border hover:bg-bg-sunken\"\n >\n {tr('channel.clearFilters', 'Clear Filters')}\n </button>\n )\n }\n />\n ) : (\n <>\n <GlassCard\n treatment=\"plain\"\n className=\"border-border-seam rounded-lg p-0 divide-y divide-[var(--color-border-seam)] overflow-hidden\"\n >\n {pagedConnections.map((conn) => {\n const health = HEALTH_CONFIG[String(conn.isHealthy)] || HEALTH_CONFIG.false;\n const HealthIcon = health.icon;\n\n return (\n <div\n key={conn.connectionId}\n role=\"button\"\n tabIndex={0}\n className=\"p-4 hover:bg-bg-sunken/50 transition-colors cursor-pointer\"\n onClick={() => navigate(`./${conn.channelId}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`./${conn.channelId}`);\n }\n }}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div\n className={`${health.color} ${\n conn.isHealthy ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n aria-label={health.label}\n >\n <HealthIcon className=\"size-5 \" aria-hidden=\"true\" />\n </div>\n <div>\n <p className=\"font-medium\">{conn.channelName}</p>\n <p className=\"text-sm text-text-secondary\">\n {conn.platform} • {conn.adapterType} • {conn.authType}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"text-right\">\n <p className=\"text-sm\">\n {conn.channelStatus === 'ACTIVE' ? (\n <span className=\"text-status-success-text\">\n {tr('channel.activeStatus', 'Active')}\n </span>\n ) : conn.channelStatus === 'INACTIVE' ? (\n <span className=\"text-text-secondary\">\n {tr('channel.inactiveStatus', 'Inactive')}\n </span>\n ) : conn.channelStatus === 'ERROR' ? (\n <span className=\"text-status-error-text\">\n {tr('channel.errorStatus', 'Error')}\n </span>\n ) : conn.channelStatus === 'SUSPENDED' ? (\n <span className=\"text-status-warning-text\">\n {tr('channel.suspendedStatus', 'Suspended')}\n </span>\n ) : conn.channelStatus === 'ARCHIVED' ? (\n <span className=\"text-text-secondary\">\n {tr('channel.archivedStatus', 'Archived')}\n </span>\n ) : (\n <span className=\"text-text-secondary\">\n {tr('channel.inactiveStatus', 'Inactive')}\n </span>\n )}\n </p>\n {conn.lastTestedAt && (\n <p className=\"text-xs text-text-secondary\">\n Last tested: {new Date(conn.lastTestedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n\n {conn.needsTokenRefresh && (\n <span className=\"px-2 py-1 text-xs bg-status-warning-bg-subtle text-status-warning-text rounded\">\n Token Expires\n </span>\n )}\n\n {conn.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1\" aria-label=\"Connection tags\">\n {conn.tagAssignments.slice(0, 3).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {conn.tagAssignments.length > 3 && (\n <span className=\"text-xs text-text-secondary\">\n +{conn.tagAssignments.length - 3}\n </span>\n )}\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n navigate(`/channels/${conn.channelId}`);\n }}\n aria-label={tr('channel.viewChannelSettings', 'View channel settings')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <Settings className=\"size-4 \" />\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </GlassCard>\n {/* Pagination */}\n {filteredConnections.length > pageSize && (\n <div className=\"flex items-center justify-between px-4 py-3 border border-border-seam rounded-lg mt-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * pageSize + 1}–\n {Math.min((currentPage + 1) * pageSize, filteredConnections.length)}\n </span>{' '}\n {tr('channel.of', 'of')}{' '}\n <span className=\"font-medium\">{filteredConnections.length}</span>\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 </>\n )}\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,5 +8,4 @@
|
|
|
8
8
|
* organizationId is injected from shell context — not shown in form.
|
|
9
9
|
* Credentials belong to createConnection mutation — not part of channel creation.
|
|
10
10
|
*/
|
|
11
|
-
export declare function CreateChannelPage(): import(
|
|
12
|
-
//# sourceMappingURL=CreateChannelPage.d.ts.map
|
|
11
|
+
export declare function CreateChannelPage(): import('react').JSX.Element;
|
|
@@ -9,7 +9,7 @@ import { AlertCircle as c, ArrowLeft as l, Bell as u, CheckCircle2 as d, Globe a
|
|
|
9
9
|
import { useNavigate as w } from "react-router";
|
|
10
10
|
import { jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
11
11
|
import { EmphasisPanel as D, GlassCard as O, OptionalFields as k, PagePurpose as A } from "@burdenoff/fe-libs/ui";
|
|
12
|
-
|
|
12
|
+
// src/channel/modules/channels/pages/CreateChannelPage.tsx
|
|
13
13
|
function j() {
|
|
14
14
|
let j = w(), { organizationId: M } = t(), { createChannel: N } = n(), P = e(), F = o(() => [
|
|
15
15
|
{
|
|
@@ -237,7 +237,7 @@ function j() {
|
|
|
237
237
|
}
|
|
238
238
|
L(!0);
|
|
239
239
|
try {
|
|
240
|
-
let e =
|
|
240
|
+
let e = {
|
|
241
241
|
name: B.name.trim(),
|
|
242
242
|
platform: B.platform,
|
|
243
243
|
organizationId: M,
|
|
@@ -246,8 +246,8 @@ function j() {
|
|
|
246
246
|
status: B.status,
|
|
247
247
|
priority: B.priority,
|
|
248
248
|
platformConfig: t
|
|
249
|
-
});
|
|
250
|
-
|
|
249
|
+
}, n = await N(e);
|
|
250
|
+
n ? j(`../${n.id}`) : z(P("channel.createFailed", "Failed to create channel"));
|
|
251
251
|
} catch (e) {
|
|
252
252
|
z(e instanceof Error ? e.message : P("channel.createFailed", "Failed to create channel"));
|
|
253
253
|
} finally {
|
|
@@ -321,7 +321,7 @@ function j() {
|
|
|
321
321
|
...t,
|
|
322
322
|
adapterType: e.target.value
|
|
323
323
|
})),
|
|
324
|
-
className: "w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary",
|
|
324
|
+
className: "w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
325
325
|
children: [/* @__PURE__ */ T("option", {
|
|
326
326
|
value: "",
|
|
327
327
|
children: P("channel.selectProvider", "Select a provider (optional)")
|
|
@@ -351,7 +351,7 @@ function j() {
|
|
|
351
351
|
...t,
|
|
352
352
|
status: e.target.value
|
|
353
353
|
})),
|
|
354
|
-
className: "w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary",
|
|
354
|
+
className: "w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
355
355
|
children: [/* @__PURE__ */ T("option", {
|
|
356
356
|
value: "ACTIVE",
|
|
357
357
|
children: P("channel.statusLabel.active", "Active")
|
|
@@ -489,7 +489,6 @@ function j() {
|
|
|
489
489
|
})
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
|
-
//#endregion
|
|
493
492
|
export { j as CreateChannelPage };
|
|
494
493
|
|
|
495
494
|
//# sourceMappingURL=CreateChannelPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateChannelPage.tsx"],"sourcesContent":["/**\n * Create Channel Page\n *\n * Form fields match the exact CreateChannelInput from global-channel-svc:\n * name (required), platform (required), organizationId (auto), description,\n * adapterType, status, priority, platformConfig\n *\n * organizationId is injected from shell context — not shown in form.\n * Credentials belong to createConnection mutation — not part of channel creation.\n */\n\nimport { useState, useMemo } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { EmphasisPanel, GlassCard, OptionalFields, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n Radio,\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n Globe,\n Hash,\n Users,\n MessageCircle,\n Send,\n MessageSquare,\n CheckCircle2,\n} from 'lucide-react';\nimport { useChannel } from '../../../providers/ChannelProvider';\nimport {\n useChannels,\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type CreateChannelInput,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { MarkdownEditor } from '../../../components/MarkdownEditor';\n\n// ============================================================================\n// PLATFORM CONFIG\n// ============================================================================\n\n// NOTE: AdapterType values MUST match the backend Prisma schema exactly.\n// See: global-channel-svc/prisma/schema/schema.prisma (enum AdapterType)\n\ntype PlatformOption = {\n value: ChannelPlatform;\n labelKey: string;\n labelFallback: string;\n descriptionKey: string;\n descriptionFallback: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; labelKey: string; labelFallback: string }[];\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function CreateChannelPage() {\n const navigate = useNavigate();\n const { organizationId } = useChannel();\n const { createChannel } = useChannels();\n const tr = useTr();\n\n const PLATFORM_OPTIONS: PlatformOption[] = useMemo(\n () => [\n {\n value: 'EMAIL',\n labelKey: 'channel.platformLabel.email',\n labelFallback: 'Email',\n descriptionKey: 'channel.platformDescription.emailDescription',\n descriptionFallback: 'Send emails via various providers',\n icon: Mail,\n color: 'text-accent-blue bg-accent-blue-subtle border-accent-blue-subtle',\n adapters: [\n { value: 'AWS_SES', labelKey: 'channel.adapter.awsSes', labelFallback: 'AWS SES' },\n {\n value: 'AZURE_ACS',\n labelKey: 'channel.adapter.azureAcs',\n labelFallback: 'Azure Communication Services',\n },\n ],\n },\n {\n value: 'SMS',\n labelKey: 'channel.platformLabel.sms',\n labelFallback: 'SMS',\n descriptionKey: 'channel.platformDescription.smsDescription',\n descriptionFallback: 'Send text messages',\n icon: Smartphone,\n color:\n 'text-status-success-text bg-status-success-bg-subtle border-status-success-bg-subtle',\n adapters: [\n { value: 'AWS_SNS', labelKey: 'channel.adapter.awsSns', labelFallback: 'AWS SNS' },\n {\n value: 'TWILIO_SMS',\n labelKey: 'channel.adapter.twilioSms',\n labelFallback: 'Twilio SMS',\n },\n ],\n },\n {\n value: 'PUSH',\n labelKey: 'channel.platformLabel.push',\n labelFallback: 'Push',\n descriptionKey: 'channel.platformDescription.pushDescription',\n descriptionFallback: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple-subtle',\n adapters: [\n {\n value: 'FCM',\n labelKey: 'channel.adapter.fcm',\n labelFallback: 'Firebase Cloud Messaging',\n },\n ],\n },\n {\n value: 'INAPP',\n labelKey: 'channel.platformLabel.inApp',\n labelFallback: 'In-App',\n descriptionKey: 'channel.platformDescription.inAppDescription',\n descriptionFallback: 'In-app notifications',\n icon: Inbox,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo-subtle',\n adapters: [\n {\n value: 'INAPP_NATIVE',\n labelKey: 'channel.adapter.inAppNative',\n labelFallback: 'Native In-App',\n },\n ],\n },\n {\n value: 'SLACK',\n labelKey: 'channel.platformLabel.slack',\n labelFallback: 'Slack',\n descriptionKey: 'channel.platformDescription.slackDescription',\n descriptionFallback: 'Workspace notifications',\n icon: Hash,\n color: 'text-accent-pink bg-accent-pink-subtle border-accent-pink-subtle',\n adapters: [\n { value: 'SLACK_BOT', labelKey: 'channel.adapter.slackBot', labelFallback: 'Slack Bot' },\n ],\n },\n {\n value: 'TEAMS',\n labelKey: 'channel.platformLabel.teams',\n labelFallback: 'Teams',\n descriptionKey: 'channel.platformDescription.teamsDescription',\n descriptionFallback: 'Teams notifications',\n icon: Users,\n color: 'text-accent-cyan bg-accent-cyan-subtle border-accent-cyan-subtle',\n adapters: [\n { value: 'TEAMS_BOT', labelKey: 'channel.adapter.teamsBot', labelFallback: 'Teams Bot' },\n ],\n },\n {\n value: 'DISCORD',\n labelKey: 'channel.platformLabel.discord',\n labelFallback: 'Discord',\n descriptionKey: 'channel.platformDescription.discordDescription',\n descriptionFallback: 'Server notifications',\n icon: MessageCircle,\n color: 'text-accent-violet bg-accent-violet-subtle border-accent-violet-subtle',\n adapters: [\n {\n value: 'DISCORD_BOT',\n labelKey: 'channel.adapter.discordBot',\n labelFallback: 'Discord Bot',\n },\n ],\n },\n {\n value: 'WHATSAPP',\n labelKey: 'channel.platformLabel.whatsapp',\n labelFallback: 'WhatsApp',\n descriptionKey: 'channel.platformDescription.whatsappDescription',\n descriptionFallback: 'Business messaging',\n icon: Send,\n color: 'text-accent-emerald bg-accent-emerald-subtle border-accent-emerald-subtle',\n adapters: [\n {\n value: 'WHATSAPP_CLOUD',\n labelKey: 'channel.adapter.whatsappCloud',\n labelFallback: 'WhatsApp Cloud API',\n },\n {\n value: 'WHATSAPP_TWILIO',\n labelKey: 'channel.adapter.whatsappTwilio',\n labelFallback: 'WhatsApp via Twilio',\n },\n ],\n },\n {\n value: 'WEBCHAT',\n labelKey: 'channel.platformLabel.webchat',\n labelFallback: 'Webchat',\n descriptionKey: 'channel.platformDescription.webchatDescription',\n descriptionFallback: 'Chat widgets',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal-subtle',\n adapters: [], // No WEBCHAT adapters in backend Prisma schema yet\n },\n {\n value: 'WEBHOOK',\n labelKey: 'channel.platformLabel.webhook',\n labelFallback: 'Webhook',\n descriptionKey: 'channel.platformDescription.webhookDescription',\n descriptionFallback: 'HTTP webhooks',\n icon: Globe,\n color: 'text-accent-orange bg-accent-orange-subtle border-accent-orange-subtle',\n adapters: [\n {\n value: 'WEBHOOK_HTTP',\n labelKey: 'channel.adapter.webhookHttp',\n labelFallback: 'HTTP Webhook',\n },\n ],\n },\n ],\n []\n );\n\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Form state — matches CreateChannelInput exactly\n const [formData, setFormData] = useState<{\n name: string; // required\n platform: ChannelPlatform; // required\n description: string; // optional\n adapterType: AdapterType | ''; // required (auto-selected based on platform)\n status: ChannelStatus; // optional — default ACTIVE on server\n priority: number; // optional — default 100 on server\n platformConfig: string; // optional — JSON string\n }>({\n name: '',\n platform: 'EMAIL',\n description: '',\n adapterType: 'AWS_SES', // Default to AWS SES for Email platform\n status: 'ACTIVE',\n priority: 100,\n platformConfig: '',\n });\n\n const availableAdapters =\n PLATFORM_OPTIONS.find((p) => p.value === formData.platform)?.adapters || [];\n\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === formData.platform);\n\n const handlePlatformChange = (platform: ChannelPlatform) => {\n setFormData((prev) => ({ ...prev, platform, adapterType: '' }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!formData.name.trim()) {\n setError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n if (!organizationId) {\n setError(\n tr(\n 'channel.validation.organizationRequired',\n 'Organization context is required. Please ensure an organization is selected.'\n )\n );\n return;\n }\n\n // Validate platformConfig JSON if provided\n let parsedPlatformConfig: Record<string, unknown> | undefined;\n if (formData.platformConfig.trim()) {\n try {\n parsedPlatformConfig = JSON.parse(formData.platformConfig);\n } catch {\n setError(tr('channel.validation.platformConfigJson', 'Platform Config must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const input: CreateChannelInput = {\n name: formData.name.trim(),\n platform: formData.platform,\n organizationId,\n description: formData.description.trim() || undefined,\n adapterType: (formData.adapterType as AdapterType) || undefined,\n status: formData.status,\n priority: formData.priority,\n platformConfig: parsedPlatformConfig,\n };\n\n const result = await createChannel(input);\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('channel.createFailed', 'Failed to create channel'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error ? err.message : tr('channel.createFailed', 'Failed to create channel')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CREATE}>\n <div className=\"p-3 sm:p-6 space-y-6 max-w-5xl mx-auto\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-surface-hover rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl font-bold text-text-primary flex items-center gap-2\">\n <Radio size={22} className=\"text-accent-primary\" />\n {tr('channel.createChannel', 'Create Channel')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr('channel.createChannelSubtitle', 'Set up a new communication channel')}\n </p>\n </div>\n </div>\n </div>\n\n {/* ── Page purpose ─────────────────────────────────────────────── */}\n <PagePurpose icon={<Radio size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.createChannelPurpose',\n 'Set up a new way to reach people — for example a transactional email sender, an SMS gateway, a Slack workspace or a push provider. Name it, pick the platform and provider, and you will get a channel you can then authenticate and send through. Create one channel per provider account you want messages to flow through.'\n )}\n </PagePurpose>\n\n {/* ── Error ────────────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-3 bg-error-500/10 border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-error-400\">{error}</p>\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* ── Row 1: Name + Description | Status + Provider (2-col) ─── */}\n <div className=\"grid gap-5 lg:grid-cols-2\">\n {/* Left: Name & Description — the primary identity of the channel */}\n <EmphasisPanel className=\"p-5 space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.basicInformation', 'Basic Information')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.name', 'Name')} <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n aria-label={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 placeholder:text-text-secondary\"\n placeholder={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"channel-create-description\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.description', 'Description')}\n </label>\n <MarkdownEditor\n id=\"channel-create-description\"\n value={formData.description}\n onChange={(next) => setFormData((prev) => ({ ...prev, description: next }))}\n aria-label={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n placeholder={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n rows={3}\n />\n </div>\n </EmphasisPanel>\n\n {/* Right: Status + Provider / Adapter */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.providerStatus', 'Provider & Status')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.providerAdapter', 'Provider / Adapter')}\n </label>\n <select\n value={formData.adapterType}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n adapterType: e.target.value as AdapterType | '',\n }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"\">\n {tr('channel.selectProvider', 'Select a provider (optional)')}\n </option>\n {availableAdapters.map((a) => (\n <option key={a.value} value={a.value}>\n {tr(a.labelKey, a.labelFallback)}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {selectedPlatform\n ? tr(\n 'channel.providersAvailableForPlatform',\n '{{count}} providers available for {{platform}}',\n {\n count: availableAdapters.length,\n platform: tr(selectedPlatform.labelKey, selectedPlatform.labelFallback),\n }\n )\n : tr('channel.selectPlatformFirst', 'Select a platform first')}\n </p>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.status', 'Status')}\n </label>\n <select\n value={formData.status}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, status: e.target.value as ChannelStatus }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"ACTIVE\">{tr('channel.statusLabel.active', 'Active')}</option>\n <option value=\"INACTIVE\">\n {tr('channel.statusLabel.inactive', 'Inactive')}\n </option>\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {formData.status === 'ACTIVE'\n ? tr('channel.statusHint.active', 'Ready to send immediately')\n : tr('channel.statusHint.inactive', 'Configure before activating')}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"create-channel-priority\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.priority', 'Priority')}\n </label>\n <input\n id=\"create-channel-priority\"\n aria-label={tr('channel.priority', 'Priority')}\n type=\"number\"\n value={formData.priority}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n priority: parseInt(e.target.value) || 0,\n }))\n }\n min={0}\n max={100}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50\"\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.priorityHint', 'Lower = higher priority (0-100)')}\n </p>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Row 2: Platform selection (full width) ─────────────────── */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')} <span className=\"text-status-error-text\">*</span>\n </h2>\n\n <div className=\"grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5\">\n {PLATFORM_OPTIONS.map((platform) => {\n const Icon = platform.icon;\n const isSelected = formData.platform === platform.value;\n return (\n <button\n key={platform.value}\n type=\"button\"\n onClick={() => handlePlatformChange(platform.value)}\n className={`relative flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all text-center ${\n isSelected\n ? `${platform.color} border-current ring-1 ring-current`\n : 'border-border-subtle hover:bg-surface-hover text-text-secondary hover:text-text-primary'\n }`}\n >\n {isSelected && (\n <CheckCircle2 size={14} className=\"absolute top-1.5 right-1.5 text-current\" />\n )}\n <Icon size={20} />\n <span className=\"text-xs font-semibold\">\n {tr(platform.labelKey, platform.labelFallback)}\n </span>\n <span className=\"text-[10px] leading-tight opacity-70\">\n {tr(platform.descriptionKey, platform.descriptionFallback)}\n </span>\n </button>\n );\n })}\n </div>\n </GlassCard>\n\n {/* ── Row 3: Advanced (collapsible) — webhook config / delivery / retry policy (platformConfig) */}\n <OptionalFields title={tr('channel.advancedOptions', 'Advanced settings')}>\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.platformConfig', 'Platform Config')}{' '}\n <span className=\"text-[10px] text-text-secondary\">\n {tr('channel.jsonMarker', '(JSON)')}\n </span>\n </label>\n <textarea\n aria-label={tr('channel.platformConfig', 'Platform Config')}\n value={formData.platformConfig}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, platformConfig: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none font-mono placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.platformConfigPlaceholder',\n '{\"fromEmail\": \"noreply@example.com\"}'\n )}\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.platformConfigHint', 'Platform-specific configuration as JSON')}\n </p>\n </div>\n </div>\n </OptionalFields>\n\n {/* ── Actions ────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between pt-2\">\n <button\n type=\"button\"\n onClick={() => navigate('/channels/list')}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-surface-hover border border-border-subtle rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !formData.name.trim()}\n className=\"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? <Loader2 size={16} className=\"animate-spin\" /> : <Save size={16} />}\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateChannelPage.js","names":["CreateChannelPage","navigate","useNavigate","useChannel","useChannels","tr","useTr","PLATFORM_OPTIONS","useMemo","Mail","Smartphone","Bell","Inbox","Hash","Users","MessageCircle","Send","MessageSquare","Globe","isSubmitting","setIsSubmitting","useState","error","setError","formData","setFormData","availableAdapters","p","selectedPlatform","handlePlatformChange","prev","PermissionGuard","CHANNEL_PERMISSIONS","ArrowLeft","Radio","PagePurpose","AlertCircle","organizationId","parsedPlatformConfig","input","result","createChannel","err","EmphasisPanel","MarkdownEditor","next","GlassCard","a","Icon","platform","isSelected","CheckCircle2","OptionalFields","Loader2","Save"],"sources":["../../../../../src/channel/modules/channels/pages/CreateChannelPage.tsx"],"sourcesContent":["/**\n * Create Channel Page\n *\n * Form fields match the exact CreateChannelInput from global-channel-svc:\n * name (required), platform (required), organizationId (auto), description,\n * adapterType, status, priority, platformConfig\n *\n * organizationId is injected from shell context — not shown in form.\n * Credentials belong to createConnection mutation — not part of channel creation.\n */\n\nimport { useState, useMemo } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { EmphasisPanel, GlassCard, OptionalFields, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n Radio,\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n Globe,\n Hash,\n Users,\n MessageCircle,\n Send,\n MessageSquare,\n CheckCircle2,\n} from 'lucide-react';\nimport { useChannel } from '../../../providers/ChannelProvider';\nimport {\n useChannels,\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type CreateChannelInput,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { MarkdownEditor } from '../../../components/MarkdownEditor';\n\n// ============================================================================\n// PLATFORM CONFIG\n// ============================================================================\n\n// NOTE: AdapterType values MUST match the backend Prisma schema exactly.\n// See: global-channel-svc/prisma/schema/schema.prisma (enum AdapterType)\n\ntype PlatformOption = {\n value: ChannelPlatform;\n labelKey: string;\n labelFallback: string;\n descriptionKey: string;\n descriptionFallback: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; labelKey: string; labelFallback: string }[];\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function CreateChannelPage() {\n const navigate = useNavigate();\n const { organizationId } = useChannel();\n const { createChannel } = useChannels();\n const tr = useTr();\n\n const PLATFORM_OPTIONS: PlatformOption[] = useMemo(\n () => [\n {\n value: 'EMAIL',\n labelKey: 'channel.platformLabel.email',\n labelFallback: 'Email',\n descriptionKey: 'channel.platformDescription.emailDescription',\n descriptionFallback: 'Send emails via various providers',\n icon: Mail,\n color: 'text-accent-blue bg-accent-blue-subtle border-accent-blue-subtle',\n adapters: [\n { value: 'AWS_SES', labelKey: 'channel.adapter.awsSes', labelFallback: 'AWS SES' },\n {\n value: 'AZURE_ACS',\n labelKey: 'channel.adapter.azureAcs',\n labelFallback: 'Azure Communication Services',\n },\n ],\n },\n {\n value: 'SMS',\n labelKey: 'channel.platformLabel.sms',\n labelFallback: 'SMS',\n descriptionKey: 'channel.platformDescription.smsDescription',\n descriptionFallback: 'Send text messages',\n icon: Smartphone,\n color:\n 'text-status-success-text bg-status-success-bg-subtle border-status-success-bg-subtle',\n adapters: [\n { value: 'AWS_SNS', labelKey: 'channel.adapter.awsSns', labelFallback: 'AWS SNS' },\n {\n value: 'TWILIO_SMS',\n labelKey: 'channel.adapter.twilioSms',\n labelFallback: 'Twilio SMS',\n },\n ],\n },\n {\n value: 'PUSH',\n labelKey: 'channel.platformLabel.push',\n labelFallback: 'Push',\n descriptionKey: 'channel.platformDescription.pushDescription',\n descriptionFallback: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple-subtle',\n adapters: [\n {\n value: 'FCM',\n labelKey: 'channel.adapter.fcm',\n labelFallback: 'Firebase Cloud Messaging',\n },\n ],\n },\n {\n value: 'INAPP',\n labelKey: 'channel.platformLabel.inApp',\n labelFallback: 'In-App',\n descriptionKey: 'channel.platformDescription.inAppDescription',\n descriptionFallback: 'In-app notifications',\n icon: Inbox,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo-subtle',\n adapters: [\n {\n value: 'INAPP_NATIVE',\n labelKey: 'channel.adapter.inAppNative',\n labelFallback: 'Native In-App',\n },\n ],\n },\n {\n value: 'SLACK',\n labelKey: 'channel.platformLabel.slack',\n labelFallback: 'Slack',\n descriptionKey: 'channel.platformDescription.slackDescription',\n descriptionFallback: 'Workspace notifications',\n icon: Hash,\n color: 'text-accent-pink bg-accent-pink-subtle border-accent-pink-subtle',\n adapters: [\n { value: 'SLACK_BOT', labelKey: 'channel.adapter.slackBot', labelFallback: 'Slack Bot' },\n ],\n },\n {\n value: 'TEAMS',\n labelKey: 'channel.platformLabel.teams',\n labelFallback: 'Teams',\n descriptionKey: 'channel.platformDescription.teamsDescription',\n descriptionFallback: 'Teams notifications',\n icon: Users,\n color: 'text-accent-cyan bg-accent-cyan-subtle border-accent-cyan-subtle',\n adapters: [\n { value: 'TEAMS_BOT', labelKey: 'channel.adapter.teamsBot', labelFallback: 'Teams Bot' },\n ],\n },\n {\n value: 'DISCORD',\n labelKey: 'channel.platformLabel.discord',\n labelFallback: 'Discord',\n descriptionKey: 'channel.platformDescription.discordDescription',\n descriptionFallback: 'Server notifications',\n icon: MessageCircle,\n color: 'text-accent-violet bg-accent-violet-subtle border-accent-violet-subtle',\n adapters: [\n {\n value: 'DISCORD_BOT',\n labelKey: 'channel.adapter.discordBot',\n labelFallback: 'Discord Bot',\n },\n ],\n },\n {\n value: 'WHATSAPP',\n labelKey: 'channel.platformLabel.whatsapp',\n labelFallback: 'WhatsApp',\n descriptionKey: 'channel.platformDescription.whatsappDescription',\n descriptionFallback: 'Business messaging',\n icon: Send,\n color: 'text-accent-emerald bg-accent-emerald-subtle border-accent-emerald-subtle',\n adapters: [\n {\n value: 'WHATSAPP_CLOUD',\n labelKey: 'channel.adapter.whatsappCloud',\n labelFallback: 'WhatsApp Cloud API',\n },\n {\n value: 'WHATSAPP_TWILIO',\n labelKey: 'channel.adapter.whatsappTwilio',\n labelFallback: 'WhatsApp via Twilio',\n },\n ],\n },\n {\n value: 'WEBCHAT',\n labelKey: 'channel.platformLabel.webchat',\n labelFallback: 'Webchat',\n descriptionKey: 'channel.platformDescription.webchatDescription',\n descriptionFallback: 'Chat widgets',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal-subtle',\n adapters: [], // No WEBCHAT adapters in backend Prisma schema yet\n },\n {\n value: 'WEBHOOK',\n labelKey: 'channel.platformLabel.webhook',\n labelFallback: 'Webhook',\n descriptionKey: 'channel.platformDescription.webhookDescription',\n descriptionFallback: 'HTTP webhooks',\n icon: Globe,\n color: 'text-accent-orange bg-accent-orange-subtle border-accent-orange-subtle',\n adapters: [\n {\n value: 'WEBHOOK_HTTP',\n labelKey: 'channel.adapter.webhookHttp',\n labelFallback: 'HTTP Webhook',\n },\n ],\n },\n ],\n []\n );\n\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Form state — matches CreateChannelInput exactly\n const [formData, setFormData] = useState<{\n name: string; // required\n platform: ChannelPlatform; // required\n description: string; // optional\n adapterType: AdapterType | ''; // required (auto-selected based on platform)\n status: ChannelStatus; // optional — default ACTIVE on server\n priority: number; // optional — default 100 on server\n platformConfig: string; // optional — JSON string\n }>({\n name: '',\n platform: 'EMAIL',\n description: '',\n adapterType: 'AWS_SES', // Default to AWS SES for Email platform\n status: 'ACTIVE',\n priority: 100,\n platformConfig: '',\n });\n\n const availableAdapters =\n PLATFORM_OPTIONS.find((p) => p.value === formData.platform)?.adapters || [];\n\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === formData.platform);\n\n const handlePlatformChange = (platform: ChannelPlatform) => {\n setFormData((prev) => ({ ...prev, platform, adapterType: '' }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!formData.name.trim()) {\n setError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n if (!organizationId) {\n setError(\n tr(\n 'channel.validation.organizationRequired',\n 'Organization context is required. Please ensure an organization is selected.'\n )\n );\n return;\n }\n\n // Validate platformConfig JSON if provided\n let parsedPlatformConfig: Record<string, unknown> | undefined;\n if (formData.platformConfig.trim()) {\n try {\n parsedPlatformConfig = JSON.parse(formData.platformConfig);\n } catch {\n setError(tr('channel.validation.platformConfigJson', 'Platform Config must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const input: CreateChannelInput = {\n name: formData.name.trim(),\n platform: formData.platform,\n organizationId,\n description: formData.description.trim() || undefined,\n adapterType: (formData.adapterType as AdapterType) || undefined,\n status: formData.status,\n priority: formData.priority,\n platformConfig: parsedPlatformConfig,\n };\n\n const result = await createChannel(input);\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('channel.createFailed', 'Failed to create channel'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error ? err.message : tr('channel.createFailed', 'Failed to create channel')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CREATE}>\n <div className=\"p-3 sm:p-6 space-y-6 max-w-5xl mx-auto\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-surface-hover rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl font-bold text-text-primary flex items-center gap-2\">\n <Radio size={22} className=\"text-accent-primary\" />\n {tr('channel.createChannel', 'Create Channel')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr('channel.createChannelSubtitle', 'Set up a new communication channel')}\n </p>\n </div>\n </div>\n </div>\n\n {/* ── Page purpose ─────────────────────────────────────────────── */}\n <PagePurpose icon={<Radio size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.createChannelPurpose',\n 'Set up a new way to reach people — for example a transactional email sender, an SMS gateway, a Slack workspace or a push provider. Name it, pick the platform and provider, and you will get a channel you can then authenticate and send through. Create one channel per provider account you want messages to flow through.'\n )}\n </PagePurpose>\n\n {/* ── Error ────────────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-3 bg-error-500/10 border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-error-400\">{error}</p>\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* ── Row 1: Name + Description | Status + Provider (2-col) ─── */}\n <div className=\"grid gap-5 lg:grid-cols-2\">\n {/* Left: Name & Description — the primary identity of the channel */}\n <EmphasisPanel className=\"p-5 space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.basicInformation', 'Basic Information')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.name', 'Name')} <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n aria-label={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 placeholder:text-text-secondary\"\n placeholder={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"channel-create-description\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.description', 'Description')}\n </label>\n <MarkdownEditor\n id=\"channel-create-description\"\n value={formData.description}\n onChange={(next) => setFormData((prev) => ({ ...prev, description: next }))}\n aria-label={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n placeholder={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n rows={3}\n />\n </div>\n </EmphasisPanel>\n\n {/* Right: Status + Provider / Adapter */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.providerStatus', 'Provider & Status')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.providerAdapter', 'Provider / Adapter')}\n </label>\n <select\n value={formData.adapterType}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n adapterType: e.target.value as AdapterType | '',\n }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"\">\n {tr('channel.selectProvider', 'Select a provider (optional)')}\n </option>\n {availableAdapters.map((a) => (\n <option key={a.value} value={a.value}>\n {tr(a.labelKey, a.labelFallback)}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {selectedPlatform\n ? tr(\n 'channel.providersAvailableForPlatform',\n '{{count}} providers available for {{platform}}',\n {\n count: availableAdapters.length,\n platform: tr(selectedPlatform.labelKey, selectedPlatform.labelFallback),\n }\n )\n : tr('channel.selectPlatformFirst', 'Select a platform first')}\n </p>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.status', 'Status')}\n </label>\n <select\n value={formData.status}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, status: e.target.value as ChannelStatus }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"ACTIVE\">{tr('channel.statusLabel.active', 'Active')}</option>\n <option value=\"INACTIVE\">\n {tr('channel.statusLabel.inactive', 'Inactive')}\n </option>\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {formData.status === 'ACTIVE'\n ? tr('channel.statusHint.active', 'Ready to send immediately')\n : tr('channel.statusHint.inactive', 'Configure before activating')}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"create-channel-priority\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.priority', 'Priority')}\n </label>\n <input\n id=\"create-channel-priority\"\n aria-label={tr('channel.priority', 'Priority')}\n type=\"number\"\n value={formData.priority}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n priority: parseInt(e.target.value) || 0,\n }))\n }\n min={0}\n max={100}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50\"\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.priorityHint', 'Lower = higher priority (0-100)')}\n </p>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Row 2: Platform selection (full width) ─────────────────── */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')} <span className=\"text-status-error-text\">*</span>\n </h2>\n\n <div className=\"grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5\">\n {PLATFORM_OPTIONS.map((platform) => {\n const Icon = platform.icon;\n const isSelected = formData.platform === platform.value;\n return (\n <button\n key={platform.value}\n type=\"button\"\n onClick={() => handlePlatformChange(platform.value)}\n className={`relative flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all text-center ${\n isSelected\n ? `${platform.color} border-current ring-1 ring-current`\n : 'border-border-subtle hover:bg-surface-hover text-text-secondary hover:text-text-primary'\n }`}\n >\n {isSelected && (\n <CheckCircle2 size={14} className=\"absolute top-1.5 right-1.5 text-current\" />\n )}\n <Icon size={20} />\n <span className=\"text-xs font-semibold\">\n {tr(platform.labelKey, platform.labelFallback)}\n </span>\n <span className=\"text-[10px] leading-tight opacity-70\">\n {tr(platform.descriptionKey, platform.descriptionFallback)}\n </span>\n </button>\n );\n })}\n </div>\n </GlassCard>\n\n {/* ── Row 3: Advanced (collapsible) — webhook config / delivery / retry policy (platformConfig) */}\n <OptionalFields title={tr('channel.advancedOptions', 'Advanced settings')}>\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.platformConfig', 'Platform Config')}{' '}\n <span className=\"text-[10px] text-text-secondary\">\n {tr('channel.jsonMarker', '(JSON)')}\n </span>\n </label>\n <textarea\n aria-label={tr('channel.platformConfig', 'Platform Config')}\n value={formData.platformConfig}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, platformConfig: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none font-mono placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.platformConfigPlaceholder',\n '{\"fromEmail\": \"noreply@example.com\"}'\n )}\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.platformConfigHint', 'Platform-specific configuration as JSON')}\n </p>\n </div>\n </div>\n </OptionalFields>\n\n {/* ── Actions ────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between pt-2\">\n <button\n type=\"button\"\n onClick={() => navigate('/channels/list')}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-surface-hover border border-border-subtle rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !formData.name.trim()}\n className=\"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? <Loader2 size={16} className=\"animate-spin\" /> : <Save size={16} />}\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,5 +7,4 @@
|
|
|
7
7
|
* Form fields match CreateConnectionInput exactly (less channelId which is
|
|
8
8
|
* resolved from the selector).
|
|
9
9
|
*/
|
|
10
|
-
export declare function CreateConnectionPage(): import(
|
|
11
|
-
//# sourceMappingURL=CreateConnectionPage.d.ts.map
|
|
10
|
+
export declare function CreateConnectionPage(): import('react').JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { AlertCircle as c, ArrowLeft as l, Loader2 as u, Save as d, Shield as f
|
|
|
8
8
|
import { useNavigate as p, useSearchParams as m } from "react-router";
|
|
9
9
|
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
10
10
|
import { EmphasisPanel as v, PagePurpose as y } from "@burdenoff/fe-libs/ui";
|
|
11
|
-
|
|
11
|
+
// src/channel/modules/channels/pages/CreateConnectionPage.tsx
|
|
12
12
|
function b() {
|
|
13
13
|
let b = p(), [x] = m(), { organizationId: S } = t(), { channels: C, isLoading: w } = r(), T = e(), E = o(() => [
|
|
14
14
|
{
|
|
@@ -46,7 +46,7 @@ function b() {
|
|
|
46
46
|
helpKey: "channel.authTypeLabel.smtpImapHelp",
|
|
47
47
|
helpFallback: "Mail server credentials"
|
|
48
48
|
}
|
|
49
|
-
], []),
|
|
49
|
+
], []), D = x.get("channelId") ?? "", [O, k] = s(D), { createConnection: A } = n(O), [j, M] = s("OAUTH2"), [N, P] = s(""), [F, I] = s(""), [L, R] = s(""), [z, B] = s(""), [V, H] = s(!1), [U, W] = s(null), G = o(() => C.find((e) => e.id === O) ?? null, [C, O]);
|
|
50
50
|
return /* @__PURE__ */ g(i, {
|
|
51
51
|
permission: a.CHANNEL_CONNECTION_CREATE,
|
|
52
52
|
children: /* @__PURE__ */ _("div", {
|
|
@@ -76,46 +76,46 @@ function b() {
|
|
|
76
76
|
className: "mb-6",
|
|
77
77
|
children: T("connection.create.purpose", "Authenticate a channel against its provider so it can actually send. Choose the channel, the auth method (OAuth, API key, bot token, webhook secret or mail credentials) and enter the secrets — they are stored encrypted. Do this once per channel; without a connection a channel can't deliver anything.")
|
|
78
78
|
}),
|
|
79
|
-
|
|
79
|
+
U && /* @__PURE__ */ _("div", {
|
|
80
80
|
className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3",
|
|
81
81
|
children: [/* @__PURE__ */ g(c, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ g("p", {
|
|
82
82
|
className: "text-sm text-status-error-text",
|
|
83
|
-
children:
|
|
83
|
+
children: U
|
|
84
84
|
})]
|
|
85
85
|
}),
|
|
86
86
|
/* @__PURE__ */ g(v, {
|
|
87
87
|
className: "p-6",
|
|
88
88
|
children: /* @__PURE__ */ _("form", {
|
|
89
89
|
onSubmit: async (e) => {
|
|
90
|
-
if (e.preventDefault(),
|
|
91
|
-
|
|
90
|
+
if (e.preventDefault(), W(null), !O) {
|
|
91
|
+
W(T("connection.validation.channelRequired", "Pick a channel to connect"));
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
if (!S) {
|
|
95
|
-
|
|
95
|
+
W(T("connection.validation.organizationRequired", "Organization context missing"));
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
let t;
|
|
99
|
-
if (
|
|
100
|
-
t = JSON.parse(
|
|
99
|
+
if (z.trim()) try {
|
|
100
|
+
t = JSON.parse(z);
|
|
101
101
|
} catch {
|
|
102
|
-
|
|
102
|
+
W(T("connection.validation.credentialsJson", "Credentials must be valid JSON"));
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
H(!0);
|
|
106
106
|
try {
|
|
107
|
-
let e = await
|
|
108
|
-
authType:
|
|
109
|
-
authCode:
|
|
110
|
-
apiKey:
|
|
111
|
-
redirectUri:
|
|
107
|
+
let e = await A({
|
|
108
|
+
authType: j,
|
|
109
|
+
authCode: N.trim() || void 0,
|
|
110
|
+
apiKey: F.trim() || void 0,
|
|
111
|
+
redirectUri: L.trim() || void 0,
|
|
112
112
|
credentials: t
|
|
113
113
|
});
|
|
114
|
-
e ? b(`../${e.id}`) :
|
|
114
|
+
e ? b(`../${e.id}`) : W(T("connection.createFailed", "Failed to create connection"));
|
|
115
115
|
} catch (e) {
|
|
116
|
-
|
|
116
|
+
W(e instanceof Error ? e.message : T("connection.createFailed", "Failed to create connection"));
|
|
117
117
|
} finally {
|
|
118
|
-
|
|
118
|
+
H(!1);
|
|
119
119
|
}
|
|
120
120
|
},
|
|
121
121
|
className: "space-y-6",
|
|
@@ -126,9 +126,9 @@ function b() {
|
|
|
126
126
|
children: [T("connection.field.channel", "Channel"), " *"]
|
|
127
127
|
}),
|
|
128
128
|
/* @__PURE__ */ _("select", {
|
|
129
|
-
value:
|
|
130
|
-
onChange: (e) =>
|
|
131
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
129
|
+
value: O,
|
|
130
|
+
onChange: (e) => k(e.target.value),
|
|
131
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
132
132
|
required: !0,
|
|
133
133
|
disabled: w,
|
|
134
134
|
children: [/* @__PURE__ */ g("option", {
|
|
@@ -144,18 +144,18 @@ function b() {
|
|
|
144
144
|
]
|
|
145
145
|
}, e.id))]
|
|
146
146
|
}),
|
|
147
|
-
|
|
147
|
+
G && /* @__PURE__ */ g("p", {
|
|
148
148
|
className: "mt-1 text-xs text-fg-muted",
|
|
149
|
-
children: T("connection.field.channelHint", "Adapter: {{adapterType}}", { adapterType:
|
|
149
|
+
children: T("connection.field.channelHint", "Adapter: {{adapterType}}", { adapterType: G.adapterType ?? "—" })
|
|
150
150
|
})
|
|
151
151
|
] }),
|
|
152
152
|
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
|
|
153
153
|
className: "block text-sm font-medium text-fg mb-2",
|
|
154
154
|
children: [T("connection.field.authType", "Authentication Type"), " *"]
|
|
155
155
|
}), /* @__PURE__ */ g("select", {
|
|
156
|
-
value:
|
|
157
|
-
onChange: (e) =>
|
|
158
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
156
|
+
value: j,
|
|
157
|
+
onChange: (e) => M(e.target.value),
|
|
158
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
159
159
|
required: !0,
|
|
160
160
|
children: E.map((e) => /* @__PURE__ */ _("option", {
|
|
161
161
|
value: e.value,
|
|
@@ -166,7 +166,7 @@ function b() {
|
|
|
166
166
|
]
|
|
167
167
|
}, e.value))
|
|
168
168
|
})] }),
|
|
169
|
-
|
|
169
|
+
j === "OAUTH2" && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
170
170
|
htmlFor: "create-conn-auth-code",
|
|
171
171
|
className: "block text-sm font-medium text-fg mb-2",
|
|
172
172
|
children: T("connection.field.authCode", "OAuth Authorization Code")
|
|
@@ -174,8 +174,8 @@ function b() {
|
|
|
174
174
|
id: "create-conn-auth-code",
|
|
175
175
|
"aria-label": T("connection.field.authCode", "OAuth Authorization Code"),
|
|
176
176
|
type: "text",
|
|
177
|
-
value:
|
|
178
|
-
onChange: (e) =>
|
|
177
|
+
value: N,
|
|
178
|
+
onChange: (e) => P(e.target.value),
|
|
179
179
|
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
180
180
|
autoComplete: "off"
|
|
181
181
|
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
@@ -186,11 +186,11 @@ function b() {
|
|
|
186
186
|
id: "create-conn-redirect-uri",
|
|
187
187
|
"aria-label": T("connection.field.redirectUri", "Redirect URI"),
|
|
188
188
|
type: "url",
|
|
189
|
-
value:
|
|
190
|
-
onChange: (e) =>
|
|
189
|
+
value: L,
|
|
190
|
+
onChange: (e) => R(e.target.value),
|
|
191
191
|
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg"
|
|
192
192
|
})] })] }),
|
|
193
|
-
(
|
|
193
|
+
(j === "API_KEY" || j === "BOT_TOKEN") && /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
194
194
|
htmlFor: "create-conn-api-key",
|
|
195
195
|
className: "block text-sm font-medium text-fg mb-2",
|
|
196
196
|
children: T("connection.field.apiKey", "API Key / Token")
|
|
@@ -198,8 +198,8 @@ function b() {
|
|
|
198
198
|
id: "create-conn-api-key",
|
|
199
199
|
"aria-label": T("connection.field.apiKey", "API Key / Token"),
|
|
200
200
|
type: "password",
|
|
201
|
-
value:
|
|
202
|
-
onChange: (e) =>
|
|
201
|
+
value: F,
|
|
202
|
+
onChange: (e) => I(e.target.value),
|
|
203
203
|
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
204
204
|
autoComplete: "new-password"
|
|
205
205
|
})] }),
|
|
@@ -210,8 +210,8 @@ function b() {
|
|
|
210
210
|
}),
|
|
211
211
|
/* @__PURE__ */ g("textarea", {
|
|
212
212
|
"aria-label": T("connection.field.credentialsExample", "{\"smtpHost\": \"smtp.example.com\"}"),
|
|
213
|
-
value:
|
|
214
|
-
onChange: (e) =>
|
|
213
|
+
value: z,
|
|
214
|
+
onChange: (e) => B(e.target.value),
|
|
215
215
|
rows: 6,
|
|
216
216
|
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg",
|
|
217
217
|
placeholder: T("connection.field.credentialsExample", "{\"smtpHost\": \"smtp.example.com\"}")
|
|
@@ -225,9 +225,9 @@ function b() {
|
|
|
225
225
|
className: "flex gap-3 pt-2",
|
|
226
226
|
children: [/* @__PURE__ */ _("button", {
|
|
227
227
|
type: "submit",
|
|
228
|
-
disabled:
|
|
228
|
+
disabled: V || !O,
|
|
229
229
|
className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
|
|
230
|
-
children: [
|
|
230
|
+
children: [V ? /* @__PURE__ */ g(u, { className: "size-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "size-4 " }), T("connection.create.submit", "Create Connection")]
|
|
231
231
|
}), /* @__PURE__ */ g("button", {
|
|
232
232
|
type: "button",
|
|
233
233
|
onClick: () => b(-1),
|
|
@@ -242,7 +242,6 @@ function b() {
|
|
|
242
242
|
})
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
|
-
//#endregion
|
|
246
245
|
export { b as CreateConnectionPage };
|
|
247
246
|
|
|
248
247
|
//# sourceMappingURL=CreateConnectionPage.js.map
|