@emdash-cms/admin 0.2.0 → 0.3.0

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/index.js CHANGED
@@ -204,9 +204,11 @@ function getMutationError(error) {
204
204
  /** Inline error banner for use inside dialogs. */
205
205
  function DialogError({ message, className }) {
206
206
  if (!message) return null;
207
+ const lines = message.split("\n");
207
208
  return /* @__PURE__ */ jsx("div", {
209
+ role: "alert",
208
210
  className: cn("rounded-md bg-kumo-danger/10 p-3 text-sm text-kumo-danger", className),
209
- children: message
211
+ children: lines.map((line, i) => /* @__PURE__ */ jsx("div", { children: line }, i))
210
212
  });
211
213
  }
212
214
 
@@ -2037,44 +2039,19 @@ async function executeWpPluginImport(url, token, config) {
2037
2039
  /**
2038
2040
  * API token management client functions
2039
2041
  */
2040
- /** Available scopes for API tokens */
2041
- const API_TOKEN_SCOPES = [
2042
- {
2043
- value: "content:read",
2044
- label: "Content Read",
2045
- description: "Read content entries"
2046
- },
2047
- {
2048
- value: "content:write",
2049
- label: "Content Write",
2050
- description: "Create, update, delete content"
2051
- },
2052
- {
2053
- value: "media:read",
2054
- label: "Media Read",
2055
- description: "Read media files"
2056
- },
2057
- {
2058
- value: "media:write",
2059
- label: "Media Write",
2060
- description: "Upload and delete media"
2061
- },
2062
- {
2063
- value: "schema:read",
2064
- label: "Schema Read",
2065
- description: "Read collection schemas"
2066
- },
2067
- {
2068
- value: "schema:write",
2069
- label: "Schema Write",
2070
- description: "Modify collection schemas"
2071
- },
2072
- {
2073
- value: "admin",
2074
- label: "Admin",
2075
- description: "Full admin access"
2076
- }
2077
- ];
2042
+ /**
2043
+ * Scope strings for personal API tokens (wire + UI iteration order).
2044
+ * Human-readable copy lives in `ApiTokenSettings` (`SCOPE_UI` + Lingui).
2045
+ */
2046
+ const API_TOKEN_SCOPES = {
2047
+ ContentRead: "content:read",
2048
+ ContentWrite: "content:write",
2049
+ MediaRead: "media:read",
2050
+ MediaWrite: "media:write",
2051
+ SchemaRead: "schema:read",
2052
+ SchemaWrite: "schema:write",
2053
+ Admin: "admin"
2054
+ };
2078
2055
  /**
2079
2056
  * Fetch all API tokens for the current user
2080
2057
  */
@@ -4370,7 +4347,10 @@ var index_default = Suggestion;
4370
4347
  const blockTransforms = [
4371
4348
  {
4372
4349
  id: "paragraph",
4373
- label: "Paragraph",
4350
+ label: {
4351
+ id: "bkQRMh",
4352
+ message: "Paragraph"
4353
+ },
4374
4354
  icon: Paragraph,
4375
4355
  transform: (editor) => {
4376
4356
  editor.chain().focus().setNode("paragraph").run();
@@ -4378,7 +4358,10 @@ const blockTransforms = [
4378
4358
  },
4379
4359
  {
4380
4360
  id: "heading1",
4381
- label: "Heading 1",
4361
+ label: {
4362
+ id: "lXKZGw",
4363
+ message: "Heading 1"
4364
+ },
4382
4365
  icon: TextHOne,
4383
4366
  transform: (editor) => {
4384
4367
  editor.chain().focus().setNode("heading", { level: 1 }).run();
@@ -4386,7 +4369,10 @@ const blockTransforms = [
4386
4369
  },
4387
4370
  {
4388
4371
  id: "heading2",
4389
- label: "Heading 2",
4372
+ label: {
4373
+ id: "El7NbA",
4374
+ message: "Heading 2"
4375
+ },
4390
4376
  icon: TextHTwo,
4391
4377
  transform: (editor) => {
4392
4378
  editor.chain().focus().setNode("heading", { level: 2 }).run();
@@ -4394,7 +4380,10 @@ const blockTransforms = [
4394
4380
  },
4395
4381
  {
4396
4382
  id: "heading3",
4397
- label: "Heading 3",
4383
+ label: {
4384
+ id: "SFN6dN",
4385
+ message: "Heading 3"
4386
+ },
4398
4387
  icon: TextHThree,
4399
4388
  transform: (editor) => {
4400
4389
  editor.chain().focus().setNode("heading", { level: 3 }).run();
@@ -4402,7 +4391,10 @@ const blockTransforms = [
4402
4391
  },
4403
4392
  {
4404
4393
  id: "blockquote",
4405
- label: "Quote",
4394
+ label: {
4395
+ id: "ZhhOwV",
4396
+ message: "Quote"
4397
+ },
4406
4398
  icon: Quotes,
4407
4399
  transform: (editor) => {
4408
4400
  editor.chain().focus().toggleBlockquote().run();
@@ -4410,7 +4402,10 @@ const blockTransforms = [
4410
4402
  },
4411
4403
  {
4412
4404
  id: "codeBlock",
4413
- label: "Code Block",
4405
+ label: {
4406
+ id: "N2eHWq",
4407
+ message: "Code Block"
4408
+ },
4414
4409
  icon: Code,
4415
4410
  transform: (editor) => {
4416
4411
  editor.chain().focus().toggleCodeBlock().run();
@@ -4418,7 +4413,10 @@ const blockTransforms = [
4418
4413
  },
4419
4414
  {
4420
4415
  id: "bulletList",
4421
- label: "Bullet List",
4416
+ label: {
4417
+ id: "s1c0ja",
4418
+ message: "Bullet List"
4419
+ },
4422
4420
  icon: List,
4423
4421
  transform: (editor) => {
4424
4422
  editor.chain().focus().toggleBulletList().run();
@@ -4426,7 +4424,10 @@ const blockTransforms = [
4426
4424
  },
4427
4425
  {
4428
4426
  id: "orderedList",
4429
- label: "Numbered List",
4427
+ label: {
4428
+ id: "upFPtm",
4429
+ message: "Numbered List"
4430
+ },
4430
4431
  icon: ListNumbers,
4431
4432
  transform: (editor) => {
4432
4433
  editor.chain().focus().toggleOrderedList().run();
@@ -4437,6 +4438,7 @@ const blockTransforms = [
4437
4438
  * Block Menu - floating menu for block-level actions
4438
4439
  */
4439
4440
  function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
4441
+ const { _: _t } = useLingui();
4440
4442
  const [showTransforms, setShowTransforms] = React.useState(false);
4441
4443
  const menuRef = React.useRef(null);
4442
4444
  const stableOnClose = useStableCallback(onClose);
@@ -4533,7 +4535,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
4533
4535
  type: "button",
4534
4536
  className: "flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-kumo-tint text-left",
4535
4537
  onClick: () => handleTransform(transform),
4536
- children: [/* @__PURE__ */ jsx(transform.icon, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: transform.label })]
4538
+ children: [/* @__PURE__ */ jsx(transform.icon, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: _t(transform.label) })]
4537
4539
  }, transform.id))
4538
4540
  ]
4539
4541
  }) : /* @__PURE__ */ jsxs("div", {
@@ -5778,8 +5780,14 @@ function convertPTMarks(marks, markDefs) {
5778
5780
  const defaultSlashCommands = [
5779
5781
  {
5780
5782
  id: "heading1",
5781
- title: "Heading 1",
5782
- description: "Large section heading",
5783
+ title: {
5784
+ id: "lXKZGw",
5785
+ message: "Heading 1"
5786
+ },
5787
+ description: {
5788
+ id: "59o/ag",
5789
+ message: "Large section heading"
5790
+ },
5783
5791
  icon: TextHOne,
5784
5792
  aliases: ["h1", "title"],
5785
5793
  command: ({ editor, range }) => {
@@ -5788,8 +5796,14 @@ const defaultSlashCommands = [
5788
5796
  },
5789
5797
  {
5790
5798
  id: "heading2",
5791
- title: "Heading 2",
5792
- description: "Medium section heading",
5799
+ title: {
5800
+ id: "El7NbA",
5801
+ message: "Heading 2"
5802
+ },
5803
+ description: {
5804
+ id: "IAcjOi",
5805
+ message: "Medium section heading"
5806
+ },
5793
5807
  icon: TextHTwo,
5794
5808
  aliases: ["h2", "subtitle"],
5795
5809
  command: ({ editor, range }) => {
@@ -5798,8 +5812,14 @@ const defaultSlashCommands = [
5798
5812
  },
5799
5813
  {
5800
5814
  id: "heading3",
5801
- title: "Heading 3",
5802
- description: "Small section heading",
5815
+ title: {
5816
+ id: "SFN6dN",
5817
+ message: "Heading 3"
5818
+ },
5819
+ description: {
5820
+ id: "Ik7fz5",
5821
+ message: "Small section heading"
5822
+ },
5803
5823
  icon: TextHThree,
5804
5824
  aliases: ["h3"],
5805
5825
  command: ({ editor, range }) => {
@@ -5808,8 +5828,14 @@ const defaultSlashCommands = [
5808
5828
  },
5809
5829
  {
5810
5830
  id: "bulletList",
5811
- title: "Bullet List",
5812
- description: "Create a bullet list",
5831
+ title: {
5832
+ id: "s1c0ja",
5833
+ message: "Bullet List"
5834
+ },
5835
+ description: {
5836
+ id: "md7u2X",
5837
+ message: "Create a bullet list"
5838
+ },
5813
5839
  icon: List,
5814
5840
  aliases: ["ul", "unordered"],
5815
5841
  command: ({ editor, range }) => {
@@ -5818,8 +5844,14 @@ const defaultSlashCommands = [
5818
5844
  },
5819
5845
  {
5820
5846
  id: "numberedList",
5821
- title: "Numbered List",
5822
- description: "Create a numbered list",
5847
+ title: {
5848
+ id: "upFPtm",
5849
+ message: "Numbered List"
5850
+ },
5851
+ description: {
5852
+ id: "fh33rM",
5853
+ message: "Create a numbered list"
5854
+ },
5823
5855
  icon: ListNumbers,
5824
5856
  aliases: ["ol", "ordered"],
5825
5857
  command: ({ editor, range }) => {
@@ -5828,8 +5860,14 @@ const defaultSlashCommands = [
5828
5860
  },
5829
5861
  {
5830
5862
  id: "quote",
5831
- title: "Quote",
5832
- description: "Insert a blockquote",
5863
+ title: {
5864
+ id: "ZhhOwV",
5865
+ message: "Quote"
5866
+ },
5867
+ description: {
5868
+ id: "l+2nGM",
5869
+ message: "Insert a blockquote"
5870
+ },
5833
5871
  icon: Quotes,
5834
5872
  aliases: ["blockquote", "cite"],
5835
5873
  command: ({ editor, range }) => {
@@ -5838,8 +5876,14 @@ const defaultSlashCommands = [
5838
5876
  },
5839
5877
  {
5840
5878
  id: "codeBlock",
5841
- title: "Code Block",
5842
- description: "Insert a code block",
5879
+ title: {
5880
+ id: "N2eHWq",
5881
+ message: "Code Block"
5882
+ },
5883
+ description: {
5884
+ id: "12gVka",
5885
+ message: "Insert a code block"
5886
+ },
5843
5887
  icon: CodeBlock,
5844
5888
  aliases: [
5845
5889
  "code",
@@ -5852,8 +5896,14 @@ const defaultSlashCommands = [
5852
5896
  },
5853
5897
  {
5854
5898
  id: "divider",
5855
- title: "Divider",
5856
- description: "Insert a horizontal rule",
5899
+ title: {
5900
+ id: "R8AthW",
5901
+ message: "Divider"
5902
+ },
5903
+ description: {
5904
+ id: "9Jd2n5",
5905
+ message: "Insert a horizontal rule"
5906
+ },
5857
5907
  icon: Minus,
5858
5908
  aliases: [
5859
5909
  "hr",
@@ -5962,6 +6012,7 @@ function createSlashCommandsExtension(options) {
5962
6012
  * Slash command menu component using Floating UI
5963
6013
  */
5964
6014
  function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedIndex }) {
6015
+ const { _: _t } = useLingui();
5965
6016
  const containerRef = React.useRef(null);
5966
6017
  const { refs, floatingStyles } = useFloating({
5967
6018
  open: state.isOpen,
@@ -5996,7 +6047,10 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
5996
6047
  className: "z-[100] rounded-lg border bg-kumo-overlay p-1 shadow-lg min-w-[220px] max-h-[300px] overflow-y-auto",
5997
6048
  children: state.items.length === 0 ? /* @__PURE__ */ jsx("p", {
5998
6049
  className: "text-sm text-kumo-subtle px-3 py-2",
5999
- children: "No results"
6050
+ children: _t({
6051
+ id: "Ev2r9A",
6052
+ message: "No results"
6053
+ })
6000
6054
  }) : state.items.map((item, index) => /* @__PURE__ */ jsxs("button", {
6001
6055
  type: "button",
6002
6056
  "data-index": index,
@@ -6007,10 +6061,10 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
6007
6061
  className: "flex flex-col",
6008
6062
  children: [/* @__PURE__ */ jsx("span", {
6009
6063
  className: "font-medium",
6010
- children: item.title
6064
+ children: typeof item.title === "string" ? item.title : _t(item.title)
6011
6065
  }), /* @__PURE__ */ jsx("span", {
6012
6066
  className: "text-xs text-kumo-subtle",
6013
- children: item.description
6067
+ children: typeof item.description === "string" ? item.description : _t(item.description)
6014
6068
  })]
6015
6069
  })]
6016
6070
  }, item.id))
@@ -6271,6 +6325,7 @@ function EditorFooter({ editor }) {
6271
6325
  * Portable Text Editor Component
6272
6326
  */
6273
6327
  function PortableTextEditor({ value, onChange, placeholder = "Start writing...", className, editable = true, "aria-labelledby": ariaLabelledby, pluginBlocks = [], focusMode: controlledFocusMode, onFocusModeChange, onEditorReady, minimal = false, onBlockSidebarOpen, onBlockSidebarClose }) {
6328
+ const { _: _t2 } = useLingui();
6274
6329
  const onChangeRef = React.useRef(onChange);
6275
6330
  React.useEffect(() => {
6276
6331
  onChangeRef.current = onChange;
@@ -6302,8 +6357,14 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6302
6357
  const cmds = [...defaultSlashCommands];
6303
6358
  cmds.push({
6304
6359
  id: "image",
6305
- title: "Image",
6306
- description: "Insert an image",
6360
+ title: {
6361
+ id: "hG89Ed",
6362
+ message: "Image"
6363
+ },
6364
+ description: {
6365
+ id: "3Lcj6W",
6366
+ message: "Insert an image"
6367
+ },
6307
6368
  icon: Image$1,
6308
6369
  aliases: [
6309
6370
  "img",
@@ -6311,7 +6372,10 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6311
6372
  "picture",
6312
6373
  "url"
6313
6374
  ],
6314
- category: "Media",
6375
+ category: {
6376
+ id: "xYilR2",
6377
+ message: "Media"
6378
+ },
6315
6379
  command: ({ editor, range }) => {
6316
6380
  editor.chain().focus().deleteRange(range).run();
6317
6381
  setMediaPickerOpen(true);
@@ -6319,15 +6383,24 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6319
6383
  });
6320
6384
  cmds.push({
6321
6385
  id: "section",
6322
- title: "Section",
6323
- description: "Insert a reusable section",
6386
+ title: {
6387
+ id: "BhZ5PL",
6388
+ message: "Section"
6389
+ },
6390
+ description: {
6391
+ id: "Pkleyv",
6392
+ message: "Insert a reusable section"
6393
+ },
6324
6394
  icon: Stack,
6325
6395
  aliases: [
6326
6396
  "pattern",
6327
6397
  "block",
6328
6398
  "template"
6329
6399
  ],
6330
- category: "Content",
6400
+ category: {
6401
+ id: "4b3oEV",
6402
+ message: "Content"
6403
+ },
6331
6404
  command: ({ editor, range }) => {
6332
6405
  editor.chain().focus().deleteRange(range).run();
6333
6406
  setSectionPickerOpen(true);
@@ -6336,25 +6409,36 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6336
6409
  for (const block of pluginBlocks) cmds.push({
6337
6410
  id: `plugin-${block.pluginId}-${block.type}`,
6338
6411
  title: block.label,
6339
- description: block.description || `Embed a ${block.label.toLowerCase()}`,
6412
+ description: block.description ?? _t2({
6413
+ id: "g0zqOw",
6414
+ message: "Embed a {0}",
6415
+ values: { 0: block.label }
6416
+ }),
6340
6417
  icon: resolveIcon(block.icon),
6341
6418
  aliases: [block.type],
6342
- category: "Embeds",
6419
+ category: {
6420
+ id: "aTofd0",
6421
+ message: "Embeds"
6422
+ },
6343
6423
  command: ({ editor, range }) => {
6344
6424
  editor.chain().focus().deleteRange(range).run();
6345
6425
  setPluginBlockModal(block);
6346
6426
  }
6347
6427
  });
6348
6428
  return cmds;
6349
- }, [pluginBlocks]);
6429
+ }, [pluginBlocks, _t2]);
6350
6430
  const filterCommandsRef = React.useRef((_q) => []);
6351
6431
  filterCommandsRef.current = (query) => {
6352
6432
  if (!query) return slashCommands;
6353
6433
  const searchText = query.toLowerCase();
6354
6434
  const titleMatches = [];
6355
6435
  const otherMatches = [];
6356
- for (const item of slashCommands) if (item.title.toLowerCase().includes(searchText)) titleMatches.push(item);
6357
- else if (item.description.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
6436
+ for (const item of slashCommands) {
6437
+ const titleStr = typeof item.title === "string" ? item.title : _t2(item.title);
6438
+ const descStr = typeof item.description === "string" ? item.description : _t2(item.description);
6439
+ if (titleStr.toLowerCase().includes(searchText)) titleMatches.push(item);
6440
+ else if (descStr.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
6441
+ }
6358
6442
  return [...titleMatches, ...otherMatches];
6359
6443
  };
6360
6444
  const initialContent = React.useMemo(() => portableTextToProsemirror(value || []), []);
@@ -10128,71 +10212,128 @@ const SLUG_LEADING_TRAILING_PATTERN = /^_|_$/g;
10128
10212
  const SUPPORT_OPTIONS = [
10129
10213
  {
10130
10214
  value: "drafts",
10131
- label: "Drafts",
10132
- description: "Save content as draft before publishing"
10215
+ label: {
10216
+ id: "M8kJqa",
10217
+ message: "Drafts"
10218
+ },
10219
+ description: {
10220
+ id: "ZpP0HR",
10221
+ message: "Save content as draft before publishing"
10222
+ }
10133
10223
  },
10134
10224
  {
10135
10225
  value: "revisions",
10136
- label: "Revisions",
10137
- description: "Track content history"
10226
+ label: {
10227
+ id: "ZdWn/b",
10228
+ message: "Revisions"
10229
+ },
10230
+ description: {
10231
+ id: "9kGBt9",
10232
+ message: "Track content history"
10233
+ }
10138
10234
  },
10139
10235
  {
10140
10236
  value: "preview",
10141
- label: "Preview",
10142
- description: "Preview content before publishing"
10237
+ label: {
10238
+ id: "rdUucN",
10239
+ message: "Preview"
10240
+ },
10241
+ description: {
10242
+ id: "KRB02E",
10243
+ message: "Preview content before publishing"
10244
+ }
10143
10245
  },
10144
10246
  {
10145
10247
  value: "search",
10146
- label: "Search",
10147
- description: "Enable full-text search on this collection"
10248
+ label: {
10249
+ id: "A1taO8",
10250
+ message: "Search"
10251
+ },
10252
+ description: {
10253
+ id: "T7NoTE",
10254
+ message: "Enable full-text search on this collection"
10255
+ }
10148
10256
  }
10149
10257
  ];
10150
- /**
10151
- * System fields that exist on every collection
10152
- * These are created automatically and cannot be modified
10153
- */
10154
10258
  const SYSTEM_FIELDS = [
10155
10259
  {
10156
10260
  slug: "id",
10157
- label: "ID",
10261
+ label: {
10262
+ id: "S0kLOH",
10263
+ message: "ID"
10264
+ },
10158
10265
  type: "text",
10159
- description: "Unique identifier (ULID)"
10266
+ description: {
10267
+ id: "PUk2oG",
10268
+ message: "Unique identifier (ULID)"
10269
+ }
10160
10270
  },
10161
10271
  {
10162
10272
  slug: "slug",
10163
- label: "Slug",
10273
+ label: {
10274
+ id: "L85WcV",
10275
+ message: "Slug"
10276
+ },
10164
10277
  type: "text",
10165
- description: "URL-friendly identifier"
10278
+ description: {
10279
+ id: "f0WSdD",
10280
+ message: "URL-friendly identifier"
10281
+ }
10166
10282
  },
10167
10283
  {
10168
10284
  slug: "status",
10169
- label: "Status",
10285
+ label: {
10286
+ id: "uAQUqI",
10287
+ message: "Status"
10288
+ },
10170
10289
  type: "text",
10171
- description: "draft, published, or archived"
10290
+ description: {
10291
+ id: "khtYSH",
10292
+ message: "draft, published, or archived"
10293
+ }
10172
10294
  },
10173
10295
  {
10174
10296
  slug: "created_at",
10175
- label: "Created At",
10297
+ label: {
10298
+ id: "88kg0+",
10299
+ message: "Created At"
10300
+ },
10176
10301
  type: "datetime",
10177
- description: "When the entry was created"
10302
+ description: {
10303
+ id: "SMcuRW",
10304
+ message: "When the entry was created"
10305
+ }
10178
10306
  },
10179
10307
  {
10180
10308
  slug: "updated_at",
10181
- label: "Updated At",
10309
+ label: {
10310
+ id: "Llcakz",
10311
+ message: "Updated At"
10312
+ },
10182
10313
  type: "datetime",
10183
- description: "When the entry was last modified"
10314
+ description: {
10315
+ id: "46AzZK",
10316
+ message: "When the entry was last modified"
10317
+ }
10184
10318
  },
10185
10319
  {
10186
10320
  slug: "published_at",
10187
- label: "Published At",
10321
+ label: {
10322
+ id: "6QwXHP",
10323
+ message: "Published At"
10324
+ },
10188
10325
  type: "datetime",
10189
- description: "When the entry was published"
10326
+ description: {
10327
+ id: "MRpwV3",
10328
+ message: "When the entry was published"
10329
+ }
10190
10330
  }
10191
10331
  ];
10192
10332
  /**
10193
10333
  * Content Type editor for creating/editing collections
10194
10334
  */
10195
10335
  function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields }) {
10336
+ const { _: _t } = useLingui();
10196
10337
  useNavigate$1();
10197
10338
  const [slug, setSlug] = React.useState(collection?.slug ?? "");
10198
10339
  const [label, setLabel] = React.useState(collection?.label ?? "");
@@ -10415,10 +10556,10 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
10415
10556
  disabled: isFromCode
10416
10557
  }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
10417
10558
  className: "text-sm font-medium",
10418
- children: option.label
10559
+ children: _t(option.label)
10419
10560
  }), /* @__PURE__ */ jsx("p", {
10420
10561
  className: "text-xs text-kumo-subtle",
10421
- children: option.description
10562
+ children: _t(option.description)
10422
10563
  })] })]
10423
10564
  }, option.value))]
10424
10565
  }),
@@ -10698,6 +10839,7 @@ function FieldRow({ field, isFromCode, onEdit, onDelete }) {
10698
10839
  });
10699
10840
  }
10700
10841
  function SystemFieldRow({ field }) {
10842
+ const { _: _t2 } = useLingui();
10701
10843
  return /* @__PURE__ */ jsxs("div", {
10702
10844
  className: "flex items-center px-4 py-2 opacity-75",
10703
10845
  children: [
@@ -10710,7 +10852,7 @@ function SystemFieldRow({ field }) {
10710
10852
  children: [
10711
10853
  /* @__PURE__ */ jsx("span", {
10712
10854
  className: "font-medium text-sm",
10713
- children: field.label
10855
+ children: _t2(field.label)
10714
10856
  }),
10715
10857
  /* @__PURE__ */ jsx("code", {
10716
10858
  className: "text-xs bg-kumo-tint px-1.5 py-0.5 rounded text-kumo-subtle",
@@ -10723,7 +10865,7 @@ function SystemFieldRow({ field }) {
10723
10865
  ]
10724
10866
  }), /* @__PURE__ */ jsx("p", {
10725
10867
  className: "text-xs text-kumo-subtle mt-0.5",
10726
- children: field.description
10868
+ children: _t2(field.description)
10727
10869
  })]
10728
10870
  })
10729
10871
  ]
@@ -15620,6 +15762,7 @@ function Redirects() {
15620
15762
  setTab("redirects");
15621
15763
  }
15622
15764
  const redirects = redirectsQuery.data?.items ?? [];
15765
+ const loopRedirectIds = new Set(redirectsQuery.data?.loopRedirectIds ?? []);
15623
15766
  return /* @__PURE__ */ jsxs("div", {
15624
15767
  className: "space-y-6",
15625
15768
  children: [
@@ -15653,173 +15796,208 @@ function Redirects() {
15653
15796
  children: "404 Errors"
15654
15797
  })]
15655
15798
  }),
15656
- tab === "redirects" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
15657
- className: "flex items-center gap-4",
15658
- children: [
15659
- /* @__PURE__ */ jsxs("div", {
15660
- className: "relative flex-1 max-w-md",
15661
- children: [/* @__PURE__ */ jsx(MagnifyingGlass, {
15662
- className: "absolute left-3 top-1/2 -translate-y-1/2 text-kumo-subtle",
15663
- size: 16
15664
- }), /* @__PURE__ */ jsx(Input, {
15665
- placeholder: "Search source or destination...",
15666
- className: "pl-10",
15667
- value: search,
15668
- onChange: (e) => setSearch(e.target.value)
15669
- })]
15670
- }),
15671
- /* @__PURE__ */ jsxs("select", {
15672
- value: filterEnabled,
15673
- onChange: (e) => setFilterEnabled(e.target.value),
15674
- className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15675
- children: [
15676
- /* @__PURE__ */ jsx("option", {
15677
- value: "all",
15678
- children: "All statuses"
15679
- }),
15680
- /* @__PURE__ */ jsx("option", {
15681
- value: "true",
15682
- children: "Enabled"
15683
- }),
15684
- /* @__PURE__ */ jsx("option", {
15685
- value: "false",
15686
- children: "Disabled"
15687
- })
15688
- ]
15689
- }),
15690
- /* @__PURE__ */ jsxs("select", {
15691
- value: filterAuto,
15692
- onChange: (e) => setFilterAuto(e.target.value),
15693
- className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15694
- children: [
15695
- /* @__PURE__ */ jsx("option", {
15696
- value: "all",
15697
- children: "All types"
15698
- }),
15699
- /* @__PURE__ */ jsx("option", {
15700
- value: "false",
15701
- children: "Manual"
15702
- }),
15703
- /* @__PURE__ */ jsx("option", {
15704
- value: "true",
15705
- children: "Auto (slug change)"
15706
- })
15707
- ]
15708
- })
15709
- ]
15710
- }), redirectsQuery.isLoading ? /* @__PURE__ */ jsx("div", {
15711
- className: "py-12 text-center text-kumo-subtle",
15712
- children: "Loading redirects..."
15713
- }) : redirects.length === 0 ? /* @__PURE__ */ jsxs("div", {
15714
- className: "py-12 text-center text-kumo-subtle",
15715
- children: [
15716
- /* @__PURE__ */ jsx(ArrowsLeftRight, {
15717
- size: 48,
15718
- className: "mx-auto mb-4 opacity-30"
15719
- }),
15720
- /* @__PURE__ */ jsx("p", {
15721
- className: "text-lg font-medium",
15722
- children: "No redirects yet"
15723
- }),
15724
- /* @__PURE__ */ jsx("p", {
15725
- className: "text-sm mt-1",
15726
- children: "Create redirect rules to manage URL changes."
15727
- })
15728
- ]
15729
- }) : /* @__PURE__ */ jsxs("div", {
15730
- className: "border rounded-lg",
15731
- children: [/* @__PURE__ */ jsxs("div", {
15732
- className: "flex items-center gap-4 py-2 px-4 border-b bg-kumo-tint/50 text-sm font-medium text-kumo-subtle",
15733
- children: [
15734
- /* @__PURE__ */ jsx("div", {
15735
- className: "flex-1",
15736
- children: "Source"
15737
- }),
15738
- /* @__PURE__ */ jsx("div", { className: "w-8 text-center" }),
15739
- /* @__PURE__ */ jsx("div", {
15740
- className: "flex-1",
15741
- children: "Destination"
15742
- }),
15743
- /* @__PURE__ */ jsx("div", {
15744
- className: "w-14 text-center",
15745
- children: "Code"
15746
- }),
15747
- /* @__PURE__ */ jsx("div", {
15748
- className: "w-16 text-right",
15749
- children: "Hits"
15750
- }),
15751
- /* @__PURE__ */ jsx("div", {
15752
- className: "w-20 text-center",
15753
- children: "Status"
15754
- }),
15755
- /* @__PURE__ */ jsx("div", { className: "w-20" })
15756
- ]
15757
- }), redirects.map((r) => /* @__PURE__ */ jsxs("div", {
15758
- className: cn("flex items-center gap-4 py-2 px-4 border-b last:border-0 text-sm", !r.enabled && "opacity-50"),
15799
+ tab === "redirects" && /* @__PURE__ */ jsxs(Fragment, { children: [
15800
+ /* @__PURE__ */ jsxs("div", {
15801
+ className: "flex items-center gap-4",
15759
15802
  children: [
15760
- /* @__PURE__ */ jsx("div", {
15761
- className: "flex-1 font-mono text-xs truncate",
15762
- title: r.source,
15763
- children: r.source
15764
- }),
15765
- /* @__PURE__ */ jsx("div", {
15766
- className: "w-8 text-center text-kumo-subtle",
15767
- children: /* @__PURE__ */ jsx(ArrowRight, { size: 14 })
15768
- }),
15769
- /* @__PURE__ */ jsx("div", {
15770
- className: "flex-1 font-mono text-xs truncate",
15771
- title: r.destination,
15772
- children: r.destination
15773
- }),
15774
- /* @__PURE__ */ jsx("div", {
15775
- className: "w-14 text-center",
15776
- children: /* @__PURE__ */ jsx(Badge, {
15777
- variant: "secondary",
15778
- children: r.type
15779
- })
15780
- }),
15781
- /* @__PURE__ */ jsx("div", {
15782
- className: "w-16 text-right tabular-nums text-kumo-subtle",
15783
- children: r.hits
15803
+ /* @__PURE__ */ jsxs("div", {
15804
+ className: "relative flex-1 max-w-md",
15805
+ children: [/* @__PURE__ */ jsx(MagnifyingGlass, {
15806
+ className: "absolute left-3 top-1/2 -translate-y-1/2 text-kumo-subtle",
15807
+ size: 16
15808
+ }), /* @__PURE__ */ jsx(Input, {
15809
+ placeholder: "Search source or destination...",
15810
+ className: "pl-10",
15811
+ value: search,
15812
+ onChange: (e) => setSearch(e.target.value)
15813
+ })]
15784
15814
  }),
15785
- /* @__PURE__ */ jsx("div", {
15786
- className: "w-20 text-center",
15787
- children: /* @__PURE__ */ jsx(Switch, {
15788
- checked: r.enabled,
15789
- onCheckedChange: (checked) => toggleMutation.mutate({
15790
- id: r.id,
15791
- enabled: checked
15815
+ /* @__PURE__ */ jsxs("select", {
15816
+ value: filterEnabled,
15817
+ onChange: (e) => setFilterEnabled(e.target.value),
15818
+ className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15819
+ children: [
15820
+ /* @__PURE__ */ jsx("option", {
15821
+ value: "all",
15822
+ children: "All statuses"
15792
15823
  }),
15793
- "aria-label": r.enabled ? "Disable redirect" : "Enable redirect"
15794
- })
15824
+ /* @__PURE__ */ jsx("option", {
15825
+ value: "true",
15826
+ children: "Enabled"
15827
+ }),
15828
+ /* @__PURE__ */ jsx("option", {
15829
+ value: "false",
15830
+ children: "Disabled"
15831
+ })
15832
+ ]
15795
15833
  }),
15796
- /* @__PURE__ */ jsxs("div", {
15797
- className: "w-20 flex items-center justify-end gap-1",
15834
+ /* @__PURE__ */ jsxs("select", {
15835
+ value: filterAuto,
15836
+ onChange: (e) => setFilterAuto(e.target.value),
15837
+ className: "h-10 rounded-md border border-kumo-line bg-kumo-base px-3 text-sm",
15798
15838
  children: [
15799
- r.auto && /* @__PURE__ */ jsx(Badge, {
15800
- variant: "outline",
15801
- className: "mr-1 text-xs",
15802
- children: "auto"
15839
+ /* @__PURE__ */ jsx("option", {
15840
+ value: "all",
15841
+ children: "All types"
15803
15842
  }),
15804
- /* @__PURE__ */ jsx("button", {
15805
- onClick: () => setEditRedirect(r),
15806
- className: "p-1 text-kumo-subtle hover:text-kumo-default",
15807
- title: "Edit redirect",
15808
- "aria-label": `Edit redirect ${r.source}`,
15809
- children: /* @__PURE__ */ jsx(PencilSimple, { size: 14 })
15843
+ /* @__PURE__ */ jsx("option", {
15844
+ value: "false",
15845
+ children: "Manual"
15810
15846
  }),
15811
- /* @__PURE__ */ jsx("button", {
15812
- onClick: () => setDeleteId(r.id),
15813
- className: "p-1 text-kumo-subtle hover:text-kumo-danger",
15814
- title: "Delete redirect",
15815
- "aria-label": `Delete redirect ${r.source}`,
15816
- children: /* @__PURE__ */ jsx(Trash, { size: 14 })
15847
+ /* @__PURE__ */ jsx("option", {
15848
+ value: "true",
15849
+ children: "Auto (slug change)"
15817
15850
  })
15818
15851
  ]
15819
15852
  })
15820
15853
  ]
15821
- }, r.id))]
15822
- })] }),
15854
+ }),
15855
+ loopRedirectIds.size > 0 && /* @__PURE__ */ jsxs("div", {
15856
+ role: "alert",
15857
+ className: "flex items-start gap-3 rounded-lg border border-kumo-warning/50 bg-kumo-warning-tint p-4",
15858
+ children: [/* @__PURE__ */ jsx(WarningCircle, {
15859
+ size: 20,
15860
+ className: "mt-0.5 shrink-0 text-kumo-warning",
15861
+ weight: "fill",
15862
+ "aria-hidden": "true"
15863
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
15864
+ className: "text-sm font-medium text-kumo-warning",
15865
+ children: "Redirect loop detected"
15866
+ }), /* @__PURE__ */ jsxs("p", {
15867
+ className: "mt-1 text-sm text-kumo-subtle",
15868
+ children: [
15869
+ loopRedirectIds.size,
15870
+ " ",
15871
+ loopRedirectIds.size === 1 ? "redirect is" : "redirects are",
15872
+ " part of a loop. Visitors hitting these paths will see an error."
15873
+ ]
15874
+ })] })]
15875
+ }),
15876
+ redirectsQuery.isLoading ? /* @__PURE__ */ jsx("div", {
15877
+ className: "py-12 text-center text-kumo-subtle",
15878
+ children: "Loading redirects..."
15879
+ }) : redirects.length === 0 ? /* @__PURE__ */ jsxs("div", {
15880
+ className: "py-12 text-center text-kumo-subtle",
15881
+ children: [
15882
+ /* @__PURE__ */ jsx(ArrowsLeftRight, {
15883
+ size: 48,
15884
+ className: "mx-auto mb-4 opacity-30"
15885
+ }),
15886
+ /* @__PURE__ */ jsx("p", {
15887
+ className: "text-lg font-medium",
15888
+ children: "No redirects yet"
15889
+ }),
15890
+ /* @__PURE__ */ jsx("p", {
15891
+ className: "text-sm mt-1",
15892
+ children: "Create redirect rules to manage URL changes."
15893
+ })
15894
+ ]
15895
+ }) : /* @__PURE__ */ jsxs("div", {
15896
+ className: "border rounded-lg",
15897
+ children: [/* @__PURE__ */ jsxs("div", {
15898
+ className: "flex items-center gap-4 py-2 px-4 border-b bg-kumo-tint/50 text-sm font-medium text-kumo-subtle",
15899
+ children: [
15900
+ /* @__PURE__ */ jsx("div", {
15901
+ className: "flex-1",
15902
+ children: "Source"
15903
+ }),
15904
+ /* @__PURE__ */ jsx("div", { className: "w-8 text-center" }),
15905
+ /* @__PURE__ */ jsx("div", {
15906
+ className: "flex-1",
15907
+ children: "Destination"
15908
+ }),
15909
+ /* @__PURE__ */ jsx("div", {
15910
+ className: "w-14 text-center",
15911
+ children: "Code"
15912
+ }),
15913
+ /* @__PURE__ */ jsx("div", {
15914
+ className: "w-16 text-right",
15915
+ children: "Hits"
15916
+ }),
15917
+ /* @__PURE__ */ jsx("div", {
15918
+ className: "w-20 text-center",
15919
+ children: "Status"
15920
+ }),
15921
+ /* @__PURE__ */ jsx("div", { className: "w-20" })
15922
+ ]
15923
+ }), redirects.map((r) => /* @__PURE__ */ jsxs("div", {
15924
+ className: cn("flex items-center gap-4 py-2 px-4 border-b last:border-0 text-sm", !r.enabled && "opacity-50"),
15925
+ children: [
15926
+ /* @__PURE__ */ jsx("div", {
15927
+ className: "flex-1 font-mono text-xs truncate",
15928
+ title: r.source,
15929
+ children: r.source
15930
+ }),
15931
+ /* @__PURE__ */ jsx("div", {
15932
+ className: "w-8 text-center text-kumo-subtle",
15933
+ children: /* @__PURE__ */ jsx(ArrowRight, { size: 14 })
15934
+ }),
15935
+ /* @__PURE__ */ jsx("div", {
15936
+ className: "flex-1 font-mono text-xs truncate",
15937
+ title: r.destination,
15938
+ children: r.destination
15939
+ }),
15940
+ /* @__PURE__ */ jsx("div", {
15941
+ className: "w-14 text-center",
15942
+ children: /* @__PURE__ */ jsx(Badge, {
15943
+ variant: "secondary",
15944
+ children: r.type
15945
+ })
15946
+ }),
15947
+ /* @__PURE__ */ jsx("div", {
15948
+ className: "w-16 text-right tabular-nums text-kumo-subtle",
15949
+ children: r.hits
15950
+ }),
15951
+ /* @__PURE__ */ jsx("div", {
15952
+ className: "w-20 text-center",
15953
+ children: /* @__PURE__ */ jsx(Switch, {
15954
+ checked: r.enabled,
15955
+ onCheckedChange: (checked) => toggleMutation.mutate({
15956
+ id: r.id,
15957
+ enabled: checked
15958
+ }),
15959
+ "aria-label": r.enabled ? "Disable redirect" : "Enable redirect"
15960
+ })
15961
+ }),
15962
+ /* @__PURE__ */ jsxs("div", {
15963
+ className: "w-20 flex items-center justify-end gap-1",
15964
+ children: [
15965
+ loopRedirectIds.has(r.id) && /* @__PURE__ */ jsx("span", {
15966
+ title: "Part of a redirect loop",
15967
+ className: "mr-1 inline-flex",
15968
+ children: /* @__PURE__ */ jsx(WarningCircle, {
15969
+ size: 14,
15970
+ weight: "fill",
15971
+ className: "text-kumo-warning",
15972
+ role: "img",
15973
+ "aria-label": "Part of a redirect loop"
15974
+ })
15975
+ }),
15976
+ r.auto && /* @__PURE__ */ jsx(Badge, {
15977
+ variant: "outline",
15978
+ className: "mr-1 text-xs",
15979
+ children: "auto"
15980
+ }),
15981
+ /* @__PURE__ */ jsx("button", {
15982
+ onClick: () => setEditRedirect(r),
15983
+ className: "p-1 text-kumo-subtle hover:text-kumo-default",
15984
+ title: "Edit redirect",
15985
+ "aria-label": `Edit redirect ${r.source}`,
15986
+ children: /* @__PURE__ */ jsx(PencilSimple, { size: 14 })
15987
+ }),
15988
+ /* @__PURE__ */ jsx("button", {
15989
+ onClick: () => setDeleteId(r.id),
15990
+ className: "p-1 text-kumo-subtle hover:text-kumo-danger",
15991
+ title: "Delete redirect",
15992
+ "aria-label": `Delete redirect ${r.source}`,
15993
+ children: /* @__PURE__ */ jsx(Trash, { size: 14 })
15994
+ })
15995
+ ]
15996
+ })
15997
+ ]
15998
+ }, r.id))]
15999
+ })
16000
+ ] }),
15823
16001
  tab === "404s" && /* @__PURE__ */ jsx(NotFoundPanel, {
15824
16002
  items: notFoundQuery.data ?? [],
15825
16003
  onCreateRedirect: handleCreateFrom404
@@ -16709,35 +16887,159 @@ function Settings() {
16709
16887
  }
16710
16888
 
16711
16889
  //#endregion
16712
- //#region src/components/settings/AllowedDomainsSettings.tsx
16890
+ //#region src/components/users/roleDefinitions.ts
16713
16891
  /**
16714
- * Allowed Domains Settings - Self-signup domain management
16715
- *
16716
- * Only available when using passkey auth. When external auth (e.g., Cloudflare Access)
16717
- * is configured, this page shows an informational message instead.
16892
+ * Canonical role levels for admin UI (badge colors, selects, labels).
16893
+ * Allowed Domains UI only offers default roles up to Editor (40), not Admin (50).
16718
16894
  */
16719
- const ROLES$1 = [
16895
+ const ROLE_ENTRIES = [
16720
16896
  {
16721
16897
  value: 10,
16722
- label: "Subscriber"
16898
+ color: "gray",
16899
+ label: {
16900
+ id: "M2vfqB",
16901
+ message: "Subscriber"
16902
+ },
16903
+ description: {
16904
+ id: "m7BmGK",
16905
+ message: "Can view content"
16906
+ }
16723
16907
  },
16724
16908
  {
16725
16909
  value: 20,
16726
- label: "Contributor"
16910
+ color: "blue",
16911
+ label: {
16912
+ id: "3L51iw",
16913
+ message: "Contributor"
16914
+ },
16915
+ description: {
16916
+ id: "WzyYFi",
16917
+ message: "Can create content"
16918
+ }
16727
16919
  },
16728
16920
  {
16729
16921
  value: 30,
16730
- label: "Author"
16922
+ color: "green",
16923
+ label: {
16924
+ id: "VbeIOx",
16925
+ message: "Author"
16926
+ },
16927
+ description: {
16928
+ id: "1onhKx",
16929
+ message: "Can publish own content"
16930
+ }
16731
16931
  },
16732
16932
  {
16733
16933
  value: 40,
16734
- label: "Editor"
16934
+ color: "purple",
16935
+ label: {
16936
+ id: "uBAxNB",
16937
+ message: "Editor"
16938
+ },
16939
+ description: {
16940
+ id: "ppZiw8",
16941
+ message: "Can manage all content"
16942
+ }
16943
+ },
16944
+ {
16945
+ value: 50,
16946
+ color: "red",
16947
+ label: {
16948
+ id: "U3pytU",
16949
+ message: "Admin"
16950
+ },
16951
+ description: {
16952
+ id: "scmRyR",
16953
+ message: "Full access"
16954
+ }
16735
16955
  }
16736
16956
  ];
16737
- function getRoleName(level) {
16738
- return ROLES$1.find((r) => r.value === level)?.label ?? "Unknown";
16957
+ const ROLE_CONFIG = Object.fromEntries(ROLE_ENTRIES.map((e) => [e.value, {
16958
+ label: e.label,
16959
+ description: e.description,
16960
+ color: e.color
16961
+ }]));
16962
+ function unknownRoleConfig(role) {
16963
+ return {
16964
+ label: {
16965
+ id: "h4POyA",
16966
+ message: "Role {role}",
16967
+ values: { role }
16968
+ },
16969
+ description: {
16970
+ id: "C52+JH",
16971
+ message: "Unknown role"
16972
+ },
16973
+ color: "gray"
16974
+ };
16975
+ }
16976
+ /** Badge / display config for a numeric role level */
16977
+ function getRoleConfig(role) {
16978
+ return ROLE_CONFIG[role] ?? unknownRoleConfig(role);
16979
+ }
16980
+
16981
+ //#endregion
16982
+ //#region src/components/users/useRolesConfig.ts
16983
+ const MSG_ROLE_UNKNOWN = {
16984
+ id: "Ef7StM",
16985
+ message: "Unknown"
16986
+ };
16987
+ /**
16988
+ * Shared resolved role strings + descriptor rows for selects (after `i18n` is active).
16989
+ */
16990
+ function useRolesConfig() {
16991
+ const { _: _t } = useLingui();
16992
+ const roles = React.useMemo(() => ROLE_ENTRIES.map(({ value, label, description }) => ({
16993
+ value,
16994
+ label: _t(label),
16995
+ description: _t(description)
16996
+ })), [_t]);
16997
+ const roleLabels = React.useMemo(() => Object.fromEntries(ROLE_ENTRIES.map((r) => [String(r.value), _t(r.label)])), [_t]);
16998
+ return {
16999
+ roleLabels,
17000
+ getRoleLabel: React.useCallback((level) => roleLabels[String(level)] ?? _t(MSG_ROLE_UNKNOWN), [roleLabels, _t]),
17001
+ roles
17002
+ };
16739
17003
  }
17004
+
17005
+ //#endregion
17006
+ //#region src/components/settings/useAllowedDomainsRolesConfig.ts
17007
+ /** Self-signup default role must not be Admin (API / product rule). */
17008
+ const MAX_SELF_SIGNUP_DEFAULT_ROLE = 40;
17009
+ /**
17010
+ * Role labels and selects for Allowed Domains (Subscriber–Editor only for defaults).
17011
+ * Built on {@link useRolesConfig}; keeps the filter + `Select` `items` shape in one place.
17012
+ */
17013
+ function useAllowedDomainsRolesConfig() {
17014
+ const { roleLabels, getRoleLabel, roles } = useRolesConfig();
17015
+ const signupRoles = React.useMemo(() => roles.filter((r) => r.value <= MAX_SELF_SIGNUP_DEFAULT_ROLE), [roles]);
17016
+ return {
17017
+ getRoleLabel,
17018
+ signupRoles,
17019
+ signupRoleItems: React.useMemo(() => {
17020
+ const entries = signupRoles.map((r) => {
17021
+ const label = roleLabels[String(r.value)];
17022
+ return [String(r.value), label ?? getRoleLabel(r.value)];
17023
+ });
17024
+ return Object.fromEntries(entries);
17025
+ }, [
17026
+ signupRoles,
17027
+ roleLabels,
17028
+ getRoleLabel
17029
+ ])
17030
+ };
17031
+ }
17032
+
17033
+ //#endregion
17034
+ //#region src/components/settings/AllowedDomainsSettings.tsx
17035
+ /**
17036
+ * Allowed Domains Settings - Self-signup domain management
17037
+ *
17038
+ * Only available when using passkey auth. When external auth (e.g., Cloudflare Access)
17039
+ * is configured, this page shows an informational message instead.
17040
+ */
16740
17041
  function AllowedDomainsSettings() {
17042
+ const { getRoleLabel, signupRoles, signupRoleItems } = useAllowedDomainsRolesConfig();
16741
17043
  const queryClient = useQueryClient();
16742
17044
  const [isAddingDomain, setIsAddingDomain] = React.useState(false);
16743
17045
  const [editingDomain, setEditingDomain] = React.useState(null);
@@ -16929,7 +17231,7 @@ function AllowedDomainsSettings() {
16929
17231
  children: domain.domain
16930
17232
  }), /* @__PURE__ */ jsxs("div", {
16931
17233
  className: "text-sm text-kumo-subtle",
16932
- children: ["Default role: ", getRoleName(domain.defaultRole)]
17234
+ children: ["Default role: ", getRoleLabel(domain.defaultRole)]
16933
17235
  })] })]
16934
17236
  }), /* @__PURE__ */ jsxs("div", {
16935
17237
  className: "flex items-center gap-2",
@@ -16990,8 +17292,8 @@ function AllowedDomainsSettings() {
16990
17292
  label: "Default Role",
16991
17293
  value: String(newRole),
16992
17294
  onValueChange: (v) => v !== null && setNewRole(Number(v)),
16993
- items: Object.fromEntries(ROLES$1.map((r) => [String(r.value), r.label])),
16994
- children: ROLES$1.map((role) => /* @__PURE__ */ jsx(Select.Option, {
17295
+ items: signupRoleItems,
17296
+ children: signupRoles.map((role) => /* @__PURE__ */ jsx(Select.Option, {
16995
17297
  value: String(role.value),
16996
17298
  children: role.label
16997
17299
  }, role.value))
@@ -17051,8 +17353,8 @@ function AllowedDomainsSettings() {
17051
17353
  label: "Default Role",
17052
17354
  value: String(editingDomain?.defaultRole ?? 30),
17053
17355
  onValueChange: (v) => v !== null && editingDomain && handleUpdateRole(editingDomain.domain, Number(v)),
17054
- items: Object.fromEntries(ROLES$1.map((r) => [String(r.value), r.label])),
17055
- children: ROLES$1.map((role) => /* @__PURE__ */ jsx(Select.Option, {
17356
+ items: signupRoleItems,
17357
+ children: signupRoles.map((role) => /* @__PURE__ */ jsx(Select.Option, {
17056
17358
  value: String(role.value),
17057
17359
  children: role.label
17058
17360
  }, role.value))
@@ -17111,25 +17413,121 @@ function AllowedDomainsSettings() {
17111
17413
  const EXPIRY_OPTIONS = [
17112
17414
  {
17113
17415
  value: "none",
17114
- label: "No expiry"
17416
+ label: {
17417
+ id: "JYZYJA",
17418
+ message: "No expiry"
17419
+ }
17115
17420
  },
17116
17421
  {
17117
17422
  value: "7d",
17118
- label: "7 days"
17423
+ label: {
17424
+ id: "rJe6vw",
17425
+ message: "7 days"
17426
+ }
17119
17427
  },
17120
17428
  {
17121
17429
  value: "30d",
17122
- label: "30 days"
17430
+ label: {
17431
+ id: "P9cEa2",
17432
+ message: "30 days"
17433
+ }
17123
17434
  },
17124
17435
  {
17125
17436
  value: "90d",
17126
- label: "90 days"
17437
+ label: {
17438
+ id: "h28hXf",
17439
+ message: "90 days"
17440
+ }
17127
17441
  },
17128
17442
  {
17129
17443
  value: "365d",
17130
- label: "1 year"
17444
+ label: {
17445
+ id: "+N7uug",
17446
+ message: "1 year"
17447
+ }
17131
17448
  }
17132
17449
  ];
17450
+ const API_TOKEN_SCOPE_VALUES = [
17451
+ {
17452
+ scope: API_TOKEN_SCOPES.ContentRead,
17453
+ label: {
17454
+ id: "DqsyRK",
17455
+ message: "Content Read"
17456
+ },
17457
+ description: {
17458
+ id: "Pho0BC",
17459
+ message: "Read content entries"
17460
+ }
17461
+ },
17462
+ {
17463
+ scope: API_TOKEN_SCOPES.ContentWrite,
17464
+ label: {
17465
+ id: "js1bld",
17466
+ message: "Content Write"
17467
+ },
17468
+ description: {
17469
+ id: "e48Hn6",
17470
+ message: "Create, update, delete content"
17471
+ }
17472
+ },
17473
+ {
17474
+ scope: API_TOKEN_SCOPES.MediaRead,
17475
+ label: {
17476
+ id: "TNFBJj",
17477
+ message: "Media Read"
17478
+ },
17479
+ description: {
17480
+ id: "IxcUJS",
17481
+ message: "Read media files"
17482
+ }
17483
+ },
17484
+ {
17485
+ scope: API_TOKEN_SCOPES.MediaWrite,
17486
+ label: {
17487
+ id: "nAGBQY",
17488
+ message: "Media Write"
17489
+ },
17490
+ description: {
17491
+ id: "SrZ1jh",
17492
+ message: "Upload and delete media"
17493
+ }
17494
+ },
17495
+ {
17496
+ scope: API_TOKEN_SCOPES.SchemaRead,
17497
+ label: {
17498
+ id: "19WSvR",
17499
+ message: "Schema Read"
17500
+ },
17501
+ description: {
17502
+ id: "nEEs8e",
17503
+ message: "Read collection schemas"
17504
+ }
17505
+ },
17506
+ {
17507
+ scope: API_TOKEN_SCOPES.SchemaWrite,
17508
+ label: {
17509
+ id: "n6+BBp",
17510
+ message: "Schema Write"
17511
+ },
17512
+ description: {
17513
+ id: "UgmJPq",
17514
+ message: "Modify collection schemas"
17515
+ }
17516
+ },
17517
+ {
17518
+ scope: API_TOKEN_SCOPES.Admin,
17519
+ label: {
17520
+ id: "U3pytU",
17521
+ message: "Admin"
17522
+ },
17523
+ description: {
17524
+ id: "5SaI4f",
17525
+ message: "Full admin access"
17526
+ }
17527
+ }
17528
+ ];
17529
+ /** Wire scopes shown on the create-token form (contract-tested vs `API_TOKEN_SCOPES` and `@emdash-cms/auth`). */
17530
+ const API_TOKEN_SCOPE_FORM_SCOPES = API_TOKEN_SCOPE_VALUES.map((row) => row.scope);
17133
17531
  function computeExpiryDate(option) {
17134
17532
  if (option === "none") return void 0;
17135
17533
  const days = parseInt(option, 10);
@@ -17139,6 +17537,7 @@ function computeExpiryDate(option) {
17139
17537
  return date.toISOString();
17140
17538
  }
17141
17539
  function ApiTokenSettings() {
17540
+ const { _: _t } = useLingui();
17142
17541
  const queryClient = useQueryClient();
17143
17542
  const [showCreateForm, setShowCreateForm] = React.useState(false);
17144
17543
  const [newToken, setNewToken] = React.useState(null);
@@ -17180,6 +17579,7 @@ function ApiTokenSettings() {
17180
17579
  copyTimeoutRef.current = setTimeout(setCopied, 2e3, false);
17181
17580
  } catch {}
17182
17581
  };
17582
+ const expirySelectItems = React.useMemo(() => Object.fromEntries(EXPIRY_OPTIONS.map((o) => [o.value, _t(o.label)])), [_t]);
17183
17583
  return /* @__PURE__ */ jsxs("div", {
17184
17584
  className: "space-y-6",
17185
17585
  children: [
@@ -17190,15 +17590,24 @@ function ApiTokenSettings() {
17190
17590
  children: /* @__PURE__ */ jsx(Button, {
17191
17591
  variant: "ghost",
17192
17592
  shape: "square",
17193
- "aria-label": "Back to settings",
17593
+ "aria-label": _t({
17594
+ id: "9aZHfH",
17595
+ message: "Back to settings"
17596
+ }),
17194
17597
  children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
17195
17598
  })
17196
17599
  }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
17197
17600
  className: "text-2xl font-bold",
17198
- children: "API Tokens"
17601
+ children: _t({
17602
+ id: "ZiooJI",
17603
+ message: "API Tokens"
17604
+ })
17199
17605
  }), /* @__PURE__ */ jsx("p", {
17200
17606
  className: "text-sm text-kumo-subtle",
17201
- children: "Create personal access tokens for programmatic API access"
17607
+ children: _t({
17608
+ id: "dhH+RW",
17609
+ message: "Create personal access tokens for programmatic API access"
17610
+ })
17202
17611
  })] })]
17203
17612
  }),
17204
17613
  newToken && /* @__PURE__ */ jsx("div", {
@@ -17210,13 +17619,20 @@ function ApiTokenSettings() {
17210
17619
  /* @__PURE__ */ jsxs("div", {
17211
17620
  className: "flex-1 min-w-0",
17212
17621
  children: [
17213
- /* @__PURE__ */ jsxs("p", {
17622
+ /* @__PURE__ */ jsx("p", {
17214
17623
  className: "font-medium text-green-800 dark:text-green-200",
17215
- children: ["Token created: ", newToken.info.name]
17624
+ children: _t({
17625
+ id: "eoLxnB",
17626
+ message: "Token created: {0}",
17627
+ values: { 0: newToken.info.name }
17628
+ })
17216
17629
  }),
17217
17630
  /* @__PURE__ */ jsx("p", {
17218
17631
  className: "text-sm text-green-700 dark:text-green-300 mt-1",
17219
- children: "Copy this token now — it won't be shown again."
17632
+ children: _t({
17633
+ id: "DyPcUA",
17634
+ message: "Copy this token now — it won't be shown again."
17635
+ })
17220
17636
  }),
17221
17637
  /* @__PURE__ */ jsxs("div", {
17222
17638
  className: "mt-3 flex items-center gap-2",
@@ -17229,21 +17645,33 @@ function ApiTokenSettings() {
17229
17645
  variant: "ghost",
17230
17646
  shape: "square",
17231
17647
  onClick: () => setTokenVisible(!tokenVisible),
17232
- "aria-label": tokenVisible ? "Hide token" : "Show token",
17648
+ "aria-label": tokenVisible ? _t({
17649
+ id: "NEZFJN",
17650
+ message: "Hide token"
17651
+ }) : _t({
17652
+ id: "10eUfR",
17653
+ message: "Show token"
17654
+ }),
17233
17655
  children: tokenVisible ? /* @__PURE__ */ jsx(EyeSlash, {}) : /* @__PURE__ */ jsx(Eye, {})
17234
17656
  }),
17235
17657
  /* @__PURE__ */ jsx(Button, {
17236
17658
  variant: "ghost",
17237
17659
  shape: "square",
17238
17660
  onClick: handleCopyToken,
17239
- "aria-label": "Copy token",
17661
+ "aria-label": _t({
17662
+ id: "BddwrJ",
17663
+ message: "Copy token"
17664
+ }),
17240
17665
  children: /* @__PURE__ */ jsx(Copy, {})
17241
17666
  })
17242
17667
  ]
17243
17668
  }),
17244
17669
  copied && /* @__PURE__ */ jsx("p", {
17245
17670
  className: "text-xs text-green-600 dark:text-green-400 mt-1",
17246
- children: "Copied to clipboard"
17671
+ children: _t({
17672
+ id: "FxVG/l",
17673
+ message: "Copied to clipboard"
17674
+ })
17247
17675
  })
17248
17676
  ]
17249
17677
  }),
@@ -17251,13 +17679,20 @@ function ApiTokenSettings() {
17251
17679
  variant: "ghost",
17252
17680
  size: "sm",
17253
17681
  onClick: () => setNewToken(null),
17254
- "aria-label": "Dismiss",
17255
- children: "Dismiss"
17682
+ "aria-label": _t({
17683
+ id: "1QfxQT",
17684
+ message: "Dismiss"
17685
+ }),
17686
+ children: _t({
17687
+ id: "1QfxQT",
17688
+ message: "Dismiss"
17689
+ })
17256
17690
  })
17257
17691
  ]
17258
17692
  })
17259
17693
  }),
17260
17694
  showCreateForm ? /* @__PURE__ */ jsx(CreateTokenForm, {
17695
+ expirySelectItems,
17261
17696
  isCreating: createMutation.isPending,
17262
17697
  error: createMutation.error?.message ?? null,
17263
17698
  onSubmit: (input) => createMutation.mutate({
@@ -17269,7 +17704,10 @@ function ApiTokenSettings() {
17269
17704
  }) : /* @__PURE__ */ jsx(Button, {
17270
17705
  icon: /* @__PURE__ */ jsx(Plus, {}),
17271
17706
  onClick: () => setShowCreateForm(true),
17272
- children: "Create Token"
17707
+ children: _t({
17708
+ id: "JkdaXO",
17709
+ message: "Create Token"
17710
+ })
17273
17711
  }),
17274
17712
  /* @__PURE__ */ jsx("div", {
17275
17713
  className: "rounded-lg border bg-kumo-base",
@@ -17278,7 +17716,10 @@ function ApiTokenSettings() {
17278
17716
  children: /* @__PURE__ */ jsx(Loader, {})
17279
17717
  }) : !tokens || tokens.length === 0 ? /* @__PURE__ */ jsx("div", {
17280
17718
  className: "py-8 text-center text-sm text-kumo-subtle",
17281
- children: "No API tokens yet. Create one to get started."
17719
+ children: _t({
17720
+ id: "aio6Hc",
17721
+ message: "No API tokens yet. Create one to get started."
17722
+ })
17282
17723
  }) : /* @__PURE__ */ jsx("div", {
17283
17724
  className: "divide-y",
17284
17725
  children: tokens.map((token) => /* @__PURE__ */ jsxs("div", {
@@ -17299,14 +17740,30 @@ function ApiTokenSettings() {
17299
17740
  /* @__PURE__ */ jsxs("div", {
17300
17741
  className: "flex gap-3 mt-1 text-xs text-kumo-subtle",
17301
17742
  children: [
17302
- /* @__PURE__ */ jsxs("span", { children: ["Scopes: ", token.scopes.join(", ")] }),
17303
- token.expiresAt && /* @__PURE__ */ jsxs("span", { children: ["Expires ", new Date(token.expiresAt).toLocaleDateString()] }),
17304
- token.lastUsedAt && /* @__PURE__ */ jsxs("span", { children: ["Last used ", new Date(token.lastUsedAt).toLocaleDateString()] })
17743
+ /* @__PURE__ */ jsx("span", { children: _t({
17744
+ id: "NSzA6n",
17745
+ message: "Scopes: {0}",
17746
+ values: { 0: token.scopes.join(", ") }
17747
+ }) }),
17748
+ token.expiresAt && /* @__PURE__ */ jsx("span", { children: _t({
17749
+ id: "oOfJ56",
17750
+ message: "Expires {0}",
17751
+ values: { 0: new Date(token.expiresAt).toLocaleDateString() }
17752
+ }) }),
17753
+ token.lastUsedAt && /* @__PURE__ */ jsx("span", { children: _t({
17754
+ id: "ylo1I0",
17755
+ message: "Last used {0}",
17756
+ values: { 0: new Date(token.lastUsedAt).toLocaleDateString() }
17757
+ }) })
17305
17758
  ]
17306
17759
  }),
17307
- /* @__PURE__ */ jsxs("div", {
17760
+ /* @__PURE__ */ jsx("div", {
17308
17761
  className: "text-xs text-kumo-subtle mt-0.5",
17309
- children: ["Created ", new Date(token.createdAt).toLocaleDateString()]
17762
+ children: _t({
17763
+ id: "MXSt4t",
17764
+ message: "Created {0}",
17765
+ values: { 0: new Date(token.createdAt).toLocaleDateString() }
17766
+ })
17310
17767
  })
17311
17768
  ]
17312
17769
  }), revokeConfirmId === token.id ? /* @__PURE__ */ jsxs("div", {
@@ -17318,14 +17775,23 @@ function ApiTokenSettings() {
17318
17775
  }),
17319
17776
  /* @__PURE__ */ jsx("span", {
17320
17777
  className: "text-sm text-kumo-danger",
17321
- children: "Revoke?"
17778
+ children: _t({
17779
+ id: "U4/l0o",
17780
+ message: "Revoke?"
17781
+ })
17322
17782
  }),
17323
17783
  /* @__PURE__ */ jsx(Button, {
17324
17784
  variant: "destructive",
17325
17785
  size: "sm",
17326
17786
  disabled: revokeMutation.isPending,
17327
17787
  onClick: () => revokeMutation.mutate(token.id),
17328
- children: revokeMutation.isPending ? "Revoking..." : "Confirm"
17788
+ children: revokeMutation.isPending ? _t({
17789
+ id: "zzTOEt",
17790
+ message: "Revoking..."
17791
+ }) : _t({
17792
+ id: "7VpPHA",
17793
+ message: "Confirm"
17794
+ })
17329
17795
  }),
17330
17796
  /* @__PURE__ */ jsx(Button, {
17331
17797
  variant: "outline",
@@ -17334,14 +17800,20 @@ function ApiTokenSettings() {
17334
17800
  setRevokeConfirmId(null);
17335
17801
  revokeMutation.reset();
17336
17802
  },
17337
- children: "Cancel"
17803
+ children: _t({
17804
+ id: "dEgA5A",
17805
+ message: "Cancel"
17806
+ })
17338
17807
  })
17339
17808
  ]
17340
17809
  }) : /* @__PURE__ */ jsx(Button, {
17341
17810
  variant: "ghost",
17342
17811
  shape: "square",
17343
17812
  onClick: () => setRevokeConfirmId(token.id),
17344
- "aria-label": "Revoke token",
17813
+ "aria-label": _t({
17814
+ id: "7/ePoE",
17815
+ message: "Revoke token"
17816
+ }),
17345
17817
  children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4 text-kumo-subtle hover:text-kumo-danger" })
17346
17818
  })]
17347
17819
  }, token.id))
@@ -17350,7 +17822,8 @@ function ApiTokenSettings() {
17350
17822
  ]
17351
17823
  });
17352
17824
  }
17353
- function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17825
+ function CreateTokenForm({ expirySelectItems, isCreating, error, onSubmit, onCancel }) {
17826
+ const { _: _t2 } = useLingui();
17354
17827
  const [name, setName] = React.useState("");
17355
17828
  const [selectedScopes, setSelectedScopes] = React.useState(/* @__PURE__ */ new Set());
17356
17829
  const [expiry, setExpiry] = React.useState("30d");
@@ -17376,7 +17849,10 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17376
17849
  children: [
17377
17850
  /* @__PURE__ */ jsx("h2", {
17378
17851
  className: "text-lg font-semibold mb-4",
17379
- children: "Create New Token"
17852
+ children: _t2({
17853
+ id: "IBeakM",
17854
+ message: "Create New Token"
17855
+ })
17380
17856
  }),
17381
17857
  error && /* @__PURE__ */ jsxs("div", {
17382
17858
  className: "mb-4 rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-3 flex items-center gap-2 text-sm text-kumo-danger",
@@ -17387,40 +17863,54 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17387
17863
  className: "space-y-4",
17388
17864
  children: [
17389
17865
  /* @__PURE__ */ jsx(Input, {
17390
- label: "Token Name",
17866
+ label: _t2({
17867
+ id: "jJrqb1",
17868
+ message: "Token Name"
17869
+ }),
17391
17870
  value: name,
17392
17871
  onChange: (e) => setName(e.target.value),
17393
- placeholder: "e.g., CI/CD Pipeline",
17872
+ placeholder: _t2({
17873
+ id: "O1SmJ+",
17874
+ message: "e.g., CI/CD Pipeline"
17875
+ }),
17394
17876
  required: true,
17395
17877
  autoFocus: true
17396
17878
  }),
17397
17879
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17398
17880
  className: "text-sm font-medium mb-2",
17399
- children: "Scopes"
17881
+ children: _t2({
17882
+ id: "N/rFzD",
17883
+ message: "Scopes"
17884
+ })
17400
17885
  }), /* @__PURE__ */ jsx("div", {
17401
17886
  className: "space-y-2",
17402
- children: API_TOKEN_SCOPES.map((scope) => /* @__PURE__ */ jsxs("label", {
17403
- className: "flex items-start gap-2 cursor-pointer",
17404
- children: [/* @__PURE__ */ jsx(Checkbox, {
17405
- checked: selectedScopes.has(scope.value),
17406
- onCheckedChange: () => toggleScope(scope.value)
17407
- }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17408
- className: "text-sm font-medium",
17409
- children: scope.label
17410
- }), /* @__PURE__ */ jsx("div", {
17411
- className: "text-xs text-kumo-subtle",
17412
- children: scope.description
17413
- })] })]
17414
- }, scope.value))
17887
+ children: API_TOKEN_SCOPE_VALUES.map(({ scope, label, description }) => {
17888
+ return /* @__PURE__ */ jsxs("label", {
17889
+ className: "flex items-start gap-2 cursor-pointer",
17890
+ children: [/* @__PURE__ */ jsx(Checkbox, {
17891
+ checked: selectedScopes.has(scope),
17892
+ onCheckedChange: () => toggleScope(scope)
17893
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
17894
+ className: "text-sm font-medium",
17895
+ children: _t2(label)
17896
+ }), /* @__PURE__ */ jsx("div", {
17897
+ className: "text-xs text-kumo-subtle",
17898
+ children: _t2(description)
17899
+ })] })]
17900
+ }, scope);
17901
+ })
17415
17902
  })] }),
17416
17903
  /* @__PURE__ */ jsx(Select, {
17417
- label: "Expiry",
17904
+ label: _t2({
17905
+ id: "agO/T/",
17906
+ message: "Expiry"
17907
+ }),
17418
17908
  value: expiry,
17419
17909
  onValueChange: (v) => v !== null && setExpiry(v),
17420
- items: Object.fromEntries(EXPIRY_OPTIONS.map((o) => [o.value, o.label])),
17910
+ items: expirySelectItems,
17421
17911
  children: EXPIRY_OPTIONS.map((option) => /* @__PURE__ */ jsx(Select.Option, {
17422
17912
  value: option.value,
17423
- children: option.label
17913
+ children: _t2(option.label)
17424
17914
  }, option.value))
17425
17915
  }),
17426
17916
  /* @__PURE__ */ jsxs("div", {
@@ -17428,12 +17918,21 @@ function CreateTokenForm({ isCreating, error, onSubmit, onCancel }) {
17428
17918
  children: [/* @__PURE__ */ jsx(Button, {
17429
17919
  type: "submit",
17430
17920
  disabled: !isValid || isCreating,
17431
- children: isCreating ? "Creating..." : "Create Token"
17921
+ children: isCreating ? _t2({
17922
+ id: "HM56Bx",
17923
+ message: "Creating..."
17924
+ }) : _t2({
17925
+ id: "JkdaXO",
17926
+ message: "Create Token"
17927
+ })
17432
17928
  }), /* @__PURE__ */ jsx(Button, {
17433
17929
  type: "button",
17434
17930
  variant: "outline",
17435
17931
  onClick: onCancel,
17436
- children: "Cancel"
17932
+ children: _t2({
17933
+ id: "dEgA5A",
17934
+ message: "Cancel"
17935
+ })
17437
17936
  })]
17438
17937
  })
17439
17938
  ]
@@ -19326,7 +19825,10 @@ async function searchContent(query) {
19326
19825
  function buildNavItems(manifest, userRole) {
19327
19826
  const items = [{
19328
19827
  id: "dashboard",
19329
- title: "Dashboard",
19828
+ title: {
19829
+ id: "7p5kLi",
19830
+ message: "Dashboard"
19831
+ },
19330
19832
  to: "/",
19331
19833
  icon: SquaresFour,
19332
19834
  keywords: ["home", "overview"]
@@ -19341,7 +19843,10 @@ function buildNavItems(manifest, userRole) {
19341
19843
  });
19342
19844
  items.push({
19343
19845
  id: "media",
19344
- title: "Media Library",
19846
+ title: {
19847
+ id: "ia4TsE",
19848
+ message: "Media Library"
19849
+ },
19345
19850
  to: "/media",
19346
19851
  icon: Image$1,
19347
19852
  keywords: [
@@ -19351,35 +19856,50 @@ function buildNavItems(manifest, userRole) {
19351
19856
  ]
19352
19857
  }, {
19353
19858
  id: "menus",
19354
- title: "Menus",
19859
+ title: {
19860
+ id: "NXjnVQ",
19861
+ message: "Menus"
19862
+ },
19355
19863
  to: "/menus",
19356
19864
  icon: List,
19357
19865
  minRole: ROLE_EDITOR$2,
19358
19866
  keywords: ["navigation"]
19359
19867
  }, {
19360
19868
  id: "widgets",
19361
- title: "Widgets",
19869
+ title: {
19870
+ id: "tL6W2K",
19871
+ message: "Widgets"
19872
+ },
19362
19873
  to: "/widgets",
19363
19874
  icon: GridFour,
19364
19875
  minRole: ROLE_EDITOR$2,
19365
19876
  keywords: ["sidebar", "footer"]
19366
19877
  }, {
19367
19878
  id: "sections",
19368
- title: "Sections",
19879
+ title: {
19880
+ id: "R4OWFD",
19881
+ message: "Sections"
19882
+ },
19369
19883
  to: "/sections",
19370
19884
  icon: Stack,
19371
19885
  minRole: ROLE_EDITOR$2,
19372
19886
  keywords: ["page builder", "blocks"]
19373
19887
  }, {
19374
19888
  id: "content-types",
19375
- title: "Content Types",
19889
+ title: {
19890
+ id: "F7Jcuy",
19891
+ message: "Content Types"
19892
+ },
19376
19893
  to: "/content-types",
19377
19894
  icon: Database,
19378
19895
  minRole: ROLE_ADMIN$2,
19379
19896
  keywords: ["schema", "collections"]
19380
19897
  }, {
19381
19898
  id: "categories",
19382
- title: "Categories",
19899
+ title: {
19900
+ id: "NUrY9o",
19901
+ message: "Categories"
19902
+ },
19383
19903
  to: "/taxonomies/$taxonomy",
19384
19904
  params: { taxonomy: "category" },
19385
19905
  icon: FileText,
@@ -19387,7 +19907,10 @@ function buildNavItems(manifest, userRole) {
19387
19907
  keywords: ["taxonomy"]
19388
19908
  }, {
19389
19909
  id: "tags",
19390
- title: "Tags",
19910
+ title: {
19911
+ id: "OYHzN1",
19912
+ message: "Tags"
19913
+ },
19391
19914
  to: "/taxonomies/$taxonomy",
19392
19915
  params: { taxonomy: "tag" },
19393
19916
  icon: FileText,
@@ -19395,35 +19918,50 @@ function buildNavItems(manifest, userRole) {
19395
19918
  keywords: ["taxonomy"]
19396
19919
  }, {
19397
19920
  id: "users",
19398
- title: "Users",
19921
+ title: {
19922
+ id: "Sxm8rQ",
19923
+ message: "Users"
19924
+ },
19399
19925
  to: "/users",
19400
19926
  icon: Users,
19401
19927
  minRole: ROLE_ADMIN$2,
19402
19928
  keywords: ["accounts", "team"]
19403
19929
  }, {
19404
19930
  id: "plugins",
19405
- title: "Plugins",
19931
+ title: {
19932
+ id: "ohUJJM",
19933
+ message: "Plugins"
19934
+ },
19406
19935
  to: "/plugins-manager",
19407
19936
  icon: PuzzlePiece,
19408
19937
  minRole: ROLE_ADMIN$2,
19409
19938
  keywords: ["extensions", "add-ons"]
19410
19939
  }, {
19411
19940
  id: "import",
19412
- title: "Import",
19941
+ title: {
19942
+ id: "l3s5ri",
19943
+ message: "Import"
19944
+ },
19413
19945
  to: "/import/wordpress",
19414
19946
  icon: Upload,
19415
19947
  minRole: ROLE_ADMIN$2,
19416
19948
  keywords: ["wordpress", "migrate"]
19417
19949
  }, {
19418
19950
  id: "settings",
19419
- title: "Settings",
19951
+ title: {
19952
+ id: "Tz0i8g",
19953
+ message: "Settings"
19954
+ },
19420
19955
  to: "/settings",
19421
19956
  icon: Gear,
19422
19957
  minRole: ROLE_ADMIN$2,
19423
19958
  keywords: ["configuration", "preferences"]
19424
19959
  }, {
19425
19960
  id: "security",
19426
- title: "Security Settings",
19961
+ title: {
19962
+ id: "zro6wS",
19963
+ message: "Security Settings"
19964
+ },
19427
19965
  to: "/settings/security",
19428
19966
  icon: Gear,
19429
19967
  minRole: ROLE_ADMIN$2,
@@ -19444,16 +19982,17 @@ function buildNavItems(manifest, userRole) {
19444
19982
  }
19445
19983
  return items.filter((item) => !item.minRole || userRole >= item.minRole);
19446
19984
  }
19447
- function filterNavItems(items, query) {
19985
+ function filterNavItems(items, query, translate) {
19448
19986
  if (!query) return items;
19449
19987
  const lowerQuery = query.toLowerCase();
19450
19988
  return items.filter((item) => {
19451
- const titleMatch = item.title.toLowerCase().includes(lowerQuery);
19989
+ const titleMatch = (typeof item.title === "string" ? item.title : translate(item.title)).toLowerCase().includes(lowerQuery);
19452
19990
  const keywordMatch = item.keywords?.some((k) => k.toLowerCase().includes(lowerQuery));
19453
19991
  return titleMatch || keywordMatch;
19454
19992
  });
19455
19993
  }
19456
19994
  function AdminCommandPalette({ manifest }) {
19995
+ const { _: _t } = useLingui();
19457
19996
  const [open, setOpen] = React.useState(false);
19458
19997
  const [query, setQuery] = React.useState("");
19459
19998
  const navigate = useNavigate$1();
@@ -19468,14 +20007,22 @@ function AdminCommandPalette({ manifest }) {
19468
20007
  });
19469
20008
  const isPendingSearch = query.length >= 2 && query !== debouncedQuery || isSearching;
19470
20009
  const allNavItems = React.useMemo(() => buildNavItems(manifest, userRole), [manifest, userRole]);
19471
- const filteredNavItems = React.useMemo(() => filterNavItems(allNavItems, query), [allNavItems, query]);
20010
+ const filteredNavItems = React.useMemo(() => filterNavItems(allNavItems, query, _t), [
20011
+ allNavItems,
20012
+ query,
20013
+ _t
20014
+ ]);
19472
20015
  const resultGroups = React.useMemo(() => {
19473
20016
  const groups = [];
19474
20017
  if (filteredNavItems.length > 0) groups.push({
19475
- label: "Navigation",
20018
+ id: "navigation",
20019
+ label: {
20020
+ id: "UxKoFf",
20021
+ message: "Navigation"
20022
+ },
19476
20023
  items: filteredNavItems.map((item) => ({
19477
20024
  id: item.id,
19478
- title: item.title,
20025
+ title: typeof item.title === "string" ? item.title : _t(item.title),
19479
20026
  to: item.to,
19480
20027
  params: item.params,
19481
20028
  icon: /* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4" })
@@ -19483,7 +20030,7 @@ function AdminCommandPalette({ manifest }) {
19483
20030
  });
19484
20031
  if (searchResults?.items && searchResults.items.length > 0) {
19485
20032
  const contentItems = searchResults.items.map((result) => {
19486
- const collectionConfig = manifest.collections[result.collection];
20033
+ const collectionLabel = manifest.collections[result.collection]?.label ?? result.collection;
19487
20034
  return {
19488
20035
  id: `content-${result.id}`,
19489
20036
  title: result.title || result.slug,
@@ -19493,12 +20040,16 @@ function AdminCommandPalette({ manifest }) {
19493
20040
  id: result.id
19494
20041
  },
19495
20042
  icon: /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
19496
- description: collectionConfig?.label || result.collection,
20043
+ description: collectionLabel,
19497
20044
  collection: result.collection
19498
20045
  };
19499
20046
  });
19500
20047
  groups.push({
19501
- label: "Content",
20048
+ id: "content",
20049
+ label: {
20050
+ id: "4b3oEV",
20051
+ message: "Content"
20052
+ },
19502
20053
  items: contentItems
19503
20054
  });
19504
20055
  }
@@ -19506,7 +20057,8 @@ function AdminCommandPalette({ manifest }) {
19506
20057
  }, [
19507
20058
  filteredNavItems,
19508
20059
  searchResults,
19509
- manifest.collections
20060
+ manifest.collections,
20061
+ _t
19510
20062
  ]);
19511
20063
  useHotkeys("mod+k", (e) => {
19512
20064
  e.preventDefault();
@@ -19534,12 +20086,15 @@ function AdminCommandPalette({ manifest }) {
19534
20086
  items: resultGroups,
19535
20087
  value: query,
19536
20088
  onValueChange: setQuery,
19537
- itemToStringValue: (group) => group.label,
20089
+ itemToStringValue: (group) => _t(group.label),
19538
20090
  onSelect: handleSelect,
19539
20091
  getSelectableItems: (groups) => groups.flatMap((g) => g.items),
19540
20092
  children: [
19541
20093
  /* @__PURE__ */ jsx(CommandPalette.Input, {
19542
- placeholder: "Search pages and content...",
20094
+ placeholder: _t({
20095
+ id: "z/bggT",
20096
+ message: "Search pages and content..."
20097
+ }),
19543
20098
  leading: /* @__PURE__ */ jsx(MagnifyingGlass, {
19544
20099
  className: "h-4 w-4 text-kumo-subtle",
19545
20100
  weight: "bold"
@@ -19547,14 +20102,17 @@ function AdminCommandPalette({ manifest }) {
19547
20102
  }),
19548
20103
  /* @__PURE__ */ jsx(CommandPalette.List, { children: isPendingSearch ? /* @__PURE__ */ jsx(CommandPalette.Loading, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CommandPalette.Results, { children: (group) => /* @__PURE__ */ jsxs(CommandPalette.Group, {
19549
20104
  items: group.items,
19550
- children: [/* @__PURE__ */ jsx(CommandPalette.GroupLabel, { children: group.label }), /* @__PURE__ */ jsx(CommandPalette.Items, { children: (item) => /* @__PURE__ */ jsx(CommandPalette.ResultItem, {
20105
+ children: [/* @__PURE__ */ jsx(CommandPalette.GroupLabel, { children: _t(group.label) }), /* @__PURE__ */ jsx(CommandPalette.Items, { children: (item) => /* @__PURE__ */ jsx(CommandPalette.ResultItem, {
19551
20106
  value: item,
19552
20107
  title: item.title,
19553
20108
  description: item.description,
19554
20109
  icon: item.icon,
19555
20110
  onClick: (e) => handleItemClick(item, e)
19556
20111
  }, item.id) })]
19557
- }, group.label) }), /* @__PURE__ */ jsx(CommandPalette.Empty, { children: "No results found" })] }) }),
20112
+ }, group.id) }), /* @__PURE__ */ jsx(CommandPalette.Empty, { children: _t({
20113
+ id: "AxPAXW",
20114
+ message: "No results found"
20115
+ }) })] }) }),
19558
20116
  /* @__PURE__ */ jsx(CommandPalette.Footer, { children: /* @__PURE__ */ jsxs("div", {
19559
20117
  className: "flex items-center gap-4 text-kumo-subtle",
19560
20118
  children: [
@@ -19563,21 +20121,30 @@ function AdminCommandPalette({ manifest }) {
19563
20121
  children: [/* @__PURE__ */ jsx("kbd", {
19564
20122
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19565
20123
  children: "Enter"
19566
- }), /* @__PURE__ */ jsx("span", { children: "to select" })]
20124
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20125
+ id: "jpBN9M",
20126
+ message: "to select"
20127
+ }) })]
19567
20128
  }),
19568
20129
  /* @__PURE__ */ jsxs("span", {
19569
20130
  className: "flex items-center gap-1",
19570
20131
  children: [/* @__PURE__ */ jsxs("kbd", {
19571
20132
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19572
20133
  children: [IS_MAC ? "Cmd" : "Ctrl", "+Enter"]
19573
- }), /* @__PURE__ */ jsx("span", { children: "new tab" })]
20134
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20135
+ id: "k2rZ7L",
20136
+ message: "new tab"
20137
+ }) })]
19574
20138
  }),
19575
20139
  /* @__PURE__ */ jsxs("span", {
19576
20140
  className: "flex items-center gap-1",
19577
20141
  children: [/* @__PURE__ */ jsx("kbd", {
19578
20142
  className: "rounded bg-kumo-control px-1.5 py-0.5 text-xs",
19579
20143
  children: "Esc"
19580
- }), /* @__PURE__ */ jsx("span", { children: "to close" })]
20144
+ }), /* @__PURE__ */ jsx("span", { children: _t({
20145
+ id: "UbVgIu",
20146
+ message: "to close"
20147
+ }) })]
19581
20148
  })
19582
20149
  ]
19583
20150
  }) })
@@ -19906,7 +20473,11 @@ function SidebarNav({ manifest }) {
19906
20473
  ] }),
19907
20474
  /* @__PURE__ */ jsx(KumoSidebar.Footer, { children: /* @__PURE__ */ jsxs("p", {
19908
20475
  className: "emdash-nav-label px-3 py-2 text-[11px] text-white/30",
19909
- children: ["EmDash CMS v", manifest.version || "0.0.0"]
20476
+ children: [
20477
+ "EmDash CMS v",
20478
+ manifest.version || "0.0.0",
20479
+ manifest.commit && ` (${manifest.commit})`
20480
+ ]
19910
20481
  }) })
19911
20482
  ]
19912
20483
  })] });
@@ -20047,13 +20618,71 @@ function Header() {
20047
20618
  *
20048
20619
  * Shown to new users on their first login to welcome them to EmDash.
20049
20620
  */
20050
- function getRoleLabel$1(role) {
20051
- if (role >= 50) return "Administrator";
20052
- if (role >= 40) return "Editor";
20053
- if (role >= 30) return "Author";
20054
- if (role >= 20) return "Contributor";
20055
- return "Subscriber";
20056
- }
20621
+ const MSG_ROLE_ADMINISTRATOR = {
20622
+ id: "Faptqt",
20623
+ message: "Administrator"
20624
+ };
20625
+ const MSG_ROLE_EDITOR = {
20626
+ id: "uBAxNB",
20627
+ message: "Editor"
20628
+ };
20629
+ const MSG_ROLE_AUTHOR = {
20630
+ id: "VbeIOx",
20631
+ message: "Author"
20632
+ };
20633
+ const MSG_ROLE_CONTRIBUTOR = {
20634
+ id: "3L51iw",
20635
+ message: "Contributor"
20636
+ };
20637
+ const MSG_ROLE_SUBSCRIBER = {
20638
+ id: "M2vfqB",
20639
+ message: "Subscriber"
20640
+ };
20641
+ function roleDescriptor(role) {
20642
+ if (role >= 50) return MSG_ROLE_ADMINISTRATOR;
20643
+ if (role >= 40) return MSG_ROLE_EDITOR;
20644
+ if (role >= 30) return MSG_ROLE_AUTHOR;
20645
+ if (role >= 20) return MSG_ROLE_CONTRIBUTOR;
20646
+ return MSG_ROLE_SUBSCRIBER;
20647
+ }
20648
+ const MSG_ACCOUNT_CREATED = {
20649
+ id: "ATCJJC",
20650
+ message: "Your account has been created successfully."
20651
+ };
20652
+ const MSG_YOUR_ROLE = {
20653
+ id: "T25KCz",
20654
+ message: "Your Role"
20655
+ };
20656
+ const MSG_SCOPE_ADMIN = {
20657
+ id: "T8yDG9",
20658
+ message: "You have full access to manage this site, including users, settings, and all content."
20659
+ };
20660
+ const MSG_SCOPE_EDITOR = {
20661
+ id: "Ic/8JR",
20662
+ message: "You can manage content, media, menus, and taxonomies."
20663
+ };
20664
+ const MSG_SCOPE_AUTHOR = {
20665
+ id: "kKNInt",
20666
+ message: "You can create and edit your own content."
20667
+ };
20668
+ const MSG_SCOPE_CONTRIBUTOR = {
20669
+ id: "nzN9WF",
20670
+ message: "You can view and contribute to the site."
20671
+ };
20672
+ function scopeDescriptor(isAdmin, userRole) {
20673
+ if (isAdmin) return MSG_SCOPE_ADMIN;
20674
+ if (userRole >= 40) return MSG_SCOPE_EDITOR;
20675
+ if (userRole >= 30) return MSG_SCOPE_AUTHOR;
20676
+ return MSG_SCOPE_CONTRIBUTOR;
20677
+ }
20678
+ const MSG_ADMIN_INVITE = {
20679
+ id: "QwxfcZ",
20680
+ message: "As an administrator, you can invite other users from the Users section."
20681
+ };
20682
+ const MSG_CLOSE = {
20683
+ id: "yz7wBu",
20684
+ message: "Close"
20685
+ };
20057
20686
  async function dismissWelcome() {
20058
20687
  const response = await apiFetch("/_emdash/api/auth/me", {
20059
20688
  method: "POST",
@@ -20063,6 +20692,7 @@ async function dismissWelcome() {
20063
20692
  if (!response.ok) await throwResponseError(response, "Failed to dismiss welcome");
20064
20693
  }
20065
20694
  function WelcomeModal({ open, onClose, userName, userRole }) {
20695
+ const { _: _t } = useLingui();
20066
20696
  const queryClient = useQueryClient();
20067
20697
  const dismissMutation = useMutation({
20068
20698
  mutationFn: dismissWelcome,
@@ -20083,8 +20713,17 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20083
20713
  const handleGetStarted = () => {
20084
20714
  dismissMutation.mutate();
20085
20715
  };
20086
- const roleLabel = getRoleLabel$1(userRole);
20716
+ const roleLabel = _t(roleDescriptor(userRole));
20087
20717
  const isAdmin = userRole >= 50;
20718
+ const firstName = userName?.split(" ")?.[0]?.trim() ?? "";
20719
+ const titleDescriptor = firstName.length > 0 ? {
20720
+ id: "7vQyJA",
20721
+ message: "Welcome to EmDash, {firstName}!",
20722
+ values: { firstName }
20723
+ } : {
20724
+ id: "N5RCak",
20725
+ message: "Welcome to EmDash!"
20726
+ };
20088
20727
  return /* @__PURE__ */ jsx(Dialog.Root, {
20089
20728
  open,
20090
20729
  onOpenChange: (isOpen) => !isOpen && handleGetStarted(),
@@ -20094,16 +20733,16 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20094
20733
  /* @__PURE__ */ jsxs("div", {
20095
20734
  className: "flex items-start justify-between gap-4",
20096
20735
  children: [/* @__PURE__ */ jsx("div", { className: "flex-1" }), /* @__PURE__ */ jsx(Dialog.Close, {
20097
- "aria-label": "Close",
20736
+ "aria-label": _t(MSG_CLOSE),
20098
20737
  render: (props) => /* @__PURE__ */ jsxs(Button, {
20099
20738
  ...props,
20100
20739
  variant: "ghost",
20101
20740
  shape: "square",
20102
- "aria-label": "Close",
20741
+ "aria-label": _t(MSG_CLOSE),
20103
20742
  className: "absolute right-4 top-4",
20104
20743
  children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
20105
20744
  className: "sr-only",
20106
- children: "Close"
20745
+ children: _t(MSG_CLOSE)
20107
20746
  })]
20108
20747
  })
20109
20748
  })]
@@ -20115,17 +20754,13 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20115
20754
  className: "mx-auto mb-4",
20116
20755
  children: /* @__PURE__ */ jsx(LogoIcon, { className: "h-16 w-16" })
20117
20756
  }),
20118
- /* @__PURE__ */ jsxs(Dialog.Title, {
20757
+ /* @__PURE__ */ jsx(Dialog.Title, {
20119
20758
  className: "text-2xl font-semibold leading-none tracking-tight",
20120
- children: [
20121
- "Welcome to EmDash",
20122
- userName ? `, ${userName.split(" ")[0]}` : "",
20123
- "!"
20124
- ]
20759
+ children: _t(titleDescriptor)
20125
20760
  }),
20126
20761
  /* @__PURE__ */ jsx(Dialog.Description, {
20127
20762
  className: "text-base text-kumo-subtle",
20128
- children: "Your account has been created successfully."
20763
+ children: _t(MSG_ACCOUNT_CREATED)
20129
20764
  })
20130
20765
  ]
20131
20766
  }),
@@ -20136,7 +20771,7 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20136
20771
  children: [
20137
20772
  /* @__PURE__ */ jsx("div", {
20138
20773
  className: "text-sm font-medium",
20139
- children: "Your Role"
20774
+ children: _t(MSG_YOUR_ROLE)
20140
20775
  }),
20141
20776
  /* @__PURE__ */ jsx("div", {
20142
20777
  className: "text-lg font-semibold text-kumo-brand",
@@ -20144,20 +20779,12 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20144
20779
  }),
20145
20780
  /* @__PURE__ */ jsx("p", {
20146
20781
  className: "text-sm text-kumo-subtle mt-1",
20147
- children: isAdmin ? "You have full access to manage this site, including users, settings, and all content." : userRole >= 40 ? "You can manage content, media, menus, and taxonomies." : userRole >= 30 ? "You can create and edit your own content." : "You can view and contribute to the site."
20782
+ children: _t(scopeDescriptor(isAdmin, userRole))
20148
20783
  })
20149
20784
  ]
20150
- }), isAdmin && /* @__PURE__ */ jsxs("p", {
20785
+ }), isAdmin && /* @__PURE__ */ jsx("p", {
20151
20786
  className: "text-sm text-kumo-subtle",
20152
- children: [
20153
- "As an administrator, you can invite other users from the",
20154
- " ",
20155
- /* @__PURE__ */ jsx("span", {
20156
- className: "font-medium",
20157
- children: "Users"
20158
- }),
20159
- " section."
20160
- ]
20787
+ children: _t(MSG_ADMIN_INVITE)
20161
20788
  })]
20162
20789
  }),
20163
20790
  /* @__PURE__ */ jsx("div", {
@@ -20166,7 +20793,13 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
20166
20793
  onClick: handleGetStarted,
20167
20794
  disabled: dismissMutation.isPending,
20168
20795
  size: "lg",
20169
- children: dismissMutation.isPending ? "Loading..." : "Get Started"
20796
+ children: dismissMutation.isPending ? _t({
20797
+ id: "Z3FXyt",
20798
+ message: "Loading..."
20799
+ }) : _t({
20800
+ id: "c3b0B0",
20801
+ message: "Get Started"
20802
+ })
20170
20803
  })
20171
20804
  })
20172
20805
  ]
@@ -21645,22 +22278,29 @@ function isPaletteItem(data) {
21645
22278
  /** Built-in widget types available in the palette */
21646
22279
  const BUILTIN_WIDGETS = [{
21647
22280
  id: "palette-content",
21648
- label: "Content Block",
21649
- description: "Rich text content",
21650
- input: {
21651
- type: "content",
21652
- title: "Content Block"
21653
- }
22281
+ label: {
22282
+ id: "ESoS5n",
22283
+ message: "Content Block"
22284
+ },
22285
+ description: {
22286
+ id: "oYoiTA",
22287
+ message: "Rich text content"
22288
+ },
22289
+ input: { type: "content" }
21654
22290
  }, {
21655
22291
  id: "palette-menu",
21656
- label: "Menu",
21657
- description: "Display a navigation menu",
21658
- input: {
21659
- type: "menu",
21660
- title: "Menu"
21661
- }
22292
+ label: {
22293
+ id: "zucql+",
22294
+ message: "Menu"
22295
+ },
22296
+ description: {
22297
+ id: "an2U5p",
22298
+ message: "Display a navigation menu"
22299
+ },
22300
+ input: { type: "menu" }
21662
22301
  }];
21663
22302
  function Widgets() {
22303
+ const { _: _t } = useLingui();
21664
22304
  const queryClient = useQueryClient();
21665
22305
  const toastManager = Toast.useToastManager();
21666
22306
  const [isCreateAreaOpen, setIsCreateAreaOpen] = React.useState(false);
@@ -21902,9 +22542,12 @@ function Widgets() {
21902
22542
  className: "space-y-2",
21903
22543
  children: [BUILTIN_WIDGETS.map((item) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
21904
22544
  id: item.id,
21905
- label: item.label,
21906
- description: item.description,
21907
- widgetInput: item.input
22545
+ label: _t(item.label),
22546
+ description: _t(item.description),
22547
+ widgetInput: {
22548
+ ...item.input,
22549
+ title: _t(item.label)
22550
+ }
21908
22551
  }, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
21909
22552
  id: `palette-comp-${comp.id}`,
21910
22553
  label: comp.label,
@@ -24668,50 +25311,11 @@ function BylinesPage() {
24668
25311
 
24669
25312
  //#endregion
24670
25313
  //#region src/components/users/RoleBadge.tsx
24671
- /** Role level to name mapping */
24672
- const ROLE_CONFIG = {
24673
- 10: {
24674
- label: "Subscriber",
24675
- color: "gray",
24676
- description: "Can view content"
24677
- },
24678
- 20: {
24679
- label: "Contributor",
24680
- color: "blue",
24681
- description: "Can create content"
24682
- },
24683
- 30: {
24684
- label: "Author",
24685
- color: "green",
24686
- description: "Can publish own content"
24687
- },
24688
- 40: {
24689
- label: "Editor",
24690
- color: "purple",
24691
- description: "Can manage all content"
24692
- },
24693
- 50: {
24694
- label: "Admin",
24695
- color: "red",
24696
- description: "Full access"
24697
- }
24698
- };
24699
- /** Get role config, with fallback for unknown roles */
24700
- function getRoleConfig(role) {
24701
- return ROLE_CONFIG[role] ?? {
24702
- label: `Role ${role}`,
24703
- color: "gray",
24704
- description: "Unknown role"
24705
- };
24706
- }
24707
- /** Get role label from role level */
24708
- function getRoleLabel(role) {
24709
- return getRoleConfig(role).label;
24710
- }
24711
25314
  /**
24712
25315
  * Role badge component with semantic colors
24713
25316
  */
24714
25317
  function RoleBadge({ role, size = "sm", showDescription = false, className }) {
25318
+ const { _: _t } = useLingui();
24715
25319
  const config = getRoleConfig(role);
24716
25320
  return /* @__PURE__ */ jsxs("span", {
24717
25321
  className: cn("inline-flex items-center rounded-full font-medium", {
@@ -24724,41 +25328,13 @@ function RoleBadge({ role, size = "sm", showDescription = false, className }) {
24724
25328
  purple: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200",
24725
25329
  red: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
24726
25330
  }[config.color], className),
24727
- title: showDescription ? void 0 : config.description,
24728
- children: [config.label, showDescription && /* @__PURE__ */ jsxs("span", {
25331
+ title: showDescription ? void 0 : _t(config.description),
25332
+ children: [_t(config.label), showDescription && /* @__PURE__ */ jsxs("span", {
24729
25333
  className: "ml-1 opacity-75",
24730
- children: ["- ", config.description]
25334
+ children: ["- ", _t(config.description)]
24731
25335
  })]
24732
25336
  });
24733
25337
  }
24734
- /** List of all roles for dropdowns */
24735
- const ROLES = [
24736
- {
24737
- value: 10,
24738
- label: "Subscriber",
24739
- description: "Can view content"
24740
- },
24741
- {
24742
- value: 20,
24743
- label: "Contributor",
24744
- description: "Can create content"
24745
- },
24746
- {
24747
- value: 30,
24748
- label: "Author",
24749
- description: "Can publish own content"
24750
- },
24751
- {
24752
- value: 40,
24753
- label: "Editor",
24754
- description: "Can manage all content"
24755
- },
24756
- {
24757
- value: 50,
24758
- label: "Admin",
24759
- description: "Full access"
24760
- }
24761
- ];
24762
25338
 
24763
25339
  //#endregion
24764
25340
  //#region src/components/users/UserList.tsx
@@ -24766,6 +25342,22 @@ const ROLES = [
24766
25342
  * User list component with search, filter, and table display
24767
25343
  */
24768
25344
  function UserList({ users, isLoading, hasMore, searchQuery, roleFilter, onSearchChange, onRoleFilterChange, onSelectUser, onInviteUser, onLoadMore }) {
25345
+ const { _: _t } = useLingui();
25346
+ const { roles, roleLabels } = useRolesConfig();
25347
+ const roleFilterSelectItems = React.useMemo(() => ({
25348
+ all: _t({
25349
+ id: "Hm90t3",
25350
+ message: "All roles"
25351
+ }),
25352
+ ...roleLabels
25353
+ }), [_t, roleLabels]);
25354
+ const roleFilterSelectOptions = React.useMemo(() => [{
25355
+ value: "all",
25356
+ label: _t({
25357
+ id: "Hm90t3",
25358
+ message: "All roles"
25359
+ })
25360
+ }, ...roles], [_t, roles]);
24769
25361
  return /* @__PURE__ */ jsxs("div", {
24770
25362
  className: "space-y-4",
24771
25363
  children: [
@@ -24795,21 +25387,15 @@ function UserList({ users, isLoading, hasMore, searchQuery, roleFilter, onSearch
24795
25387
  onChange: (e) => onSearchChange(e.target.value),
24796
25388
  "aria-label": "Search users"
24797
25389
  })]
24798
- }), /* @__PURE__ */ jsxs(Select, {
25390
+ }), /* @__PURE__ */ jsx(Select, {
24799
25391
  value: roleFilter?.toString() ?? "all",
24800
25392
  onValueChange: (value) => onRoleFilterChange(value === "all" || value === null ? void 0 : parseInt(value, 10)),
24801
- items: {
24802
- all: "All roles",
24803
- ...Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label]))
24804
- },
25393
+ items: roleFilterSelectItems,
24805
25394
  "aria-label": "Filter by role",
24806
- children: [/* @__PURE__ */ jsx(Select.Option, {
24807
- value: "all",
24808
- children: "All roles"
24809
- }), ROLES.map((role) => /* @__PURE__ */ jsx(Select.Option, {
24810
- value: role.value.toString(),
24811
- children: role.label
24812
- }, role.value))]
25395
+ children: roleFilterSelectOptions.map((option) => /* @__PURE__ */ jsx(Select.Option, {
25396
+ value: option.value,
25397
+ children: option.label
25398
+ }, option.value))
24813
25399
  })]
24814
25400
  }),
24815
25401
  /* @__PURE__ */ jsx("div", {
@@ -24986,6 +25572,7 @@ function UserListSkeleton() {
24986
25572
  * User detail slide-over panel with inline editing
24987
25573
  */
24988
25574
  function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, recoverySent, recoveryError, currentUserId, onClose, onSave, onDisable, onEnable, onSendRecovery }) {
25575
+ const { roles, roleLabels, getRoleLabel } = useRolesConfig();
24989
25576
  const [name, setName] = React.useState(user?.name ?? "");
24990
25577
  const [email, setEmail] = React.useState(user?.email ?? "");
24991
25578
  const [role, setRole] = React.useState(user?.role ?? 30);
@@ -25097,8 +25684,8 @@ function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, reco
25097
25684
  label: "Role",
25098
25685
  value: role.toString(),
25099
25686
  onValueChange: (v) => v !== null && setRole(parseInt(v, 10)),
25100
- items: Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label])),
25101
- children: ROLES.map((r) => /* @__PURE__ */ jsx(Select.Option, {
25687
+ items: roleLabels,
25688
+ children: roles.map((r) => /* @__PURE__ */ jsx(Select.Option, {
25102
25689
  value: r.value.toString(),
25103
25690
  children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: r.label }), /* @__PURE__ */ jsx("div", {
25104
25691
  className: "text-xs text-kumo-subtle",
@@ -25309,6 +25896,7 @@ function UserDetailSkeleton() {
25309
25896
  * Invite user modal — sends invite email or shows copy-link fallback
25310
25897
  */
25311
25898
  function InviteUserModal({ open, isSending, error, inviteUrl, onOpenChange, onInvite }) {
25899
+ const { roles, roleLabels } = useRolesConfig();
25312
25900
  const [email, setEmail] = React.useState("");
25313
25901
  const [role, setRole] = React.useState(30);
25314
25902
  const [copied, setCopied] = React.useState(false);
@@ -25436,8 +26024,8 @@ function InviteUserModal({ open, isSending, error, inviteUrl, onOpenChange, onIn
25436
26024
  label: "Role",
25437
26025
  value: role.toString(),
25438
26026
  onValueChange: (v) => v !== null && setRole(parseInt(v, 10)),
25439
- items: Object.fromEntries(ROLES.map((r) => [r.value.toString(), r.label])),
25440
- children: ROLES.map((r) => /* @__PURE__ */ jsx(Select.Option, {
26027
+ items: roleLabels,
26028
+ children: roles.map((r) => /* @__PURE__ */ jsx(Select.Option, {
25441
26029
  value: r.value.toString(),
25442
26030
  children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: r.label }), /* @__PURE__ */ jsx("div", {
25443
26031
  className: "text-xs text-kumo-subtle",
@@ -25492,6 +26080,7 @@ function useDebounce(value, delay) {
25492
26080
  return debouncedValue;
25493
26081
  }
25494
26082
  function UsersPage() {
26083
+ const { getRoleLabel } = useRolesConfig();
25495
26084
  const queryClient = useQueryClient();
25496
26085
  const [searchQuery, setSearchQuery] = React.useState("");
25497
26086
  const [roleFilter, setRoleFilter] = React.useState();