@byline/admin 2.5.2 → 2.6.1

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 +61 -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 +59 -0
  72. package/dist/fields/relation/relation-picker.js +237 -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 +138 -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 +343 -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,28 +1,33 @@
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
5
  import { passwordSchema } from "@byline/core/validation";
6
+ import { useTranslation } from "@byline/i18n/react";
6
7
  import { Alert, Button, InputPassword, LoaderEllipsis } from "@byline/ui/react";
7
8
  import classnames from "classnames";
8
9
  import { z } from "zod";
10
+ import { translateValidationError } from "../../../lib/translate-validation-error.js";
9
11
  import { useBylineAdminServices } from "../../../services/admin-services-context.js";
10
12
  import set_password_module from "./set-password.module.js";
11
- const setPasswordFormSchema = z.object({
12
- password: passwordSchema,
13
- confirm: z.string({
14
- message: 'Please confirm the password'
15
- })
16
- }).refine((v)=>v.password === v.confirm, {
17
- message: 'Passwords do not match',
18
- path: [
19
- 'confirm'
20
- ]
21
- });
22
13
  function SetPassword({ user, onClose, onSuccess }) {
23
14
  const { setAdminUserPassword } = useBylineAdminServices();
15
+ const { t } = useTranslation('byline-admin');
24
16
  const [formError, setFormError] = useState(null);
25
17
  const [successMessage, setSuccessMessage] = useState(null);
18
+ const setPasswordFormSchema = useMemo(()=>z.object({
19
+ password: passwordSchema,
20
+ confirm: z.string({
21
+ message: t('account.changePassword.errors.confirmRequired')
22
+ })
23
+ }).refine((v)=>v.password === v.confirm, {
24
+ message: t('account.changePassword.errors.mismatch'),
25
+ path: [
26
+ 'confirm'
27
+ ]
28
+ }), [
29
+ t
30
+ ]);
26
31
  const form = useForm({
27
32
  defaultValues: {
28
33
  password: '',
@@ -46,7 +51,7 @@ function SetPassword({ user, onClose, onSuccess }) {
46
51
  password: value.password
47
52
  }
48
53
  });
49
- setSuccessMessage('Password updated.');
54
+ setSuccessMessage(t('account.changePassword.feedback.updated'));
50
55
  form.reset({
51
56
  password: '',
52
57
  confirm: ''
@@ -54,9 +59,9 @@ function SetPassword({ user, onClose, onSuccess }) {
54
59
  onSuccess?.(updated);
55
60
  } catch (err) {
56
61
  const code = getErrorCode(err);
57
- if ('admin.users.versionConflict' === code) return void setFormError('This user has been modified elsewhere since you opened this form. Reload to refresh and try again.');
58
- if ('admin.users.notFound' === code) return void setFormError('This user no longer exists.');
59
- setFormError('Could not set the password. Please try again.');
62
+ if ('admin.users.versionConflict' === code) return void setFormError(t('adminUsers.update.errors.versionConflict'));
63
+ if ('admin.users.notFound' === code) return void setFormError(t('adminUsers.update.errors.notFound'));
64
+ setFormError(t('adminUsers.setPassword.errors.fallback'));
60
65
  }
61
66
  }
62
67
  });
@@ -79,29 +84,23 @@ function SetPassword({ user, onClose, onSuccess }) {
79
84
  intent: "success",
80
85
  children: successMessage
81
86
  }) : null,
