@burdenoff/microfe-channel 2026.625.2 → 2026.625.3

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 (29) hide show
  1. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +19 -12
  2. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  3. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +407 -353
  4. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  5. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +93 -87
  6. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  7. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +154 -134
  8. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  9. package/dist/channel/modules/channels/pages/ChannelsListPage.js +279 -244
  10. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  11. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +277 -232
  12. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  13. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +159 -129
  14. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  15. package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -22
  16. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  17. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +72 -65
  18. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  19. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +99 -79
  20. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  21. package/dist/channel/modules/channels/pages/EditChannelPage.js +154 -148
  22. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  23. package/dist/channel/modules/channels/pages/EditConnectionPage.js +69 -62
  24. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  25. package/dist/channel/modules/channels/pages/SendMessagePage.js +131 -124
  26. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  27. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +174 -154
  28. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  29. package/package.json +2 -2
@@ -7,9 +7,9 @@ import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
7
7
  import { AlertCircle as c, ArrowLeft as l, Loader2 as u, Save as d, Shield as f } from "lucide-react";
8
8
  import { useNavigate as p, useSearchParams as m } from "react-router-dom";
9
9
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
10
- import { GlassCard as v } from "@burdenoff/fe-libs/ui";
10
+ import { EmphasisPanel as v, PagePurpose as y } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/channel/modules/channels/pages/CreateConnectionPage.tsx
12
- var y = [
12
+ var b = [
13
13
  {
14
14
  value: "OAUTH2",
15
15
  label: "OAuth 2.0",
@@ -36,11 +36,11 @@ var y = [
36
36
  help: "Mail server credentials"
37
37
  }
38
38
  ];
39
- function b() {
40
- let b = p(), [x] = m(), { organizationId: S } = e(), { channels: C, isLoading: w } = n(), { t: T } = s(), E = (e, t) => {
41
- let n = T(e);
39
+ function x() {
40
+ let x = p(), [S] = m(), { organizationId: C } = e(), { channels: w, isLoading: T } = n(), { t: E } = s(), D = (e, t) => {
41
+ let n = E(e);
42
42
  return n === e ? t : n;
43
- }, [D, O] = o(x.get("channelId") ?? ""), { createConnection: k } = t(D), [A, j] = o("OAUTH2"), [M, N] = o(""), [P, F] = o(""), [I, L] = o(""), [R, z] = o(""), [B, V] = o(!1), [H, U] = o(null), W = a(() => C.find((e) => e.id === D) ?? null, [C, D]);
43
+ }, [O, k] = o(S.get("channelId") ?? ""), { createConnection: A } = t(O), [j, M] = o("OAUTH2"), [N, P] = o(""), [F, I] = o(""), [L, R] = o(""), [z, B] = o(""), [V, H] = o(!1), [U, W] = o(null), G = a(() => w.find((e) => e.id === O) ?? null, [w, O]);
44
44
  return /* @__PURE__ */ g(r, {
45
45
  permission: i.CHANNEL_CONNECTION_CREATE,
46
46
  children: /* @__PURE__ */ _("div", {
@@ -48,61 +48,68 @@ function b() {
48
48
  children: [
49
49
  /* @__PURE__ */ _("button", {
50
50
  type: "button",
51
- onClick: () => b(-1),
51
+ onClick: () => x(-1),
52
52
  className: "flex items-center gap-2 text-fg-muted hover:text-fg mb-6",
53
- children: [/* @__PURE__ */ g(l, { className: "size-4 " }), E("common.back", "Back")]
53
+ children: [/* @__PURE__ */ g(l, { className: "size-4 " }), D("common.back", "Back")]
54
54
  }),
55
55
  /* @__PURE__ */ _("div", {
56
56
  className: "flex items-center gap-3 mb-6",
57
57
  children: [/* @__PURE__ */ g(f, { className: "size-7 text-accent-blue" }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h1", {
58
58
  className: "text-2xl font-bold text-fg",
59
- children: E("connection.create.title", "Create Connection")
59
+ children: D("connection.create.title", "Create Connection")
60
60
  }), /* @__PURE__ */ g("p", {
61
61
  className: "text-sm text-fg-muted",
62
- children: E("connection.create.subtitle", "Authenticate a channel against its platform provider.")
62
+ children: D("connection.create.subtitle", "Authenticate a channel against its platform provider.")
63
63
  })] })]
64
64
  }),
65
- H && /* @__PURE__ */ _("div", {
65
+ /* @__PURE__ */ g(y, {
66
+ icon: /* @__PURE__ */ g(f, {
67
+ size: 15,
68
+ "aria-hidden": "true"
69
+ }),
70
+ className: "mb-6",
71
+ children: D("connection.create.purpose", "Authenticate a channel against its provider so it can actually send. Choose the channel, the auth method (OAuth, API key, bot token, webhook secret or mail credentials) and enter the secrets — they are stored encrypted. Do this once per channel; without a connection a channel can't deliver anything.")
72
+ }),
73
+ U && /* @__PURE__ */ _("div", {
66
74
  className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3",
67
75
  children: [/* @__PURE__ */ g(c, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ g("p", {
68
76
  className: "text-sm text-status-error-text",
69
- children: H
77
+ children: U
70
78
  })]
71
79
  }),
72
80
  /* @__PURE__ */ g(v, {
73
- treatment: "plain",
74
- className: "border-border-seam p-6",
81
+ className: "p-6",
75
82
  children: /* @__PURE__ */ _("form", {
76
83
  onSubmit: async (e) => {
77
- if (e.preventDefault(), U(null), !D) {
78
- U(E("connection.validation.channelRequired", "Pick a channel to connect"));
84
+ if (e.preventDefault(), W(null), !O) {
85
+ W(D("connection.validation.channelRequired", "Pick a channel to connect"));
79
86
  return;
80
87
  }
81
- if (!S) {
82
- U(E("connection.validation.organizationRequired", "Organization context missing"));
88
+ if (!C) {
89
+ W(D("connection.validation.organizationRequired", "Organization context missing"));
83
90
  return;
84
91
  }
85
92
  let t;
86
- if (R.trim()) try {
87
- t = JSON.parse(R);
93
+ if (z.trim()) try {
94
+ t = JSON.parse(z);
88
95
  } catch {
89
- U(E("connection.validation.credentialsJson", "Credentials must be valid JSON"));
96
+ W(D("connection.validation.credentialsJson", "Credentials must be valid JSON"));
90
97
  return;
91
98
  }
92
- V(!0);
99
+ H(!0);
93
100
  try {
94
- let e = await k({
95
- authType: A,
96
- authCode: M.trim() || void 0,
97
- apiKey: P.trim() || void 0,
98
- redirectUri: I.trim() || void 0,
101
+ let e = await A({
102
+ authType: j,
103
+ authCode: N.trim() || void 0,
104
+ apiKey: F.trim() || void 0,
105
+ redirectUri: L.trim() || void 0,
99
106
  credentials: t
100
107
  });
101
- e ? b(`../${e.id}`) : U(E("connection.createFailed", "Failed to create connection"));
108
+ e ? x(`../${e.id}`) : W(D("connection.createFailed", "Failed to create connection"));
102
109
  } catch (e) {
103
- U(e instanceof Error ? e.message : E("connection.createFailed", "Failed to create connection"));
110
+ W(e instanceof Error ? e.message : D("connection.createFailed", "Failed to create connection"));
104
111
  } finally {
105
- V(!1);
112
+ H(!1);
106
113
  }
107
114
  },
108
115
  className: "space-y-6",
@@ -110,18 +117,18 @@ function b() {
110
117
  /* @__PURE__ */ _("div", { children: [
111
118
  /* @__PURE__ */ _("label", {
112
119
  className: "block text-sm font-medium text-fg mb-2",
113
- children: [E("connection.field.channel", "Channel"), " *"]
120
+ children: [D("connection.field.channel", "Channel"), " *"]
114
121
  }),
115
122
  /* @__PURE__ */ _("select", {
116
- value: D,
117
- onChange: (e) => O(e.target.value),
123
+ value: O,
124
+ onChange: (e) => k(e.target.value),
118
125
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
119
126
  required: !0,
120
- disabled: w,
127
+ disabled: T,
121
128
  children: [/* @__PURE__ */ g("option", {
122
129
  value: "",
123
- children: w ? E("common.loading", "Loading...") : E("connection.field.channelPlaceholder", "Select a channel")
124
- }), C.map((e) => /* @__PURE__ */ _("option", {
130
+ children: T ? D("common.loading", "Loading...") : D("connection.field.channelPlaceholder", "Select a channel")
131
+ }), w.map((e) => /* @__PURE__ */ _("option", {
125
132
  value: e.id,
126
133
  children: [
127
134
  e.name,
@@ -131,20 +138,20 @@ function b() {
131
138
  ]
132
139
  }, e.id))]
133
140
  }),
134
- W && /* @__PURE__ */ _("p", {
141
+ G && /* @__PURE__ */ _("p", {
135
142
  className: "mt-1 text-xs text-fg-muted",
136
- children: [E("connection.field.channelHint", "Adapter: "), W.adapterType ?? "—"]
143
+ children: [D("connection.field.channelHint", "Adapter: "), G.adapterType ?? "—"]
137
144
  })
138
145
  ] }),
139
146
  /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
140
147
  className: "block text-sm font-medium text-fg mb-2",
141
- children: [E("connection.field.authType", "Authentication Type"), " *"]
148
+ children: [D("connection.field.authType", "Authentication Type"), " *"]
142
149
  }), /* @__PURE__ */ g("select", {
143
- value: A,
144
- onChange: (e) => j(e.target.value),
150
+ value: j,
151
+ onChange: (e) => M(e.target.value),
145
152
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
146
153
  required: !0,
147
- children: y.map((e) => /* @__PURE__ */ _("option", {
154
+ children: b.map((e) => /* @__PURE__ */ _("option", {
148
155
  value: e.value,
149
156
  children: [
150
157
  e.label,
@@ -153,73 +160,73 @@ function b() {
153
160
  ]
154
161
  }, e.value))
155
162
  })] }),
156
- A === "OAUTH2" && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
163
+ j === "OAUTH2" && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
157
164
  htmlFor: "create-conn-auth-code",
158
165
  className: "block text-sm font-medium text-fg mb-2",
159
- children: E("connection.field.authCode", "OAuth Authorization Code")
166
+ children: D("connection.field.authCode", "OAuth Authorization Code")
160
167
  }), /* @__PURE__ */ g("input", {
161
168
  id: "create-conn-auth-code",
162
- "aria-label": E("connection.field.authCode", "OAuth Authorization Code"),
169
+ "aria-label": D("connection.field.authCode", "OAuth Authorization Code"),
163
170
  type: "text",
164
- value: M,
165
- onChange: (e) => N(e.target.value),
171
+ value: N,
172
+ onChange: (e) => P(e.target.value),
166
173
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
167
174
  autoComplete: "off"
168
175
  })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
169
176
  htmlFor: "create-conn-redirect-uri",
170
177
  className: "block text-sm font-medium text-fg mb-2",
171
- children: E("connection.field.redirectUri", "Redirect URI")
178
+ children: D("connection.field.redirectUri", "Redirect URI")
172
179
  }), /* @__PURE__ */ g("input", {
173
180
  id: "create-conn-redirect-uri",
174
- "aria-label": E("connection.field.redirectUri", "Redirect URI"),
181
+ "aria-label": D("connection.field.redirectUri", "Redirect URI"),
175
182
  type: "url",
176
- value: I,
177
- onChange: (e) => L(e.target.value),
183
+ value: L,
184
+ onChange: (e) => R(e.target.value),
178
185
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg"
179
186
  })] })] }),
180
- (A === "API_KEY" || A === "BOT_TOKEN") && /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
187
+ (j === "API_KEY" || j === "BOT_TOKEN") && /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
181
188
  htmlFor: "create-conn-api-key",
182
189
  className: "block text-sm font-medium text-fg mb-2",
183
- children: E("connection.field.apiKey", "API Key / Token")
190
+ children: D("connection.field.apiKey", "API Key / Token")
184
191
  }), /* @__PURE__ */ g("input", {
185
192
  id: "create-conn-api-key",
186
- "aria-label": E("connection.field.apiKey", "API Key / Token"),
193
+ "aria-label": D("connection.field.apiKey", "API Key / Token"),
187
194
  type: "password",
188
- value: P,
189
- onChange: (e) => F(e.target.value),
195
+ value: F,
196
+ onChange: (e) => I(e.target.value),
190
197
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
191
198
  autoComplete: "new-password"
192
199
  })] }),
193
200
  /* @__PURE__ */ _("div", { children: [
194
201
  /* @__PURE__ */ g("label", {
195
202
  className: "block text-sm font-medium text-fg mb-2",
196
- children: E("connection.field.credentials", "Additional Credentials (JSON)")
203
+ children: D("connection.field.credentials", "Additional Credentials (JSON)")
197
204
  }),
198
205
  /* @__PURE__ */ g("textarea", {
199
206
  "aria-label": "{\"smtpHost\": \"smtp.example.com\"}",
200
- value: R,
201
- onChange: (e) => z(e.target.value),
207
+ value: z,
208
+ onChange: (e) => B(e.target.value),
202
209
  rows: 6,
203
210
  className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg",
204
211
  placeholder: "{\"smtpHost\": \"smtp.example.com\"}"
205
212
  }),
206
213
  /* @__PURE__ */ g("p", {
207
214
  className: "mt-1 text-xs text-fg-muted",
208
- children: E("connection.field.credentialsHint", "Encrypted at rest with AES-256-GCM. Never logged in plaintext.")
215
+ children: D("connection.field.credentialsHint", "Encrypted at rest with AES-256-GCM. Never logged in plaintext.")
209
216
  })
210
217
  ] }),
211
218
  /* @__PURE__ */ _("div", {
212
219
  className: "flex gap-3 pt-2",
213
220
  children: [/* @__PURE__ */ _("button", {
214
221
  type: "submit",
215
- disabled: B || !D,
222
+ disabled: V || !O,
216
223
  className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
217
- children: [B ? /* @__PURE__ */ g(u, { className: "size-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "size-4 " }), E("connection.create.submit", "Create Connection")]
224
+ children: [V ? /* @__PURE__ */ g(u, { className: "size-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "size-4 " }), D("connection.create.submit", "Create Connection")]
218
225
  }), /* @__PURE__ */ g("button", {
219
226
  type: "button",
220
- onClick: () => b(-1),
227
+ onClick: () => x(-1),
221
228
  className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
222
- children: E("common.cancel", "Cancel")
229
+ children: D("common.cancel", "Cancel")
223
230
  })]
224
231
  })
225
232
  ]
@@ -230,6 +237,6 @@ function b() {
230
237
  });
231
238
  }
232
239
  //#endregion
233
- export { b as CreateConnectionPage };
240
+ export { x as CreateConnectionPage };
234
241
 
235
242
  //# sourceMappingURL=CreateConnectionPage.js.map
@@ -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 { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\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 <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: ')}\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 </GlassCard>\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 { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\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 <PagePurpose icon={<Shield size={15} aria-hidden=\"true\" />} className=\"mb-6\">\n {tr(\n 'connection.create.purpose',\n \"Authenticate a channel against its provider so it can actually send. Choose the channel, the auth method (OAuth, API key, bot token, webhook secret or mail credentials) and enter the secrets — they are stored encrypted. Do this once per channel; without a connection a channel can't deliver anything.\"\n )}\n </PagePurpose>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <EmphasisPanel className=\"p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: ')}\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 </EmphasisPanel>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}