@byline/admin 2.5.2 → 2.6.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.
Files changed (260) hide show
  1. package/dist/fields/array/array-field.d.ts +14 -0
  2. package/dist/fields/array/array-field.js +177 -0
  3. package/dist/fields/array/array-field.module.js +11 -0
  4. package/dist/fields/array/array-field_module.css +32 -0
  5. package/dist/fields/blocks/blocks-field.d.ts +13 -0
  6. package/dist/fields/blocks/blocks-field.js +245 -0
  7. package/dist/fields/blocks/blocks-field.module.js +26 -0
  8. package/dist/fields/blocks/blocks-field_module.css +107 -0
  9. package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
  10. package/dist/fields/checkbox/checkbox-field.js +28 -0
  11. package/dist/fields/checkbox/checkbox-field.module.js +6 -0
  12. package/dist/fields/checkbox/checkbox-field_module.css +4 -0
  13. package/dist/fields/column-formatter.d.ts +20 -0
  14. package/dist/fields/column-formatter.js +15 -0
  15. package/dist/fields/date-time-formatter.d.ts +16 -0
  16. package/dist/fields/date-time-formatter.js +8 -0
  17. package/dist/fields/datetime/datetime-field.d.ts +16 -0
  18. package/dist/fields/datetime/datetime-field.js +37 -0
  19. package/dist/fields/datetime/datetime-field.module.js +5 -0
  20. package/dist/fields/datetime/datetime-field_module.css +4 -0
  21. package/dist/fields/draggable-context-menu.d.ts +6 -0
  22. package/dist/fields/draggable-context-menu.js +85 -0
  23. package/dist/fields/draggable-context-menu.module.js +15 -0
  24. package/dist/fields/draggable-context-menu_module.css +91 -0
  25. package/dist/fields/field-helpers.d.ts +26 -0
  26. package/dist/fields/field-helpers.js +50 -0
  27. package/dist/fields/field-renderer.d.ts +37 -0
  28. package/dist/fields/field-renderer.js +206 -0
  29. package/dist/fields/field-renderer.module.js +8 -0
  30. package/dist/fields/field-renderer_module.css +11 -0
  31. package/dist/fields/field-services-context.d.ts +16 -0
  32. package/dist/fields/field-services-context.js +13 -0
  33. package/dist/fields/field-services-types.d.ts +63 -0
  34. package/dist/fields/field-services-types.js +1 -0
  35. package/dist/fields/file/file-field.d.ts +19 -0
  36. package/dist/fields/file/file-field.js +225 -0
  37. package/dist/fields/file/file-field.module.js +18 -0
  38. package/dist/fields/file/file-field_module.css +131 -0
  39. package/dist/fields/file/file-upload-field.d.ts +21 -0
  40. package/dist/fields/file/file-upload-field.js +130 -0
  41. package/dist/fields/file/file-upload-field.module.js +15 -0
  42. package/dist/fields/file/file-upload-field_module.css +74 -0
  43. package/dist/fields/group/group-field.d.ts +15 -0
  44. package/dist/fields/group/group-field.js +59 -0
  45. package/dist/fields/group/group-field.module.js +9 -0
  46. package/dist/fields/group/group-field_module.css +27 -0
  47. package/dist/fields/image/image-field.d.ts +19 -0
  48. package/dist/fields/image/image-field.js +241 -0
  49. package/dist/fields/image/image-field.module.js +22 -0
  50. package/dist/fields/image/image-field_module.css +121 -0
  51. package/dist/fields/image/image-upload-field.d.ts +21 -0
  52. package/dist/fields/image/image-upload-field.js +190 -0
  53. package/dist/fields/image/image-upload-field.module.js +19 -0
  54. package/dist/fields/image/image-upload-field_module.css +92 -0
  55. package/dist/fields/local-date-time.d.ts +27 -0
  56. package/dist/fields/local-date-time.js +49 -0
  57. package/dist/fields/locale-badge.d.ts +18 -0
  58. package/dist/fields/locale-badge.js +10 -0
  59. package/dist/fields/locale-badge.module.js +5 -0
  60. package/dist/fields/locale-badge_module.css +27 -0
  61. package/dist/fields/numerical/numerical-field.d.ts +18 -0
  62. package/dist/fields/numerical/numerical-field.js +74 -0
  63. package/dist/fields/relation/relation-display.d.ts +40 -0
  64. package/dist/fields/relation/relation-display.js +58 -0
  65. package/dist/fields/relation/relation-display.module.js +9 -0
  66. package/dist/fields/relation/relation-display_module.css +21 -0
  67. package/dist/fields/relation/relation-field.d.ts +18 -0
  68. package/dist/fields/relation/relation-field.js +138 -0
  69. package/dist/fields/relation/relation-field.module.js +13 -0
  70. package/dist/fields/relation/relation-field_module.css +62 -0
  71. package/dist/fields/relation/relation-picker.d.ts +49 -0
  72. package/dist/fields/relation/relation-picker.js +236 -0
  73. package/dist/fields/relation/relation-picker.module.js +26 -0
  74. package/dist/fields/relation/relation-picker_module.css +124 -0
  75. package/dist/fields/relation/relation-summary.d.ts +31 -0
  76. package/dist/fields/relation/relation-summary.js +50 -0
  77. package/dist/fields/relation/relation-summary.module.js +11 -0
  78. package/dist/fields/relation/relation-summary_module.css +37 -0
  79. package/dist/fields/select/select-field.d.ts +16 -0
  80. package/dist/fields/select/select-field.js +50 -0
  81. package/dist/fields/select/select-field.module.js +5 -0
  82. package/dist/fields/select/select-field_module.css +4 -0
  83. package/dist/fields/sortable-item.d.ts +15 -0
  84. package/dist/fields/sortable-item.js +81 -0
  85. package/dist/fields/sortable-item.module.js +22 -0
  86. package/dist/fields/sortable-item_module.css +124 -0
  87. package/dist/fields/text/text-field.d.ts +20 -0
  88. package/dist/fields/text/text-field.js +104 -0
  89. package/dist/fields/text/text-field.module.js +6 -0
  90. package/dist/fields/text/text-field_module.css +5 -0
  91. package/dist/fields/text-area/text-area-field.d.ts +20 -0
  92. package/dist/fields/text-area/text-area-field.js +105 -0
  93. package/dist/fields/text-area/text-area-field.module.js +6 -0
  94. package/dist/fields/text-area/text-area-field_module.css +5 -0
  95. package/dist/fields/use-field-change-handler.d.ts +23 -0
  96. package/dist/fields/use-field-change-handler.js +52 -0
  97. package/dist/forms/document-actions.d.ts +48 -0
  98. package/dist/forms/document-actions.js +475 -0
  99. package/dist/forms/document-actions.module.js +34 -0
  100. package/dist/forms/document-actions_module.css +118 -0
  101. package/dist/forms/form-context.d.ts +89 -0
  102. package/dist/forms/form-context.js +466 -0
  103. package/dist/forms/form-renderer.d.ts +98 -0
  104. package/dist/forms/form-renderer.js +597 -0
  105. package/dist/forms/form-renderer.module.js +46 -0
  106. package/dist/forms/form-renderer_module.css +245 -0
  107. package/dist/forms/navigation-guard.d.ts +54 -0
  108. package/dist/forms/navigation-guard.js +22 -0
  109. package/dist/forms/path-widget.d.ts +36 -0
  110. package/dist/forms/path-widget.js +116 -0
  111. package/dist/forms/path-widget.module.js +8 -0
  112. package/dist/forms/path-widget_module.css +29 -0
  113. package/dist/forms/upload-executor.d.ts +57 -0
  114. package/dist/forms/upload-executor.js +94 -0
  115. package/dist/lib/translate-validation-error.d.ts +36 -0
  116. package/dist/lib/translate-validation-error.js +11 -0
  117. package/dist/modules/admin-account/commands.d.ts +2 -1
  118. package/dist/modules/admin-account/commands.js +13 -2
  119. package/dist/modules/admin-account/components/change-password.js +45 -36
  120. package/dist/modules/admin-account/components/container.js +185 -134
  121. package/dist/modules/admin-account/components/preferences.d.ts +8 -0
  122. package/dist/modules/admin-account/components/preferences.js +152 -0
  123. package/dist/modules/admin-account/components/preferences.module.js +11 -0
  124. package/dist/modules/admin-account/components/preferences_module.css +41 -0
  125. package/dist/modules/admin-account/components/update.js +50 -31
  126. package/dist/modules/admin-account/index.d.ts +3 -3
  127. package/dist/modules/admin-account/index.js +2 -2
  128. package/dist/modules/admin-account/schemas.d.ts +4 -0
  129. package/dist/modules/admin-account/schemas.js +4 -1
  130. package/dist/modules/admin-account/service.d.ts +1 -0
  131. package/dist/modules/admin-account/service.js +8 -0
  132. package/dist/modules/admin-permissions/components/inspector.js +31 -41
  133. package/dist/modules/admin-roles/components/create.js +43 -26
  134. package/dist/modules/admin-roles/components/permissions.js +26 -35
  135. package/dist/modules/admin-roles/components/update.js +26 -16
  136. package/dist/modules/admin-users/components/create.js +60 -40
  137. package/dist/modules/admin-users/components/roles.js +9 -15
  138. package/dist/modules/admin-users/components/set-password.js +30 -31
  139. package/dist/modules/admin-users/components/update.js +58 -39
  140. package/dist/modules/admin-users/dto.js +1 -0
  141. package/dist/modules/admin-users/repository.d.ts +17 -0
  142. package/dist/modules/admin-users/schemas.d.ts +4 -0
  143. package/dist/modules/admin-users/schemas.js +6 -2
  144. package/dist/modules/auth/components/sign-in-form.js +10 -8
  145. package/dist/presentation/group.d.ts +27 -0
  146. package/dist/presentation/group.js +14 -0
  147. package/dist/presentation/group.module.js +6 -0
  148. package/dist/presentation/group_module.css +19 -0
  149. package/dist/presentation/row.d.ts +25 -0
  150. package/dist/presentation/row.js +8 -0
  151. package/dist/presentation/row.module.js +5 -0
  152. package/dist/presentation/row_module.css +18 -0
  153. package/dist/presentation/tabs.d.ts +25 -0
  154. package/dist/presentation/tabs.js +39 -0
  155. package/dist/presentation/tabs.module.js +10 -0
  156. package/dist/presentation/tabs_module.css +68 -0
  157. package/dist/react.d.ts +66 -0
  158. package/dist/react.js +36 -0
  159. package/dist/services/admin-services-types.d.ts +16 -0
  160. package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
  161. package/dist/widgets/diff-viewer/diff-modal.js +149 -0
  162. package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
  163. package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
  164. package/dist/widgets/status-badge/status-badge.d.ts +25 -0
  165. package/dist/widgets/status-badge/status-badge.js +37 -0
  166. package/dist/widgets/status-badge/status-badge.module.js +7 -0
  167. package/dist/widgets/status-badge/status-badge_module.css +20 -0
  168. package/package.json +14 -4
  169. package/src/fields/array/array-field.module.css +48 -0
  170. package/src/fields/array/array-field.tsx +267 -0
  171. package/src/fields/blocks/blocks-field.module.css +148 -0
  172. package/src/fields/blocks/blocks-field.tsx +323 -0
  173. package/src/fields/checkbox/checkbox-field.module.css +4 -0
  174. package/src/fields/checkbox/checkbox-field.tsx +54 -0
  175. package/src/fields/column-formatter.tsx +31 -0
  176. package/src/fields/date-time-formatter.tsx +22 -0
  177. package/src/fields/datetime/datetime-field.module.css +13 -0
  178. package/src/fields/datetime/datetime-field.tsx +54 -0
  179. package/src/fields/draggable-context-menu.module.css +127 -0
  180. package/src/fields/draggable-context-menu.tsx +87 -0
  181. package/src/fields/field-helpers.ts +69 -0
  182. package/src/fields/field-renderer.module.css +22 -0
  183. package/src/fields/field-renderer.tsx +288 -0
  184. package/src/fields/field-services-context.tsx +35 -0
  185. package/src/fields/field-services-types.ts +68 -0
  186. package/src/fields/file/file-field.module.css +153 -0
  187. package/src/fields/file/file-field.tsx +286 -0
  188. package/src/fields/file/file-upload-field.module.css +101 -0
  189. package/src/fields/file/file-upload-field.tsx +187 -0
  190. package/src/fields/group/group-field.module.css +43 -0
  191. package/src/fields/group/group-field.tsx +84 -0
  192. package/src/fields/image/image-field.module.css +155 -0
  193. package/src/fields/image/image-field.tsx +306 -0
  194. package/src/fields/image/image-upload-field.module.css +123 -0
  195. package/src/fields/image/image-upload-field.tsx +276 -0
  196. package/src/fields/local-date-time.tsx +88 -0
  197. package/src/fields/locale-badge.module.css +37 -0
  198. package/src/fields/locale-badge.tsx +32 -0
  199. package/src/fields/numerical/numerical-field.tsx +114 -0
  200. package/src/fields/relation/relation-display.module.css +36 -0
  201. package/src/fields/relation/relation-display.tsx +130 -0
  202. package/src/fields/relation/relation-field.module.css +83 -0
  203. package/src/fields/relation/relation-field.tsx +211 -0
  204. package/src/fields/relation/relation-picker.module.css +168 -0
  205. package/src/fields/relation/relation-picker.tsx +326 -0
  206. package/src/fields/relation/relation-summary.module.css +55 -0
  207. package/src/fields/relation/relation-summary.tsx +123 -0
  208. package/src/fields/select/select-field.module.css +13 -0
  209. package/src/fields/select/select-field.tsx +61 -0
  210. package/src/fields/sortable-item.module.css +167 -0
  211. package/src/fields/sortable-item.tsx +106 -0
  212. package/src/fields/text/text-field.module.css +13 -0
  213. package/src/fields/text/text-field.tsx +146 -0
  214. package/src/fields/text-area/text-area-field.module.css +13 -0
  215. package/src/fields/text-area/text-area-field.tsx +147 -0
  216. package/src/fields/use-field-change-handler.ts +112 -0
  217. package/src/forms/document-actions.module.css +160 -0
  218. package/src/forms/document-actions.tsx +482 -0
  219. package/src/forms/form-context.tsx +704 -0
  220. package/src/forms/form-renderer.module.css +321 -0
  221. package/src/forms/form-renderer.tsx +891 -0
  222. package/src/forms/navigation-guard.tsx +98 -0
  223. package/src/forms/path-widget.module.css +41 -0
  224. package/src/forms/path-widget.test.tsx +217 -0
  225. package/src/forms/path-widget.tsx +183 -0
  226. package/src/forms/upload-executor.ts +192 -0
  227. package/src/lib/translate-validation-error.ts +56 -0
  228. package/src/modules/admin-account/commands.ts +13 -0
  229. package/src/modules/admin-account/components/change-password.tsx +46 -31
  230. package/src/modules/admin-account/components/container.tsx +83 -38
  231. package/src/modules/admin-account/components/preferences.module.css +60 -0
  232. package/src/modules/admin-account/components/preferences.tsx +203 -0
  233. package/src/modules/admin-account/components/update.tsx +53 -27
  234. package/src/modules/admin-account/index.ts +3 -0
  235. package/src/modules/admin-account/schemas.ts +13 -0
  236. package/src/modules/admin-account/service.ts +12 -0
  237. package/src/modules/admin-permissions/components/inspector.tsx +22 -14
  238. package/src/modules/admin-roles/components/create.tsx +51 -23
  239. package/src/modules/admin-roles/components/permissions.tsx +25 -21
  240. package/src/modules/admin-roles/components/update.tsx +37 -19
  241. package/src/modules/admin-users/components/create.tsx +63 -34
  242. package/src/modules/admin-users/components/roles.tsx +9 -8
  243. package/src/modules/admin-users/components/set-password.tsx +34 -28
  244. package/src/modules/admin-users/components/update.tsx +58 -36
  245. package/src/modules/admin-users/dto.ts +1 -0
  246. package/src/modules/admin-users/repository.ts +17 -0
  247. package/src/modules/admin-users/schemas.ts +12 -0
  248. package/src/modules/auth/components/sign-in-form.tsx +14 -8
  249. package/src/presentation/group.module.css +41 -0
  250. package/src/presentation/group.tsx +40 -0
  251. package/src/presentation/row.module.css +32 -0
  252. package/src/presentation/row.tsx +33 -0
  253. package/src/presentation/tabs.module.css +107 -0
  254. package/src/presentation/tabs.tsx +84 -0
  255. package/src/react.ts +84 -0
  256. package/src/services/admin-services-types.ts +18 -0
  257. package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
  258. package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
  259. package/src/widgets/status-badge/status-badge.module.css +31 -0
  260. package/src/widgets/status-badge/status-badge.tsx +71 -0
