@burdenoff/microfe-channel 2026.526.1 → 2026.530.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 (47) hide show
  1. package/dist/channel/ChannelRoutes.js +5 -6
  2. package/dist/channel/ChannelRoutes.js.map +1 -1
  3. package/dist/channel/components/AdapterCredentialsForm.js +14 -2
  4. package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
  5. package/dist/channel/components/FeatureNotEnabled.js +1 -1
  6. package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
  7. package/dist/channel/components/PermissionGuard.js +1 -1
  8. package/dist/channel/components/PermissionGuard.js.map +1 -1
  9. package/dist/channel/config/adapterCredentials.js +19 -11
  10. package/dist/channel/config/adapterCredentials.js.map +1 -1
  11. package/dist/channel/constants/permissions.js +3 -0
  12. package/dist/channel/constants/permissions.js.map +1 -1
  13. package/dist/channel/hooks/useChannelSubscription.js.map +1 -1
  14. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +244 -220
  15. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  16. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +416 -390
  17. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  18. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +30 -15
  19. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  20. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +8 -2
  21. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  22. package/dist/channel/modules/channels/pages/ChannelsListPage.js +241 -213
  23. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  24. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +107 -98
  25. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  26. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +160 -140
  27. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  28. package/dist/channel/modules/channels/pages/CreateChannelPage.js +47 -50
  29. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  30. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +16 -5
  31. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  32. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +11 -4
  33. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  34. package/dist/channel/modules/channels/pages/EditChannelPage.js +17 -8
  35. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  36. package/dist/channel/modules/channels/pages/EditConnectionPage.js +11 -6
  37. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  38. package/dist/channel/modules/channels/pages/SendMessagePage.js +21 -9
  39. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  40. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +8 -1
  41. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  42. package/dist/channel/pages/ChannelLandingPage.js +50 -35
  43. package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
  44. package/dist/channel/providers/ChannelProvider.js +1 -1
  45. package/dist/channel/providers/ChannelProvider.js.map +1 -1
  46. package/package.json +2 -2
  47. package/dist/channel/modules/channels/pages/index.js +0 -14
@@ -141,9 +141,8 @@ function T() {
141
141
  P(null);
142
142
  }
143
143
  }, le = (e) => w[e] || w.PENDING;
