@burdenoff/microfe-channel 2026.805.1 → 2026.830.2

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 (50) hide show
  1. package/dist/channel/ChannelRoot.d.ts.map +1 -1
  2. package/dist/channel/ChannelRoot.js +32 -35
  3. package/dist/channel/ChannelRoot.js.map +1 -1
  4. package/dist/channel/components/AdapterCredentialsForm.d.ts.map +1 -1
  5. package/dist/channel/components/AdapterCredentialsForm.js +52 -58
  6. package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
  7. package/dist/channel/components/FeatureNotEnabled.d.ts.map +1 -1
  8. package/dist/channel/components/FeatureNotEnabled.js +27 -22
  9. package/dist/channel/components/FeatureNotEnabled.js.map +1 -1
  10. package/dist/channel/components/MarkdownEditor.d.ts.map +1 -1
  11. package/dist/channel/components/MarkdownEditor.js +78 -77
  12. package/dist/channel/components/MarkdownEditor.js.map +1 -1
  13. package/dist/channel/components/PermissionGuard.d.ts.map +1 -1
  14. package/dist/channel/components/PermissionGuard.js +6 -9
  15. package/dist/channel/components/PermissionGuard.js.map +1 -1
  16. package/dist/channel/hooks/useTr.d.ts +17 -0
  17. package/dist/channel/hooks/useTr.d.ts.map +1 -0
  18. package/dist/channel/hooks/useTr.js +14 -0
  19. package/dist/channel/hooks/useTr.js.map +1 -0
  20. package/dist/channel/modules/channels/i18n.js +24 -24
  21. package/dist/channel/modules/channels/i18n.js.map +1 -1
  22. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +2 -2
  23. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  24. package/dist/channel/modules/channels/pages/ChannelSettingsPage.d.ts.map +1 -1
  25. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +232 -226
  26. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  27. package/dist/channel/modules/channels/pages/ChannelUsersPage.d.ts.map +1 -1
  28. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +152 -146
  29. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  30. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.d.ts.map +1 -1
  31. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +112 -114
  32. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  33. package/dist/channel/modules/channels/pages/CreateChannelPage.d.ts.map +1 -1
  34. package/dist/channel/modules/channels/pages/CreateChannelPage.js +300 -269
  35. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  36. package/dist/channel/modules/channels/pages/CreateConnectionPage.d.ts.map +1 -1
  37. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +108 -102
  38. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  39. package/dist/channel/modules/channels/pages/EditChannelPage.d.ts.map +1 -1
  40. package/dist/channel/modules/channels/pages/EditChannelPage.js +384 -343
  41. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  42. package/dist/channel/modules/channels/pages/SendMessagePage.d.ts.map +1 -1
  43. package/dist/channel/modules/channels/pages/SendMessagePage.js +170 -156
  44. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  45. package/dist/channel/pages/ChannelLandingPage.d.ts.map +1 -1
  46. package/dist/channel/pages/ChannelLandingPage.js +47 -50
  47. package/dist/channel/pages/ChannelLandingPage.js.map +1 -1
  48. package/dist/translations/en.d.ts +271 -4
  49. package/dist/translations/en.d.ts.map +1 -1
  50. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
- import { useRef as e, useState as t } from "react";
2
- import { Bold as n, Code as r, Italic as i, Link as a, List as o } from "lucide-react";
3
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
4
- import { Markdown as l } from "@burdenoff/fe-libs/shared/components/Markdown";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { useRef as t, useState as n } from "react";
3
+ import { Bold as r, Code as i, Italic as a, Link as o, List as s } from "lucide-react";
4
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
+ import { Markdown as u } from "@burdenoff/fe-libs/shared/components/Markdown";
5
6
  //#region src/channel/components/MarkdownEditor.tsx
