@burdenoff/microfe-channel 2026.530.2 → 2026.624.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.
Files changed (31) hide show
  1. package/dist/channel/components/AdapterCredentialsForm.js +11 -11
  2. package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
  3. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +53 -53
  4. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  5. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +78 -78
  6. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  7. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +26 -26
  8. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  9. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +32 -32
  10. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  11. package/dist/channel/modules/channels/pages/ChannelsListPage.js +41 -41
  12. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  13. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +30 -30
  14. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  15. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +29 -29
  16. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  17. package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -28
  18. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  19. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +8 -8
  20. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  21. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +28 -28
  22. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  23. package/dist/channel/modules/channels/pages/EditChannelPage.js +25 -25
  24. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  25. package/dist/channel/modules/channels/pages/EditConnectionPage.js +4 -4
  26. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  27. package/dist/channel/modules/channels/pages/SendMessagePage.js +27 -27
  28. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  29. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +29 -29
  30. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  31. package/package.json +1 -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-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\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\nconst AUTH_TYPES: { value: ChannelAuthType; label: string; help: string }[] = [\n { value: 'OAUTH2', label: 'OAuth 2.0', help: 'Authorization code flow' },\n { value: 'API_KEY', label: 'API Key', help: 'Static API key' },\n { value: 'BOT_TOKEN', label: 'Bot Token', help: 'Slack/Discord/Teams bot token' },\n { value: 'WEBHOOK', label: 'Webhook', help: 'Shared secret for HMAC verification' },\n { value: 'SMTP_IMAP', label: 'SMTP / IMAP', help: 'Mail server credentials' },\n];\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = useChannels();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const 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 {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 <form\n onSubmit={handleSubmit}\n className=\"space-y-6 bg-bg-elevated rounded-lg p-6 border border-border\"\n >\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 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: ')}\n {selectedChannel.adapterType ?? '—'}\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 rounded-md text-fg\"\n required\n >\n {AUTH_TYPES.map((a) => (\n <option key={a.value} value={a.value}>\n {a.label}: {a.help}\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 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 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 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='{\"smtpHost\": \"smtp.example.com\"}'\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 rounded-md font-mono text-sm text-fg\"\n placeholder='{\"smtpHost\": \"smtp.example.com\"}'\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 rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
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-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\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\nconst AUTH_TYPES: { value: ChannelAuthType; label: string; help: string }[] = [\n { value: 'OAUTH2', label: 'OAuth 2.0', help: 'Authorization code flow' },\n { value: 'API_KEY', label: 'API Key', help: 'Static API key' },\n { value: 'BOT_TOKEN', label: 'Bot Token', help: 'Slack/Discord/Teams bot token' },\n { value: 'WEBHOOK', label: 'Webhook', help: 'Shared secret for HMAC verification' },\n { value: 'SMTP_IMAP', label: 'SMTP / IMAP', help: 'Mail server credentials' },\n];\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = useChannels();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const 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 {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 <form\n onSubmit={handleSubmit}\n className=\"space-y-6 bg-bg-elevated rounded-lg p-6 border border-border-subtle\"\n >\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: ')}\n {selectedChannel.adapterType ?? '—'}\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 {a.label}: {a.help}\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='{\"smtpHost\": \"smtp.example.com\"}'\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='{\"smtpHost\": \"smtp.example.com\"}'\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 </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
12
12
  var w = {
13
13
  PENDING: {
14
14
  label: "Pending",
15
- color: "text-muted-foreground bg-muted",
15
+ color: "text-text-secondary bg-bg-sunken",
16
16
  icon: f
17
17
  },
18
18
  QUEUED: {
@@ -183,16 +183,16 @@ function T() {
183
183
  type: "button",
184
184
  onClick: () => f(`/channels/${d}`),
185
185
  "aria-label": T("channel.backToChannel", "Back to channel"),
186
- className: "p-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
186
+ className: "p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors",
187
187
  children: /* @__PURE__ */ S(u, {
188
188
  size: 20,
189
189
  "aria-hidden": "true"
190
190
  })
191
191
  }), /* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h1", {
192
- className: "text-xl sm:text-2xl font-bold text-foreground",
192
+ className: "text-xl sm:text-2xl font-bold text-text-primary",
193
193
  children: T("channel.deliveryHistory", "Delivery History")
194
194
  }), /* @__PURE__ */ C("p", {
195
- className: "text-sm text-muted-foreground mt-1",
195
+ className: "text-sm text-text-secondary mt-1",
196
196
  children: [G.name, " - Message deliveries"]
197
197
  })] })]