144
- return K ? /* @__PURE__ */ S("div", {
144
+ return K ? /* @__PURE__ */ S("output", {
145
145
  className: "flex items-center justify-center min-h-screen",
146
- role: "status",
147
146
  "aria-label": T("channel.loading", "Loading"),
148
147
  children: /* @__PURE__ */ S(m, {
149
148
  size: 32,
@@ -164,6 +163,7 @@ function T() {
164
163
  children: q || "Channel not found"
165
164
  }) }),
166
165
  /* @__PURE__ */ S("button", {
166
+ type: "button",
167
167
  onClick: () => f(`/channels/${d}`),
168
168
  className: "ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
169
169
  children: "Go Back"
@@ -180,6 +180,7 @@ function T() {
180
180
  children: [/* @__PURE__ */ C("div", {
181
181
  className: "flex items-center gap-3",
182
182
  children: [/* @__PURE__ */ S("button", {
183
+ type: "button",
183
184
  onClick: () => f(`/channels/${d}`),
184
185
  "aria-label": T("channel.backToChannel", "Back to channel"),
185
186
  className: "p-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors",
@@ -221,6 +222,7 @@ function T() {
221
222
  ]
222
223
  }),
223
224
  /* @__PURE__ */ C("button", {
225
+ type: "button",
224
226
  onClick: () => void X(V[U]),
225
227
  disabled: k,
226
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",
@@ -230,8 +232,9 @@ function T() {
230
232
  }), T("channel.refresh", "Refresh")]
231
233
  }),
232
234
  /* @__PURE__ */ S(n, {
233
- permission: r.CHANNEL_ADMIN,
235
+ permission: r.CHANNEL_DELIVERY_EXPORT,
234
236
  children: /* @__PURE__ */ C("button", {
237
+ type: "button",
235
238
  onClick: () => void re(),
236
239
  disabled: D,
237
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",
@@ -267,6 +270,7 @@ function T() {
267
270
  children: F
268
271
  }),
269
272
  /* @__PURE__ */ S("button", {
273
+ type: "button",
270
274
  onClick: () => I(null),
271
275
  className: "text-status-error-text hover:text-status-error-text",
272
276
  children: T("channel.dismiss", "Dismiss")
@@ -324,7 +328,7 @@ function T() {
324
328
  className: "animate-spin text-accent-primary mx-auto"
325
329
  }), /* @__PURE__ */ S("p", {
326
330
  className: "text-sm text-muted-foreground mt-2",
327
- children: "Loading delivery attempts..."
331
+ children: "Loading delivery attempts…"
328
332
  })]
329
333
  }) }) : j.length === 0 ? /* @__PURE__ */ S("tr", { children: /* @__PURE__ */ S("td", {
330
334
  colSpan: 6,
@@ -385,6 +389,7 @@ function T() {
385
389
  children: a && /* @__PURE__ */ S(n, {
386
390
  permission: r.CHANNEL_DELIVERY_EXECUTE,
387
391
  children: /* @__PURE__ */ C("button", {
392
+ type: "button",
388
393
  onClick: () => ce(e.id),
389
394
  disabled: N === e.id,
390
395
  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",
@@ -425,11 +430,13 @@ function T() {
425
430
  }), /* @__PURE__ */ C("div", {
426
431
  className: "flex items-center gap-2",
427
432
  children: [/* @__PURE__ */ C("button", {
433
+ type: "button",
428
434
  onClick: ae,
429
435
  disabled: !se || k,
430
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",
431
437
  children: [/* @__PURE__ */ S(te, { size: 16 }), T("channel.previous", "Previous")]
432
438
  }), /* @__PURE__ */ C("button", {
439
+ type: "button",
433
440
  onClick: () => void ie(),
434
441
  disabled: !oe || k,
435
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",
@@ -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 <div\n className=\"flex items-center justify-center min-h-screen\"\n role=\"status\"\n aria-label={tr('channel.loading', 'Loading')}\n >\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" aria-hidden=\"true\" />\n </div>\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 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 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 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_ADMIN}>\n <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 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 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 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 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-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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"}
@@ -219,16 +219,17 @@ 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: "w-8 h-8 animate-spin text-muted-foreground" })
222
+ children: /* @__PURE__ */ D(h, { className: "size-8 animate-spin text-muted-foreground" })
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: "w-12 h-12 text-destructive" }),
226
+ /* @__PURE__ */ D(u, { className: "size-12 text-destructive" }),
227
227
  /* @__PURE__ */ D("h1", {
228
228
  className: "text-xl font-semibold",
229
229
  children: C("channel.channelNotFound", "Channel not found")
230
230
  }),
231
231
  /* @__PURE__ */ D("button", {
232
+ type: "button",
232
233
  onClick: () => f("../list"),
233
234
  className: "text-primary hover:underline",
234
235
  children: C("channel.backToChannels", "Back to channels")
@@ -245,9 +246,10 @@ function j() {
245
246
  children: /* @__PURE__ */ O("div", {
246
247
  className: "flex items-center gap-4",
247
248
  children: [/* @__PURE__ */ D("button", {
249
+ type: "button",
248
250
  onClick: () => f(`/channels/${p}`),
249
251
  className: "p-2 hover:bg-muted rounded-lg transition-colors",
250
- children: /* @__PURE__ */ D(d, { className: "w-5 h-5" })
252
+ children: /* @__PURE__ */ D(d, { className: "size-5 " })
251
253
  }), /* @__PURE__ */ O("div", { children: [/* @__PURE__ */ D("h1", {
252
254
  className: "text-2xl font-bold",
253
255
  children: C("channel.editChannel", "Edit Channel")
@@ -264,7 +266,7 @@ function j() {
264
266
  children: [
265
267
  G && /* @__PURE__ */ O("div", {
266
268
  className: "p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3",
267
- children: [/* @__PURE__ */ D(u, { className: "w-5 h-5 text-destructive shrink-0" }), /* @__PURE__ */ D("p", {
269
+ children: [/* @__PURE__ */ D(u, { className: "size-5 text-destructive shrink-0" }), /* @__PURE__ */ D("p", {
268
270
  className: "text-sm text-destructive",
269
271
  children: G
270
272
  })]
@@ -275,7 +277,7 @@ function j() {
275
277
  className: "flex items-center gap-2",
276
278
  children: Z && /* @__PURE__ */ O(E, { children: [(() => {
277
279
  let e = Z.icon;
278
- return /* @__PURE__ */ D(e, { className: "w-5 h-5 text-muted-foreground" });
280
+ return /* @__PURE__ */ D(e, { className: "size-5 text-muted-foreground" });
279
281
  })(), /* @__PURE__ */ D("span", {
280
282
  className: "font-medium",
281
283
  children: X
@@ -291,6 +293,7 @@ function j() {
291
293
  className: "text-sm font-medium",
292
294
  children: [C("channel.channelName", "Channel Name"), " *"]
293
295
  }), /* @__PURE__ */ D("input", {
296
+ "aria-label": C("channel.editNamePlaceholder", "e.g., Production Email, Customer SMS"),
294
297
  type: "text",
295
298
  value: j,
296
299
  onChange: (e) => M(e.target.value),
@@ -304,6 +307,7 @@ function j() {
304
307
  className: "text-sm font-medium",
305
308
  children: C("channel.description", "Description")
306
309
  }), /* @__PURE__ */ D("textarea", {
310
+ "aria-label": C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
307
311
  value: N,
308
312
  onChange: (e) => P(e.target.value),
309
313
  placeholder: C("channel.editDescriptionPlaceholder", "Optional description for this channel"),
@@ -385,6 +389,7 @@ function j() {
385
389
  children: C("channel.confirmDelete", "Confirm delete?")
386
390
  }),
387
391
  /* @__PURE__ */ O("button", {
392
+ type: "button",
388
393
  onClick: async () => {
389
394
  if (!(!p || !q)) {
390
395
  e("medium"), W(!0);
@@ -397,9 +402,10 @@ function j() {
397
402
  },
398
403
  disabled: U,
399
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",
400
- children: [U ? /* @__PURE__ */ D(h, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ D(S, { className: "w-4 h-4" }), C("channel.delete", "Delete")]
405
+ children: [U ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.delete", "Delete")]
401
406
  }),
402
407
  /* @__PURE__ */ D("button", {
408
+ type: "button",
403
409
  onClick: () => J(!1),
404
410
  className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
405
411
  children: C("channel.cancel", "Cancel")
@@ -408,17 +414,20 @@ function j() {
408
414
  }) : /* @__PURE__ */ D(i, {
409
415
  permission: a.CHANNEL_DELETE,
410
416
  children: /* @__PURE__ */ O("button", {
417
+ type: "button",
411
418
  onClick: () => J(!0),
412
419
  className: "px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2",
413
- children: [/* @__PURE__ */ D(S, { className: "w-4 h-4" }), C("channel.deleteChannel", "Delete Channel")]
420
+ children: [/* @__PURE__ */ D(S, { className: "size-4 " }), C("channel.deleteChannel", "Delete Channel")]
414
421
  })
415
422
  }) }), /* @__PURE__ */ O("div", {
416
423
  className: "flex items-center gap-3",
417
424
  children: [/* @__PURE__ */ D("button", {
425
+ type: "button",
418
426
  onClick: () => f(`/channels/${p}`),
419
427
  className: "px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors",
420
428
  children: C("channel.cancel", "Cancel")
421
429
  }), /* @__PURE__ */ O("button", {
430
+ type: "button",
422
431
  onClick: async () => {
423
432
  if (p) {
424
433
  if (!j.trim()) {
@@ -443,7 +452,7 @@ function j() {
443
452
  },
444
453
  disabled: V || !j.trim(),
445
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",
446
- children: [V ? /* @__PURE__ */ D(h, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ D(y, { className: "w-4 h-4" }), C("channel.saveChanges", "Save Changes")]
455
+ children: [V ? /* @__PURE__ */ D(h, { className: "size-4 animate-spin" }) : /* @__PURE__ */ D(y, { className: "size-4 " }), C("channel.saveChanges", "Save Changes")]
447
456
  })]
448
457
  })]
449
458
  })
@@ -1 +1 @@
1
- {"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-muted-foreground bg-muted/50 border-border',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = 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 // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"w-8 h-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"w-12 h-12 text-destructive\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button onClick={() => navigate('../list')} className=\"text-primary hover:underline\">\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-background\">\n {/* Header */}\n <div className=\"border-b bg-card\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"w-5 h-5\" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3\">\n <AlertCircle className=\"w-5 h-5 text-destructive shrink-0\" />\n <p className=\"text-sm text-destructive\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <div className=\"p-4 rounded-lg bg-muted/50 border space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"w-5 h-5 text-muted-foreground\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </div>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"w-4 h-4\" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 text-sm bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Trash2 className=\"w-4 h-4\" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n 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\"\n >\n {isSaving ? (\n <Loader2 className=\"w-4 h-4 animate-spin\" />\n ) : (\n <Save className=\"w-4 h-4\" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-muted-foreground bg-muted/50 border-border',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = 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 // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-destructive\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-background\">\n {/* Header */}\n <div className=\"border-b bg-card\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-muted rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-destructive/10 border border-destructive/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-destructive shrink-0\" />\n <p className=\"text-sm text-destructive\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <div className=\"p-4 rounded-lg bg-muted/50 border space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"size-5 text-muted-foreground\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </div>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n aria-label={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-lg transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 text-sm bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-muted-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n 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\"\n >\n {isSaving ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -46,20 +46,21 @@ function b() {
46
46
  E
47
47
  ]), k ? /* @__PURE__ */ v("div", {
48
48
  className: "container mx-auto p-6 flex items-center justify-center min-h-64",
49
- children: /* @__PURE__ */ v(p, { className: "w-6 h-6 animate-spin text-fg-muted" })
49
+ children: /* @__PURE__ */ v(p, { className: "size-6 animate-spin text-fg-muted" })
50
50
  }) : D ? /* @__PURE__ */ v(o, {
51
51
  permission: s.CHANNEL_CONNECTION_UPDATE,
52
52
  children: /* @__PURE__ */ y("div", {
53
53
  className: "container mx-auto p-6 max-w-2xl",
54
54
  children: [
55
55
  /* @__PURE__ */ y("button", {
56
+ type: "button",
56
57
  onClick: () => b(-1),
57
58
  className: "flex items-center gap-2 text-fg-muted hover:text-fg mb-6",
58
- children: [/* @__PURE__ */ v(f, { className: "w-4 h-4" }), E("common.back", "Back")]
59
+ children: [/* @__PURE__ */ v(f, { className: "size-4 " }), E("common.back", "Back")]
59
60
  }),
60
61
  /* @__PURE__ */ y("div", {
61
62
  className: "flex items-center gap-3 mb-6",
62
- children: [/* @__PURE__ */ v(h, { className: "w-7 h-7 text-accent-blue" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
63
+ children: [/* @__PURE__ */ v(h, { className: "size-7 text-accent-blue" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
63
64
  className: "text-2xl font-bold text-fg",
64
65
  children: E("connection.edit.title", "Edit Connection")
65
66
  }), /* @__PURE__ */ v("p", {
@@ -69,7 +70,7 @@ function b() {
69
70
  }),
70
71
  j && /* @__PURE__ */ y("div", {
71
72
  className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3",
72
- children: [/* @__PURE__ */ v(d, { className: "w-5 h-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
73
+ children: [/* @__PURE__ */ v(d, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
73
74
  className: "text-sm text-status-error-text",
74
75
  children: j
75
76
  })]
@@ -149,6 +150,7 @@ function b() {
149
150
  className: "block text-sm font-medium text-fg mb-2",
150
151
  children: E("connection.field.credentials", "Credentials (JSON)")
151
152
  }), /* @__PURE__ */ v("textarea", {
153
+ "aria-label": E("connection.field.credentialsLeaveBlank", "Leave blank to keep existing credentials"),
152
154
  value: F,
153
155
  onChange: (e) => I(e.target.value),
154
156
  rows: 6,
@@ -156,9 +158,12 @@ function b() {
156
158
  placeholder: E("connection.field.credentialsLeaveBlank", "Leave blank to keep existing credentials")
157
159
  })] }),
158
160
  /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
161
+ htmlFor: "edit-conn-platform-metadata",
159
162
  className: "block text-sm font-medium text-fg mb-2",
160
163
  children: E("connection.field.platformMetadata", "Platform Metadata (JSON)")
161
164
  }), /* @__PURE__ */ v("textarea", {
165
+ id: "edit-conn-platform-metadata",
166
+ "aria-label": E("connection.field.platformMetadata", "Platform Metadata (JSON)"),
162
167
  value: L,
163
168
  onChange: (e) => R(e.target.value),
164
169
  rows: 4,
@@ -170,7 +175,7 @@ function b() {
170
175
  type: "submit",
171
176
  disabled: z,
172
177
  className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
173
- children: [z ? /* @__PURE__ */ v(p, { className: "w-4 h-4 animate-spin" }) : /* @__PURE__ */ v(m, { className: "w-4 h-4" }), E("common.save", "Save")]
178
+ children: [z ? /* @__PURE__ */ v(p, { className: "size-4 animate-spin" }) : /* @__PURE__ */ v(m, { className: "size-4 " }), E("common.save", "Save")]
174
179
  }), /* @__PURE__ */ v("button", {
175
180
  type: "button",
176
181
  onClick: () => b(-1),
@@ -186,7 +191,7 @@ function b() {
186
191
  className: "container mx-auto p-6 max-w-2xl",
187
192
  children: /* @__PURE__ */ y("div", {
188
193
  className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3",
189
- children: [/* @__PURE__ */ v(d, { className: "w-5 h-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
194
+ children: [/* @__PURE__ */ v(d, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
190
195
  className: "text-sm text-status-error-text",
191
196
  children: j ?? E("connection.notFound", "Connection not found")
192
197
  })]