6
- var u = (e, t, n) => (r, i, a) => {
7
+ var d = (e, t, n) => (r, i, a) => {
7
8
  let o = r.slice(0, i), s = r.slice(i, a), c = r.slice(a);
8
9
  if (s.length === 0) {
9
10
  let r = `${e}${n}${t}`, a = i + e.length, s = a + n.length;
@@ -19,14 +20,14 @@ var u = (e, t, n) => (r, i, a) => {
19
20
  cursorStart: u,
20
21
  cursorEnd: u
21
22
  };
22
- }, d = (e, t, n) => {
23
+ }, f = (e, t, n) => {
23
24
  let r = e.slice(0, t), i = e.slice(t, n), a = e.slice(n), o = `[${i.length > 0 ? i : "text"}](https://example.com)`, s = t + o.length - 1, c = s + 1;
24
25
  return {
25
26
  nextValue: `${r}${o}${a}`,
26
27
  cursorStart: s,
27
28
  cursorEnd: c
28
29
  };
29
- }, f = (e, t, n) => {
30
+ }, p = (e, t, n) => {
30
31
  let r = e.slice(0, t), i = e.slice(t, n), a = e.slice(n);
31
32
  if (i.length === 0) {
32
33
  let e = t + 2;
@@ -43,123 +44,123 @@ var u = (e, t, n) => (r, i, a) => {
43
44
  cursorEnd: t + o.length
44
45
  };
45
46
  };
46
- function p({ value: p, onChange: m, label: h, placeholder: g, rows: _ = 5, "aria-label": v, className: y = "", textareaClassName: b = "", id: x }) {
47
- let [S, C] = t("write"), w = e(null), T = (e) => {
48
- let t = w.current;
47
+ function m({ value: m, onChange: h, label: g, placeholder: _, rows: v = 5, "aria-label": y, className: b = "", textareaClassName: x = "", id: S }) {
48
+ let [C, w] = n("write"), T = t(null), E = e(), D = (e) => {
49
+ let t = T.current;
49
50
  if (!t) return;
50
- let { nextValue: n, cursorStart: r, cursorEnd: i } = e(p, t.selectionStart ?? 0, t.selectionEnd ?? 0);
51
- m(n), requestAnimationFrame(() => {
51
+ let { nextValue: n, cursorStart: r, cursorEnd: i } = e(m, t.selectionStart ?? 0, t.selectionEnd ?? 0);
52
+ h(n), requestAnimationFrame(() => {
52
53
  t.focus(), t.setSelectionRange(r, i);
53
54
  });
54
- }, E = (e) => {
55
+ }, O = (e) => {
55
56
  switch (e) {
56
57
  case "bold":
57
- T(u("**", "**", "bold text"));
58
+ D(d("**", "**", "bold text"));
58
59
  break;
59
60
  case "italic":
60
- T(u("*", "*", "italic text"));
61
+ D(d("*", "*", "italic text"));
61
62
  break;
62
63
  case "link":
63
- T(d);
64
+ D(f);
64
65
  break;
65
66
  case "code":
66
- T(u("`", "`", "code"));
67
+ D(d("`", "`", "code"));
67
68
  break;
68
69
  case "list":
69
- T(f);
70
+ D(p);
70
71
  break;
71
72
  }
72
73
  };
73
- return /* @__PURE__ */ c("div", {
74
+ return /* @__PURE__ */ l("div", {
74
75
  className: "space-y-2",
75
- children: [h && /* @__PURE__ */ s("label", {
76
- htmlFor: x,
76
+ children: [g && /* @__PURE__ */ c("label", {
77
+ htmlFor: S,
77
78
  className: "text-sm font-medium text-text-primary",
78
- children: h
79
- }), /* @__PURE__ */ c("div", {
80
- className: `border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary ${y}`,
81
- children: [/* @__PURE__ */ c("div", {
79
+ children: g
80
+ }), /* @__PURE__ */ l("div", {
81
+ className: `border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary ${b}`,
82
+ children: [/* @__PURE__ */ l("div", {
82
83
  className: "flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1",
83
- children: [/* @__PURE__ */ c("div", {
84
+ children: [/* @__PURE__ */ l("div", {
84
85
  className: "flex items-center gap-1",
85
86
  children: [
86
- /* @__PURE__ */ s("button", {
87
+ /* @__PURE__ */ c("button", {
87
88
  type: "button",
88
- onClick: () => E("bold"),
89
- "aria-label": "Bold",
90
- title: "Bold",
89
+ onClick: () => O("bold"),
90
+ "aria-label": E("channel.markdown.bold", "Bold"),
91
+ title: E("channel.markdown.bold", "Bold"),
91
92
  className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
92
- children: /* @__PURE__ */ s(n, { className: "size-4" })
93
+ children: /* @__PURE__ */ c(r, { className: "size-4" })
93
94
  }),
94
- /* @__PURE__ */ s("button", {
95
+ /* @__PURE__ */ c("button", {
95
96
  type: "button",
96
- onClick: () => E("italic"),
97
- "aria-label": "Italic",
98
- title: "Italic",
97
+ onClick: () => O("italic"),
98
+ "aria-label": E("channel.markdown.italic", "Italic"),
99
+ title: E("channel.markdown.italic", "Italic"),
99
100
  className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
100
- children: /* @__PURE__ */ s(i, { className: "size-4" })
101
+ children: /* @__PURE__ */ c(a, { className: "size-4" })
101
102
  }),
102
- /* @__PURE__ */ s("button", {
103
+ /* @__PURE__ */ c("button", {
103
104
  type: "button",
104
- onClick: () => E("link"),
105
- "aria-label": "Link",
106
- title: "Link",
105
+ onClick: () => O("link"),
106
+ "aria-label": E("channel.markdown.link", "Link"),
107
+ title: E("channel.markdown.link", "Link"),
107
108
  className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
108
- children: /* @__PURE__ */ s(a, { className: "size-4" })
109
+ children: /* @__PURE__ */ c(o, { className: "size-4" })
109
110
  }),
110
- /* @__PURE__ */ s("button", {
111
+ /* @__PURE__ */ c("button", {
111
112
  type: "button",
112
- onClick: () => E("code"),
113
- "aria-label": "Code",
114
- title: "Code",
113
+ onClick: () => O("code"),
114
+ "aria-label": E("channel.markdown.code", "Code"),
115
+ title: E("channel.markdown.code", "Code"),
115
116
  className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
116
- children: /* @__PURE__ */ s(r, { className: "size-4" })
117
+ children: /* @__PURE__ */ c(i, { className: "size-4" })
117
118
  }),
118
- /* @__PURE__ */ s("button", {
119
+ /* @__PURE__ */ c("button", {
119
120
  type: "button",
120
- onClick: () => E("list"),
121
- "aria-label": "Bullet list",
122
- title: "Bullet list",
121
+ onClick: () => O("list"),
122
+ "aria-label": E("channel.markdown.bulletList", "Bullet list"),
123
+ title: E("channel.markdown.bulletList", "Bullet list"),
123
124
  className: "p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors",
124
- children: /* @__PURE__ */ s(o, { className: "size-4" })
125
+ children: /* @__PURE__ */ c(s, { className: "size-4" })
125
126
  })
126
127
  ]
127
- }), /* @__PURE__ */ c("div", {
128
+ }), /* @__PURE__ */ l("div", {
128
129
  className: "flex items-center gap-1",
129
130
  role: "tablist",
130
- "aria-label": "Markdown editor tabs",
131
- children: [/* @__PURE__ */ s("button", {
131
+ "aria-label": E("channel.markdown.tabsLabel", "Markdown editor tabs"),
132
+ children: [/* @__PURE__ */ c("button", {
132
133
  type: "button",
133
134
  role: "tab",
134
- "aria-selected": S === "write",
135
- onClick: () => C("write"),
136
- className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${S === "write" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
137
- children: "Write"
138
- }), /* @__PURE__ */ s("button", {
135
+ "aria-selected": C === "write",
136
+ onClick: () => w("write"),
137
+ className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${C === "write" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
138
+ children: E("channel.markdown.write", "Write")
139
+ }), /* @__PURE__ */ c("button", {
139
140
  type: "button",
140
141
  role: "tab",
141
- "aria-selected": S === "preview",
142
- onClick: () => C("preview"),
143
- className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${S === "preview" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
144
- children: "Preview"
142
+ "aria-selected": C === "preview",
143
+ onClick: () => w("preview"),
144
+ className: `px-3 py-1 text-xs font-medium rounded-md transition-colors ${C === "preview" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary hover:bg-bg-surface"}`,
145
+ children: E("channel.markdown.preview", "Preview")
145
146
  })]
146
147
  })]
147
- }), /* @__PURE__ */ s("div", {
148
+ }), /* @__PURE__ */ c("div", {
148
149
  className: "relative",
149
- children: S === "write" ? /* @__PURE__ */ s("textarea", {
150
- ref: w,
151
- id: x,
152
- "aria-label": v,
153
- value: p,
154
- onChange: (e) => m(e.target.value),
155
- placeholder: g,
156
- rows: _,
157
- className: `w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none ${b}`
158
- }) : /* @__PURE__ */ s("div", {
150
+ children: C === "write" ? /* @__PURE__ */ c("textarea", {
151
+ ref: T,
152
+ id: S,
153
+ "aria-label": y,
154
+ value: m,
155
+ onChange: (e) => h(e.target.value),
156
+ placeholder: _,
157
+ rows: v,
158
+ className: `w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none ${x}`
159
+ }) : /* @__PURE__ */ c("div", {
159
160
  className: "min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary",
160
- children: p.trim() ? /* @__PURE__ */ s(l, { children: p }) : /* @__PURE__ */ s("p", {
161
+ children: m.trim() ? /* @__PURE__ */ c(u, { children: m }) : /* @__PURE__ */ c("p", {
161
162
  className: "text-text-secondary italic",
162
- children: g || "Nothing to preview yet."
163
+ children: _ || E("channel.markdown.nothingToPreview", "Nothing to preview yet.")
163
164
  })
164
165
  })
165
166
  })]
@@ -167,6 +168,6 @@ function p({ value: p, onChange: m, label: h, placeholder: g, rows: _ = 5, "aria
167
168
  });
168
169
  }
169
170
  //#endregion
170
- export { p as MarkdownEditor };
171
+ export { m as MarkdownEditor };
171
172
 
172
173
  //# sourceMappingURL=MarkdownEditor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/channel/components/MarkdownEditor.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components/Markdown';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface MarkdownEditorProps {\n /** Current markdown value. */\n value: string;\n /** Called whenever the markdown value changes. */\n onChange: (value: string) => void;\n /** Optional visible label. */\n label?: string;\n /** Placeholder for the write tab textarea. */\n placeholder?: string;\n /** Approximate visible rows for the textarea. */\n rows?: number;\n /** Accessible label for the textarea (used when no visible label). */\n 'aria-label'?: string;\n /** Additional classes for the outer container. */\n className?: string;\n /** Additional classes for the textarea. */\n textareaClassName?: string;\n /** Unique id, forwarded to the textarea. */\n id?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\ntype InsertAction = (\n value: string,\n selectionStart: number,\n selectionEnd: number\n) => { nextValue: string; cursorStart: number; cursorEnd: number };\n\nconst wrapWith = (prefix: string, suffix: string, fallback: string): InsertAction => {\n return (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = `${prefix}${fallback}${suffix}`;\n const cursorStart = start + prefix.length;\n const cursorEnd = cursorStart + fallback.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n }\n\n const wrapped = `${prefix}${selected}${suffix}`;\n const cursorStart = start + wrapped.length;\n return { nextValue: `${before}${wrapped}${after}`, cursorStart, cursorEnd: cursorStart };\n };\n};\n\nconst insertLink: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n const linkText = selected.length > 0 ? selected : 'text';\n const insertion = `[${linkText}](https://example.com)`;\n const cursorStart = start + insertion.length - 1;\n const cursorEnd = cursorStart + 1;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n};\n\nconst insertList: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = '- ';\n const cursorStart = start + insertion.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd: cursorStart };\n }\n\n const lines = selected.split('\\n');\n const listified = lines.map((line) => (line.startsWith('- ') ? line : `- ${line}`)).join('\\n');\n const nextValue = `${before}${listified}${after}`;\n const cursorStart = start;\n const cursorEnd = start + listified.length;\n return { nextValue, cursorStart, cursorEnd };\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function MarkdownEditor({\n value,\n onChange,\n label,\n placeholder,\n rows = 5,\n 'aria-label': ariaLabel,\n className = '',\n textareaClassName = '',\n id,\n}: MarkdownEditorProps) {\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const apply = (action: InsertAction) => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n\n const start = textarea.selectionStart ?? 0;\n const end = textarea.selectionEnd ?? 0;\n const { nextValue, cursorStart, cursorEnd } = action(value, start, end);\n\n onChange(nextValue);\n\n // Re-focus and restore selection after the next render has applied the value.\n requestAnimationFrame(() => {\n textarea.focus();\n textarea.setSelectionRange(cursorStart, cursorEnd);\n });\n };\n\n const handleToolbarAction = (action: 'bold' | 'italic' | 'link' | 'code' | 'list') => {\n switch (action) {\n case 'bold':\n apply(wrapWith('**', '**', 'bold text'));\n break;\n case 'italic':\n apply(wrapWith('*', '*', 'italic text'));\n break;\n case 'link':\n apply(insertLink);\n break;\n case 'code':\n apply(wrapWith('`', '`', 'code'));\n break;\n case 'list':\n apply(insertList);\n break;\n }\n };\n\n const containerBase =\n 'border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary';\n const textareaBase =\n 'w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none';\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"text-sm font-medium text-text-primary\">\n {label}\n </label>\n )}\n <div className={`${containerBase} ${className}`}>\n <div className=\"flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('bold')}\n aria-label=\"Bold\"\n title=\"Bold\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Bold className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('italic')}\n aria-label=\"Italic\"\n title=\"Italic\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Italic className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('link')}\n aria-label=\"Link\"\n title=\"Link\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Link className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('code')}\n aria-label=\"Code\"\n title=\"Code\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Code className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('list')}\n aria-label=\"Bullet list\"\n title=\"Bullet list\"\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <List className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"flex items-center gap-1\" role=\"tablist\" aria-label=\"Markdown editor tabs\">\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'write'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n Write\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'preview'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n Preview\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n {activeTab === 'write' ? (\n <textarea\n ref={textareaRef}\n id={id}\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n rows={rows}\n className={`${textareaBase} ${textareaClassName}`}\n />\n ) : (\n <div className=\"min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary\">\n {value.trim() ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">\n {placeholder || 'Nothing to preview yet.'}\n </p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MarkdownEditor.js","names":[],"sources":["../../../src/channel/components/MarkdownEditor.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport { Bold, Italic, Link, Code, List } from 'lucide-react';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components/Markdown';\nimport { useTr } from '../hooks/useTr';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface MarkdownEditorProps {\n /** Current markdown value. */\n value: string;\n /** Called whenever the markdown value changes. */\n onChange: (value: string) => void;\n /** Optional visible label. */\n label?: string;\n /** Placeholder for the write tab textarea. */\n placeholder?: string;\n /** Approximate visible rows for the textarea. */\n rows?: number;\n /** Accessible label for the textarea (used when no visible label). */\n 'aria-label'?: string;\n /** Additional classes for the outer container. */\n className?: string;\n /** Additional classes for the textarea. */\n textareaClassName?: string;\n /** Unique id, forwarded to the textarea. */\n id?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\ntype InsertAction = (\n value: string,\n selectionStart: number,\n selectionEnd: number\n) => { nextValue: string; cursorStart: number; cursorEnd: number };\n\nconst wrapWith = (prefix: string, suffix: string, fallback: string): InsertAction => {\n return (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = `${prefix}${fallback}${suffix}`;\n const cursorStart = start + prefix.length;\n const cursorEnd = cursorStart + fallback.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n }\n\n const wrapped = `${prefix}${selected}${suffix}`;\n const cursorStart = start + wrapped.length;\n return { nextValue: `${before}${wrapped}${after}`, cursorStart, cursorEnd: cursorStart };\n };\n};\n\nconst insertLink: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n const linkText = selected.length > 0 ? selected : 'text';\n const insertion = `[${linkText}](https://example.com)`;\n const cursorStart = start + insertion.length - 1;\n const cursorEnd = cursorStart + 1;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd };\n};\n\nconst insertList: InsertAction = (value, start, end) => {\n const before = value.slice(0, start);\n const selected = value.slice(start, end);\n const after = value.slice(end);\n\n if (selected.length === 0) {\n const insertion = '- ';\n const cursorStart = start + insertion.length;\n return { nextValue: `${before}${insertion}${after}`, cursorStart, cursorEnd: cursorStart };\n }\n\n const lines = selected.split('\\n');\n const listified = lines.map((line) => (line.startsWith('- ') ? line : `- ${line}`)).join('\\n');\n const nextValue = `${before}${listified}${after}`;\n const cursorStart = start;\n const cursorEnd = start + listified.length;\n return { nextValue, cursorStart, cursorEnd };\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function MarkdownEditor({\n value,\n onChange,\n label,\n placeholder,\n rows = 5,\n 'aria-label': ariaLabel,\n className = '',\n textareaClassName = '',\n id,\n}: MarkdownEditorProps) {\n const [activeTab, setActiveTab] = useState<'write' | 'preview'>('write');\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const tr = useTr();\n\n const apply = (action: InsertAction) => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n\n const start = textarea.selectionStart ?? 0;\n const end = textarea.selectionEnd ?? 0;\n const { nextValue, cursorStart, cursorEnd } = action(value, start, end);\n\n onChange(nextValue);\n\n // Re-focus and restore selection after the next render has applied the value.\n requestAnimationFrame(() => {\n textarea.focus();\n textarea.setSelectionRange(cursorStart, cursorEnd);\n });\n };\n\n const handleToolbarAction = (action: 'bold' | 'italic' | 'link' | 'code' | 'list') => {\n switch (action) {\n case 'bold':\n apply(wrapWith('**', '**', 'bold text'));\n break;\n case 'italic':\n apply(wrapWith('*', '*', 'italic text'));\n break;\n case 'link':\n apply(insertLink);\n break;\n case 'code':\n apply(wrapWith('`', '`', 'code'));\n break;\n case 'list':\n apply(insertList);\n break;\n }\n };\n\n const containerBase =\n 'border border-border-subtle rounded-lg overflow-hidden bg-bg-surface focus-within:ring-2 focus-within:ring-primary';\n const textareaBase =\n 'w-full px-4 py-2 bg-bg-surface text-text-primary placeholder:text-text-secondary resize-none focus:outline-none';\n\n return (\n <div className=\"space-y-2\">\n {label && (\n <label htmlFor={id} className=\"text-sm font-medium text-text-primary\">\n {label}\n </label>\n )}\n <div className={`${containerBase} ${className}`}>\n <div className=\"flex items-center justify-between border-b border-border-subtle bg-bg-sunken px-2 py-1\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('bold')}\n aria-label={tr('channel.markdown.bold', 'Bold')}\n title={tr('channel.markdown.bold', 'Bold')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Bold className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('italic')}\n aria-label={tr('channel.markdown.italic', 'Italic')}\n title={tr('channel.markdown.italic', 'Italic')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Italic className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('link')}\n aria-label={tr('channel.markdown.link', 'Link')}\n title={tr('channel.markdown.link', 'Link')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Link className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('code')}\n aria-label={tr('channel.markdown.code', 'Code')}\n title={tr('channel.markdown.code', 'Code')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <Code className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleToolbarAction('list')}\n aria-label={tr('channel.markdown.bulletList', 'Bullet list')}\n title={tr('channel.markdown.bulletList', 'Bullet list')}\n className=\"p-1.5 rounded-md text-text-secondary hover:text-text-primary hover:bg-bg-surface transition-colors\"\n >\n <List className=\"size-4\" />\n </button>\n </div>\n\n <div\n className=\"flex items-center gap-1\"\n role=\"tablist\"\n aria-label={tr('channel.markdown.tabsLabel', 'Markdown editor tabs')}\n >\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'write'}\n onClick={() => setActiveTab('write')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'write'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n {tr('channel.markdown.write', 'Write')}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === 'preview'}\n onClick={() => setActiveTab('preview')}\n className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${\n activeTab === 'preview'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface'\n }`}\n >\n {tr('channel.markdown.preview', 'Preview')}\n </button>\n </div>\n </div>\n\n <div className=\"relative\">\n {activeTab === 'write' ? (\n <textarea\n ref={textareaRef}\n id={id}\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n rows={rows}\n className={`${textareaBase} ${textareaClassName}`}\n />\n ) : (\n <div className=\"min-h-[theme(spacing.32)] max-h-96 overflow-auto px-4 py-2 text-sm text-text-primary\">\n {value.trim() ? (\n <Markdown>{value}</Markdown>\n ) : (\n <p className=\"text-text-secondary italic\">\n {placeholder ||\n tr('channel.markdown.nothingToPreview', 'Nothing to preview yet.')}\n </p>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionGuard.d.ts","sourceRoot":"","sources":["../../../src/channel/components/PermissionGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,UAAU,oBAAoB;IAC5B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CA0BpD,CAAC"}
1
+ {"version":3,"file":"PermissionGuard.d.ts","sourceRoot":"","sources":["../../../src/channel/components/PermissionGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,UAAU,oBAAoB;IAC5B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAsBpD,CAAC"}
@@ -1,20 +1,17 @@
1
- import { AccessDenied as e } from "@burdenoff/fe-libs/shared/components";
2
- import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import { useTr as e } from "../hooks/useTr.js";
2
+ import { AccessDenied as t } from "@burdenoff/fe-libs/shared/components";
3
3
  import { Loader2 as n } from "lucide-react";
4
4
  import { usePermissions as r } from "@burdenoff/fe-libs/shared/providers/shell";
5
5
  import { Fragment as i, jsx as a } from "react/jsx-runtime";
6
6
  //#region src/channel/components/PermissionGuard.tsx
7
7
  var o = ({ permission: o, children: s }) => {
8
- let { hasPermission: c, isLoading: l } = r(), { t: u } = t(), d = (e, t) => {
9
- let n = u(e);
10
- return n === e ? t : n;
11
- };
8
+ let { hasPermission: c, isLoading: l } = r(), u = e();
12
9
  return l ? /* @__PURE__ */ a("div", {
13
10
  className: "flex min-h-screen items-center justify-center bg-bg-canvas",
14
11
  children: /* @__PURE__ */ a(n, { className: "size-8 animate-spin text-primary" })
15
- }) : c(o) ? /* @__PURE__ */ a(i, { children: s }) : /* @__PURE__ */ a(e, {
16
- title: d("channel.accessDenied", "Access Denied"),
17
- description: d("channel.noPermissionView", "You don't have permission to view this page.")
12
+ }) : c(o) ? /* @__PURE__ */ a(i, { children: s }) : /* @__PURE__ */ a(t, {
13
+ title: u("channel.accessDenied", "Access Denied"),
14
+ description: u("channel.noPermissionView", "You don't have permission to view this page.")
18
15
  });
19
16
  };
20
17
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionGuard.js","names":[],"sources":["../../../src/channel/components/PermissionGuard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { ChannelPermission } from '../constants/permissions';\n\ninterface PermissionGuardProps {\n permission: ChannelPermission;\n children: ReactNode;\n}\n\n/**\n * Wraps page content with a permission check.\n * Shows a loading spinner while permissions resolve, then either\n * renders children (if permitted) or an AccessDenied screen.\n */\nexport const PermissionGuard: FC<PermissionGuardProps> = ({ permission, children }) => {\n const { hasPermission, isLoading } = usePermissions();\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 if (isLoading) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-bg-canvas\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n }\n\n if (!hasPermission(permission)) {\n return (\n <AccessDenied\n title={tr('channel.accessDenied', 'Access Denied')}\n description={tr('channel.noPermissionView', \"You don't have permission to view this page.\")}\n />\n );\n }\n\n return <>{children}</>;\n};\n"],"mappings":";;;;;;AAiBA,IAAa,KAA6C,EAAE,eAAY,kBAAe;CACrF,IAAM,EAAE,kBAAe,iBAAc,GAAgB,EAC/C,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;;AAoBpC,QAjBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA,GAIL,EAAc,EAAW,GASvB,kBAAA,GAAA,EAAG,aAAY,CAAA,GAPlB,kBAAC,GAAD;EACE,OAAO,EAAG,wBAAwB,gBAAgB;EAClD,aAAa,EAAG,4BAA4B,+CAA+C;EAC3F,CAAA"}
1
+ {"version":3,"file":"PermissionGuard.js","names":[],"sources":["../../../src/channel/components/PermissionGuard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { useTr } from '../hooks/useTr';\nimport type { ChannelPermission } from '../constants/permissions';\n\ninterface PermissionGuardProps {\n permission: ChannelPermission;\n children: ReactNode;\n}\n\n/**\n * Wraps page content with a permission check.\n * Shows a loading spinner while permissions resolve, then either\n * renders children (if permitted) or an AccessDenied screen.\n */\nexport const PermissionGuard: FC<PermissionGuardProps> = ({ permission, children }) => {\n const { hasPermission, isLoading } = usePermissions();\n const tr = useTr();\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-screen items-center justify-center bg-bg-canvas\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n </div>\n );\n }\n\n if (!hasPermission(permission)) {\n return (\n <AccessDenied\n title={tr('channel.accessDenied', 'Access Denied')}\n description={tr('channel.noPermissionView', \"You don't have permission to view this page.\")}\n />\n );\n }\n\n return <>{children}</>;\n};\n"],"mappings":";;;;;;AAiBA,IAAa,KAA6C,EAAE,eAAY,kBAAe;CACrF,IAAM,EAAE,kBAAe,iBAAc,GAAgB,EAC/C,IAAK,GAAO;AAmBlB,QAjBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,oCAAqC,CAAA;EACpD,CAAA,GAIL,EAAc,EAAW,GASvB,kBAAA,GAAA,EAAG,aAAY,CAAA,GAPlB,kBAAC,GAAD;EACE,OAAO,EAAG,wBAAwB,gBAAgB;EAClD,aAAa,EAAG,4BAA4B,+CAA+C;EAC3F,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * useTr — translation hook with fallback semantics.
3
+ *
4
+ * Wraps `useI18n().t()` so that when a key has no translation (i.e. the
5
+ * provider returns the key itself), the caller's English fallback is used
6
+ * instead. This keeps consumer call sites consistent and avoids leaking
7
+ * untranslated keys into the UI.
8
+ *
9
+ * Supports optional interpolation params using `{{key}}` placeholders.
10
+ *
11
+ * @example
12
+ * const tr = useTr();
13
+ * return <h1>{tr('channel.dashboard.title', 'Dashboard')}</h1>;
14
+ * return <p>{tr('channel.selectedCount', '{{count}} selected', { count: 5 })}</p>;
15
+ */
16
+ export declare function useTr(): (key: string, fallback: string, params?: Record<string, string | number>) => string;
17
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/channel/hooks/useTr.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,IAAI,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KACrC,MAAM,CAcV"}
@@ -0,0 +1,14 @@
1
+ import { useCallback as e } from "react";
2
+ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ //#region src/channel/hooks/useTr.ts
4
+ function n() {
5
+ let { t: n } = t();
6
+ return e((e, t, r) => {
7
+ let i = n(e), a = i === e ? t : i;
8
+ return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
9
+ }, [n]);
10
+ }
11
+ //#endregion
12
+ export { n as useTr };
13
+
14
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../src/channel/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('channel.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('channel.selectedCount', '{{count}} selected', { count: 5 })}</p>;\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,EAAI,EACnB,IAAM,MAAe,IAAM,IAAW;AAE5C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5E,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ"}
@@ -1,102 +1,102 @@
1
1
  //#region src/channel/modules/channels/i18n.ts
2
2
  var e = {
3
3
  EMAIL: {
4
- key: "channel.platform.email",
4
+ key: "channel.platformLabel.email",
5
5
  fallback: "Email"
6
6
  },
7
7
  SMS: {
8
- key: "channel.platform.sms",
8
+ key: "channel.platformLabel.sms",
9
9
  fallback: "SMS"
10
10
  },
11
11
  PUSH: {
12
- key: "channel.platform.push",
12
+ key: "channel.platformLabel.push",
13
13
  fallback: "Push"
14
14
  },
15
15
  INAPP: {
16
- key: "channel.platform.inApp",
16
+ key: "channel.platformLabel.inApp",
17
17
  fallback: "In-App"
18
18
  },
19
19
  WEBHOOK: {
20
- key: "channel.platform.webhook",
20
+ key: "channel.platformLabel.webhook",
21
21
  fallback: "Webhook"
22
22
  },
23
23
  SLACK: {
24
- key: "channel.platform.slack",
24
+ key: "channel.platformLabel.slack",
25
25
  fallback: "Slack"
26
26
  },
27
27
  TEAMS: {
28
- key: "channel.platform.teams",
28
+ key: "channel.platformLabel.teams",
29
29
  fallback: "Teams"
30
30
  },
31
31
  DISCORD: {
32
- key: "channel.platform.discord",
32
+ key: "channel.platformLabel.discord",
33
33
  fallback: "Discord"
34
34
  },
35
35
  WHATSAPP: {
36
- key: "channel.platform.whatsapp",
36
+ key: "channel.platformLabel.whatsapp",
37
37
  fallback: "WhatsApp"
38
38
  },
39
39
  WEBCHAT: {
40
- key: "channel.platform.webchat",
40
+ key: "channel.platformLabel.webchat",
41
41
  fallback: "Webchat"
42
42
  }
43
43
  }, t = {
44
44
  ACTIVE: {
45
- key: "channel.status.active",
45
+ key: "channel.statusLabel.active",
46
46
  fallback: "Active"
47
47
  },
48
48
  INACTIVE: {
49
- key: "channel.status.inactive",
49
+ key: "channel.statusLabel.inactive",
50
50
  fallback: "Inactive"
51
51
  },
52
52
  ERROR: {
53
- key: "channel.status.error",
53
+ key: "channel.statusLabel.error",
54
54
  fallback: "Error"
55
55
  },
56
56
  SUSPENDED: {
57
- key: "channel.status.suspended",
57
+ key: "channel.statusLabel.suspended",
58
58
  fallback: "Suspended"
59
59
  },
60
60
  ARCHIVED: {
61
- key: "channel.status.archived",
61
+ key: "channel.statusLabel.archived",
62
62
  fallback: "Archived"
63
63
  }
64
64
  }, n = {
65
65
  HEALTHY: {
66
- key: "channel.health.healthy",
66
+ key: "channel.healthLabel.healthy",
67
67
  fallback: "Healthy"
68
68
  },
69
69
  DEGRADED: {
70
- key: "channel.health.degraded",
70
+ key: "channel.healthLabel.degraded",
71
71
  fallback: "Degraded"
72
72
  },
73
73
  UNHEALTHY: {
74
- key: "channel.health.unhealthy",
74
+ key: "channel.healthLabel.unhealthy",
75
75
  fallback: "Unhealthy"
76
76
  },
77
77
  UNKNOWN: {
78
- key: "channel.health.unknown",
78
+ key: "channel.healthLabel.unknown",
79
79
  fallback: "Unknown"
80
80
  }
81
81
  }, r = {
82
82
  API_KEY: {
83
- key: "channel.authType.apiKey",
83
+ key: "channel.authTypeLabel.apiKey",
84
84
  fallback: "API Key"
85
85
  },
86
86
  OAUTH2: {
87
- key: "channel.authType.oauth2",
87
+ key: "channel.authTypeLabel.oauth2",
88
88
  fallback: "OAuth 2.0"
89
89
  },
90
90
  BOT_TOKEN: {
91
- key: "channel.authType.botToken",
91
+ key: "channel.authTypeLabel.botToken",
92
92
  fallback: "Bot Token"
93
93
  },
94
94
  WEBHOOK: {
95
- key: "channel.authType.webhook",
95
+ key: "channel.authTypeLabel.webhook",
96
96
  fallback: "Webhook"
97
97
  },
98
98
  SMTP_IMAP: {
99
- key: "channel.authType.smtpImap",
99
+ key: "channel.authTypeLabel.smtpImap",
100
100
  fallback: "SMTP/IMAP"
101
101
  }
102
102
  };
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.js","names":[],"sources":["../../../../src/channel/modules/channels/i18n.ts"],"sourcesContent":["import type {\n ChannelAuthType,\n ChannelPlatform,\n ChannelStatus,\n ConnectionHealth,\n} from '../../hooks/useChannels';\n\nexport type TranslateFn = (key: string, fallback: string) => string;\n\ntype I18nLabel = {\n key: string;\n fallback: string;\n};\n\nexport const PLATFORM_I18N: Record<ChannelPlatform, I18nLabel> = {\n EMAIL: { key: 'channel.platform.email', fallback: 'Email' },\n SMS: { key: 'channel.platform.sms', fallback: 'SMS' },\n PUSH: { key: 'channel.platform.push', fallback: 'Push' },\n INAPP: { key: 'channel.platform.inApp', fallback: 'In-App' },\n WEBHOOK: { key: 'channel.platform.webhook', fallback: 'Webhook' },\n SLACK: { key: 'channel.platform.slack', fallback: 'Slack' },\n TEAMS: { key: 'channel.platform.teams', fallback: 'Teams' },\n DISCORD: { key: 'channel.platform.discord', fallback: 'Discord' },\n WHATSAPP: { key: 'channel.platform.whatsapp', fallback: 'WhatsApp' },\n WEBCHAT: { key: 'channel.platform.webchat', fallback: 'Webchat' },\n};\n\nexport const STATUS_I18N: Record<ChannelStatus, I18nLabel> = {\n ACTIVE: { key: 'channel.status.active', fallback: 'Active' },\n INACTIVE: { key: 'channel.status.inactive', fallback: 'Inactive' },\n ERROR: { key: 'channel.status.error', fallback: 'Error' },\n SUSPENDED: { key: 'channel.status.suspended', fallback: 'Suspended' },\n ARCHIVED: { key: 'channel.status.archived', fallback: 'Archived' },\n};\n\nexport const HEALTH_I18N: Record<ConnectionHealth, I18nLabel> = {\n HEALTHY: { key: 'channel.health.healthy', fallback: 'Healthy' },\n DEGRADED: { key: 'channel.health.degraded', fallback: 'Degraded' },\n UNHEALTHY: { key: 'channel.health.unhealthy', fallback: 'Unhealthy' },\n UNKNOWN: { key: 'channel.health.unknown', fallback: 'Unknown' },\n};\n\nexport const AUTH_TYPE_I18N: Record<ChannelAuthType, I18nLabel> = {\n API_KEY: { key: 'channel.authType.apiKey', fallback: 'API Key' },\n OAUTH2: { key: 'channel.authType.oauth2', fallback: 'OAuth 2.0' },\n BOT_TOKEN: { key: 'channel.authType.botToken', fallback: 'Bot Token' },\n WEBHOOK: { key: 'channel.authType.webhook', fallback: 'Webhook' },\n SMTP_IMAP: { key: 'channel.authType.smtpImap', fallback: 'SMTP/IMAP' },\n};\n\nexport function translateLabel<T extends string>(\n value: T,\n labels: Partial<Record<T, I18nLabel>>,\n tr: TranslateFn\n): string {\n const label = labels[value];\n return label ? tr(label.key, label.fallback) : value;\n}\n"],"mappings":";AAcA,IAAa,IAAoD;CAC/D,OAAO;EAAE,KAAK;EAA0B,UAAU;EAAS;CAC3D,KAAK;EAAE,KAAK;EAAwB,UAAU;EAAO;CACrD,MAAM;EAAE,KAAK;EAAyB,UAAU;EAAQ;CACxD,OAAO;EAAE,KAAK;EAA0B,UAAU;EAAU;CAC5D,SAAS;EAAE,KAAK;EAA4B,UAAU;EAAW;CACjE,OAAO;EAAE,KAAK;EAA0B,UAAU;EAAS;CAC3D,OAAO;EAAE,KAAK;EAA0B,UAAU;EAAS;CAC3D,SAAS;EAAE,KAAK;EAA4B,UAAU;EAAW;CACjE,UAAU;EAAE,KAAK;EAA6B,UAAU;EAAY;CACpE,SAAS;EAAE,KAAK;EAA4B,UAAU;EAAW;CAClE,EAEY,IAAgD;CAC3D,QAAQ;EAAE,KAAK;EAAyB,UAAU;EAAU;CAC5D,UAAU;EAAE,KAAK;EAA2B,UAAU;EAAY;CAClE,OAAO;EAAE,KAAK;EAAwB,UAAU;EAAS;CACzD,WAAW;EAAE,KAAK;EAA4B,UAAU;EAAa;CACrE,UAAU;EAAE,KAAK;EAA2B,UAAU;EAAY;CACnE,EAEY,IAAmD;CAC9D,SAAS;EAAE,KAAK;EAA0B,UAAU;EAAW;CAC/D,UAAU;EAAE,KAAK;EAA2B,UAAU;EAAY;CAClE,WAAW;EAAE,KAAK;EAA4B,UAAU;EAAa;CACrE,SAAS;EAAE,KAAK;EAA0B,UAAU;EAAW;CAChE,EAEY,IAAqD;CAChE,SAAS;EAAE,KAAK;EAA2B,UAAU;EAAW;CAChE,QAAQ;EAAE,KAAK;EAA2B,UAAU;EAAa;CACjE,WAAW;EAAE,KAAK;EAA6B,UAAU;EAAa;CACtE,SAAS;EAAE,KAAK;EAA4B,UAAU;EAAW;CACjE,WAAW;EAAE,KAAK;EAA6B,UAAU;EAAa;CACvE;AAED,SAAgB,EACd,GACA,GACA,GACQ;CACR,IAAM,IAAQ,EAAO;AACrB,QAAO,IAAQ,EAAG,EAAM,KAAK,EAAM,SAAS,GAAG"}
1
+ {"version":3,"file":"i18n.js","names":[],"sources":["../../../../src/channel/modules/channels/i18n.ts"],"sourcesContent":["import type {\n ChannelAuthType,\n ChannelPlatform,\n ChannelStatus,\n ConnectionHealth,\n} from '../../hooks/useChannels';\n\nexport type TranslateFn = (key: string, fallback: string) => string;\n\ntype I18nLabel = {\n key: string;\n fallback: string;\n};\n\nexport const PLATFORM_I18N: Record<ChannelPlatform, I18nLabel> = {\n EMAIL: { key: 'channel.platformLabel.email', fallback: 'Email' },\n SMS: { key: 'channel.platformLabel.sms', fallback: 'SMS' },\n PUSH: { key: 'channel.platformLabel.push', fallback: 'Push' },\n INAPP: { key: 'channel.platformLabel.inApp', fallback: 'In-App' },\n WEBHOOK: { key: 'channel.platformLabel.webhook', fallback: 'Webhook' },\n SLACK: { key: 'channel.platformLabel.slack', fallback: 'Slack' },\n TEAMS: { key: 'channel.platformLabel.teams', fallback: 'Teams' },\n DISCORD: { key: 'channel.platformLabel.discord', fallback: 'Discord' },\n WHATSAPP: { key: 'channel.platformLabel.whatsapp', fallback: 'WhatsApp' },\n WEBCHAT: { key: 'channel.platformLabel.webchat', fallback: 'Webchat' },\n};\n\nexport const STATUS_I18N: Record<ChannelStatus, I18nLabel> = {\n ACTIVE: { key: 'channel.statusLabel.active', fallback: 'Active' },\n INACTIVE: { key: 'channel.statusLabel.inactive', fallback: 'Inactive' },\n ERROR: { key: 'channel.statusLabel.error', fallback: 'Error' },\n SUSPENDED: { key: 'channel.statusLabel.suspended', fallback: 'Suspended' },\n ARCHIVED: { key: 'channel.statusLabel.archived', fallback: 'Archived' },\n};\n\nexport const HEALTH_I18N: Record<ConnectionHealth, I18nLabel> = {\n HEALTHY: { key: 'channel.healthLabel.healthy', fallback: 'Healthy' },\n DEGRADED: { key: 'channel.healthLabel.degraded', fallback: 'Degraded' },\n UNHEALTHY: { key: 'channel.healthLabel.unhealthy', fallback: 'Unhealthy' },\n UNKNOWN: { key: 'channel.healthLabel.unknown', fallback: 'Unknown' },\n};\n\nexport const AUTH_TYPE_I18N: Record<ChannelAuthType, I18nLabel> = {\n API_KEY: { key: 'channel.authTypeLabel.apiKey', fallback: 'API Key' },\n OAUTH2: { key: 'channel.authTypeLabel.oauth2', fallback: 'OAuth 2.0' },\n BOT_TOKEN: { key: 'channel.authTypeLabel.botToken', fallback: 'Bot Token' },\n WEBHOOK: { key: 'channel.authTypeLabel.webhook', fallback: 'Webhook' },\n SMTP_IMAP: { key: 'channel.authTypeLabel.smtpImap', fallback: 'SMTP/IMAP' },\n};\n\nexport function translateLabel<T extends string>(\n value: T,\n labels: Partial<Record<T, I18nLabel>>,\n tr: TranslateFn\n): string {\n const label = labels[value];\n return label ? tr(label.key, label.fallback) : value;\n}\n"],"mappings":";AAcA,IAAa,IAAoD;CAC/D,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,KAAK;EAAE,KAAK;EAA6B,UAAU;EAAO;CAC1D,MAAM;EAAE,KAAK;EAA8B,UAAU;EAAQ;CAC7D,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAU;CACjE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,OAAO;EAAE,KAAK;EAA+B,UAAU;EAAS;CAChE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,UAAU;EAAE,KAAK;EAAkC,UAAU;EAAY;CACzE,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACvE,EAEY,IAAgD;CAC3D,QAAQ;EAAE,KAAK;EAA8B,UAAU;EAAU;CACjE,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACvE,OAAO;EAAE,KAAK;EAA6B,UAAU;EAAS;CAC9D,WAAW;EAAE,KAAK;EAAiC,UAAU;EAAa;CAC1E,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACxE,EAEY,IAAmD;CAC9D,SAAS;EAAE,KAAK;EAA+B,UAAU;EAAW;CACpE,UAAU;EAAE,KAAK;EAAgC,UAAU;EAAY;CACvE,WAAW;EAAE,KAAK;EAAiC,UAAU;EAAa;CAC1E,SAAS;EAAE,KAAK;EAA+B,UAAU;EAAW;CACrE,EAEY,IAAqD;CAChE,SAAS;EAAE,KAAK;EAAgC,UAAU;EAAW;CACrE,QAAQ;EAAE,KAAK;EAAgC,UAAU;EAAa;CACtE,WAAW;EAAE,KAAK;EAAkC,UAAU;EAAa;CAC3E,SAAS;EAAE,KAAK;EAAiC,UAAU;EAAW;CACtE,WAAW;EAAE,KAAK;EAAkC,UAAU;EAAa;CAC5E;AAED,SAAgB,EACd,GACA,GACA,GACQ;CACR,IAAM,IAAQ,EAAO;AACrB,QAAO,IAAQ,EAAG,EAAM,KAAK,EAAM,SAAS,GAAG"}
@@ -299,14 +299,14 @@ function j() {
299
299
  iconClass: "text-accent-primary",
300
300
  value: H,
301
301
  label: j("channel.totalChannels", "Total Channels"),
302
- sub: $.active > 0 ? `${$.active} ${j("channel.status.active", "Active").toLowerCase()}, ${H - $.active} ${j("channel.status.inactive", "Inactive").toLowerCase()}` : void 0
302
+ sub: $.active > 0 ? `${$.active} ${j("channel.statusLabel.active", "Active").toLowerCase()}, ${H - $.active} ${j("channel.statusLabel.inactive", "Inactive").toLowerCase()}` : void 0
303
303
  }),
304
304
  /* @__PURE__ */ w(A, {
305
305
  icon: fe,
306
306
  iconClass: "text-status-success-text",
307
307
  value: $.healthy,
308
308
  label: j("channel.healthy", "Healthy"),
309
- sub: $.unhealthy > 0 ? `${$.unhealthy} ${j("channel.health.unhealthy", "Unhealthy").toLowerCase()}` : H > 0 ? $.healthy === H ? j("channel.allSystemsNominal", "All systems nominal") : `${H - $.healthy} ${j("channel.health.unchecked", "unchecked")}` : void 0
309
+ sub: $.unhealthy > 0 ? `${$.unhealthy} ${j("channel.healthLabel.unhealthy", "Unhealthy").toLowerCase()}` : H > 0 ? $.healthy === H ? j("channel.allSystemsNominal", "All systems nominal") : `${H - $.healthy} ${j("channel.healthLabel.unchecked", "unchecked")}` : void 0
310
310
  }),
311
311
  /* @__PURE__ */ w(A, {
312
312
  icon: h,