@byline/ui 2.4.0 → 2.4.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 (95) hide show
  1. package/dist/react.d.ts +10 -18
  2. package/dist/react.js +2 -15
  3. package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal.d.ts +8 -1
  4. package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal.js +4 -6
  5. package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
  6. package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal_module.css +9 -9
  7. package/dist/{admin/components/collections → widgets/status-badge}/status-badge.js +1 -1
  8. package/dist/{admin/components/collections → widgets/status-badge}/status-badge.module.js +3 -3
  9. package/dist/{admin/components/collections → widgets/status-badge}/status-badge_module.css +3 -3
  10. package/package.json +2 -4
  11. package/src/react.ts +12 -34
  12. package/src/{admin/components/collections → widgets/diff-viewer}/diff-modal.tsx +16 -5
  13. package/src/{admin/components/collections → widgets/status-badge}/status-badge.tsx +1 -1
  14. package/dist/admin/components/admin-account/change-password.d.ts +0 -8
  15. package/dist/admin/components/admin-account/change-password.js +0 -192
  16. package/dist/admin/components/admin-account/change-password.module.js +0 -8
  17. package/dist/admin/components/admin-account/change-password_module.css +0 -27
  18. package/dist/admin/components/admin-account/container.d.ts +0 -29
  19. package/dist/admin/components/admin-account/container.js +0 -299
  20. package/dist/admin/components/admin-account/container.module.js +0 -28
  21. package/dist/admin/components/admin-account/container_module.css +0 -106
  22. package/dist/admin/components/admin-account/update.d.ts +0 -8
  23. package/dist/admin/components/admin-account/update.js +0 -207
  24. package/dist/admin/components/admin-account/update.module.js +0 -8
  25. package/dist/admin/components/admin-account/update_module.css +0 -27
  26. package/dist/admin/components/admin-permissions/inspector.d.ts +0 -4
  27. package/dist/admin/components/admin-permissions/inspector.js +0 -284
  28. package/dist/admin/components/admin-permissions/inspector.module.js +0 -56
  29. package/dist/admin/components/admin-permissions/inspector_module.css +0 -238
  30. package/dist/admin/components/admin-roles/create.d.ts +0 -7
  31. package/dist/admin/components/admin-roles/create.js +0 -177
  32. package/dist/admin/components/admin-roles/create.module.js +0 -8
  33. package/dist/admin/components/admin-roles/create_module.css +0 -27
  34. package/dist/admin/components/admin-roles/permissions.d.ts +0 -10
  35. package/dist/admin/components/admin-roles/permissions.js +0 -303
  36. package/dist/admin/components/admin-roles/permissions.module.js +0 -44
  37. package/dist/admin/components/admin-roles/permissions_module.css +0 -192
  38. package/dist/admin/components/admin-roles/update.d.ts +0 -8
  39. package/dist/admin/components/admin-roles/update.js +0 -166
  40. package/dist/admin/components/admin-roles/update.module.js +0 -8
  41. package/dist/admin/components/admin-roles/update_module.css +0 -27
  42. package/dist/admin/components/admin-users/create.d.ts +0 -8
  43. package/dist/admin/components/admin-users/create.js +0 -268
  44. package/dist/admin/components/admin-users/create.module.js +0 -10
  45. package/dist/admin/components/admin-users/create_module.css +0 -45
  46. package/dist/admin/components/admin-users/roles.d.ts +0 -11
  47. package/dist/admin/components/admin-users/roles.js +0 -148
  48. package/dist/admin/components/admin-users/roles.module.js +0 -18
  49. package/dist/admin/components/admin-users/roles_module.css +0 -75
  50. package/dist/admin/components/admin-users/set-password.d.ts +0 -8
  51. package/dist/admin/components/admin-users/set-password.js +0 -170
  52. package/dist/admin/components/admin-users/set-password.module.js +0 -9
  53. package/dist/admin/components/admin-users/set-password_module.css +0 -31
  54. package/dist/admin/components/admin-users/update.d.ts +0 -8
  55. package/dist/admin/components/admin-users/update.js +0 -254
  56. package/dist/admin/components/admin-users/update.module.js +0 -9
  57. package/dist/admin/components/admin-users/update_module.css +0 -34
  58. package/dist/admin/components/auth/sign-in-form.d.ts +0 -12
  59. package/dist/admin/components/auth/sign-in-form.js +0 -115
  60. package/dist/admin/components/auth/sign-in-form.module.js +0 -12
  61. package/dist/admin/components/auth/sign-in-form_module.css +0 -41
  62. package/dist/admin/components/collections/diff-modal.module.js +0 -14
  63. package/dist/services/admin-services-context.d.ts +0 -16
  64. package/dist/services/admin-services-context.js +0 -13
  65. package/dist/services/admin-services-types.d.ts +0 -129
  66. package/dist/services/admin-services-types.js +0 -1
  67. package/src/admin/components/admin-account/change-password.module.css +0 -40
  68. package/src/admin/components/admin-account/change-password.tsx +0 -232
  69. package/src/admin/components/admin-account/container.module.css +0 -158
  70. package/src/admin/components/admin-account/container.tsx +0 -230
  71. package/src/admin/components/admin-account/update.module.css +0 -40
  72. package/src/admin/components/admin-account/update.tsx +0 -263
  73. package/src/admin/components/admin-permissions/inspector.module.css +0 -326
  74. package/src/admin/components/admin-permissions/inspector.tsx +0 -298
  75. package/src/admin/components/admin-roles/create.module.css +0 -40
  76. package/src/admin/components/admin-roles/create.tsx +0 -218
  77. package/src/admin/components/admin-roles/permissions.module.css +0 -279
  78. package/src/admin/components/admin-roles/permissions.tsx +0 -396
  79. package/src/admin/components/admin-roles/update.module.css +0 -40
  80. package/src/admin/components/admin-roles/update.tsx +0 -218
  81. package/src/admin/components/admin-users/create.module.css +0 -63
  82. package/src/admin/components/admin-users/create.tsx +0 -323
  83. package/src/admin/components/admin-users/roles.module.css +0 -119
  84. package/src/admin/components/admin-users/roles.tsx +0 -172
  85. package/src/admin/components/admin-users/set-password.module.css +0 -46
  86. package/src/admin/components/admin-users/set-password.tsx +0 -199
  87. package/src/admin/components/admin-users/update.module.css +0 -49
  88. package/src/admin/components/admin-users/update.tsx +0 -328
  89. package/src/admin/components/auth/sign-in-form.module.css +0 -62
  90. package/src/admin/components/auth/sign-in-form.tsx +0 -132
  91. package/src/services/admin-services-context.tsx +0 -35
  92. package/src/services/admin-services-types.ts +0 -177
  93. /package/dist/{admin/components/collections → widgets/status-badge}/status-badge.d.ts +0 -0
  94. /package/src/{admin/components/collections → widgets/diff-viewer}/diff-modal.module.css +0 -0
  95. /package/src/{admin/components/collections → widgets/status-badge}/status-badge.module.css +0 -0