82
- /*#__PURE__*/ jsxs("p", {
87
+ /*#__PURE__*/ jsx("p", {
83
88
  className: "muted",
84
- children: [
85
- "Sets a new password for",
86
- ' ',
87
- /*#__PURE__*/ jsx("span", {
88
- className: classnames('byline-user-set-password-target', set_password_module.target),
89
- children: user.email
90
- }),
91
- ". The user will need to sign in again with the new password."
92
- ]
89
+ children: t('adminUsers.setPassword.intro', {
90
+ email: user.email
91
+ })
93
92
  }),
94
93
  /*#__PURE__*/ jsx(form.Field, {
95
94
  name: "password",
96
95
  children: (field)=>/*#__PURE__*/ jsx(InputPassword, {
97
- label: "New password",
96
+ label: t('account.changePassword.fields.new'),
98
97
  id: "password",
99
98
  name: field.name,
100
99
  value: field.state.value,
101
100
  onBlur: field.handleBlur,
102
101
  onChange: (e)=>field.handleChange(e.currentTarget.value),
103
102
  error: field.state.meta.errors.length > 0,
104
- errorText: firstError(field.state.meta.errors),
103
+ errorText: translateValidationError(t, firstError(field.state.meta.errors)),
105
104
  autoComplete: "new-password",
106
105
  required: true
107
106
  })
@@ -109,7 +108,7 @@ function SetPassword({ user, onClose, onSuccess }) {
109
108
  /*#__PURE__*/ jsx(form.Field, {
110
109
  name: "confirm",
111
110
  children: (field)=>/*#__PURE__*/ jsx(InputPassword, {
112
- label: "Confirm new password",
111
+ label: t('account.changePassword.fields.confirm'),
113
112
  id: "confirm",
114
113
  name: field.name,
115
114
  value: field.state.value,
@@ -130,7 +129,7 @@ function SetPassword({ user, onClose, onSuccess }) {
130
129
  size: "sm",
131
130
  onClick: onClose,
132
131
  className: classnames('byline-user-set-password-action', set_password_module.action),
133
- children: successMessage ? 'Close' : 'Cancel'
132
+ children: successMessage ? t('common.actions.close') : t('common.actions.cancel')
134
133
  }),
135
134
  /*#__PURE__*/ jsx(form.Subscribe, {
136
135
  selector: (state)=>({
@@ -146,7 +145,7 @@ function SetPassword({ user, onClose, onSuccess }) {
146
145
  className: classnames('byline-user-set-password-action', set_password_module.action),
147
146
  children: true === isSubmitting ? /*#__PURE__*/ jsx(LoaderEllipsis, {
148
147
  size: 42
149
- }) : 'Save'
148
+ }) : t('common.actions.save')
150
149
  })
151
150
  })
152
151
  ]
@@ -1,23 +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, Checkbox, 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 updateUserSchema = 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
- is_super_admin: z.boolean(),
18
- is_enabled: z.boolean(),
19
- is_email_verified: z.boolean()
20
- });
11
+ const MAX_NAME = 100;
12
+ const MAX_USERNAME = 100;
13
+ const MAX_EMAIL = 254;
21
14
  function defaultsFrom(user) {
22
15
  return {
23
16
  given_name: user.given_name ?? '',
@@ -46,8 +39,30 @@ function buildPatch(values, user) {
46
39
  }
47
40
  function UpdateUser({ user, onClose, onSuccess }) {
48
41
  const { updateAdminUser } = useBylineAdminServices();
42
+ const { t } = useTranslation('byline-admin');
49
43
  const [formError, setFormError] = useState(null);
50
44
  const [successMessage, setSuccessMessage] = useState(null);
45
+ const updateUserSchema = useMemo(()=>z.object({
46
+ given_name: z.string().max(MAX_NAME, t('account.update.errors.givenNameTooLong', {
47
+ max: MAX_NAME
48
+ })),
49
+ family_name: z.string().max(MAX_NAME, t('account.update.errors.familyNameTooLong', {
50
+ max: MAX_NAME
51
+ })),
52
+ username: z.string().max(MAX_USERNAME, t('account.update.errors.usernameTooLong', {
53
+ max: MAX_USERNAME
54
+ })),
55
+ email: z.email({
56
+ message: t('account.update.errors.invalidEmail')
57
+ }).min(3).max(MAX_EMAIL, t('account.update.errors.emailTooLong', {
58
+ max: MAX_EMAIL
59
+ })),
60
+ is_super_admin: z.boolean(),
61
+ is_enabled: z.boolean(),
62
+ is_email_verified: z.boolean()
63
+ }), [
64
+ t
65
+ ]);
51
66
  const form = useForm({
52
67
  defaultValues: defaultsFrom(user),
53
68
  validationLogic: revalidateLogic({
@@ -61,7 +76,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
61
76
  setFormError(null);
62
77
  setSuccessMessage(null);
63
78
  const patch = buildPatch(value, user);
64
- if (0 === Object.keys(patch).length) return void setSuccessMessage('No changes to save.');
79
+ if (0 === Object.keys(patch).length) return void setSuccessMessage(t('common.feedback.noChanges'));
65
80
  try {
66
81
  const updated = await updateAdminUser({
67
82
  data: {
@@ -70,23 +85,27 @@ function UpdateUser({ user, onClose, onSuccess }) {
70
85
  patch
71
86
  }
72
87
  });
73
- setSuccessMessage('Saved.');
88
+ setSuccessMessage(t('common.feedback.saved'));
74
89
  onSuccess?.(updated);
75
90
  } catch (err) {
76
91
  const code = getErrorCode(err);
77
- if ('admin.users.emailInUse' === code) return void form.setFieldMeta('email', (meta)=>({
78
- ...meta,
79
- errorMap: {
80
- ...meta.errorMap,
81
- onServer: 'This email is already in use.'
82
- },
83
- errors: [
84
- 'This email is already in use.'
85
- ]
86
- }));
87
- if ('admin.users.versionConflict' === code) return void setFormError('This user has been modified elsewhere since you opened this form. Reload to get the latest values and try again.');
88
- if ('admin.users.notFound' === code) return void setFormError('This user no longer exists.');
89
- setFormError('Could not save changes. Please try again.');
92
+ if ('admin.users.emailInUse' === code) {
93
+ const message = t('account.update.errors.emailInUse');
94
+ form.setFieldMeta('email', (meta)=>({
95
+ ...meta,
96
+ errorMap: {
97
+ ...meta.errorMap,
98
+ onServer: message
99
+ },
100
+ errors: [
101
+ message
102
+ ]
103
+ }));
104
+ return;
105
+ }
106
+ if ('admin.users.versionConflict' === code) return void setFormError(t('adminUsers.update.errors.versionConflict'));
107
+ if ('admin.users.notFound' === code) return void setFormError(t('adminUsers.update.errors.notFound'));
108
+ setFormError(t('common.errors.couldNotSave'));
90
109
  }
91
110
  }
92
111
  });
@@ -112,7 +131,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
112
131
  /*#__PURE__*/ jsx(form.Field, {
113
132
  name: "given_name",
114
133
  children: (field)=>/*#__PURE__*/ jsx(Input, {
115
- label: "Given name",
134
+ label: t('account.update.fields.givenName'),
116
135
  id: "given_name",
117
136
  name: field.name,
118
137
  value: field.state.value,
@@ -126,7 +145,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
126
145
  /*#__PURE__*/ jsx(form.Field, {
127
146
  name: "family_name",
128
147
  children: (field)=>/*#__PURE__*/ jsx(Input, {
129
- label: "Family name",
148
+ label: t('account.update.fields.familyName'),
130
149
  id: "family_name",
131
150
  name: field.name,
132
151
  value: field.state.value,
@@ -140,7 +159,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
140
159
  /*#__PURE__*/ jsx(form.Field, {
141
160
  name: "username",
142
161
  children: (field)=>/*#__PURE__*/ jsx(Input, {
143
- label: "Username",
162
+ label: t('account.update.fields.username'),
144
163
  id: "username",
145
164
  name: field.name,
146
165
  value: field.state.value,
@@ -148,14 +167,14 @@ function UpdateUser({ user, onClose, onSuccess }) {
148
167
  onChange: (e)=>field.handleChange(e.currentTarget.value),
149
168
  error: field.state.meta.errors.length > 0,
150
169
  errorText: firstError(field.state.meta.errors),
151
- helpText: "Optional. Leave blank to clear.",
170
+ helpText: t('account.update.fields.usernameHelp'),
152
171
  autoComplete: "username"
153
172
  })
154
173
  }),
155
174
  /*#__PURE__*/ jsx(form.Field, {
156
175
  name: "email",
157
176
  children: (field)=>/*#__PURE__*/ jsx(Input, {
158
- label: "Email",
177
+ label: t('common.fields.email'),
159
178
  id: "email",
160
179
  name: field.name,
161
180
  type: "email",
@@ -176,10 +195,10 @@ function UpdateUser({ user, onClose, onSuccess }) {
176
195
  children: (field)=>/*#__PURE__*/ jsx(Checkbox, {
177
196
  id: "is_enabled",
178
197
  name: field.name,
179
- label: "Enabled",
198
+ label: t('adminUsers.create.flags.enabledLabel'),
180
199
  checked: field.state.value,
181
200
  onCheckedChange: (checked)=>field.handleChange(true === checked),
182
- helpText: "Disabled accounts cannot sign in."
201
+ helpText: t('adminUsers.create.flags.enabledHelp')
183
202
  })
184
203
  }),
185
204
  /*#__PURE__*/ jsx(form.Field, {
@@ -187,7 +206,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
187
206
  children: (field)=>/*#__PURE__*/ jsx(Checkbox, {
188
207
  id: "is_email_verified",
189
208
  name: field.name,
190
- label: "Email verified",
209
+ label: t('adminUsers.create.flags.emailVerifiedLabel'),
191
210
  checked: field.state.value,
192
211
  onCheckedChange: (checked)=>field.handleChange(true === checked)
193
212
  })
@@ -197,10 +216,10 @@ function UpdateUser({ user, onClose, onSuccess }) {
197
216
  children: (field)=>/*#__PURE__*/ jsx(Checkbox, {
198
217
  id: "is_super_admin",
199
218
  name: field.name,
200
- label: "Super admin",
219
+ label: t('adminUsers.create.flags.superAdminLabel'),
201
220
  checked: field.state.value,
202
221
  onCheckedChange: (checked)=>field.handleChange(true === checked),
203
- helpText: "Super admins bypass every ability check — grant with care."
222
+ helpText: t('adminUsers.create.flags.superAdminHelp')
204
223
  })
205
224
  })
206
225
  ]
@@ -214,7 +233,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
214
233
  size: "sm",
215
234
  onClick: onClose,
216
235
  className: classnames('byline-user-update-action', update_module.action),
217
- children: successMessage ? 'Close' : 'Cancel'
236
+ children: successMessage ? t('common.actions.close') : t('common.actions.cancel')
218
237
  }),
219
238
  /*#__PURE__*/ jsx(form.Subscribe, {
220
239
  selector: (state)=>({
@@ -230,7 +249,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
230
249
  className: classnames('byline-user-update-action', update_module.action),
231
250
  children: true === isSubmitting ? /*#__PURE__*/ jsx(LoaderEllipsis, {
232
251
  size: 42
233
- }) : 'Save'
252
+ }) : t('common.actions.save')
234
253
  })
235
254
  })
236
255
  ]
@@ -13,6 +13,7 @@ function toAdminUser(row) {
13
13
  is_super_admin: row.is_super_admin,
14
14
  is_enabled: row.is_enabled,
15
15
  is_email_verified: row.is_email_verified,
16
+ preferred_locale: row.preferred_locale,
16
17
  created_at: row.created_at,
17
18
  updated_at: row.updated_at
18
19
  };
@@ -48,6 +48,13 @@ export interface AdminUserRow {
48
48
  is_super_admin: boolean;
49
49
  is_enabled: boolean;
50
50
  is_email_verified: boolean;
51
+ /**
52
+ * Admin interface locale preference. `null` means "use the detection
53
+ * cascade" (cookie → Accept-Language → defaultLocale). Stored as a
54
+ * BCP 47 code; validated at the command layer against the host's
55
+ * `i18n.interface.locales`.
56
+ */
57
+ preferred_locale: string | null;
51
58
  created_at: Date;
52
59
  updated_at: Date;
53
60
  }
@@ -69,6 +76,8 @@ export interface CreateAdminUserInput {
69
76
  is_super_admin?: boolean;
70
77
  is_enabled?: boolean;
71
78
  is_email_verified?: boolean;
79
+ /** Initial locale preference. `null` defers to the detection cascade. */
80
+ preferred_locale?: string | null;
72
81
  }
73
82
  export interface UpdateAdminUserInput {
74
83
  given_name?: string | null;
@@ -79,6 +88,8 @@ export interface UpdateAdminUserInput {
79
88
  is_enabled?: boolean;
80
89
  is_email_verified?: boolean;
81
90
  remember_me?: boolean;
91
+ /** Pass `null` to clear and fall back to the detection cascade. */
92
+ preferred_locale?: string | null;
82
93
  }
83
94
  export type AdminUserListOrder = 'given_name' | 'family_name' | 'email' | 'username' | 'created_at' | 'updated_at';
84
95
  export interface ListAdminUsersOptions {
@@ -136,6 +147,12 @@ export interface AdminUsersRepository {
136
147
  setPasswordHash(id: string, expectedVid: number, passwordHash: string): Promise<AdminUserRow>;
137
148
  /** Toggle enabled state. Vid-less — admin intent is independent of other edits. */
138
149
  setEnabled(id: string, enabled: boolean): Promise<void>;
150
+ /**
151
+ * Set the admin interface locale preference. Vid-less — user preference
152
+ * is independent of content state. Pass `null` to clear and fall back
153
+ * to the detection cascade (cookie → Accept-Language → defaultLocale).
154
+ */
155
+ setPreferredLocale(id: string, locale: string | null): Promise<void>;
139
156
  recordLoginSuccess(id: string, ip: string | null): Promise<void>;
140
157
  recordLoginFailure(id: string): Promise<void>;
141
158
  /**
@@ -34,6 +34,7 @@ export declare const createAdminUserRequestSchema: z.ZodObject<{
34
34
  is_super_admin: z.ZodOptional<z.ZodBoolean>;
35
35
  is_enabled: z.ZodOptional<z.ZodBoolean>;
36
36
  is_email_verified: z.ZodOptional<z.ZodBoolean>;
37
+ preferred_locale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
38
  }, z.core.$strip>;
38
39
  export type CreateAdminUserRequest = z.infer<typeof createAdminUserRequestSchema>;
39
40
  export declare const updateAdminUserRequestSchema: z.ZodObject<{
@@ -47,6 +48,7 @@ export declare const updateAdminUserRequestSchema: z.ZodObject<{
47
48
  is_super_admin: z.ZodOptional<z.ZodBoolean>;
48
49
  is_enabled: z.ZodOptional<z.ZodBoolean>;
49
50
  is_email_verified: z.ZodOptional<z.ZodBoolean>;
51
+ preferred_locale: z.ZodOptional<z.ZodNullable<z.ZodString>>;
50
52
  }, z.core.$strip>;
51
53
  }, z.core.$strip>;
52
54
  export type UpdateAdminUserRequest = z.infer<typeof updateAdminUserRequestSchema>;
@@ -88,6 +90,7 @@ export declare const adminUserResponseSchema: z.ZodObject<{
88
90
  is_super_admin: z.ZodBoolean;
89
91
  is_enabled: z.ZodBoolean;
90
92
  is_email_verified: z.ZodBoolean;
93
+ preferred_locale: z.ZodNullable<z.ZodString>;
91
94
  created_at: z.ZodDate;
92
95
  updated_at: z.ZodDate;
93
96
  }, z.core.$strip>;
@@ -107,6 +110,7 @@ export declare const adminUserListResponseSchema: z.ZodObject<{
107
110
  is_super_admin: z.ZodBoolean;
108
111
  is_enabled: z.ZodBoolean;
109
112
  is_email_verified: z.ZodBoolean;
113
+ preferred_locale: z.ZodNullable<z.ZodString>;
110
114
  created_at: z.ZodDate;
111
115
  updated_at: z.ZodDate;
112
116
  }, z.core.$strip>>;
@@ -12,6 +12,7 @@ const emailSchema = z.email({
12
12
  message: 'email must be a valid address'
13
13
  }).min(3).max(254).transform((v)=>v.toLowerCase());
14
14
  const nameSchema = z.string().min(1).max(100);
15
+ const preferredLocaleSchema = 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');
15
16
  const orderSchema = z["enum"]([
16
17
  'given_name',
17
18
  'family_name',
@@ -38,7 +39,8 @@ const createAdminUserRequestSchema = z.object({
38
39
  username: z.string().min(1).max(100).nullish(),
39
40
  is_super_admin: z.boolean().optional(),
40
41
  is_enabled: z.boolean().optional(),
41
- is_email_verified: z.boolean().optional()
42
+ is_email_verified: z.boolean().optional(),
43
+ preferred_locale: preferredLocaleSchema.nullish()
42
44
  });
43
45
  const updateAdminUserRequestSchema = z.object({
44
46
  id: idSchema,
@@ -50,7 +52,8 @@ const updateAdminUserRequestSchema = z.object({
50
52
  username: z.string().min(1).max(100).nullish(),
51
53
  is_super_admin: z.boolean().optional(),
52
54
  is_enabled: z.boolean().optional(),
53
- is_email_verified: z.boolean().optional()
55
+ is_email_verified: z.boolean().optional(),
56
+ preferred_locale: preferredLocaleSchema.nullish()
54
57
  }).refine((p)=>Object.keys(p).length > 0, {
55
58
  message: 'patch cannot be empty'
56
59
  })
@@ -84,6 +87,7 @@ const adminUserResponseSchema = z.object({
84
87
  is_super_admin: z.boolean(),
85
88
  is_enabled: z.boolean(),
86
89
  is_email_verified: z.boolean(),
90
+ preferred_locale: z.string().nullable(),
87
91
  created_at: z.date(),
88
92
  updated_at: z.date()
89
93
  });
@@ -1,12 +1,14 @@
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 { Alert, Button, Card, Input, LoaderEllipsis } from "@byline/ui/react";
5
6
  import classnames from "classnames";
6
7
  import { useBylineAdminServices } from "../../../services/admin-services-context.js";
7
8
  import sign_in_form_module from "./sign-in-form.module.js";
8
9
  function SignInForm({ callbackUrl, homeUrl }) {
9
10
  const { adminSignIn } = useBylineAdminServices();
11
+ const { t } = useTranslation('byline-admin');
10
12
  const [email, setEmail] = useState('');
11
13
  const [password, setPassword] = useState('');
12
14
  const [pending, setPending] = useState(false);
@@ -14,7 +16,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
14
16
  async function handleSubmit(event) {
15
17
  event.preventDefault();
16
18
  if (pending) return;
17
- if (0 === email.trim().length || 0 === password.length) return void setError('Enter your email and password.');
19
+ if (0 === email.trim().length || 0 === password.length) return void setError(t('auth.signIn.errors.empty'));
18
20
  setPending(true);
19
21
  setError(null);
20
22
  try {
@@ -28,7 +30,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
28
30
  window.location.assign(target);
29
31
  } catch (err) {
30
32
  console.warn('sign-in failed', err);
31
- setError('Invalid credentials.');
33
+ setError(t('auth.signIn.errors.invalidCredentials'));
32
34
  setPending(false);
33
35
  }
34
36
  }
@@ -39,11 +41,11 @@ function SignInForm({ callbackUrl, homeUrl }) {
39
41
  children: [
40
42
  /*#__PURE__*/ jsx(Card.Title, {
41
43
  children: /*#__PURE__*/ jsx("h2", {
42
- children: "Sign in"
44
+ children: t('auth.signIn.title')
43
45
  })
44
46
  }),
45
47
  /*#__PURE__*/ jsx(Card.Description, {
46
- children: "Sign in to the Byline admin."
48
+ children: t("auth.signIn.description")
47
49
  }),
48
50
  error && /*#__PURE__*/ jsx(Alert, {
49
51
  intent: "danger",
@@ -62,7 +64,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
62
64
  className: classnames('byline-sign-in-fields', sign_in_form_module.fields),
63
65
  children: [
64
66
  /*#__PURE__*/ jsx(Input, {
65
- label: "Email",
67
+ label: t('common.fields.email'),
66
68
  id: "email",
67
69
  name: "email",
68
70
  type: "email",
@@ -73,7 +75,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
73
75
  disabled: pending
74
76
  }),
75
77
  /*#__PURE__*/ jsx(Input, {
76
- label: "Password",
78
+ label: t('common.fields.password'),
77
79
  id: "password",
78
80
  name: "password",
79
81
  type: "password",
@@ -91,7 +93,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
91
93
  homeUrl && /*#__PURE__*/ jsx("a", {
92
94
  href: homeUrl,
93
95
  className: classnames('byline-sign-in-home-link', sign_in_form_module["home-link"]),
94
- children: "Home"
96
+ children: t('common.actions.home')
95
97
  }),
96
98
  /*#__PURE__*/ jsx(Button, {
97
99
  type: "submit",
@@ -101,7 +103,7 @@ function SignInForm({ callbackUrl, homeUrl }) {
101
103
  size: 30,
102
104
  color: "#aaaaaa"
103
105
  }) : /*#__PURE__*/ jsx("span", {
104
- children: "Sign In"
106
+ children: t('common.actions.signIn')
105
107
  })
106
108
  })
107
109
  ]
@@ -0,0 +1,27 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ interface AdminGroupProps {
10
+ /** Optional heading rendered as a `<legend>` above the cluster. */
11
+ label?: string;
12
+ children: ReactNode;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Labelled fieldset clustering related fields together.
17
+ *
18
+ * Used by `FormRenderer` when a `CollectionAdminConfig` declares a `groups`
19
+ * primitive. Renders a bordered, padded `<fieldset>` with an optional
20
+ * `<legend>` for the label.
21
+ *
22
+ * Stable override handles: `.byline-admin-group` on the fieldset and
23
+ * `.byline-admin-group-legend` on the legend (alongside the hashed
24
+ * CSS-modules locals).
25
+ */
26
+ export declare const AdminGroup: ({ label, children, className }: AdminGroupProps) => import("react").JSX.Element;
27
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import group_module from "./group.module.js";
4
+ const AdminGroup = ({ label, children, className })=>/*#__PURE__*/ jsxs("fieldset", {
5
+ className: classnames('byline-admin-group', group_module.group, className),
6
+ children: [
7
+ label && /*#__PURE__*/ jsx("legend", {
8
+ className: classnames('byline-admin-group-legend', group_module.legend),
9
+ children: label
10
+ }),
11
+ children
12
+ ]
13
+ });
14
+ export { AdminGroup };
@@ -0,0 +1,6 @@
1
+ import "./group_module.css";
2
+ const group_module = {
3
+ group: "group-VH6uAe",
4
+ legend: "legend-dpiU3e"
5
+ };
6
+ export default group_module;
@@ -0,0 +1,19 @@
1
+ :is(.group-VH6uAe, .byline-admin-group) {
2
+ gap: var(--spacing-16);
3
+ padding: var(--spacing-12);
4
+ border: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
5
+ border-radius: var(--border-radius-md);
6
+ flex-direction: column;
7
+ display: flex;
8
+ }
9
+
10
+ :is(.legend-dpiU3e, .byline-admin-group-legend) {
11
+ padding-inline: var(--spacing-4);
12
+ font-size: var(--font-size-sm);
13
+ font-weight: var(--font-weight-medium);
14
+ }
15
+
16
+ :is(:is([data-theme="dark"], .dark) .group-VH6uAe, :is([data-theme="dark"], .dark) .byline-admin-group) {
17
+ border-color: var(--gray-700);
18
+ }
19
+
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { ReactNode } from 'react';
9
+ interface AdminRowProps {
10
+ children: ReactNode;
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Horizontal flex-row layout for admin form fields.
15
+ *
16
+ * Used by `FormRenderer` when a `CollectionAdminConfig` declares a `rows`
17
+ * primitive. Members are rendered side-by-side above the `sm` breakpoint
18
+ * and stack vertically below it. `flex-1` + `min-width: 0` lets two text
19
+ * inputs share the row evenly without overflowing.
20
+ *
21
+ * The element carries `.byline-admin-row` as a stable global class for
22
+ * host overrides (alongside the hashed CSS-modules local).
23
+ */
24
+ export declare const AdminRow: ({ children, className }: AdminRowProps) => import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import row_module from "./row.module.js";
4
+ const AdminRow = ({ children, className })=>/*#__PURE__*/ jsx("div", {
5
+ className: classnames('byline-admin-row', row_module.row, className),
6
+ children: children
7
+ });
8
+ export { AdminRow };
@@ -0,0 +1,5 @@
1
+ import "./row_module.css";
2
+ const row_module = {
3
+ row: "row-WWZ32_"
4
+ };
5
+ export default row_module;