@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":"CreateConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateConnectionPage.tsx"],"sourcesContent":["/**\n * Create Connection Page\n *\n * Standalone page mounted at /channels/connections/create.\n * User picks a target channel, then provides auth details.\n *\n * Form fields match CreateConnectionInput exactly (less channelId which is\n * resolved from the selector).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = useChannels();\n const tr = useTr();\n\n const AUTH_TYPES: {\n value: ChannelAuthType;\n labelKey: string;\n labelFallback: string;\n helpKey: string;\n helpFallback: string;\n }[] = useMemo(\n () => [\n {\n value: 'OAUTH2',\n labelKey: 'channel.authTypeLabel.oauth2',\n labelFallback: 'OAuth 2.0',\n helpKey: 'channel.authTypeLabel.oauth2Help',\n helpFallback: 'Authorization code flow',\n },\n {\n value: 'API_KEY',\n labelKey: 'channel.authTypeLabel.apiKey',\n labelFallback: 'API Key',\n helpKey: 'channel.authTypeLabel.apiKeyHelp',\n helpFallback: 'Static API key',\n },\n {\n value: 'BOT_TOKEN',\n labelKey: 'channel.authTypeLabel.botToken',\n labelFallback: 'Bot Token',\n helpKey: 'channel.authTypeLabel.botTokenHelp',\n helpFallback: 'Slack/Discord/Teams bot token',\n },\n {\n value: 'WEBHOOK',\n labelKey: 'channel.authTypeLabel.webhook',\n labelFallback: 'Webhook',\n helpKey: 'channel.authTypeLabel.webhookHelp',\n helpFallback: 'Shared secret for HMAC verification',\n },\n {\n value: 'SMTP_IMAP',\n labelKey: 'channel.authTypeLabel.smtpImap',\n labelFallback: 'SMTP / IMAP',\n helpKey: 'channel.authTypeLabel.smtpImapHelp',\n helpFallback: 'Mail server credentials',\n },\n ],\n []\n );\n\n const initialChannelId = searchParams.get('channelId') ?? '';\n const [selectedChannelId, setSelectedChannelId] = useState<string>(initialChannelId);\n const { createConnection } = useChannel(selectedChannelId);\n\n const [authType, setAuthType] = useState<ChannelAuthType>('OAUTH2');\n const [authCode, setAuthCode] = useState('');\n const [apiKey, setApiKey] = useState('');\n const [redirectUri, setRedirectUri] = useState('');\n const [credentials, setCredentials] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const selectedChannel = useMemo(\n () => channels.find((c) => c.id === selectedChannelId) ?? null,\n [channels, selectedChannelId]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!selectedChannelId) {\n setError(tr('connection.validation.channelRequired', 'Pick a channel to connect'));\n return;\n }\n if (!organizationId) {\n setError(tr('connection.validation.organizationRequired', 'Organization context missing'));\n return;\n }\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const result = await createConnection({\n authType,\n authCode: authCode.trim() || undefined,\n apiKey: apiKey.trim() || undefined,\n redirectUri: redirectUri.trim() || undefined,\n credentials: parsedCredentials,\n });\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('connection.createFailed', 'Failed to create connection'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.createFailed', 'Failed to create connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.create.title', 'Create Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr(\n 'connection.create.subtitle',\n 'Authenticate a channel against its platform provider.'\n )}\n </p>\n </div>\n </div>\n\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />} className=\"mb-6\">\n {tr(\n 'connection.create.purpose',\n \"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.\"\n )}\n </PagePurpose>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <EmphasisPanel className=\"p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: {{adapterType}}', {\n adapterType: selectedChannel.adapterType ?? '—',\n })}\n </p>\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.authType', 'Authentication Type')} *\n </label>\n <select\n value={authType}\n onChange={(e) => setAuthType(e.target.value as ChannelAuthType)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n >\n {AUTH_TYPES.map((a) => (\n <option key={a.value} value={a.value}>\n {tr(a.labelKey, a.labelFallback)}: {tr(a.helpKey, a.helpFallback)}\n </option>\n ))}\n </select>\n </div>\n\n {authType === 'OAUTH2' && (\n <>\n <div>\n <label\n htmlFor=\"create-conn-auth-code\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.authCode', 'OAuth Authorization Code')}\n </label>\n <input\n id=\"create-conn-auth-code\"\n aria-label={tr('connection.field.authCode', 'OAuth Authorization Code')}\n type=\"text\"\n value={authCode}\n onChange={(e) => setAuthCode(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"off\"\n />\n </div>\n <div>\n <label\n htmlFor=\"create-conn-redirect-uri\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.redirectUri', 'Redirect URI')}\n </label>\n <input\n id=\"create-conn-redirect-uri\"\n aria-label={tr('connection.field.redirectUri', 'Redirect URI')}\n type=\"url\"\n value={redirectUri}\n onChange={(e) => setRedirectUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n />\n </div>\n </>\n )}\n\n {(authType === 'API_KEY' || authType === 'BOT_TOKEN') && (\n <div>\n <label\n htmlFor=\"create-conn-api-key\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.apiKey', 'API Key / Token')}\n </label>\n <input\n id=\"create-conn-api-key\"\n aria-label={tr('connection.field.apiKey', 'API Key / Token')}\n type=\"password\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"new-password\"\n />\n </div>\n )}\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Additional Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsExample',\n '{\"smtpHost\": \"smtp.example.com\"}'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsExample',\n '{\"smtpHost\": \"smtp.example.com\"}'\n )}\n />\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr(\n 'connection.field.credentialsHint',\n 'Encrypted at rest with AES-256-GCM. Never logged in plaintext.'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !selectedChannelId}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('connection.create.submit', 'Create Connection')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border-subtle rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateConnectionPage.js","names":["useChannelContext","useChannel","_Fragment","useChannel$1","CreateConnectionPage","navigate","useNavigate","searchParams","useSearchParams","useChannel","channelsLoading","useChannels","tr","useTr","AUTH_TYPES","useMemo","initialChannelId","selectedChannelId","setSelectedChannelId","useState","authType","setAuthType","authCode","setAuthCode","apiKey","setApiKey","redirectUri","setRedirectUri","credentials","setCredentials","isSubmitting","setIsSubmitting","error","setError","selectedChannel","channels","c","PermissionGuard","CHANNEL_PERMISSIONS","ArrowLeft","Shield","PagePurpose","AlertCircle","EmphasisPanel","organizationId","parsedCredentials","result","createConnection","err","a","Fragment","Loader2","Save"],"sources":["../../../../../src/channel/modules/channels/pages/CreateConnectionPage.tsx"],"sourcesContent":["/**\n * Create Connection Page\n *\n * Standalone page mounted at /channels/connections/create.\n * User picks a target channel, then provides auth details.\n *\n * Form fields match CreateConnectionInput exactly (less channelId which is\n * resolved from the selector).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useTr } from '../../../hooks/useTr';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = useChannels();\n const tr = useTr();\n\n const AUTH_TYPES: {\n value: ChannelAuthType;\n labelKey: string;\n labelFallback: string;\n helpKey: string;\n helpFallback: string;\n }[] = useMemo(\n () => [\n {\n value: 'OAUTH2',\n labelKey: 'channel.authTypeLabel.oauth2',\n labelFallback: 'OAuth 2.0',\n helpKey: 'channel.authTypeLabel.oauth2Help',\n helpFallback: 'Authorization code flow',\n },\n {\n value: 'API_KEY',\n labelKey: 'channel.authTypeLabel.apiKey',\n labelFallback: 'API Key',\n helpKey: 'channel.authTypeLabel.apiKeyHelp',\n helpFallback: 'Static API key',\n },\n {\n value: 'BOT_TOKEN',\n labelKey: 'channel.authTypeLabel.botToken',\n labelFallback: 'Bot Token',\n helpKey: 'channel.authTypeLabel.botTokenHelp',\n helpFallback: 'Slack/Discord/Teams bot token',\n },\n {\n value: 'WEBHOOK',\n labelKey: 'channel.authTypeLabel.webhook',\n labelFallback: 'Webhook',\n helpKey: 'channel.authTypeLabel.webhookHelp',\n helpFallback: 'Shared secret for HMAC verification',\n },\n {\n value: 'SMTP_IMAP',\n labelKey: 'channel.authTypeLabel.smtpImap',\n labelFallback: 'SMTP / IMAP',\n helpKey: 'channel.authTypeLabel.smtpImapHelp',\n helpFallback: 'Mail server credentials',\n },\n ],\n []\n );\n\n const initialChannelId = searchParams.get('channelId') ?? '';\n const [selectedChannelId, setSelectedChannelId] = useState<string>(initialChannelId);\n const { createConnection } = useChannel(selectedChannelId);\n\n const [authType, setAuthType] = useState<ChannelAuthType>('OAUTH2');\n const [authCode, setAuthCode] = useState('');\n const [apiKey, setApiKey] = useState('');\n const [redirectUri, setRedirectUri] = useState('');\n const [credentials, setCredentials] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const selectedChannel = useMemo(\n () => channels.find((c) => c.id === selectedChannelId) ?? null,\n [channels, selectedChannelId]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!selectedChannelId) {\n setError(tr('connection.validation.channelRequired', 'Pick a channel to connect'));\n return;\n }\n if (!organizationId) {\n setError(tr('connection.validation.organizationRequired', 'Organization context missing'));\n return;\n }\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const result = await createConnection({\n authType,\n authCode: authCode.trim() || undefined,\n apiKey: apiKey.trim() || undefined,\n redirectUri: redirectUri.trim() || undefined,\n credentials: parsedCredentials,\n });\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('connection.createFailed', 'Failed to create connection'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.createFailed', 'Failed to create connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.create.title', 'Create Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr(\n 'connection.create.subtitle',\n 'Authenticate a channel against its platform provider.'\n )}\n </p>\n </div>\n </div>\n\n <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />} className=\"mb-6\">\n {tr(\n 'connection.create.purpose',\n \"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.\"\n )}\n </PagePurpose>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <EmphasisPanel className=\"p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n 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\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: {{adapterType}}', {\n adapterType: selectedChannel.adapterType ?? '—',\n })}\n </p>\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.authType', 'Authentication Type')} *\n </label>\n <select\n value={authType}\n onChange={(e) => setAuthType(e.target.value as ChannelAuthType)}\n 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\"\n required\n >\n {AUTH_TYPES.map((a) => (\n <option key={a.value} value={a.value}>\n {tr(a.labelKey, a.labelFallback)}: {tr(a.helpKey, a.helpFallback)}\n </option>\n ))}\n </select>\n </div>\n\n {authType === 'OAUTH2' && (\n <>\n <div>\n <label\n htmlFor=\"create-conn-auth-code\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.authCode', 'OAuth Authorization Code')}\n </label>\n <input\n id=\"create-conn-auth-code\"\n aria-label={tr('connection.field.authCode', 'OAuth Authorization Code')}\n type=\"text\"\n value={authCode}\n onChange={(e) => setAuthCode(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"off\"\n />\n </div>\n <div>\n <label\n htmlFor=\"create-conn-redirect-uri\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.redirectUri', 'Redirect URI')}\n </label>\n <input\n id=\"create-conn-redirect-uri\"\n aria-label={tr('connection.field.redirectUri', 'Redirect URI')}\n type=\"url\"\n value={redirectUri}\n onChange={(e) => setRedirectUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n />\n </div>\n </>\n )}\n\n {(authType === 'API_KEY' || authType === 'BOT_TOKEN') && (\n <div>\n <label\n htmlFor=\"create-conn-api-key\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.apiKey', 'API Key / Token')}\n </label>\n <input\n id=\"create-conn-api-key\"\n aria-label={tr('connection.field.apiKey', 'API Key / Token')}\n type=\"password\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"new-password\"\n />\n </div>\n )}\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Additional Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsExample',\n '{\"smtpHost\": \"smtp.example.com\"}'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsExample',\n '{\"smtpHost\": \"smtp.example.com\"}'\n )}\n />\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr(\n 'connection.field.credentialsHint',\n 'Encrypted at rest with AES-256-GCM. Never logged in plaintext.'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !selectedChannelId}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('connection.create.submit', 'Create Connection')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border-subtle rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </EmphasisPanel>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,5 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Displays message delivery attempts for a specific channel with retry capability
|
|
5
5
|
*/
|
|
6
|
-
export declare function DeliveryHistoryPage(): import(
|
|
7
|
-
//# sourceMappingURL=DeliveryHistoryPage.d.ts.map
|
|
6
|
+
export declare function DeliveryHistoryPage(): import('react').JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useChannel as e } from "../../../
|
|
2
|
-
import { useChannel as t } from "../../../
|
|
1
|
+
import { useChannel as e } from "../../../hooks/useChannels.js";
|
|
2
|
+
import { useChannel as t } from "../../../providers/ChannelProvider.js";
|
|
3
3
|
import { PermissionGuard as n } from "../../../components/PermissionGuard.js";
|
|
4
4
|
import { CHANNEL_PERMISSIONS as r } from "../../../constants/permissions.js";
|
|
5
5
|
import { useExportDeliveryAttempts as ee } from "../hooks/useExport.js";
|
|
@@ -10,7 +10,7 @@ import { useNavigate as y, useParams as b } from "react-router";
|
|
|
10
10
|
import { usePermissions as re } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
11
11
|
import { Fragment as ie, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
12
12
|
import { CTAOverflowMenu as C, EmphasisPanel as w, IllustratedEmptyState as ae, PagePurpose as oe, ResponsiveTable as se } from "@burdenoff/fe-libs/ui";
|
|
13
|
-
|
|
13
|
+
// src/channel/modules/channels/pages/DeliveryHistoryPage.tsx
|
|
14
14
|
var T = {
|
|
15
15
|
PENDING: {
|
|
16
16
|
label: "Pending",
|
|
@@ -77,7 +77,7 @@ function E() {
|
|
|
77
77
|
let { channelId: u } = b(), f = y(), { hasPermission: v } = re(), E = v(r.CHANNEL_DELIVERY_EXPORT), { t: ce } = c(), D = (e, t) => {
|
|
78
78
|
let n = ce(e);
|
|
79
79
|
return n === e ? t : n;
|
|
80
|
-
}, { organizationId: O } =
|
|
80
|
+
}, { organizationId: O } = t(), { triggerExport: le, isExporting: k, exportError: A } = ee(O ?? void 0, u), [j, M] = s(!0), [N, P] = s([]), [F, I] = s(null), [L, R] = s(null), [z, B] = s(25), [V, H] = s(0), [U, W] = s([void 0]), [G, K] = s(0), { channel: q, isLoading: ue, error: J, fetchDeliveryAttempts: Y, retryDelivery: de } = e(u), X = i(async (e) => {
|
|
81
81
|
if (u) {
|
|
82
82
|
M(!0), R(null);
|
|
83
83
|
try {
|
|
@@ -101,11 +101,7 @@ function E() {
|
|
|
101
101
|
W([void 0]), K(0), Z.current(void 0);
|
|
102
102
|
});
|
|
103
103
|
}, [z]), a(() => {
|
|
104
|
-
|
|
105
|
-
$.current = !1;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
Promise.resolve().then(() => Z.current(Q.current[G]));
|
|
104
|
+
$.current ? $.current = !1 : Promise.resolve().then(() => Z.current(Q.current[G]));
|
|
109
105
|
}, [G]), a(() => {
|
|
110
106
|
if (!u || G !== 0) return;
|
|
111
107
|
let e = setInterval(() => {
|
|
@@ -193,7 +189,7 @@ function E() {
|
|
|
193
189
|
key: "actions",
|
|
194
190
|
header: D("channel.actions", "Actions"),
|
|
195
191
|
priority: "secondary",
|
|
196
|
-
cell: (e) => e.status
|
|
192
|
+
cell: (e) => e.status !== "FAILED" && e.status !== "BOUNCED" && e.status !== "REJECTED" ? null : /* @__PURE__ */ x(n, {
|
|
197
193
|
permission: r.CHANNEL_DELIVERY_EXECUTE,
|
|
198
194
|
children: /* @__PURE__ */ S("button", {
|
|
199
195
|
type: "button",
|
|
@@ -206,7 +202,7 @@ function E() {
|
|
|
206
202
|
className: F === e.id ? "animate-spin" : ""
|
|
207
203
|
}), F === e.id ? D("channel.retrying", "Retrying...") : D("channel.retry", "Retry")]
|
|
208
204
|
})
|
|
209
|
-
})
|
|
205
|
+
})
|
|
210
206
|
}
|
|
211
207
|
];
|
|
212
208
|
return ue ? /* @__PURE__ */ x("output", {
|
|
@@ -268,7 +264,7 @@ function E() {
|
|
|
268
264
|
children: [/* @__PURE__ */ S("select", {
|
|
269
265
|
value: z,
|
|
270
266
|
onChange: (e) => B(Number(e.target.value)),
|
|
271
|
-
className: "px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary",
|
|
267
|
+
className: "px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
|
|
272
268
|
children: [
|
|
273
269
|
/* @__PURE__ */ x("option", {
|
|
274
270
|
value: 10,
|
|
@@ -428,7 +424,6 @@ function E() {
|
|
|
428
424
|
})
|
|
429
425
|
});
|
|
430
426
|
}
|
|
431
|
-
//#endregion
|
|
432
427
|
export { E as DeliveryHistoryPage };
|
|
433
428
|
|
|
434
429
|
//# sourceMappingURL=DeliveryHistoryPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeliveryHistoryPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/DeliveryHistoryPage.tsx"],"sourcesContent":["/**\n * Delivery History Page\n *\n * Displays message delivery attempts for a specific channel with retry capability\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n ResponsiveTable,\n IllustratedEmptyState,\n PagePurpose,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type DeliveryStatus, type DeliveryAttempt } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportDeliveryAttempts } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n DeliveryStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n QUEUED: { label: 'Queued', color: 'text-accent-primary bg-accent-primary-subtle', icon: Clock },\n SENDING: { label: 'Sending', color: 'text-accent-primary bg-accent-primary/10', icon: Send },\n SENT: {\n label: 'Sent',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n DELIVERED: {\n label: 'Delivered',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n FAILED: {\n label: 'Failed',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n BOUNCED: {\n label: 'Bounced',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertCircle,\n },\n REJECTED: {\n label: 'Rejected',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n RATE_LIMITED: {\n label: 'Rate Limited',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n RETRYING: {\n label: 'Retrying',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n OPENED: {\n label: 'Opened',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n CLICKED: {\n label: 'Clicked',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n};\n\nexport function DeliveryHistoryPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportDeliveries,\n isExporting: isExportingDeliveries,\n exportError: exportDeliveriesError,\n } = useExportDeliveryAttempts(organizationId ?? undefined, channelId);\n const [isLoadingAttempts, setIsLoadingAttempts] = useState(true);\n const [attempts, setAttempts] = useState<DeliveryAttempt[]>([]);\n const [retryingId, setRetryingId] = useState<string | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n // Cursor stack: index 0 is always undefined (first page), subsequent entries are cursors\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchDeliveryAttempts, retryDelivery } = useChannel(channelId);\n\n const loadAttempts = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingAttempts(true);\n setRetryError(null);\n try {\n const result = await fetchDeliveryAttempts(limit, cursor);\n setAttempts(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingAttempts(false);\n }\n },\n [channelId, fetchDeliveryAttempts, limit]\n );\n\n const loadAttemptsRef = useRef(loadAttempts);\n const cursorStackRef = useRef(cursorStack);\n useEffect(() => {\n loadAttemptsRef.current = loadAttempts;\n cursorStackRef.current = cursorStack;\n });\n const isFirstPageRender = useRef(true);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadAttemptsRef.current(undefined);\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadAttemptsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n // Poll for delivery attempt updates every 60 s on first page only\n useEffect(() => {\n if (!channelId || currentPageIdx !== 0) return;\n const timer = setInterval(() => {\n void loadAttempts(undefined);\n }, 60_000);\n return () => clearInterval(timer);\n }, [channelId, currentPageIdx, loadAttempts]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadAttempts(cursorStack[currentPageIdx]);\n if (nextCursor) {\n setCursorStack((prev) => {\n const next = [...prev];\n if (!next[currentPageIdx + 1]) {\n next[currentPageIdx + 1] = nextCursor;\n }\n return next;\n });\n }\n setCurrentPageIdx((p) => p + 1);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadAttempts(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const hasNextPage = attempts.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n const handleRetry = async (attemptId: string) => {\n setRetryingId(attemptId);\n setRetryError(null);\n try {\n const result = await retryDelivery(attemptId);\n if (!result.success) {\n setRetryError(result.error || 'Retry failed');\n } else {\n await loadAttempts(cursorStack[currentPageIdx]);\n }\n } finally {\n setRetryingId(null);\n }\n };\n\n const getStatusConfig = (status: DeliveryStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n const deliveryColumns: ResponsiveTableColumn<DeliveryAttempt>[] = [\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n return (\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span className={`text-sm font-medium ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n );\n },\n },\n {\n key: 'content',\n header: tr('channel.content', 'Content'),\n priority: 'primary',\n cellClassName: 'text-sm text-text-primary truncate max-w-xs',\n cell: (delivery) => delivery.content,\n },\n {\n key: 'sentAt',\n header: tr('channel.sentAt', 'Sent At'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary',\n cell: (delivery) => (delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-'),\n },\n {\n key: 'retries',\n header: tr('channel.retries', 'Retries'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary tabular-nums',\n cell: (delivery) => delivery.retryCount,\n },\n {\n key: 'error',\n header: tr('channel.error', 'Error'),\n priority: 'secondary',\n cellClassName: 'text-sm text-status-error-text max-w-xs truncate',\n cell: (delivery) => delivery.errorMessage || '-',\n },\n {\n key: 'actions',\n header: tr('channel.actions', 'Actions'),\n priority: 'secondary',\n cell: (delivery) => {\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n if (!canRetry) return null;\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleRetry(delivery.id)}\n disabled={retryingId === delivery.id}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium text-accent-primary hover:bg-accent-primary/10 rounded transition-colors disabled:opacity-50\"\n title={tr('channel.retryDelivery', 'Retry this delivery')}\n >\n <RotateCcw size={12} className={retryingId === delivery.id ? 'animate-spin' : ''} />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n );\n },\n },\n ];\n\n // Loading state\n if (isLoading) {\n return (\n <output\n className=\"flex items-center justify-center min-h-screen\"\n aria-label={tr('channel.loading', 'Loading')}\n >\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" aria-hidden=\"true\" />\n </output>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_READ}>\n <div className=\"space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - Message deliveries\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingAttempts ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingAttempts,\n onSelect: () => void loadAttempts(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingDeliveries,\n onSelect: () => void exportDeliveries(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportDeliveriesError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportDeliveriesError}</p>\n </div>\n )}\n\n {/* Error Banner */}\n {retryError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{retryError}</p>\n <button\n type=\"button\"\n onClick={() => setRetryError(null)}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n {tr('channel.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Page purpose */}\n <PagePurpose icon={<Send size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.deliveryHistoryPurpose',\n 'A per-message audit trail of everything this channel has tried to send — sent, delivered, opened, bounced or failed — with the error returned by the provider. Use it to confirm a notification actually went out, to debug why a delivery failed, and to retry failed attempts one by one. Records are kept for 7 days.'\n )}\n </PagePurpose>\n\n {/* Emphasis — delivery total summary */}\n <EmphasisPanel className=\"flex items-center gap-3 p-4\">\n <span className=\"rounded-lg bg-accent-orange/10 p-2\">\n <Send size={18} className=\"text-accent-orange\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {total}{' '}\n {total === 1\n ? tr('channel.deliveryAttemptSingular', 'delivery attempt')\n : tr('channel.deliveryAttemptsPlural', 'delivery attempts')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.deliveryHistoryBannerHint',\n 'All message attempts sent through this channel, retained for 7 days. Retry any failed delivery to attempt sending again.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Deliveries List */}\n {isLoadingAttempts ? (\n <div className=\"bg-surface border border-border-seam rounded-lg px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-text-secondary mt-2\">Loading delivery attempts…</p>\n </div>\n ) : (\n <ResponsiveTable\n columns={deliveryColumns}\n data={attempts}\n rowKey={(delivery) => delivery.id}\n className=\"bg-surface border border-border-seam rounded-lg\"\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n description={tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n />\n }\n />\n )}\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.page', 'Page')}{' '}\n <span className=\"font-medium\">{currentPageIdx + 1}</span>\n {' · '}\n {tr('channel.totalRecords', 'Total')}: <span className=\"font-medium\">{total}</span>\n </>\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage || isLoadingAttempts}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft size={16} />\n {tr('channel.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleNextPage()}\n disabled={!hasNextPage || isLoadingAttempts}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DeliveryHistoryPage.js","names":["useChannelContext","_Fragment","useChannel$1","STATUS_CONFIG","Clock","Send","CheckCircle2","XCircle","AlertCircle","RefreshCw","DeliveryHistoryPage","useParams","navigate","useNavigate","usePermissions","canExport","hasPermission","CHANNEL_PERMISSIONS","useI18n","tr","value","t","key","fallback","exportDeliveries","isExportingDeliveries","exportDeliveriesError","useExportDeliveryAttempts","organizationId","channelId","isLoadingAttempts","setIsLoadingAttempts","useState","attempts","setAttempts","retryingId","setRetryingId","retryError","setRetryError","limit","setLimit","total","setTotal","cursorStack","setCursorStack","currentPageIdx","setCurrentPageIdx","useChannel","loadAttempts","useCallback","result","fetchDeliveryAttempts","cursor","loadAttemptsRef","useRef","cursorStackRef","isFirstPageRender","useEffect","timer","handleNextPage","nextCursor","next","prev","p","handlePrevPage","prevIdx","hasNextPage","hasPrevPage","handleRetry","attemptId","retryDelivery","getStatusConfig","status","deliveryColumns","statusConfig","delivery","StatusIcon","PermissionGuard","RotateCcw","isLoading","Loader2","error","channel","ArrowLeft","CTAOverflowMenu","Download","PagePurpose","EmphasisPanel","ResponsiveTable","IllustratedEmptyState","Fragment","ChevronLeft","ChevronRight"],"sources":["../../../../../src/channel/modules/channels/pages/DeliveryHistoryPage.tsx"],"sourcesContent":["/**\n * Delivery History Page\n *\n * Displays message delivery attempts for a specific channel with retry capability\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n ResponsiveTable,\n IllustratedEmptyState,\n PagePurpose,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useChannel, type DeliveryStatus, type DeliveryAttempt } from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportDeliveryAttempts } from '../hooks/useExport';\n\nconst STATUS_CONFIG: Record<\n DeliveryStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: { label: 'Pending', color: 'text-text-secondary bg-bg-sunken', icon: Clock },\n QUEUED: { label: 'Queued', color: 'text-accent-primary bg-accent-primary-subtle', icon: Clock },\n SENDING: { label: 'Sending', color: 'text-accent-primary bg-accent-primary/10', icon: Send },\n SENT: {\n label: 'Sent',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n DELIVERED: {\n label: 'Delivered',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n FAILED: {\n label: 'Failed',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n BOUNCED: {\n label: 'Bounced',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertCircle,\n },\n REJECTED: {\n label: 'Rejected',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n RATE_LIMITED: {\n label: 'Rate Limited',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n RETRYING: {\n label: 'Retrying',\n color: 'text-accent-primary bg-accent-primary/10',\n icon: RefreshCw,\n },\n OPENED: {\n label: 'Opened',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n CLICKED: {\n label: 'Clicked',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n};\n\nexport function DeliveryHistoryPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXPORT);\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportDeliveries,\n isExporting: isExportingDeliveries,\n exportError: exportDeliveriesError,\n } = useExportDeliveryAttempts(organizationId ?? undefined, channelId);\n const [isLoadingAttempts, setIsLoadingAttempts] = useState(true);\n const [attempts, setAttempts] = useState<DeliveryAttempt[]>([]);\n const [retryingId, setRetryingId] = useState<string | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n // Cursor stack: index 0 is always undefined (first page), subsequent entries are cursors\n const [cursorStack, setCursorStack] = useState<(string | undefined)[]>([undefined]);\n const [currentPageIdx, setCurrentPageIdx] = useState(0);\n\n const { channel, isLoading, error, fetchDeliveryAttempts, retryDelivery } = useChannel(channelId);\n\n const loadAttempts = useCallback(\n async (cursor?: string) => {\n if (!channelId) return;\n setIsLoadingAttempts(true);\n setRetryError(null);\n try {\n const result = await fetchDeliveryAttempts(limit, cursor);\n setAttempts(result.items);\n setTotal(result.total);\n return result.nextCursor;\n } finally {\n setIsLoadingAttempts(false);\n }\n },\n [channelId, fetchDeliveryAttempts, limit]\n );\n\n const loadAttemptsRef = useRef(loadAttempts);\n const cursorStackRef = useRef(cursorStack);\n useEffect(() => {\n loadAttemptsRef.current = loadAttempts;\n cursorStackRef.current = cursorStack;\n });\n const isFirstPageRender = useRef(true);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n setCursorStack([undefined]);\n setCurrentPageIdx(0);\n void loadAttemptsRef.current(undefined);\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n void Promise.resolve().then(() =>\n loadAttemptsRef.current(cursorStackRef.current[currentPageIdx])\n );\n }, [currentPageIdx]);\n\n // Poll for delivery attempt updates every 60 s on first page only\n useEffect(() => {\n if (!channelId || currentPageIdx !== 0) return;\n const timer = setInterval(() => {\n void loadAttempts(undefined);\n }, 60_000);\n return () => clearInterval(timer);\n }, [channelId, currentPageIdx, loadAttempts]);\n\n const handleNextPage = useCallback(async () => {\n const nextCursor = await loadAttempts(cursorStack[currentPageIdx]);\n if (nextCursor) {\n setCursorStack((prev) => {\n const next = [...prev];\n if (!next[currentPageIdx + 1]) {\n next[currentPageIdx + 1] = nextCursor;\n }\n return next;\n });\n }\n setCurrentPageIdx((p) => p + 1);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const handlePrevPage = useCallback(() => {\n const prevIdx = currentPageIdx - 1;\n setCurrentPageIdx(prevIdx);\n void loadAttempts(cursorStack[prevIdx]);\n }, [cursorStack, currentPageIdx, loadAttempts]);\n\n const hasNextPage = attempts.length === limit;\n const hasPrevPage = currentPageIdx > 0;\n\n const handleRetry = async (attemptId: string) => {\n setRetryingId(attemptId);\n setRetryError(null);\n try {\n const result = await retryDelivery(attemptId);\n if (!result.success) {\n setRetryError(result.error || 'Retry failed');\n } else {\n await loadAttempts(cursorStack[currentPageIdx]);\n }\n } finally {\n setRetryingId(null);\n }\n };\n\n const getStatusConfig = (status: DeliveryStatus) => {\n return STATUS_CONFIG[status] || STATUS_CONFIG.PENDING;\n };\n\n const deliveryColumns: ResponsiveTableColumn<DeliveryAttempt>[] = [\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n return (\n <div className=\"flex items-center gap-2\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span className={`text-sm font-medium ${statusConfig.color}`}>\n {statusConfig.label}\n </span>\n </div>\n );\n },\n },\n {\n key: 'content',\n header: tr('channel.content', 'Content'),\n priority: 'primary',\n cellClassName: 'text-sm text-text-primary truncate max-w-xs',\n cell: (delivery) => delivery.content,\n },\n {\n key: 'sentAt',\n header: tr('channel.sentAt', 'Sent At'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary',\n cell: (delivery) => (delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-'),\n },\n {\n key: 'retries',\n header: tr('channel.retries', 'Retries'),\n priority: 'secondary',\n cellClassName: 'text-sm text-text-secondary tabular-nums',\n cell: (delivery) => delivery.retryCount,\n },\n {\n key: 'error',\n header: tr('channel.error', 'Error'),\n priority: 'secondary',\n cellClassName: 'text-sm text-status-error-text max-w-xs truncate',\n cell: (delivery) => delivery.errorMessage || '-',\n },\n {\n key: 'actions',\n header: tr('channel.actions', 'Actions'),\n priority: 'secondary',\n cell: (delivery) => {\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n if (!canRetry) return null;\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleRetry(delivery.id)}\n disabled={retryingId === delivery.id}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium text-accent-primary hover:bg-accent-primary/10 rounded transition-colors disabled:opacity-50\"\n title={tr('channel.retryDelivery', 'Retry this delivery')}\n >\n <RotateCcw size={12} className={retryingId === delivery.id ? 'animate-spin' : ''} />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n );\n },\n },\n ];\n\n // Loading state\n if (isLoading) {\n return (\n <output\n className=\"flex items-center justify-center min-h-screen\"\n aria-label={tr('channel.loading', 'Loading')}\n >\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" aria-hidden=\"true\" />\n </output>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_READ}>\n <div className=\"space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - Message deliveries\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingAttempts ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingAttempts,\n onSelect: () => void loadAttempts(cursorStack[currentPageIdx]),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingDeliveries,\n onSelect: () => void exportDeliveries(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportDeliveriesError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportDeliveriesError}</p>\n </div>\n )}\n\n {/* Error Banner */}\n {retryError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{retryError}</p>\n <button\n type=\"button\"\n onClick={() => setRetryError(null)}\n className=\"text-status-error-text hover:text-status-error-text\"\n >\n {tr('channel.dismiss', 'Dismiss')}\n </button>\n </div>\n )}\n\n {/* Page purpose */}\n <PagePurpose icon={<Send size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.deliveryHistoryPurpose',\n 'A per-message audit trail of everything this channel has tried to send — sent, delivered, opened, bounced or failed — with the error returned by the provider. Use it to confirm a notification actually went out, to debug why a delivery failed, and to retry failed attempts one by one. Records are kept for 7 days.'\n )}\n </PagePurpose>\n\n {/* Emphasis — delivery total summary */}\n <EmphasisPanel className=\"flex items-center gap-3 p-4\">\n <span className=\"rounded-lg bg-accent-orange/10 p-2\">\n <Send size={18} className=\"text-accent-orange\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {total}{' '}\n {total === 1\n ? tr('channel.deliveryAttemptSingular', 'delivery attempt')\n : tr('channel.deliveryAttemptsPlural', 'delivery attempts')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.deliveryHistoryBannerHint',\n 'All message attempts sent through this channel, retained for 7 days. Retry any failed delivery to attempt sending again.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Deliveries List */}\n {isLoadingAttempts ? (\n <div className=\"bg-surface border border-border-seam rounded-lg px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-text-secondary mt-2\">Loading delivery attempts…</p>\n </div>\n ) : (\n <ResponsiveTable\n columns={deliveryColumns}\n data={attempts}\n rowKey={(delivery) => delivery.id}\n className=\"bg-surface border border-border-seam rounded-lg\"\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n description={tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n />\n }\n />\n )}\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.page', 'Page')}{' '}\n <span className=\"font-medium\">{currentPageIdx + 1}</span>\n {' · '}\n {tr('channel.totalRecords', 'Total')}: <span className=\"font-medium\">{total}</span>\n </>\n )}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrevPage}\n disabled={!hasPrevPage || isLoadingAttempts}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft size={16} />\n {tr('channel.previous', 'Previous')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleNextPage()}\n disabled={!hasNextPage || isLoadingAttempts}\n className=\"flex items-center gap-1 px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -4,5 +4,4 @@
|
|
|
4
4
|
* Allows editing an existing channel's configuration.
|
|
5
5
|
* Uses the same form structure as CreateChannelPage but pre-fills existing values.
|
|
6
6
|
*/
|
|
7
|
-
export declare function EditChannelPage(): import(
|
|
8
|
-
//# sourceMappingURL=EditChannelPage.d.ts.map
|
|
7
|
+
export declare function EditChannelPage(): import('react').JSX.Element;
|