@@ -1,20 +1,16 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useMemo, useState } from "react";
4
4
  import { revalidateLogic, useForm } from "@tanstack/react-form-start";
5
+ import { useTranslation } from "@byline/i18n/react";
5
6
  import { Alert, Button, Input, LoaderEllipsis } from "@byline/ui/react";
6
7
  import classnames from "classnames";
7
8
  import { z } from "zod";
8
9
  import { useBylineAdminServices } from "../../../services/admin-services-context.js";
9
10
  import update_module from "./update.module.js";
10
- const updateAccountSchema = z.object({
11
- given_name: z.string().max(100, 'Given name must not exceed 100 characters'),
12
- family_name: z.string().max(100, 'Family name must not exceed 100 characters'),
13
- username: z.string().max(100, 'Username must not exceed 100 characters'),
14
- email: z.email({
15
- message: 'Enter a valid email address'
16
- }).min(3).max(254, 'Email must not exceed 254 characters')
17
- });
11
+ const MAX_NAME = 100;
12
+ const MAX_USERNAME = 100;
13
+ const MAX_EMAIL = 254;
18
14
  function defaultsFrom(account) {
19
15
  return {
20
16
  given_name: account.given_name ?? '',
@@ -37,8 +33,27 @@ function buildPatch(values, account) {
37
33
  }
38
34
  function UpdateAccount({ account, onClose, onSuccess }) {
39
35
  const { updateAccount } = useBylineAdminServices();
36
+ const { t } = useTranslation('byline-admin');
40
37
  const [formError, setFormError] = useState(null);
41
38
  const [successMessage, setSuccessMessage] = useState(null);
39
+ const updateAccountSchema = useMemo(()=>z.object({
40
+ given_name: z.string().max(MAX_NAME, t('account.update.errors.givenNameTooLong', {
41
+ max: MAX_NAME
42
+ })),
43
+ family_name: z.string().max(MAX_NAME, t('account.update.errors.familyNameTooLong', {
44
+ max: MAX_NAME
45
+ })),
46
+ username: z.string().max(MAX_USERNAME, t('account.update.errors.usernameTooLong', {
47
+ max: MAX_USERNAME
48
+ })),
49
+ email: z.email({
50
+ message: t('account.update.errors.invalidEmail')
51
+ }).min(3).max(MAX_EMAIL, t('account.update.errors.emailTooLong', {
52
+ max: MAX_EMAIL
53
+ }))
54
+ }), [
55
+ t
56
+ ]);
42
57
  const form = useForm({
43
58
  defaultValues: defaultsFrom(account),
44
59
  validationLogic: revalidateLogic({
@@ -52,7 +67,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
52
67
  setFormError(null);
53
68
  setSuccessMessage(null);
54
69
  const patch = buildPatch(value, account);
55
- if (0 === Object.keys(patch).length) return void setSuccessMessage('No changes to save.');
70
+ if (0 === Object.keys(patch).length) return void setSuccessMessage(t('common.feedback.noChanges'));
56
71
  try {
57
72
  const updated = await updateAccount({
58
73
  data: {
@@ -60,23 +75,27 @@ function UpdateAccount({ account, onClose, onSuccess }) {
60
75
  patch
61
76
  }
62
77
  });
63
- setSuccessMessage('Saved.');
78
+ setSuccessMessage(t('common.feedback.saved'));
64
79
  onSuccess?.(updated);
65
80
  } catch (err) {
66
81
  const code = getErrorCode(err);
67
- if ('admin.users.emailInUse' === code) return void form.setFieldMeta('email', (meta)=>({
68
- ...meta,
69
- errorMap: {
70
- ...meta.errorMap,
71
- onServer: 'This email is already in use.'
72
- },
73
- errors: [
74
- 'This email is already in use.'
75
- ]
76
- }));
77
- if ('admin.users.versionConflict' === code) return void setFormError('Your account has been modified elsewhere since you opened this form. Reload to refresh and try again.');
78
- if ('admin.account.notFound' === code) return void setFormError('Your admin account could not be found. Please sign in again.');
79
- setFormError('Could not save changes. Please try again.');
82
+ if ('admin.users.emailInUse' === code) {
83
+ const message = t('account.update.errors.emailInUse');
84
+ form.setFieldMeta('email', (meta)=>({
85
+ ...meta,
86
+ errorMap: {
87
+ ...meta.errorMap,
88
+ onServer: message
89
+ },
90
+ errors: [
91
+ message
92
+ ]
93
+ }));
94
+ return;
95
+ }
96
+ if ('admin.users.versionConflict' === code) return void setFormError(t('common.errors.versionConflict'));
97
+ if ('admin.account.notFound' === code) return void setFormError(t('common.errors.accountNotFound'));
98
+ setFormError(t('common.errors.couldNotSave'));
80
99
  }
81
100
  }
82
101
  });
@@ -102,7 +121,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
102
121
  /*#__PURE__*/ jsx(form.Field, {
103
122
  name: "given_name",
104
123
  children: (field)=>/*#__PURE__*/ jsx(Input, {
105
- label: "Given name",
124
+ label: t('account.update.fields.givenName'),
106
125
  id: "given_name",
107
126
  name: field.name,
108
127
  value: field.state.value,
@@ -116,7 +135,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
116
135
  /*#__PURE__*/ jsx(form.Field, {
117
136
  name: "family_name",
118
137
  children: (field)=>/*#__PURE__*/ jsx(Input, {
119
- label: "Family name",
138
+ label: t('account.update.fields.familyName'),
120
139
  id: "family_name",
121
140
  name: field.name,
122
141
  value: field.state.value,
@@ -130,7 +149,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
130
149
  /*#__PURE__*/ jsx(form.Field, {
131
150
  name: "username",
132
151
  children: (field)=>/*#__PURE__*/ jsx(Input, {
133
- label: "Username",
152
+ label: t('account.update.fields.username'),
134
153
  id: "username",
135
154
  name: field.name,
136
155
  value: field.state.value,
@@ -138,14 +157,14 @@ function UpdateAccount({ account, onClose, onSuccess }) {
138
157
  onChange: (e)=>field.handleChange(e.currentTarget.value),
139
158
  error: field.state.meta.errors.length > 0,
140
159
  errorText: firstError(field.state.meta.errors),
141
- helpText: "Optional. Leave blank to clear.",
160
+ helpText: t('account.update.fields.usernameHelp'),
142
161
  autoComplete: "username"
143
162
  })
144
163
  }),
145
164
  /*#__PURE__*/ jsx(form.Field, {
146
165
  name: "email",
147
166
  children: (field)=>/*#__PURE__*/ jsx(Input, {
148
- label: "Email",
167
+ label: t('common.fields.email'),
149
168
  id: "email",
150
169
  name: field.name,
151
170
  type: "email",
@@ -167,7 +186,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
167
186
  size: "sm",
168
187
  onClick: onClose,
169
188
  className: classnames('byline-account-update-action', update_module.action),
170
- children: successMessage ? 'Close' : 'Cancel'
189
+ children: successMessage ? t('common.actions.close') : t('common.actions.cancel')
171
190
  }),
172
191
  /*#__PURE__*/ jsx(form.Subscribe, {
173
192
  selector: (state)=>({
@@ -183,7 +202,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
183
202
  className: classnames('byline-account-update-action', update_module.action),
184
203
  children: true === isSubmitting ? /*#__PURE__*/ jsx(LoaderEllipsis, {
185
204
  size: 42
186
- }) : 'Save'
205
+ }) : t('common.actions.save')
187
206
  })
188
207
  })
189
208
  ]
@@ -28,9 +28,9 @@
28
28
  * yet — that depends on `RefreshTokensRepository` semantics and a
29
29
  * "sign out everywhere on password change" follow-up.
30
30
  */
31
- export { changeAccountPasswordCommand, getAccountCommand, updateAccountCommand, } from './commands.js';
31
+ export { changeAccountPasswordCommand, getAccountCommand, setPreferredLocaleCommand, updateAccountCommand, } from './commands.js';
32
32
  export { AdminAccountError, type AdminAccountErrorCode, AdminAccountErrorCodes, ERR_ADMIN_ACCOUNT_INVALID_CURRENT_PASSWORD, ERR_ADMIN_ACCOUNT_NOT_FOUND, } from './errors.js';
33
- export { accountResponseSchema, changeAccountPasswordRequestSchema, getAccountRequestSchema, okResponseSchema, updateAccountRequestSchema, } from './schemas.js';
33
+ export { accountResponseSchema, changeAccountPasswordRequestSchema, getAccountRequestSchema, okResponseSchema, setPreferredLocaleRequestSchema, updateAccountRequestSchema, } from './schemas.js';
34
34
  export { AdminAccountService } from './service.js';
35
35
  export type { AdminAccountCommandDeps } from './commands.js';
36
- export type { AccountResponse, ChangeAccountPasswordRequest, GetAccountRequest, OkResponse, UpdateAccountRequest, } from './schemas.js';
36
+ export type { AccountResponse, ChangeAccountPasswordRequest, GetAccountRequest, OkResponse, SetPreferredLocaleRequest, UpdateAccountRequest, } from './schemas.js';
@@ -1,4 +1,4 @@
1
- export { changeAccountPasswordCommand, getAccountCommand, updateAccountCommand } from "./commands.js";
1
+ export { changeAccountPasswordCommand, getAccountCommand, setPreferredLocaleCommand, updateAccountCommand } from "./commands.js";
2
2
  export { AdminAccountError, AdminAccountErrorCodes, ERR_ADMIN_ACCOUNT_INVALID_CURRENT_PASSWORD, ERR_ADMIN_ACCOUNT_NOT_FOUND } from "./errors.js";
3
- export { accountResponseSchema, changeAccountPasswordRequestSchema, getAccountRequestSchema, okResponseSchema, updateAccountRequestSchema } from "./schemas.js";
3
+ export { accountResponseSchema, changeAccountPasswordRequestSchema, getAccountRequestSchema, okResponseSchema, setPreferredLocaleRequestSchema, updateAccountRequestSchema } from "./schemas.js";
4
4
  export { AdminAccountService } from "./service.js";
@@ -26,5 +26,9 @@ export declare const changeAccountPasswordRequestSchema: z.ZodObject<{
26
26
  newPassword: z.ZodString;
27
27
  }, z.core.$strip>;
28
28
  export type ChangeAccountPasswordRequest = z.infer<typeof changeAccountPasswordRequestSchema>;
29
+ export declare const setPreferredLocaleRequestSchema: z.ZodObject<{
30
+ locale: z.ZodNullable<z.ZodString>;
31
+ }, z.core.$strip>;
32
+ export type SetPreferredLocaleRequest = z.infer<typeof setPreferredLocaleRequestSchema>;
29
33
  export { adminUserResponseSchema as accountResponseSchema, okResponseSchema };
30
34
  export type { AdminUserResponse as AccountResponse, OkResponse } from '../admin-users/schemas.js';
@@ -30,5 +30,8 @@ const changeAccountPasswordRequestSchema = z.object({
30
30
  }),
31
31
  newPassword: passwordSchema
32
32
  });
33
+ const setPreferredLocaleRequestSchema = z.object({
34
+ locale: z.string().min(2).max(16).regex(/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/, 'must be a BCP 47 locale tag').nullable()
35
+ });
33
36
  export { adminUserResponseSchema as accountResponseSchema, okResponseSchema } from "../admin-users/schemas.js";
34
- export { changeAccountPasswordRequestSchema, getAccountRequestSchema, updateAccountRequestSchema };
37
+ export { changeAccountPasswordRequestSchema, getAccountRequestSchema, setPreferredLocaleRequestSchema, updateAccountRequestSchema };
@@ -39,5 +39,6 @@ export declare class AdminAccountService {
39
39
  });
40
40
  getAccount(actorId: string): Promise<AccountResponse>;
41
41
  updateAccount(actorId: string, request: UpdateAccountRequest): Promise<AccountResponse>;
42
+ setPreferredLocale(actorId: string, locale: string | null): Promise<AccountResponse>;
42
43
  changePassword(actorId: string, request: ChangeAccountPasswordRequest): Promise<AccountResponse>;
43
44
  }
@@ -50,6 +50,14 @@ class AdminAccountService {
50
50
  const row = await _class_private_field_get(this, _repo).update(actorId, request.vid, request.patch);
51
51
  return toAdminUser(row);
52
52
  }
53
+ async setPreferredLocale(actorId, locale) {
54
+ const current = await _class_private_field_get(this, _repo).getById(actorId);
55
+ if (!current) throw ERR_ADMIN_ACCOUNT_NOT_FOUND();
56
+ await _class_private_field_get(this, _repo).setPreferredLocale(actorId, locale);
57
+ const updated = await _class_private_field_get(this, _repo).getById(actorId);
58
+ if (!updated) throw ERR_ADMIN_ACCOUNT_NOT_FOUND();
59
+ return toAdminUser(updated);
60
+ }
53
61
  async changePassword(actorId, request) {
54
62
  const withHash = await _class_private_field_get(this, _repo).getByIdForSignIn(actorId);
55
63
  if (!withHash) throw ERR_ADMIN_ACCOUNT_NOT_FOUND();
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
+ import { useTranslation } from "@byline/i18n/react";
4
5
  import { Button, Container, LoaderRing, Section } from "@byline/ui/react";
5
6
  import classnames from "classnames";
6
7
  import { useBylineAdminServices } from "../../../services/admin-services-context.js";
@@ -42,22 +43,21 @@ function displayUser(user) {
42
43
  return parts.length > 0 ? `${parts.join(' ')} (${user.email})` : user.email;
43
44
  }
44
45
  function MatrixPanel({ matrix }) {
46
+ const { t } = useTranslation('byline-admin');
45
47
  return /*#__PURE__*/ jsxs("div", {
46
48
  className: classnames('byline-inspector-matrix', inspector_module.matrix),
47
49
  children: [
48
50
  /*#__PURE__*/ jsxs("div", {
49
51
  children: [
50
- /*#__PURE__*/ jsxs("h4", {
52
+ /*#__PURE__*/ jsx("h4", {
51
53
  className: classnames('byline-inspector-matrix-title', inspector_module["matrix-title"]),
52
- children: [
53
- "Roles (",
54
- matrix.roles.length,
55
- ")"
56
- ]
54
+ children: t('adminPermissions.matrix.rolesTitle', {
55
+ count: matrix.roles.length
56
+ })
57
57
  }),
58
58
  0 === matrix.roles.length ? /*#__PURE__*/ jsx("p", {
59
59
  className: classnames('muted', 'byline-inspector-matrix-empty', inspector_module["matrix-empty"]),
60
- children: "No role grants this ability."
60
+ children: t('adminPermissions.matrix.rolesEmpty')
61
61
  }) : /*#__PURE__*/ jsx("ul", {
62
62
  className: classnames('byline-inspector-matrix-list', inspector_module["matrix-list"]),
63
63
  children: matrix.roles.map((role)=>/*#__PURE__*/ jsxs("li", {
@@ -81,17 +81,15 @@ function MatrixPanel({ matrix }) {
81
81
  }),
82
82
  /*#__PURE__*/ jsxs("div", {
83
83
  children: [
84
- /*#__PURE__*/ jsxs("h4", {
84
+ /*#__PURE__*/ jsx("h4", {
85
85
  className: classnames('byline-inspector-matrix-title', inspector_module["matrix-title"]),
86
- children: [
87
- "Admin users (",
88
- matrix.users.length,
89
- ")"
90
- ]
86
+ children: t('adminPermissions.matrix.usersTitle', {
87
+ count: matrix.users.length
88
+ })
91
89
  }),
92
90
  0 === matrix.users.length ? /*#__PURE__*/ jsx("p", {
93
91
  className: classnames('muted', 'byline-inspector-matrix-empty', inspector_module["matrix-empty"]),
94
- children: "No admin user holds this ability."
92
+ children: t('adminPermissions.matrix.usersEmpty')
95
93
  }) : /*#__PURE__*/ jsx("ul", {
96
94
  className: classnames('byline-inspector-matrix-list', inspector_module["matrix-list"]),
97
95
  children: matrix.users.map((user)=>/*#__PURE__*/ jsx("li", {
@@ -105,7 +103,9 @@ function MatrixPanel({ matrix }) {
105
103
  });
106
104
  }
107
105
  function AbilityRow({ ability, matrix, loading, onToggle, expanded }) {
106
+ const { t } = useTranslation('byline-admin');
108
107
  const sv = sourceVariant(ability.source);
108
+ const sourceKey = ability.source ?? 'unknown';
109
109
  return /*#__PURE__*/ jsxs("div", {
110
110
  className: classnames('byline-inspector-row', inspector_module.row),
111
111
  children: [
@@ -124,7 +124,7 @@ function AbilityRow({ ability, matrix, loading, onToggle, expanded }) {
124
124
  }),
125
125
  /*#__PURE__*/ jsx("span", {
126
126
  className: classnames('byline-inspector-row-source', inspector_module["row-source"], sv.global, sv.local),
127
- children: ability.source ?? 'unknown'
127
+ children: t(`adminPermissions.source.${sourceKey}`)
128
128
  })
129
129
  ]
130
130
  }),
@@ -142,7 +142,7 @@ function AbilityRow({ ability, matrix, loading, onToggle, expanded }) {
142
142
  size: "xs",
143
143
  intent: "secondary",
144
144
  onClick: onToggle,
145
- children: expanded ? 'Hide' : 'Holders'
145
+ children: expanded ? t('adminPermissions.row.hideButton') : t('adminPermissions.row.holdersButton')
146
146
  })
147
147
  ]
148
148
  }),
@@ -155,7 +155,7 @@ function AbilityRow({ ability, matrix, loading, onToggle, expanded }) {
155
155
  }),
156
156
  /*#__PURE__*/ jsx("span", {
157
157
  className: "muted",
158
- children: "Loading…"
158
+ children: t('common.loading')
159
159
  })
160
160
  ]
161
161
  }) : matrix ? /*#__PURE__*/ jsx(MatrixPanel, {
@@ -165,6 +165,7 @@ function AbilityRow({ ability, matrix, loading, onToggle, expanded }) {
165
165
  });
166
166
  }
167
167
  function GroupSection({ group, matrices, loading, expanded, onToggle }) {
168
+ const { t } = useTranslation('byline-admin');
168
169
  return /*#__PURE__*/ jsxs("details", {
169
170
  open: true,
170
171
  className: classnames('byline-inspector-group', inspector_module.group),
@@ -176,12 +177,11 @@ function GroupSection({ group, matrices, loading, expanded, onToggle }) {
176
177
  className: classnames('byline-inspector-group-name', inspector_module["group-name"]),
177
178
  children: group.group
178
179
  }),
179
- /*#__PURE__*/ jsxs("span", {
180
+ /*#__PURE__*/ jsx("span", {
180
181
  className: classnames('muted', 'byline-inspector-group-count', inspector_module["group-count"]),
181
- children: [
182
- group.abilities.length,
183
- " abilities"
184
- ]
182
+ children: t('adminPermissions.group.abilitiesCount', {
183
+ count: group.abilities.length
184
+ })
185
185
  })
186
186
  ]
187
187
  }),
@@ -200,6 +200,7 @@ function GroupSection({ group, matrices, loading, expanded, onToggle }) {
200
200
  }
201
201
  function AbilitiesInspector({ data }) {
202
202
  const { whoHasAbility } = useBylineAdminServices();
203
+ const { t } = useTranslation('byline-admin');
203
204
  const [expanded, setExpanded] = useState(new Set());
204
205
  const [loading, setLoading] = useState(new Set());
205
206
  const [matrices, setMatrices] = useState({});
@@ -239,34 +240,23 @@ function AbilitiesInspector({ data }) {
239
240
  children: [
240
241
  /*#__PURE__*/ jsx("h1", {
241
242
  className: classnames('byline-inspector-title', inspector_module.title),
242
- children: "Abilities Inspector"
243
+ children: t('adminPermissions.title')
243
244
  }),
244
- /*#__PURE__*/ jsxs("span", {
245
+ /*#__PURE__*/ jsx("span", {
245
246
  className: classnames('byline-inspector-count-pill', inspector_module["count-pill"]),
246
- children: [
247
- data.total,
248
- " registered"
249
- ]
247
+ children: t('adminPermissions.countPill', {
248
+ count: data.total
249
+ })
250
250
  })
251
251
  ]
252
252
  }),
253
- /*#__PURE__*/ jsxs("p", {
253
+ /*#__PURE__*/ jsx("p", {
254
254
  className: classnames('muted', 'byline-inspector-lead', inspector_module.lead),
255
- children: [
256
- "Read-only view of every ability registered through ",
257
- /*#__PURE__*/ jsx("code", {
258
- children: "bylineCore.abilities"
259
- }),
260
- ". Collections auto-register CRUD + workflow abilities; admin subsystems contribute their own keys at composition root via ",
261
- /*#__PURE__*/ jsx("code", {
262
- children: "registerAdminAbilities"
263
- }),
264
- "."
265
- ]
255
+ children: t('adminPermissions.lead')
266
256
  }),
267
257
  0 === data.groups.length ? /*#__PURE__*/ jsx("p", {
268
258
  className: classnames('muted', 'byline-inspector-empty', inspector_module.empty),
269
- children: "No abilities are registered."
259
+ children: t('adminPermissions.empty')
270
260
  }) : /*#__PURE__*/ jsx("div", {
271
261
  className: classnames('byline-inspector-groups', inspector_module.groups),
272
262
  children: data.groups.map((group)=>/*#__PURE__*/ jsx(GroupSection, {
@@ -1,19 +1,16 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useMemo, useState } from "react";
4
4
  import { revalidateLogic, useForm } from "@tanstack/react-form-start";
5
+ import { useTranslation } from "@byline/i18n/react";
5
6
  import { Alert, Button, Input, LoaderEllipsis, TextArea } from "@byline/ui/react";
6
7
  import classnames from "classnames";
7
8
  import { z } from "zod";
8
9
  import { useBylineAdminServices } from "../../../services/admin-services-context.js";
9
10
  import create_module from "./create.module.js";
10
- const createAdminRoleFormSchema = z.object({
11
- name: z.string().min(1, 'Name is required').max(128, 'Name must not exceed 128 characters'),
12
- machine_name: z.string().min(1, 'Machine name is required').max(128, 'Machine name must not exceed 128 characters').regex(/^[a-z0-9][a-z0-9_-]*$/, {
13
- message: 'Lowercase letters, numbers, hyphens, and underscores only'
14
- }),
15
- description: z.string().max(2000, "Description must not exceed 2000 characters")
16
- });
11
+ const MAX_NAME = 128;
12
+ const MAX_MACHINE_NAME = 128;
13
+ const MAX_DESCRIPTION = 2000;
17
14
  const initialValues = {
18
15
  name: '',
19
16
  machine_name: '',
@@ -24,7 +21,23 @@ function normaliseText(value) {
24
21
  }
25
22
  function CreateAdminRole({ onClose, onSuccess }) {
26
23
  const { createAdminRole } = useBylineAdminServices();
24
+ const { t } = useTranslation('byline-admin');
27
25
  const [formError, setFormError] = useState(null);
26
+ const createAdminRoleFormSchema = useMemo(()=>z.object({
27
+ name: z.string().min(1, t('adminRoles.create.errors.nameRequired')).max(MAX_NAME, t('adminRoles.create.errors.nameTooLong', {
28
+ max: MAX_NAME
29
+ })),
30
+ machine_name: z.string().min(1, t('adminRoles.create.errors.machineNameRequired')).max(MAX_MACHINE_NAME, t('adminRoles.create.errors.machineNameTooLong', {
31
+ max: MAX_MACHINE_NAME
32
+ })).regex(/^[a-z0-9][a-z0-9_-]*$/, {
33
+ message: t('adminRoles.create.errors.machineNameInvalid')
34
+ }),
35
+ description: z.string().max(MAX_DESCRIPTION, t("adminRoles.create.errors.descriptionTooLong", {
36
+ max: MAX_DESCRIPTION
37
+ }))
38
+ }), [
39
+ t
40
+ ]);
28
41
  const form = useForm({
29
42
  defaultValues: initialValues,
30
43
  validationLogic: revalidateLogic({
@@ -48,17 +61,21 @@ function CreateAdminRole({ onClose, onSuccess }) {
48
61
  onSuccess?.(created);
49
62
  } catch (err) {
50
63
  const code = getErrorCode(err);
51
- if ('admin.roles.machineNameInUse' === code) return void form.setFieldMeta('machine_name', (meta)=>({
52
- ...meta,
53
- errorMap: {
54
- ...meta.errorMap,
55
- onServer: 'This machine name is already in use.'
56
- },
57
- errors: [
58
- 'This machine name is already in use.'
59
- ]
60
- }));
61
- setFormError('Could not create this admin role. Please try again.');
64
+ if ('admin.roles.machineNameInUse' === code) {
65
+ const message = t('adminRoles.create.errors.machineNameInUse');
66
+ form.setFieldMeta('machine_name', (meta)=>({
67
+ ...meta,
68
+ errorMap: {
69
+ ...meta.errorMap,
70
+ onServer: message
71
+ },
72
+ errors: [
73
+ message
74
+ ]
75
+ }));
76
+ return;
77
+ }
78
+ setFormError(t('adminRoles.create.errors.fallback'));
62
79
  }
63
80
  }
64
81
  });
@@ -80,7 +97,7 @@ function CreateAdminRole({ onClose, onSuccess }) {
80
97
  /*#__PURE__*/ jsx(form.Field, {
81
98
  name: "name",
82
99
  children: (field)=>/*#__PURE__*/ jsx(Input, {
83
- label: "Name",
100
+ label: t('adminRoles.fields.name'),
84
101
  id: "new-role-name",
85
102
  name: field.name,
86
103
  value: field.state.value,
@@ -88,14 +105,14 @@ function CreateAdminRole({ onClose, onSuccess }) {
88
105
  onChange: (e)=>field.handleChange(e.currentTarget.value),
89
106
  error: field.state.meta.errors.length > 0,
90
107
  errorText: firstError(field.state.meta.errors),
91
- helpText: "Human-readable label, e.g. 'Editor'.",
108
+ helpText: t('adminRoles.create.fields.nameHelp'),
92
109
  required: true
93
110
  })
94
111
  }),
95
112
  /*#__PURE__*/ jsx(form.Field, {
96
113
  name: "machine_name",
97
114
  children: (field)=>/*#__PURE__*/ jsx(Input, {
98
- label: "Machine name",
115
+ label: t('adminRoles.fields.machineName'),
99
116
  id: "new-role-machine-name",
100
117
  name: field.name,
101
118
  value: field.state.value,
@@ -103,14 +120,14 @@ function CreateAdminRole({ onClose, onSuccess }) {
103
120
  onChange: (e)=>field.handleChange(e.currentTarget.value),
104
121
  error: field.state.meta.errors.length > 0,
105
122
  errorText: firstError(field.state.meta.errors),
106
- helpText: "Stable code-side handle, e.g. 'editor'. Cannot be changed later.",
123
+ helpText: t('adminRoles.create.fields.machineNameHelp'),
107
124
  required: true
108
125
  })
109
126
  }),
110
127
  /*#__PURE__*/ jsx(form.Field, {
111
128
  name: "description",
112
129
  children: (field)=>/*#__PURE__*/ jsx(TextArea, {
113
- label: "Description",
130
+ label: t("adminRoles.fields.description"),
114
131
  id: "new-role-description",
115
132
  name: field.name,
116
133
  value: field.state.value,
@@ -130,7 +147,7 @@ function CreateAdminRole({ onClose, onSuccess }) {
130
147
  size: "sm",
131
148
  onClick: onClose,
132
149
  className: classnames('byline-role-create-action', create_module.action),
133
- children: "Cancel"
150
+ children: t('common.actions.cancel')
134
151
  }),
135
152
  /*#__PURE__*/ jsx(form.Subscribe, {
136
153
  selector: (state)=>({
@@ -145,7 +162,7 @@ function CreateAdminRole({ onClose, onSuccess }) {
145
162
  className: classnames('byline-role-create-action', create_module.action),
146
163
  children: true === isSubmitting ? /*#__PURE__*/ jsx(LoaderEllipsis, {
147
164
  size: 42
148
- }) : 'Save'
165
+ }) : t('common.actions.save')
149
166
  })
150
167
  })
151
168
  ]