198
198
  }), /* @__PURE__ */ C("div", {
@@ -201,7 +201,7 @@ function T() {
201
201
  /* @__PURE__ */ C("select", {
202
202
  value: L,
203
203
  onChange: (e) => R(Number(e.target.value)),
204
- className: "px-3 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-foreground",
204
+ 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",
205
205
  children: [
206
206
  /* @__PURE__ */ S("option", {
207
207
  value: 10,
@@ -225,7 +225,7 @@ function T() {
225
225
  type: "button",
226
226
  onClick: () => void X(V[U]),
227
227
  disabled: k,
228
- className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50",
228
+ className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50",
229
229
  children: [/* @__PURE__ */ S(h, {
230
230
  size: 16,
231
231
  className: k ? "animate-spin" : ""
@@ -237,7 +237,7 @@ function T() {
237
237
  type: "button",
238
238
  onClick: () => void re(),
239
239
  disabled: D,
240
- className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
240
+ className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border-subtle hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
241
241
  title: T("channel.exportCSV", "Export to CSV"),
242
242
  children: [D ? /* @__PURE__ */ S(m, {
243
243
  size: 16,
@@ -278,43 +278,43 @@ function T() {
278
278
  ]
279
279
  }),
280
280
  /* @__PURE__ */ S("div", {
281
- className: "p-4 bg-surface border border-border rounded-lg",
281
+ className: "p-4 bg-surface border border-border-subtle rounded-lg",
282
282
  children: /* @__PURE__ */ S("p", {
283
- className: "text-sm text-muted-foreground",
283
+ className: "text-sm text-text-secondary",
284
284
  children: "Delivery history shows all message attempts sent through this channel. Data is retained for 7 days. Click retry on a failed delivery to attempt sending again."
285
285
  })
286
286
  }),
287
287
  /* @__PURE__ */ S("div", {
288
- className: "bg-surface border border-border rounded-lg overflow-hidden",
288
+ className: "bg-surface border border-border-subtle rounded-lg overflow-hidden",
289
289
  children: /* @__PURE__ */ S("div", {
290
290
  className: "overflow-x-auto min-w-full",
291
291
  children: /* @__PURE__ */ C("table", {
292
292
  className: "w-full min-w-[700px]",
293
293
  children: [/* @__PURE__ */ S("thead", {
294
- className: "bg-muted/50 border-b border-border",
294
+ className: "bg-bg-sunken/50 border-b border-border-subtle",
295
295
  children: /* @__PURE__ */ C("tr", { children: [
296
296
  /* @__PURE__ */ S("th", {
297
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
297
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
298
298
  children: T("channel.status", "Status")
299
299
  }),
300
300
  /* @__PURE__ */ S("th", {
301
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
301
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
302
302
  children: T("channel.content", "Content")
303
303
  }),
304
304
  /* @__PURE__ */ S("th", {
305
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
305
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
306
306
  children: T("channel.sentAt", "Sent At")
307
307
  }),
308
308
  /* @__PURE__ */ S("th", {
309
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
309
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
310
310
  children: T("channel.retries", "Retries")
311
311
  }),
312
312
  /* @__PURE__ */ S("th", {
313
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
313
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
314
314
  children: T("channel.error", "Error")
315
315
  }),
316
316
  /* @__PURE__ */ S("th", {
317
- className: "px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider",
317
+ className: "px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider",
318
318
  children: T("channel.actions", "Actions")
319
319
  })
320
320
  ] })
@@ -327,7 +327,7 @@ function T() {
327
327
  size: 24,
328
328
  className: "animate-spin text-accent-primary mx-auto"
329
329
  }), /* @__PURE__ */ S("p", {
330
- className: "text-sm text-muted-foreground mt-2",
330
+ className: "text-sm text-text-secondary mt-2",
331
331
  children: "Loading delivery attempts…"
332
332
  })]
333
333
  }) }) : j.length === 0 ? /* @__PURE__ */ S("tr", { children: /* @__PURE__ */ S("td", {
@@ -338,14 +338,14 @@ function T() {
338
338
  children: [
339
339
  /* @__PURE__ */ S(_, {
340
340
  size: 32,
341
- className: "text-muted-foreground"
341
+ className: "text-text-secondary"
342
342
  }),
343
343
  /* @__PURE__ */ S("p", {
344
- className: "text-sm font-medium text-foreground",
344
+ className: "text-sm font-medium text-text-primary",
345
345
  children: T("channel.noDeliveryAttempts", "No delivery attempts yet")
346
346
  }),
347
347
  /* @__PURE__ */ S("p", {
348
- className: "text-xs text-muted-foreground",
348
+ className: "text-xs text-text-secondary",
349
349
  children: T("channel.sendMessageToSeeHistory", "Send a message through this channel to see delivery history")
350
350
  })
351
351
  ]
@@ -353,7 +353,7 @@ function T() {
353
353
  }) }) : j.map((e) => {
354
354
  let t = le(e.status), i = t.icon, a = e.status === "FAILED" || e.status === "BOUNCED" || e.status === "REJECTED";
355
355
  return /* @__PURE__ */ C("tr", {
356
- className: "hover:bg-muted/30 transition-colors",
356
+ className: "hover:bg-bg-sunken/30 transition-colors",
357
357
  children: [
358
358
  /* @__PURE__ */ S("td", {
359
359
  className: "px-4 py-3",
@@ -369,15 +369,15 @@ function T() {
369
369
  })
370
370
  }),
371
371
  /* @__PURE__ */ S("td", {
372
- className: "px-4 py-3 text-sm text-foreground truncate max-w-xs",
372
+ className: "px-4 py-3 text-sm text-text-primary truncate max-w-xs",
373
373
  children: e.content
374
374
  }),
375
375
  /* @__PURE__ */ S("td", {
376
- className: "px-4 py-3 text-sm text-muted-foreground",
376
+ className: "px-4 py-3 text-sm text-text-secondary",
377
377
  children: e.sentAt ? new Date(e.sentAt).toLocaleString() : "-"
378
378
  }),
379
379
  /* @__PURE__ */ S("td", {
380
- className: "px-4 py-3 text-sm text-muted-foreground",
380
+ className: "px-4 py-3 text-sm text-text-secondary",
381
381
  children: e.retryCount
382
382
  }),
383
383
  /* @__PURE__ */ S("td", {
@@ -411,7 +411,7 @@ function T() {
411
411
  /* @__PURE__ */ C("div", {
412
412
  className: "flex items-center justify-between",
413
413
  children: [/* @__PURE__ */ S("p", {
414
- className: "text-sm text-muted-foreground",
414
+ className: "text-sm text-text-secondary",
415
415
  children: z > 0 && /* @__PURE__ */ C(x, { children: [
416
416
  T("channel.page", "Page"),
417
417
  " ",
@@ -433,13 +433,13 @@ function T() {
433
433
  type: "button",
434
434
  onClick: ae,
435
435
  disabled: !se || k,
436
- className: "flex items-center gap-1 px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
436
+ 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",
437
437
  children: [/* @__PURE__ */ S(te, { size: 16 }), T("channel.previous", "Previous")]
438
438
  }), /* @__PURE__ */ C("button", {
439
439
  type: "button",
440
440
  onClick: () => void ie(),
441
441
  disabled: !oe || k,
442
- className: "flex items-center gap-1 px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
442
+ 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",
443
443
  children: [T("channel.next", "Next"), /* @__PURE__ */ S(ne, { size: 16 })]
444
444
  })]
445
445
  })]
@@ -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-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\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-muted-foreground bg-muted', 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 { 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 // 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-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">\n {tr('channel.deliveryHistory', 'Delivery History')}\n </h1>\n <p className=\"text-sm text-muted-foreground 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 rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-foreground\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <button\n type=\"button\"\n onClick={() => void loadAttempts(cursorStack[currentPageIdx])}\n disabled={isLoadingAttempts}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50\"\n >\n <RefreshCw size={16} className={isLoadingAttempts ? 'animate-spin' : ''} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportDeliveries()}\n disabled={isExportingDeliveries}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tr('channel.exportCSV', 'Export to CSV')}\n >\n {isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n </div>\n </div>\n\n {/* Export Error Banner */}\n {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 {/* Info Banner */}\n <div className=\"p-4 bg-surface border border-border rounded-lg\">\n <p className=\"text-sm text-muted-foreground\">\n Delivery history shows all message attempts sent through this channel. Data is retained\n for 7 days. Click retry on a failed delivery to attempt sending again.\n </p>\n </div>\n\n {/* Deliveries List */}\n <div className=\"bg-surface border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full min-w-[700px]\">\n <thead className=\"bg-muted/50 border-b border-border\">\n <tr>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.content', 'Content')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.sentAt', 'Sent At')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.retries', 'Retries')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.error', 'Error')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {isLoadingAttempts ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-12 text-center\">\n <Loader2 size={24} className=\"animate-spin text-accent-primary mx-auto\" />\n <p className=\"text-sm text-muted-foreground mt-2\">\n Loading delivery attempts…\n </p>\n </td>\n </tr>\n ) : attempts.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-12 text-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Send size={32} className=\"text-muted-foreground\" />\n <p className=\"text-sm font-medium text-foreground\">\n {tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n </p>\n </div>\n </td>\n </tr>\n ) : (\n attempts.map((delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n return (\n <tr key={delivery.id} className=\"hover:bg-muted/30 transition-colors\">\n <td className=\"px-4 py-3\">\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 </td>\n <td className=\"px-4 py-3 text-sm text-foreground truncate max-w-xs\">\n {delivery.content}\n </td>\n <td className=\"px-4 py-3 text-sm text-muted-foreground\">\n {delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-'}\n </td>\n <td className=\"px-4 py-3 text-sm text-muted-foreground\">\n {delivery.retryCount}\n </td>\n <td className=\"px-4 py-3 text-sm text-status-error-text max-w-xs truncate\">\n {delivery.errorMessage || '-'}\n </td>\n <td className=\"px-4 py-3\">\n {canRetry && (\n <PermissionGuard\n permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}\n >\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\n size={12}\n className={retryingId === delivery.id ? 'animate-spin' : ''}\n />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n )}\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-muted-foreground\">\n {total > 0 && (\n <>\n {tr('channel.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 rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft size={16} />\n {tr('channel.previous', 'Previous')}\n </button>\n <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 rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"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-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n RefreshCw,\n Send,\n RotateCcw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\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 { 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 // 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 <button\n type=\"button\"\n onClick={() => void loadAttempts(cursorStack[currentPageIdx])}\n disabled={isLoadingAttempts}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors disabled:opacity-50\"\n >\n <RefreshCw size={16} className={isLoadingAttempts ? 'animate-spin' : ''} />\n {tr('channel.refresh', 'Refresh')}\n </button>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportDeliveries()}\n disabled={isExportingDeliveries}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border-subtle hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tr('channel.exportCSV', 'Export to CSV')}\n >\n {isExportingDeliveries ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n </div>\n </div>\n\n {/* Export Error Banner */}\n {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 {/* Info Banner */}\n <div className=\"p-4 bg-surface border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-text-secondary\">\n Delivery history shows all message attempts sent through this channel. Data is retained\n for 7 days. Click retry on a failed delivery to attempt sending again.\n </p>\n </div>\n\n {/* Deliveries List */}\n <div className=\"bg-surface border border-border-subtle rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto min-w-full\">\n <table className=\"w-full min-w-[700px]\">\n <thead className=\"bg-bg-sunken/50 border-b border-border-subtle\">\n <tr>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.status', 'Status')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.content', 'Content')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.sentAt', 'Sent At')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.retries', 'Retries')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.error', 'Error')}\n </th>\n <th className=\"px-4 py-3 text-left text-xs font-medium text-text-secondary uppercase tracking-wider\">\n {tr('channel.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {isLoadingAttempts ? (\n <tr>\n <td colSpan={6} className=\"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\">\n Loading delivery attempts…\n </p>\n </td>\n </tr>\n ) : attempts.length === 0 ? (\n <tr>\n <td colSpan={6} className=\"px-4 py-12 text-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Send size={32} className=\"text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('channel.noDeliveryAttempts', 'No delivery attempts yet')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'channel.sendMessageToSeeHistory',\n 'Send a message through this channel to see delivery history'\n )}\n </p>\n </div>\n </td>\n </tr>\n ) : (\n attempts.map((delivery) => {\n const statusConfig = getStatusConfig(delivery.status as DeliveryStatus);\n const StatusIcon = statusConfig.icon;\n const canRetry =\n delivery.status === 'FAILED' ||\n delivery.status === 'BOUNCED' ||\n delivery.status === 'REJECTED';\n return (\n <tr key={delivery.id} className=\"hover:bg-bg-sunken/30 transition-colors\">\n <td className=\"px-4 py-3\">\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 </td>\n <td className=\"px-4 py-3 text-sm text-text-primary truncate max-w-xs\">\n {delivery.content}\n </td>\n <td className=\"px-4 py-3 text-sm text-text-secondary\">\n {delivery.sentAt ? new Date(delivery.sentAt).toLocaleString() : '-'}\n </td>\n <td className=\"px-4 py-3 text-sm text-text-secondary\">\n {delivery.retryCount}\n </td>\n <td className=\"px-4 py-3 text-sm text-status-error-text max-w-xs truncate\">\n {delivery.errorMessage || '-'}\n </td>\n <td className=\"px-4 py-3\">\n {canRetry && (\n <PermissionGuard\n permission={CHANNEL_PERMISSIONS.CHANNEL_DELIVERY_EXECUTE}\n >\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\n size={12}\n className={retryingId === delivery.id ? 'animate-spin' : ''}\n />\n {retryingId === delivery.id\n ? tr('channel.retrying', 'Retrying...')\n : tr('channel.retry', 'Retry')}\n </button>\n </PermissionGuard>\n )}\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-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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"}
@@ -178,7 +178,7 @@ var k = [
178
178
  label: "Webhook",
179
179
  description: "Generic HTTP webhook",
180
180
  icon: b,
181
- color: "text-muted-foreground bg-muted/50 border-border",
181
+ color: "text-text-secondary bg-bg-sunken/50 border-border-subtle",
182
182
  adapters: [{
183
183
  value: "WEBHOOK_HTTP",
184
184
  label: "HTTP Webhook"
@@ -219,11 +219,11 @@ function j() {
219
219
  let X = m?.platform || "EMAIL", Z = k.find((e) => e.value === X);
220
220
  return g ? /* @__PURE__ */ D("div", {
221
221
  className: "flex items-center justify-center min-h-screen",
222
- children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-muted-foreground" })
222
+ children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-text-secondary" })
223
223
  }) : _ || !m ? /* @__PURE__ */ O("div", {
224
224
  className: "flex flex-col items-center justify-center min-h-screen gap-4",
225
225
  children: [
226
- /* @__PURE__ */ D(u, { className: "size-12 text-destructive" }),
226
+ /* @__PURE__ */ D(u, { className: "size-12 text-status-error-text" }),
227
227
  /* @__PURE__ */ D("h1", {
228
228
  className: "text-xl font-semibold",
229
229
  children: C("channel.channelNotFound", "Channel not found")
@@ -238,9 +238,9 @@ function j() {
238
238
  }) : /* @__PURE__ */ D(i, {
239
239
  permission: a.CHANNEL_UPDATE,
240
240
  children: /* @__PURE__ */ O("div", {
241
- className: "min-h-screen bg-background",
241
+ className: "min-h-screen bg-bg-surface",
242
242
  children: [/* @__PURE__ */ D("div", {
243
- className: "border-b bg-card",
243
+ className: "border-b bg-bg-surface",
244
244
  children: /* @__PURE__ */ D("div", {
245
245
  className: "container mx-auto px-6 py-4",
246
246
  children: /* @__PURE__ */ O("div", {
@@ -248,13 +248,13 @@ function j() {
248
248
  children: [/* @__PURE__ */ D("button", {
249
249
  type: "button",
250
250
  onClick: () => f(`/channels/${p}`),
251
- className: "p-2 hover:bg-muted rounded-lg transition-colors",
251
+ className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
252
252
  children: /* @__PURE__ */ D(d, { className: "size-5 " })
253
253
  }), /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("h1", {
254
254
  className: "text-2xl font-bold",
255
255
  children: C("channel.editChannel", "Edit Channel")
256
256
  }), /* @__PURE__ */ D("p", {
257
- className: "text-sm text-muted-foreground",
257
+ className: "text-sm text-text-secondary",
258
258
  children: C("channel.updateChannelConfig", "Update channel configuration")
259
259
  })] })]
260
260
  })
@@ -265,25 +265,25 @@ function j() {
265
265
  className: "space-y-6",
266
266
  children: [
267
267
  G && /* @__PURE__ */ O("div", {
268
- className: "p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3",
269
- children: [/* @__PURE__ */ D(u, { className: "size-5 text-destructive shrink-0" }), /* @__PURE__ */ D("p", {
270
- className: "text-sm text-destructive",
268
+ className: "p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3",
269
+ children: [/* @__PURE__ */ D(u, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ D("p", {
270
+ className: "text-sm text-status-error-text",
271
271
  children: G
272
272
  })]
273
273
  }),
274
274
  /* @__PURE__ */ O("div", {
275
- className: "p-4 rounded-lg bg-muted/50 border space-y-2",
275
+ className: "p-4 rounded-lg bg-bg-sunken/50 border space-y-2",
276
276
  children: [/* @__PURE__ */ D("div", {
277
277
  className: "flex items-center gap-2",
278
278
  children: Z && /* @__PURE__ */ O(E, { children: [(() => {
279
279
  let e = Z.icon;
280
- return /* @__PURE__ */ D(e, { className: "size-5 text-muted-foreground" });
280
+ return /* @__PURE__ */ D(e, { className: "size-5 text-text-secondary" });
281
281
  })(), /* @__PURE__ */ D("span", {
282
282
  className: "font-medium",
283
283
  children: X
284
284
  })] })
285
285
  }), /* @__PURE__ */ D("p", {
286
- className: "text-sm text-muted-foreground",
286
+ className: "text-sm text-text-secondary",
287
287
  children: C("channel.platformLockedHint", "Platform cannot be changed after channel creation. Create a new channel to use a different platform.")
288
288
  })]
289
289
  }),
@@ -298,7 +298,7 @@ function j() {
298
298
  value: j,
299
299
  onChange: (e) => M(e.target.value),
300
300
  placeholder: C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
301
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
301
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
302
302
  })]
303
303
  }),
304
304
  /* @__PURE__ */ O("div", {
@@ -312,7 +312,7 @@ function j() {
312
312
  onChange: (e) => P(e.target.value),
313
313
  placeholder: C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
314
314
  rows: 3,
315
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none"
315
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none"
316
316
  })]
317
317
  }),
318
318
  Z && /* @__PURE__ */ O("div", {
@@ -323,7 +323,7 @@ function j() {
323
323
  }), /* @__PURE__ */ D("select", {
324
324
  value: F,
325
325
  onChange: (e) => I(e.target.value),
326
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
326
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
327
327
  children: Z.adapters.map((e) => /* @__PURE__ */ D("option", {
328
328
  value: e.value,
329
329
  children: e.label
@@ -342,14 +342,14 @@ function j() {
342
342
  /* @__PURE__ */ D("select", {
343
343
  value: L,
344
344
  onChange: (e) => R(Number(e.target.value)),
345
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
345
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
346
346
  children: A.map((e) => /* @__PURE__ */ D("option", {
347
347
  value: e.value,
348
348
  children: e.label
349
349
  }, e.value))
350
350
  }),
351
351
  /* @__PURE__ */ D("p", {
352
- className: "text-xs text-muted-foreground",
352
+ className: "text-xs text-text-secondary",
353
353
  children: C("channel.priorityFailoverHint", "Higher priority channels are used first in failover")
354
354
  })
355
355
  ]
@@ -361,7 +361,7 @@ function j() {
361
361
  }), /* @__PURE__ */ O("select", {
362
362
  value: z,
363
363
  onChange: (e) => B(e.target.value),
364
- className: "w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary",
364
+ className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
365
365
  children: [
366
366
  /* @__PURE__ */ D("option", {
367
367
  value: "INACTIVE",
@@ -385,7 +385,7 @@ function j() {
385
385
  className: "flex items-center gap-2",
386
386
  children: [
387
387
  /* @__PURE__ */ D("span", {
388
- className: "text-sm text-muted-foreground",
388
+ className: "text-sm text-text-secondary",
389
389
  children: C("channel.confirmDelete", "Confirm delete?")
390
390
  }),
391
391
  /* @__PURE__ */ O("button", {
@@ -401,13 +401,13 @@ function j() {
401
401
  }
402
402
  },
403
403
  disabled: U,
404
- className: "px-4 py-2 text-sm bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive/90 transition-colors flex items-center gap-2",
404
+ className: "px-4 py-2 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors flex items-center gap-2",
405
405
  children: [U ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.delete", "Delete")]
406
406
  }),
407
407
  /* @__PURE__ */ D("button", {
408
408
  type: "button",
409
409
  onClick: () => J(!1),
410
- className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
410
+ className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors",
411
411
  children: C("channel.cancel", "Cancel")
412
412
  })
413
413
  ]
@@ -416,7 +416,7 @@ function j() {
416
416
  children: /* @__PURE__ */ O("button", {
417
417
  type: "button",
418
418
  onClick: () => J(!0),
419
- className: "px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2",
419
+ className: "px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg/10 rounded-lg transition-colors flex items-center gap-2",
420
420
  children: [/* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.deleteChannel", "Delete Channel")]
421
421
  })
422
422
  }) }), /* @__PURE__ */ O("div", {
@@ -424,7 +424,7 @@ function j() {
424
424
  children: [/* @__PURE__ */ D("button", {
425
425
  type: "button",
426
426
  onClick: () => f(`/channels/${p}`),
427
- className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
427
+ className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors",
428
428
  children: C("channel.cancel", "Cancel")
429
429
  }), /* @__PURE__ */ O("button", {
430
430
  type: "button",
@@ -451,7 +451,7 @@ function j() {
451
451
  }
452
452
  },
453
453
  disabled: V || !j.trim(),
454
- className: "px-6 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors flex items-center gap-2 disabled:opacity-50",
454
+ className: "px-6 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2 disabled:opacity-50",
455
455
  children: [V ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(y, { className: "size-4 " }), C("channel.saveChanges", "Save Changes")]
456
456
  })]
457
457
  })]