@@ -1,218 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * This Source Code is subject to the terms of the Mozilla Public
5
- * License, v. 2.0. If a copy of the MPL was not distributed with this
6
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
- *
8
- * Copyright (c) Infonomic Company Limited
9
- */
10
-
11
- /**
12
- * Role details drawer form.
13
- *
14
- * Mirrors the admin-users update pattern: TanStack Form + Zod, blur-then-
15
- * change validation, diff-against-original patch, optimistic concurrency
16
- * via `vid`. `machine_name` is read-only here — it is captured at create
17
- * time and immutable thereafter.
18
- */
19
-
20
- import { useState } from 'react'
21
- import { revalidateLogic, useForm } from '@tanstack/react-form-start'
22
-
23
- import type { AdminRoleResponse } from '@byline/admin/admin-roles'
24
- import cx from 'classnames'
25
- import { z } from 'zod'
26
-
27
- import { useBylineAdminServices } from '../../../services/admin-services-context.js'
28
- import { Alert, Button, Input, LoaderEllipsis, TextArea } from '../../../uikit.js'
29
- import styles from './update.module.css'
30
- import type { UpdateAdminRoleInput } from '../../../services/admin-services-types.js'
31
-
32
- const updateRoleSchema = z.object({
33
- name: z.string().min(1, 'Name is required').max(128, 'Name must not exceed 128 characters'),
34
- description: z.string().max(2000, 'Description must not exceed 2000 characters'),
35
- })
36
-
37
- type UpdateRoleValues = z.infer<typeof updateRoleSchema>
38
-
39
- function defaultsFrom(role: AdminRoleResponse): UpdateRoleValues {
40
- return {
41
- name: role.name,
42
- description: role.description ?? '',
43
- }
44
- }
45
-
46
- /** Build a patch object containing only fields whose values differ from the original role. */
47
- function buildPatch(
48
- values: UpdateRoleValues,
49
- role: AdminRoleResponse
50
- ): UpdateAdminRoleInput['patch'] {
51
- const patch: UpdateAdminRoleInput['patch'] = {}
52
- const normaliseText = (value: string): string | null => (value.trim().length > 0 ? value : null)
53
- const nextDescription = normaliseText(values.description)
54
- const nextName = values.name.trim()
55
-
56
- if (nextName !== role.name) patch.name = nextName
57
- if (nextDescription !== role.description) patch.description = nextDescription
58
- return patch
59
- }
60
-
61
- interface UpdateRoleProps {
62
- role: AdminRoleResponse
63
- onClose?: () => void
64
- onSuccess?: (role: AdminRoleResponse) => void
65
- }
66
-
67
- export function UpdateRole({ role, onClose, onSuccess }: UpdateRoleProps) {
68
- const { updateAdminRole } = useBylineAdminServices()
69
- const [formError, setFormError] = useState<string | null>(null)
70
- const [successMessage, setSuccessMessage] = useState<string | null>(null)
71
-
72
- const form = useForm({
73
- defaultValues: defaultsFrom(role),
74
- validationLogic: revalidateLogic({
75
- mode: 'blur',
76
- modeAfterSubmission: 'change',
77
- }),
78
- validators: {
79
- onDynamic: updateRoleSchema,
80
- },
81
- onSubmit: async ({ value }) => {
82
- setFormError(null)
83
- setSuccessMessage(null)
84
- const patch = buildPatch(value, role)
85
- if (Object.keys(patch).length === 0) {
86
- setSuccessMessage('No changes to save.')
87
- return
88
- }
89
-
90
- try {
91
- const updated = await updateAdminRole({
92
- data: { id: role.id, vid: role.vid, patch },
93
- })
94
- setSuccessMessage('Saved.')
95
- onSuccess?.(updated)
96
- } catch (err) {
97
- const code = getErrorCode(err)
98
- if (code === 'admin.roles.versionConflict') {
99
- setFormError(
100
- 'This role has been modified elsewhere since you opened this form. Reload to get the latest values and try again.'
101
- )
102
- return
103
- }
104
- if (code === 'admin.roles.notFound') {
105
- setFormError('This role no longer exists.')
106
- return
107
- }
108
- setFormError('Could not save changes. Please try again.')
109
- }
110
- },
111
- })
112
-
113
- return (
114
- <div className={cx('byline-role-update-wrap', styles.wrap)}>
115
- <form
116
- noValidate
117
- onSubmit={(event) => {
118
- event.preventDefault()
119
- event.stopPropagation()
120
- void form.handleSubmit()
121
- }}
122
- className={cx('byline-role-update-form', styles.form)}
123
- >
124
- {formError ? <Alert intent="danger">{formError}</Alert> : null}
125
- {successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
126
-
127
- <form.Field name="name">
128
- {(field) => (
129
- <Input
130
- label="Name"
131
- id="role-name"
132
- name={field.name}
133
- value={field.state.value}
134
- onBlur={field.handleBlur}
135
- onChange={(e) => field.handleChange(e.currentTarget.value)}
136
- error={field.state.meta.errors.length > 0}
137
- errorText={firstError(field.state.meta.errors)}
138
- required
139
- />
140
- )}
141
- </form.Field>
142
-
143
- <Input
144
- label="Machine name"
145
- id="role-machine-name"
146
- name="machine_name"
147
- value={role.machine_name}
148
- readOnly
149
- disabled
150
- helpText="The stable code-side handle. Cannot be changed after creation."
151
- />
152
-
153
- <form.Field name="description">
154
- {(field) => (
155
- <TextArea
156
- label="Description"
157
- id="role-description"
158
- name={field.name}
159
- value={field.state.value}
160
- onBlur={field.handleBlur}
161
- onChange={(e) => field.handleChange(e.currentTarget.value)}
162
- error={field.state.meta.errors.length > 0}
163
- errorText={firstError(field.state.meta.errors)}
164
- rows={3}
165
- />
166
- )}
167
- </form.Field>
168
-
169
- <div className={cx('byline-role-update-actions', styles.actions)}>
170
- <Button
171
- type="button"
172
- intent="secondary"
173
- size="sm"
174
- onClick={onClose}
175
- className={cx('byline-role-update-action', styles.action)}
176
- >
177
- {successMessage ? 'Close' : 'Cancel'}
178
- </Button>
179
- <form.Subscribe
180
- selector={(state) => ({
181
- canSubmit: state.canSubmit,
182
- isSubmitting: state.isSubmitting,
183
- })}
184
- >
185
- {({ canSubmit, isSubmitting }) => (
186
- <Button
187
- size="sm"
188
- intent="primary"
189
- type="submit"
190
- disabled={!canSubmit || isSubmitting}
191
- className={cx('byline-role-update-action', styles.action)}
192
- >
193
- {isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
194
- </Button>
195
- )}
196
- </form.Subscribe>
197
- </div>
198
- </form>
199
- </div>
200
- )
201
- }
202
-
203
- function firstError(errors: readonly unknown[]): string | undefined {
204
- for (const err of errors) {
205
- if (typeof err === 'string') return err
206
- if (err && typeof err === 'object' && 'message' in err) {
207
- const msg = (err as { message?: unknown }).message
208
- if (typeof msg === 'string') return msg
209
- }
210
- }
211
- return undefined
212
- }
213
-
214
- function getErrorCode(err: unknown): string | null {
215
- return typeof (err as { code?: unknown })?.code === 'string'
216
- ? (err as { code: string }).code
217
- : null
218
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * CreateAdminUser — drawer form for creating a new admin user.
3
- *
4
- * Override handles:
5
- * .byline-user-create-wrap — outer container
6
- * .byline-user-create-form — vertical-stack form element
7
- * .byline-user-create-grid — two-column name grid (collapses on mobile)
8
- * .byline-user-create-flags — checkbox stack (enabled / verified / super)
9
- * .byline-user-create-actions — Cancel/Save row
10
- * .byline-user-create-action — buttons in the actions row
11
- */
12
-
13
- .wrap,
14
- :global(.byline-user-create-wrap) {
15
- display: flex;
16
- flex-direction: column;
17
- gap: var(--spacing-8);
18
- padding: var(--spacing-4);
19
- margin-top: var(--spacing-4);
20
- }
21
-
22
- .form,
23
- :global(.byline-user-create-form) {
24
- display: flex;
25
- flex-direction: column;
26
- gap: var(--spacing-16);
27
- padding-top: var(--spacing-8);
28
- }
29
-
30
- .grid,
31
- :global(.byline-user-create-grid) {
32
- display: grid;
33
- grid-template-columns: 1fr;
34
- gap: var(--spacing-16);
35
- }
36
-
37
- @media (min-width: 40rem) {
38
- .grid,
39
- :global(.byline-user-create-grid) {
40
- grid-template-columns: 1fr 1fr;
41
- }
42
- }
43
-
44
- .flags,
45
- :global(.byline-user-create-flags) {
46
- display: flex;
47
- flex-direction: column;
48
- gap: var(--spacing-8);
49
- }
50
-
51
- .actions,
52
- :global(.byline-user-create-actions) {
53
- display: flex;
54
- align-items: center;
55
- justify-content: flex-end;
56
- gap: var(--spacing-8);
57
- margin-top: var(--spacing-16);
58
- }
59
-
60
- .action,
61
- :global(.byline-user-create-action) {
62
- min-width: 4rem;
63
- }
@@ -1,323 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * This Source Code is subject to the terms of the Mozilla Public
5
- * License, v. 2.0. If a copy of the MPL was not distributed with this
6
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
- *
8
- * Copyright (c) Infonomic Company Limited
9
- */
10
-
11
- /**
12
- * Create-admin-user drawer form.
13
- *
14
- * Rendered inside the list-view drawer. Same TanStack Form + Zod shape
15
- * as the AccountDetails form — fields, blur-then-change validation,
16
- * form-level alerts for errors that don't map onto a single field. On
17
- * success the parent list-view navigates to the new user's detail page
18
- * so the admin can finish configuring (roles, initial enablement).
19
- *
20
- * Extension point: future props (e.g. a `roles` array once the
21
- * admin-roles module lands) plug in here — the list-route loader
22
- * already runs in parallel, so adding a side-data fetch is a one-line
23
- * change in the loader and a prop here.
24
- */
25
-
26
- import { useState } from 'react'
27
- import { revalidateLogic, useForm } from '@tanstack/react-form-start'
28
-
29
- import type { AdminUserResponse } from '@byline/admin/admin-users'
30
- import { passwordSchema } from '@byline/core/validation'
31
- import cx from 'classnames'
32
- import { z } from 'zod'
33
-
34
- import { useBylineAdminServices } from '../../../services/admin-services-context.js'
35
- import { Alert, Button, Checkbox, Input, LoaderEllipsis } from '../../../uikit.js'
36
- import styles from './create.module.css'
37
-
38
- const createAdminUserFormSchema = z.object({
39
- email: z
40
- .email({ message: 'Enter a valid email address' })
41
- .min(3)
42
- .max(254, 'Email must not exceed 254 characters'),
43
- password: passwordSchema,
44
- given_name: z.string().max(100, 'Given name must not exceed 100 characters'),
45
- family_name: z.string().max(100, 'Family name must not exceed 100 characters'),
46
- username: z.string().max(100, 'Username must not exceed 100 characters'),
47
- is_super_admin: z.boolean(),
48
- is_enabled: z.boolean(),
49
- is_email_verified: z.boolean(),
50
- })
51
-
52
- type CreateAdminUserValues = z.infer<typeof createAdminUserFormSchema>
53
-
54
- const initialValues: CreateAdminUserValues = {
55
- email: '',
56
- password: '',
57
- given_name: '',
58
- family_name: '',
59
- username: '',
60
- is_super_admin: false,
61
- // Sensible defaults for an admin-created row. A brand-new admin user
62
- // is almost always meant to sign in straight away; the admin picks
63
- // whether they arrive pre-verified.
64
- is_enabled: true,
65
- is_email_verified: false,
66
- }
67
-
68
- function normaliseText(value: string): string | null {
69
- return value.trim().length > 0 ? value : null
70
- }
71
-
72
- interface CreateAdminUserProps {
73
- onClose?: () => void
74
- /** Called on successful create with the new user so the parent can navigate. */
75
- onSuccess?: (user: AdminUserResponse) => void
76
- }
77
-
78
- export function CreateAdminUser({ onClose, onSuccess }: CreateAdminUserProps) {
79
- const { createAdminUser } = useBylineAdminServices()
80
- const [formError, setFormError] = useState<string | null>(null)
81
-
82
- const form = useForm({
83
- defaultValues: initialValues,
84
- validationLogic: revalidateLogic({
85
- mode: 'blur',
86
- modeAfterSubmission: 'change',
87
- }),
88
- validators: {
89
- onDynamic: createAdminUserFormSchema,
90
- },
91
- onSubmit: async ({ value }) => {
92
- setFormError(null)
93
- try {
94
- const created = await createAdminUser({
95
- data: {
96
- email: value.email,
97
- password: value.password,
98
- given_name: normaliseText(value.given_name),
99
- family_name: normaliseText(value.family_name),
100
- username: normaliseText(value.username),
101
- is_super_admin: value.is_super_admin,
102
- is_enabled: value.is_enabled,
103
- is_email_verified: value.is_email_verified,
104
- },
105
- })
106
- // Clear the form so the next drawer open starts fresh — the
107
- // list-view shows the success as a toast and keeps the drawer
108
- // closed until the user clicks "+" again.
109
- form.reset(initialValues)
110
- onSuccess?.(created)
111
- } catch (err) {
112
- const code = getErrorCode(err)
113
- if (code === 'admin.users.emailInUse') {
114
- form.setFieldMeta('email', (meta) => ({
115
- ...meta,
116
- errorMap: { ...meta.errorMap, onServer: 'This email is already in use.' },
117
- errors: ['This email is already in use.'],
118
- }))
119
- return
120
- }
121
- setFormError('Could not create this admin user. Please try again.')
122
- }
123
- },
124
- })
125
-
126
- return (
127
- <div className={cx('byline-user-create-wrap', styles.wrap)}>
128
- <form
129
- noValidate
130
- onSubmit={(event) => {
131
- event.preventDefault()
132
- event.stopPropagation()
133
- void form.handleSubmit()
134
- }}
135
- className={cx('byline-user-create-form', styles.form)}
136
- >
137
- {formError ? <Alert intent="danger">{formError}</Alert> : null}
138
-
139
- <div className={cx('byline-user-create-grid', styles.grid)}>
140
- <form.Field name="given_name">
141
- {(field) => (
142
- <Input
143
- label="Given name"
144
- id="new-given-name"
145
- name={field.name}
146
- value={field.state.value}
147
- onBlur={field.handleBlur}
148
- onChange={(e) => field.handleChange(e.currentTarget.value)}
149
- error={field.state.meta.errors.length > 0}
150
- errorText={firstError(field.state.meta.errors)}
151
- autoComplete="given-name"
152
- />
153
- )}
154
- </form.Field>
155
-
156
- <form.Field name="family_name">
157
- {(field) => (
158
- <Input
159
- label="Family name"
160
- id="new-family-name"
161
- name={field.name}
162
- value={field.state.value}
163
- onBlur={field.handleBlur}
164
- onChange={(e) => field.handleChange(e.currentTarget.value)}
165
- error={field.state.meta.errors.length > 0}
166
- errorText={firstError(field.state.meta.errors)}
167
- autoComplete="family-name"
168
- />
169
- )}
170
- </form.Field>
171
- </div>
172
-
173
- <form.Field name="username">
174
- {(field) => (
175
- <Input
176
- label="Username"
177
- id="new-username"
178
- name={field.name}
179
- value={field.state.value}
180
- onBlur={field.handleBlur}
181
- onChange={(e) => field.handleChange(e.currentTarget.value)}
182
- error={field.state.meta.errors.length > 0}
183
- errorText={firstError(field.state.meta.errors)}
184
- helpText="Optional."
185
- autoComplete="username"
186
- />
187
- )}
188
- </form.Field>
189
-
190
- <form.Field name="email">
191
- {(field) => (
192
- <Input
193
- label="Email"
194
- id="new-email"
195
- name={field.name}
196
- type="email"
197
- value={field.state.value}
198
- onBlur={field.handleBlur}
199
- onChange={(e) => field.handleChange(e.currentTarget.value)}
200
- error={field.state.meta.errors.length > 0}
201
- errorText={firstError(field.state.meta.errors)}
202
- autoComplete="email"
203
- required
204
- />
205
- )}
206
- </form.Field>
207
-
208
- <form.Field name="password">
209
- {(field) => (
210
- <Input
211
- label="Initial password"
212
- id="new-password"
213
- name={field.name}
214
- type="password"
215
- value={field.state.value}
216
- onBlur={field.handleBlur}
217
- onChange={(e) => field.handleChange(e.currentTarget.value)}
218
- error={field.state.meta.errors.length > 0}
219
- errorText={firstError(field.state.meta.errors)}
220
- helpText="The user can change it from their own account after signing in."
221
- autoComplete="new-password"
222
- required
223
- />
224
- )}
225
- </form.Field>
226
-
227
- <div className={cx('byline-user-create-flags', styles.flags)}>
228
- <form.Field name="is_enabled">
229
- {(field) => (
230
- <Checkbox
231
- id="new-is-enabled"
232
- name={field.name}
233
- label="Enabled"
234
- checked={field.state.value}
235
- onCheckedChange={(checked) => field.handleChange(checked === true)}
236
- helpText="Disabled accounts cannot sign in."
237
- />
238
- )}
239
- </form.Field>
240
-
241
- <form.Field name="is_email_verified">
242
- {(field) => (
243
- <Checkbox
244
- id="new-is-email-verified"
245
- name={field.name}
246
- label="Email verified"
247
- checked={field.state.value}
248
- onCheckedChange={(checked) => field.handleChange(checked === true)}
249
- helpText="Skip the verification flow for this account."
250
- />
251
- )}
252
- </form.Field>
253
-
254
- <form.Field name="is_super_admin">
255
- {(field) => (
256
- <Checkbox
257
- id="new-is-super-admin"
258
- name={field.name}
259
- label="Super admin"
260
- checked={field.state.value}
261
- onCheckedChange={(checked) => field.handleChange(checked === true)}
262
- helpText="Super admins bypass every ability check — grant with care."
263
- />
264
- )}
265
- </form.Field>
266
- </div>
267
-
268
- <div className={cx('byline-user-create-actions', styles.actions)}>
269
- <Button
270
- type="button"
271
- intent="secondary"
272
- size="sm"
273
- onClick={onClose}
274
- className={cx('byline-user-create-action', styles.action)}
275
- >
276
- Cancel
277
- </Button>
278
- <form.Subscribe
279
- selector={(state) => ({
280
- canSubmit: state.canSubmit,
281
- isSubmitting: state.isSubmitting,
282
- })}
283
- >
284
- {({ canSubmit, isSubmitting }) => (
285
- <Button
286
- size="sm"
287
- intent="primary"
288
- type="submit"
289
- disabled={!canSubmit || isSubmitting}
290
- className={cx('byline-user-create-action', styles.action)}
291
- >
292
- {isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
293
- </Button>
294
- )}
295
- </form.Subscribe>
296
- </div>
297
- </form>
298
- </div>
299
- )
300
- }
301
-
302
- function firstError(errors: readonly unknown[]): string | undefined {
303
- for (const err of errors) {
304
- if (typeof err === 'string') return err
305
- if (err && typeof err === 'object' && 'message' in err) {
306
- const msg = (err as { message?: unknown }).message
307
- if (typeof msg === 'string') return msg
308
- }
309
- }
310
- return undefined
311
- }
312
-
313
- function getErrorCode(err: unknown): string | null {
314
- if (err && typeof err === 'object') {
315
- const e = err as { code?: unknown; cause?: unknown }
316
- if (typeof e.code === 'string') return e.code
317
- if (e.cause && typeof e.cause === 'object' && 'code' in e.cause) {
318
- const cause = e.cause as { code?: unknown }
319
- if (typeof cause.code === 'string') return cause.code
320
- }
321
- }
322
- return null
323
- }