@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.
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +19 -12
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +407 -353
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +93 -87
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +154 -134
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +279 -244
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +277 -232
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +159 -129
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -22
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +72 -65
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +99 -79
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +154 -148
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +69 -62
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +131 -124
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +174 -154
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- 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 {
|
|
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
|
|
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
|
|
40
|
-
let
|
|
41
|
-
let n =
|
|
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
|
-
}, [
|
|
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: () =>
|
|
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 " }),
|
|
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:
|
|
59
|
+
children: D("connection.create.title", "Create Connection")
|
|
60
60
|
}), /* @__PURE__ */ g("p", {
|
|
61
61
|
className: "text-sm text-fg-muted",
|
|
62
|
-
children:
|
|
62
|
+
children: D("connection.create.subtitle", "Authenticate a channel against its platform provider.")
|
|
63
63
|
})] })]
|
|
64
64
|
}),
|
|
65
|
-
|
|
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:
|
|
77
|
+
children: U
|
|
70
78
|
})]
|
|
71
79
|
}),
|
|
72
80
|
/* @__PURE__ */ g(v, {
|
|
73
|
-
|
|
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(),
|
|
78
|
-
|
|
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 (!
|
|
82
|
-
|
|
88
|
+
if (!C) {
|
|
89
|
+
W(D("connection.validation.organizationRequired", "Organization context missing"));
|
|
83
90
|
return;
|
|
84
91
|
}
|
|
85
92
|
let t;
|
|
86
|
-
if (
|
|
87
|
-
t = JSON.parse(
|
|
93
|
+
if (z.trim()) try {
|
|
94
|
+
t = JSON.parse(z);
|
|
88
95
|
} catch {
|
|
89
|
-
|
|
96
|
+
W(D("connection.validation.credentialsJson", "Credentials must be valid JSON"));
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
|
-
|
|
99
|
+
H(!0);
|
|
93
100
|
try {
|
|
94
|
-
let e = await
|
|
95
|
-
authType:
|
|
96
|
-
authCode:
|
|
97
|
-
apiKey:
|
|
98
|
-
redirectUri:
|
|
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 ?
|
|
108
|
+
e ? x(`../${e.id}`) : W(D("connection.createFailed", "Failed to create connection"));
|
|
102
109
|
} catch (e) {
|
|
103
|
-
|
|
110
|
+
W(e instanceof Error ? e.message : D("connection.createFailed", "Failed to create connection"));
|
|
104
111
|
} finally {
|
|
105
|
-
|
|
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: [
|
|
120
|
+
children: [D("connection.field.channel", "Channel"), " *"]
|
|
114
121
|
}),
|
|
115
122
|
/* @__PURE__ */ _("select", {
|
|
116
|
-
value:
|
|
117
|
-
onChange: (e) =>
|
|
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:
|
|
127
|
+
disabled: T,
|
|
121
128
|
children: [/* @__PURE__ */ g("option", {
|
|
122
129
|
value: "",
|
|
123
|
-
children:
|
|
124
|
-
}),
|
|
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
|
-
|
|
141
|
+
G && /* @__PURE__ */ _("p", {
|
|
135
142
|
className: "mt-1 text-xs text-fg-muted",
|
|
136
|
-
children: [
|
|
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: [
|
|
148
|
+
children: [D("connection.field.authType", "Authentication Type"), " *"]
|
|
142
149
|
}), /* @__PURE__ */ g("select", {
|
|
143
|
-
value:
|
|
144
|
-
onChange: (e) =>
|
|
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:
|
|
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
|
-
|
|
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:
|
|
166
|
+
children: D("connection.field.authCode", "OAuth Authorization Code")
|
|
160
167
|
}), /* @__PURE__ */ g("input", {
|
|
161
168
|
id: "create-conn-auth-code",
|
|
162
|
-
"aria-label":
|
|
169
|
+
"aria-label": D("connection.field.authCode", "OAuth Authorization Code"),
|
|
163
170
|
type: "text",
|
|
164
|
-
value:
|
|
165
|
-
onChange: (e) =>
|
|
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:
|
|
178
|
+
children: D("connection.field.redirectUri", "Redirect URI")
|
|
172
179
|
}), /* @__PURE__ */ g("input", {
|
|
173
180
|
id: "create-conn-redirect-uri",
|
|
174
|
-
"aria-label":
|
|
181
|
+
"aria-label": D("connection.field.redirectUri", "Redirect URI"),
|
|
175
182
|
type: "url",
|
|
176
|
-
value:
|
|
177
|
-
onChange: (e) =>
|
|
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
|
-
(
|
|
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:
|
|
190
|
+
children: D("connection.field.apiKey", "API Key / Token")
|
|
184
191
|
}), /* @__PURE__ */ g("input", {
|
|
185
192
|
id: "create-conn-api-key",
|
|
186
|
-
"aria-label":
|
|
193
|
+
"aria-label": D("connection.field.apiKey", "API Key / Token"),
|
|
187
194
|
type: "password",
|
|
188
|
-
value:
|
|
189
|
-
onChange: (e) =>
|
|
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:
|
|
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:
|
|
201
|
-
onChange: (e) =>
|
|
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:
|
|
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:
|
|
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: [
|
|
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: () =>
|
|
227
|
+
onClick: () => x(-1),
|
|
221
228
|
className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
|
|
222
|
-
children:
|
|
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 {
|
|
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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"}
|