@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.
- package/dist/react.d.ts +10 -18
- package/dist/react.js +2 -15
- package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal.d.ts +8 -1
- package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal.js +4 -6
- package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
- package/dist/{admin/components/collections → widgets/diff-viewer}/diff-modal_module.css +9 -9
- package/dist/{admin/components/collections → widgets/status-badge}/status-badge.js +1 -1
- package/dist/{admin/components/collections → widgets/status-badge}/status-badge.module.js +3 -3
- package/dist/{admin/components/collections → widgets/status-badge}/status-badge_module.css +3 -3
- package/package.json +2 -4
- package/src/react.ts +12 -34
- package/src/{admin/components/collections → widgets/diff-viewer}/diff-modal.tsx +16 -5
- package/src/{admin/components/collections → widgets/status-badge}/status-badge.tsx +1 -1
- package/dist/admin/components/admin-account/change-password.d.ts +0 -8
- package/dist/admin/components/admin-account/change-password.js +0 -192
- package/dist/admin/components/admin-account/change-password.module.js +0 -8
- package/dist/admin/components/admin-account/change-password_module.css +0 -27
- package/dist/admin/components/admin-account/container.d.ts +0 -29
- package/dist/admin/components/admin-account/container.js +0 -299
- package/dist/admin/components/admin-account/container.module.js +0 -28
- package/dist/admin/components/admin-account/container_module.css +0 -106
- package/dist/admin/components/admin-account/update.d.ts +0 -8
- package/dist/admin/components/admin-account/update.js +0 -207
- package/dist/admin/components/admin-account/update.module.js +0 -8
- package/dist/admin/components/admin-account/update_module.css +0 -27
- package/dist/admin/components/admin-permissions/inspector.d.ts +0 -4
- package/dist/admin/components/admin-permissions/inspector.js +0 -284
- package/dist/admin/components/admin-permissions/inspector.module.js +0 -56
- package/dist/admin/components/admin-permissions/inspector_module.css +0 -238
- package/dist/admin/components/admin-roles/create.d.ts +0 -7
- package/dist/admin/components/admin-roles/create.js +0 -177
- package/dist/admin/components/admin-roles/create.module.js +0 -8
- package/dist/admin/components/admin-roles/create_module.css +0 -27
- package/dist/admin/components/admin-roles/permissions.d.ts +0 -10
- package/dist/admin/components/admin-roles/permissions.js +0 -303
- package/dist/admin/components/admin-roles/permissions.module.js +0 -44
- package/dist/admin/components/admin-roles/permissions_module.css +0 -192
- package/dist/admin/components/admin-roles/update.d.ts +0 -8
- package/dist/admin/components/admin-roles/update.js +0 -166
- package/dist/admin/components/admin-roles/update.module.js +0 -8
- package/dist/admin/components/admin-roles/update_module.css +0 -27
- package/dist/admin/components/admin-users/create.d.ts +0 -8
- package/dist/admin/components/admin-users/create.js +0 -268
- package/dist/admin/components/admin-users/create.module.js +0 -10
- package/dist/admin/components/admin-users/create_module.css +0 -45
- package/dist/admin/components/admin-users/roles.d.ts +0 -11
- package/dist/admin/components/admin-users/roles.js +0 -148
- package/dist/admin/components/admin-users/roles.module.js +0 -18
- package/dist/admin/components/admin-users/roles_module.css +0 -75
- package/dist/admin/components/admin-users/set-password.d.ts +0 -8
- package/dist/admin/components/admin-users/set-password.js +0 -170
- package/dist/admin/components/admin-users/set-password.module.js +0 -9
- package/dist/admin/components/admin-users/set-password_module.css +0 -31
- package/dist/admin/components/admin-users/update.d.ts +0 -8
- package/dist/admin/components/admin-users/update.js +0 -254
- package/dist/admin/components/admin-users/update.module.js +0 -9
- package/dist/admin/components/admin-users/update_module.css +0 -34
- package/dist/admin/components/auth/sign-in-form.d.ts +0 -12
- package/dist/admin/components/auth/sign-in-form.js +0 -115
- package/dist/admin/components/auth/sign-in-form.module.js +0 -12
- package/dist/admin/components/auth/sign-in-form_module.css +0 -41
- package/dist/admin/components/collections/diff-modal.module.js +0 -14
- package/dist/services/admin-services-context.d.ts +0 -16
- package/dist/services/admin-services-context.js +0 -13
- package/dist/services/admin-services-types.d.ts +0 -129
- package/dist/services/admin-services-types.js +0 -1
- package/src/admin/components/admin-account/change-password.module.css +0 -40
- package/src/admin/components/admin-account/change-password.tsx +0 -232
- package/src/admin/components/admin-account/container.module.css +0 -158
- package/src/admin/components/admin-account/container.tsx +0 -230
- package/src/admin/components/admin-account/update.module.css +0 -40
- package/src/admin/components/admin-account/update.tsx +0 -263
- package/src/admin/components/admin-permissions/inspector.module.css +0 -326
- package/src/admin/components/admin-permissions/inspector.tsx +0 -298
- package/src/admin/components/admin-roles/create.module.css +0 -40
- package/src/admin/components/admin-roles/create.tsx +0 -218
- package/src/admin/components/admin-roles/permissions.module.css +0 -279
- package/src/admin/components/admin-roles/permissions.tsx +0 -396
- package/src/admin/components/admin-roles/update.module.css +0 -40
- package/src/admin/components/admin-roles/update.tsx +0 -218
- package/src/admin/components/admin-users/create.module.css +0 -63
- package/src/admin/components/admin-users/create.tsx +0 -323
- package/src/admin/components/admin-users/roles.module.css +0 -119
- package/src/admin/components/admin-users/roles.tsx +0 -172
- package/src/admin/components/admin-users/set-password.module.css +0 -46
- package/src/admin/components/admin-users/set-password.tsx +0 -199
- package/src/admin/components/admin-users/update.module.css +0 -49
- package/src/admin/components/admin-users/update.tsx +0 -328
- package/src/admin/components/auth/sign-in-form.module.css +0 -62
- package/src/admin/components/auth/sign-in-form.tsx +0 -132
- package/src/services/admin-services-context.tsx +0 -35
- package/src/services/admin-services-types.ts +0 -177
- /package/dist/{admin/components/collections → widgets/status-badge}/status-badge.d.ts +0 -0
- /package/src/{admin/components/collections → widgets/diff-viewer}/diff-modal.module.css +0 -0
- /package/src/{admin/components/collections → widgets/status-badge}/status-badge.module.css +0 -0
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserRoles — drawer for assigning roles to a user.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-user-roles-wrap — outer container
|
|
6
|
-
* .byline-user-roles-empty — "no roles created yet" empty-state
|
|
7
|
-
* .byline-user-roles-empty-hint — inline machine-name hint
|
|
8
|
-
* .byline-user-roles-list — flat checkbox list
|
|
9
|
-
* .byline-user-roles-row — single role row
|
|
10
|
-
* .byline-user-roles-label — clickable label
|
|
11
|
-
* .byline-user-roles-label-head — name + machine-name pill row
|
|
12
|
-
* .byline-user-roles-name — role display name
|
|
13
|
-
* .byline-user-roles-machine — inline code pill (machine name)
|
|
14
|
-
* .byline-user-roles-description — role description
|
|
15
|
-
* .byline-user-roles-actions — Cancel/Save row
|
|
16
|
-
* .byline-user-roles-action — buttons in the actions row
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
.wrap,
|
|
20
|
-
:global(.byline-user-roles-wrap) {
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-direction: column;
|
|
23
|
-
gap: var(--spacing-12);
|
|
24
|
-
padding: var(--spacing-4);
|
|
25
|
-
margin-top: var(--spacing-4);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.empty,
|
|
29
|
-
:global(.byline-user-roles-empty) {
|
|
30
|
-
margin: 0;
|
|
31
|
-
font-style: italic;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.empty-hint,
|
|
35
|
-
:global(.byline-user-roles-empty-hint) {
|
|
36
|
-
/* Inherits muted colour from the parent .muted utility. */
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.list,
|
|
40
|
-
:global(.byline-user-roles-list) {
|
|
41
|
-
display: flex;
|
|
42
|
-
flex-direction: column;
|
|
43
|
-
gap: var(--spacing-4);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.row,
|
|
47
|
-
:global(.byline-user-roles-row) {
|
|
48
|
-
display: flex;
|
|
49
|
-
align-items: flex-start;
|
|
50
|
-
gap: var(--spacing-8);
|
|
51
|
-
padding: var(--spacing-4) 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.label,
|
|
55
|
-
:global(.byline-user-roles-label) {
|
|
56
|
-
min-width: 0;
|
|
57
|
-
flex: 1 1 0;
|
|
58
|
-
cursor: pointer;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.label-head,
|
|
62
|
-
:global(.byline-user-roles-label-head) {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: var(--spacing-8);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.name,
|
|
69
|
-
:global(.byline-user-roles-name) {
|
|
70
|
-
font-size: var(--font-size-sm);
|
|
71
|
-
font-weight: var(--font-weight-medium);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.machine,
|
|
75
|
-
:global(.byline-user-roles-machine) {
|
|
76
|
-
background-color: var(--gray-100);
|
|
77
|
-
padding: 0.125rem 0.375rem;
|
|
78
|
-
font-size: var(--font-size-xs);
|
|
79
|
-
border-radius: var(--border-radius-sm);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.description,
|
|
83
|
-
:global(.byline-user-roles-description) {
|
|
84
|
-
margin-bottom: 0;
|
|
85
|
-
font-size: var(--font-size-xs);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.actions,
|
|
89
|
-
:global(.byline-user-roles-actions) {
|
|
90
|
-
display: flex;
|
|
91
|
-
align-items: center;
|
|
92
|
-
justify-content: flex-end;
|
|
93
|
-
gap: var(--spacing-8);
|
|
94
|
-
margin-top: var(--spacing-16);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.action,
|
|
98
|
-
:global(.byline-user-roles-action) {
|
|
99
|
-
min-width: 4rem;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/*
|
|
103
|
-
* Checkbox shrink-fit — see admin-roles/permissions.module.css for the
|
|
104
|
-
* rationale; the same override is needed here for the per-user role
|
|
105
|
-
* checkbox list. `!important` is required to win over the uikit
|
|
106
|
-
* Checkbox's `width: 100%` default at equal specificity.
|
|
107
|
-
*/
|
|
108
|
-
.checkbox-auto,
|
|
109
|
-
:global(.byline-user-roles-checkbox-auto) {
|
|
110
|
-
/* biome-ignore lint/complexity/noImportantStyles: see admin-roles/permissions.module.css */
|
|
111
|
-
width: auto !important;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
:is([data-theme="dark"], :global(.dark)) {
|
|
115
|
-
.machine,
|
|
116
|
-
:global(.byline-user-roles-machine) {
|
|
117
|
-
background-color: var(--canvas-800);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -1,172 +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
|
-
* User-roles drawer.
|
|
13
|
-
*
|
|
14
|
-
* Renders a flat checkbox list of every available role, pre-checked
|
|
15
|
-
* from the user's current assignments. On save we wholesale-replace
|
|
16
|
-
* the user's role-set via `setUserRoles`; the response carries the
|
|
17
|
-
* authoritative stored set so the editor's "initial" baseline resets
|
|
18
|
-
* cleanly.
|
|
19
|
-
*
|
|
20
|
-
* Standard drawer pattern (no view/edit mode toggle) — role lists are
|
|
21
|
-
* short by design and the drawer is a short-lived edit context, not
|
|
22
|
-
* a steady-state inspector. Save + Cancel are always visible; Save
|
|
23
|
-
* is disabled until dirty.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { useState } from 'react'
|
|
27
|
-
|
|
28
|
-
import type { AdminRoleResponse, UserRolesResponse } from '@byline/admin/admin-roles'
|
|
29
|
-
import type { AdminUserResponse } from '@byline/admin/admin-users'
|
|
30
|
-
import cx from 'classnames'
|
|
31
|
-
|
|
32
|
-
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
33
|
-
import { Alert, Button, Checkbox, LoaderEllipsis } from '../../../uikit.js'
|
|
34
|
-
import styles from './roles.module.css'
|
|
35
|
-
|
|
36
|
-
interface UserRolesProps {
|
|
37
|
-
user: AdminUserResponse
|
|
38
|
-
allRoles: AdminRoleResponse[]
|
|
39
|
-
initialRoleIds: string[]
|
|
40
|
-
onClose?: () => void
|
|
41
|
-
onSaved?: (response: UserRolesResponse) => void
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function setsEqual(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
45
|
-
if (a.size !== b.size) return false
|
|
46
|
-
for (const item of a) if (!b.has(item)) return false
|
|
47
|
-
return true
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function UserRoles({ user, allRoles, initialRoleIds, onClose, onSaved }: UserRolesProps) {
|
|
51
|
-
const { setUserRoles } = useBylineAdminServices()
|
|
52
|
-
const [initialSet, setInitialSet] = useState<ReadonlySet<string>>(() => new Set(initialRoleIds))
|
|
53
|
-
const [selected, setSelected] = useState<Set<string>>(() => new Set(initialRoleIds))
|
|
54
|
-
const [saving, setSaving] = useState(false)
|
|
55
|
-
const [error, setError] = useState<string | null>(null)
|
|
56
|
-
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
57
|
-
|
|
58
|
-
const isDirty = !setsEqual(selected, initialSet)
|
|
59
|
-
|
|
60
|
-
function handleToggle(roleId: string, checked: boolean): void {
|
|
61
|
-
setSelected((current) => {
|
|
62
|
-
const next = new Set(current)
|
|
63
|
-
if (checked) next.add(roleId)
|
|
64
|
-
else next.delete(roleId)
|
|
65
|
-
return next
|
|
66
|
-
})
|
|
67
|
-
setSuccessMessage(null)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function handleSave(): Promise<void> {
|
|
71
|
-
if (saving) return
|
|
72
|
-
setSaving(true)
|
|
73
|
-
setError(null)
|
|
74
|
-
setSuccessMessage(null)
|
|
75
|
-
try {
|
|
76
|
-
const response = await setUserRoles({
|
|
77
|
-
data: { userId: user.id, roleIds: Array.from(selected) },
|
|
78
|
-
})
|
|
79
|
-
const storedSet = new Set(response.roles.map((r) => r.id))
|
|
80
|
-
setInitialSet(storedSet)
|
|
81
|
-
setSelected(new Set(storedSet))
|
|
82
|
-
setSuccessMessage('Saved.')
|
|
83
|
-
onSaved?.(response)
|
|
84
|
-
} catch (err) {
|
|
85
|
-
const code = getErrorCode(err)
|
|
86
|
-
if (code === 'admin.roles.userNotFound') {
|
|
87
|
-
setError('This user no longer exists.')
|
|
88
|
-
} else if (code === 'admin.roles.notFound') {
|
|
89
|
-
setError('One or more selected roles no longer exist. Reload the page and try again.')
|
|
90
|
-
} else {
|
|
91
|
-
setError('Could not save roles. Please try again.')
|
|
92
|
-
}
|
|
93
|
-
} finally {
|
|
94
|
-
setSaving(false)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div className={cx('byline-user-roles-wrap', styles.wrap)}>
|
|
100
|
-
{error ? <Alert intent="danger">{error}</Alert> : null}
|
|
101
|
-
{successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
|
|
102
|
-
|
|
103
|
-
{allRoles.length === 0 ? (
|
|
104
|
-
<p className={cx('muted', 'byline-user-roles-empty', styles.empty)}>
|
|
105
|
-
No roles have been created yet. Create roles in{' '}
|
|
106
|
-
<span className="muted">/admin/roles</span> first.
|
|
107
|
-
</p>
|
|
108
|
-
) : (
|
|
109
|
-
<div className={cx('byline-user-roles-list', styles.list)}>
|
|
110
|
-
{allRoles.map((role) => (
|
|
111
|
-
<div key={role.id} className={cx('byline-user-roles-row', styles.row)}>
|
|
112
|
-
<Checkbox
|
|
113
|
-
id={`role-${role.id}`}
|
|
114
|
-
name={`role-${role.id}`}
|
|
115
|
-
checked={selected.has(role.id)}
|
|
116
|
-
disabled={saving}
|
|
117
|
-
onCheckedChange={(checked) => handleToggle(role.id, checked === true)}
|
|
118
|
-
containerClasses={cx('byline-user-roles-checkbox-auto', styles['checkbox-auto'])}
|
|
119
|
-
componentClasses={cx('byline-user-roles-checkbox-auto', styles['checkbox-auto'])}
|
|
120
|
-
/>
|
|
121
|
-
<label
|
|
122
|
-
htmlFor={`role-${role.id}`}
|
|
123
|
-
className={cx('byline-user-roles-label', styles.label)}
|
|
124
|
-
>
|
|
125
|
-
<div className={cx('byline-user-roles-label-head', styles['label-head'])}>
|
|
126
|
-
<span className={cx('byline-user-roles-name', styles.name)}>{role.name}</span>
|
|
127
|
-
<code className={cx('byline-user-roles-machine', styles.machine)}>
|
|
128
|
-
{role.machine_name}
|
|
129
|
-
</code>
|
|
130
|
-
</div>
|
|
131
|
-
{role.description ? (
|
|
132
|
-
<p className={cx('muted', 'byline-user-roles-description', styles.description)}>
|
|
133
|
-
{role.description}
|
|
134
|
-
</p>
|
|
135
|
-
) : null}
|
|
136
|
-
</label>
|
|
137
|
-
</div>
|
|
138
|
-
))}
|
|
139
|
-
</div>
|
|
140
|
-
)}
|
|
141
|
-
|
|
142
|
-
<div className={cx('byline-user-roles-actions', styles.actions)}>
|
|
143
|
-
<Button
|
|
144
|
-
type="button"
|
|
145
|
-
intent="secondary"
|
|
146
|
-
size="xs"
|
|
147
|
-
onClick={onClose}
|
|
148
|
-
disabled={saving}
|
|
149
|
-
className={cx('byline-user-roles-action', styles.action)}
|
|
150
|
-
>
|
|
151
|
-
{successMessage ? 'Close' : 'Cancel'}
|
|
152
|
-
</Button>
|
|
153
|
-
<Button
|
|
154
|
-
type="button"
|
|
155
|
-
intent="primary"
|
|
156
|
-
size="xs"
|
|
157
|
-
onClick={() => void handleSave()}
|
|
158
|
-
disabled={saving || !isDirty}
|
|
159
|
-
className={cx('byline-user-roles-action', styles.action)}
|
|
160
|
-
>
|
|
161
|
-
{saving ? <LoaderEllipsis size={30} /> : 'Save'}
|
|
162
|
-
</Button>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function getErrorCode(err: unknown): string | null {
|
|
169
|
-
return typeof (err as { code?: unknown })?.code === 'string'
|
|
170
|
-
? (err as { code: string }).code
|
|
171
|
-
: null
|
|
172
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SetAdminUserPassword — privileged "set password" drawer form.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-user-set-password-wrap — outer container
|
|
6
|
-
* .byline-user-set-password-form — vertical-stack form element
|
|
7
|
-
* .byline-user-set-password-target — bold target email span in the lead-in
|
|
8
|
-
* .byline-user-set-password-actions — Cancel/Save row
|
|
9
|
-
* .byline-user-set-password-action — buttons in the actions row
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
.wrap,
|
|
13
|
-
:global(.byline-user-set-password-wrap) {
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
gap: var(--spacing-8);
|
|
17
|
-
padding: var(--spacing-4);
|
|
18
|
-
margin-top: var(--spacing-4);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.form,
|
|
22
|
-
:global(.byline-user-set-password-form) {
|
|
23
|
-
display: flex;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
gap: var(--spacing-16);
|
|
26
|
-
padding-top: var(--spacing-8);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.target,
|
|
30
|
-
:global(.byline-user-set-password-target) {
|
|
31
|
-
font-weight: var(--font-weight-semibold);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.actions,
|
|
35
|
-
:global(.byline-user-set-password-actions) {
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
justify-content: flex-end;
|
|
39
|
-
gap: var(--spacing-8);
|
|
40
|
-
margin-top: var(--spacing-16);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.action,
|
|
44
|
-
:global(.byline-user-set-password-action) {
|
|
45
|
-
min-width: 4rem;
|
|
46
|
-
}
|
|
@@ -1,199 +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
|
-
* Set-password drawer form.
|
|
13
|
-
*
|
|
14
|
-
* Admin-facing "set this user's password" flow — used when an admin is
|
|
15
|
-
* resetting someone else's password. Server-side policy (min 12 chars,
|
|
16
|
-
* max 256) is duplicated client-side via Zod for immediate field
|
|
17
|
-
* validation. A matching-confirmation field catches typos without a
|
|
18
|
-
* round-trip.
|
|
19
|
-
*
|
|
20
|
-
* The server fn returns the updated user so we can lift the bumped
|
|
21
|
-
* `vid` back into the container; the drawer doesn't need to re-fetch.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { useState } from 'react'
|
|
25
|
-
import { revalidateLogic, useForm } from '@tanstack/react-form-start'
|
|
26
|
-
|
|
27
|
-
import type { AdminUserResponse } from '@byline/admin/admin-users'
|
|
28
|
-
import { passwordSchema } from '@byline/core/validation'
|
|
29
|
-
import cx from 'classnames'
|
|
30
|
-
import { z } from 'zod'
|
|
31
|
-
|
|
32
|
-
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
33
|
-
import { Alert, Button, InputPassword, LoaderEllipsis } from '../../../uikit.js'
|
|
34
|
-
import styles from './set-password.module.css'
|
|
35
|
-
|
|
36
|
-
const setPasswordFormSchema = z
|
|
37
|
-
.object({
|
|
38
|
-
password: passwordSchema,
|
|
39
|
-
confirm: z.string({ message: 'Please confirm the password' }),
|
|
40
|
-
})
|
|
41
|
-
.refine((v) => v.password === v.confirm, {
|
|
42
|
-
message: 'Passwords do not match',
|
|
43
|
-
path: ['confirm'],
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
type SetPasswordValues = z.infer<typeof setPasswordFormSchema>
|
|
47
|
-
|
|
48
|
-
interface SetPasswordProps {
|
|
49
|
-
user: AdminUserResponse
|
|
50
|
-
onClose?: () => void
|
|
51
|
-
onSuccess?: (user: AdminUserResponse) => void
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function SetPassword({ user, onClose, onSuccess }: SetPasswordProps) {
|
|
55
|
-
const { setAdminUserPassword } = useBylineAdminServices()
|
|
56
|
-
const [formError, setFormError] = useState<string | null>(null)
|
|
57
|
-
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
58
|
-
|
|
59
|
-
const form = useForm({
|
|
60
|
-
defaultValues: { password: '', confirm: '' } as SetPasswordValues,
|
|
61
|
-
validationLogic: revalidateLogic({
|
|
62
|
-
mode: 'blur',
|
|
63
|
-
modeAfterSubmission: 'change',
|
|
64
|
-
}),
|
|
65
|
-
validators: {
|
|
66
|
-
onDynamic: setPasswordFormSchema,
|
|
67
|
-
},
|
|
68
|
-
onSubmit: async ({ value }) => {
|
|
69
|
-
setFormError(null)
|
|
70
|
-
setSuccessMessage(null)
|
|
71
|
-
try {
|
|
72
|
-
const updated = await setAdminUserPassword({
|
|
73
|
-
data: { id: user.id, vid: user.vid, password: value.password },
|
|
74
|
-
})
|
|
75
|
-
setSuccessMessage('Password updated.')
|
|
76
|
-
form.reset({ password: '', confirm: '' })
|
|
77
|
-
onSuccess?.(updated)
|
|
78
|
-
} catch (err) {
|
|
79
|
-
const code = getErrorCode(err)
|
|
80
|
-
if (code === 'admin.users.versionConflict') {
|
|
81
|
-
setFormError(
|
|
82
|
-
'This user has been modified elsewhere since you opened this form. Reload to refresh and try again.'
|
|
83
|
-
)
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
if (code === 'admin.users.notFound') {
|
|
87
|
-
setFormError('This user no longer exists.')
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
setFormError('Could not set the password. Please try again.')
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div className={cx('byline-user-set-password-wrap', styles.wrap)}>
|
|
97
|
-
<form
|
|
98
|
-
noValidate
|
|
99
|
-
onSubmit={(event) => {
|
|
100
|
-
event.preventDefault()
|
|
101
|
-
event.stopPropagation()
|
|
102
|
-
void form.handleSubmit()
|
|
103
|
-
}}
|
|
104
|
-
className={cx('byline-user-set-password-form', styles.form)}
|
|
105
|
-
>
|
|
106
|
-
{formError ? <Alert intent="danger">{formError}</Alert> : null}
|
|
107
|
-
{successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
|
|
108
|
-
|
|
109
|
-
<p className="muted">
|
|
110
|
-
Sets a new password for{' '}
|
|
111
|
-
<span className={cx('byline-user-set-password-target', styles.target)}>{user.email}</span>
|
|
112
|
-
. The user will need to sign in again with the new password.
|
|
113
|
-
</p>
|
|
114
|
-
|
|
115
|
-
<form.Field name="password">
|
|
116
|
-
{(field) => (
|
|
117
|
-
<InputPassword
|
|
118
|
-
label="New password"
|
|
119
|
-
id="password"
|
|
120
|
-
name={field.name}
|
|
121
|
-
value={field.state.value}
|
|
122
|
-
onBlur={field.handleBlur}
|
|
123
|
-
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
124
|
-
error={field.state.meta.errors.length > 0}
|
|
125
|
-
errorText={firstError(field.state.meta.errors)}
|
|
126
|
-
autoComplete="new-password"
|
|
127
|
-
required
|
|
128
|
-
/>
|
|
129
|
-
)}
|
|
130
|
-
</form.Field>
|
|
131
|
-
|
|
132
|
-
<form.Field name="confirm">
|
|
133
|
-
{(field) => (
|
|
134
|
-
<InputPassword
|
|
135
|
-
label="Confirm new password"
|
|
136
|
-
id="confirm"
|
|
137
|
-
name={field.name}
|
|
138
|
-
value={field.state.value}
|
|
139
|
-
onBlur={field.handleBlur}
|
|
140
|
-
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
141
|
-
error={field.state.meta.errors.length > 0}
|
|
142
|
-
errorText={firstError(field.state.meta.errors)}
|
|
143
|
-
autoComplete="new-password"
|
|
144
|
-
required
|
|
145
|
-
/>
|
|
146
|
-
)}
|
|
147
|
-
</form.Field>
|
|
148
|
-
|
|
149
|
-
<div className={cx('byline-user-set-password-actions', styles.actions)}>
|
|
150
|
-
<Button
|
|
151
|
-
type="button"
|
|
152
|
-
intent="secondary"
|
|
153
|
-
size="sm"
|
|
154
|
-
onClick={onClose}
|
|
155
|
-
className={cx('byline-user-set-password-action', styles.action)}
|
|
156
|
-
>
|
|
157
|
-
{successMessage ? 'Close' : 'Cancel'}
|
|
158
|
-
</Button>
|
|
159
|
-
<form.Subscribe
|
|
160
|
-
selector={(state) => ({
|
|
161
|
-
canSubmit: state.canSubmit,
|
|
162
|
-
isSubmitting: state.isSubmitting,
|
|
163
|
-
isDirty: state.isDirty,
|
|
164
|
-
})}
|
|
165
|
-
>
|
|
166
|
-
{({ canSubmit, isSubmitting }) => (
|
|
167
|
-
<Button
|
|
168
|
-
size="sm"
|
|
169
|
-
intent="primary"
|
|
170
|
-
type="submit"
|
|
171
|
-
disabled={!canSubmit || isSubmitting}
|
|
172
|
-
className={cx('byline-user-set-password-action', styles.action)}
|
|
173
|
-
>
|
|
174
|
-
{isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
|
|
175
|
-
</Button>
|
|
176
|
-
)}
|
|
177
|
-
</form.Subscribe>
|
|
178
|
-
</div>
|
|
179
|
-
</form>
|
|
180
|
-
</div>
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function firstError(errors: readonly unknown[]): string | undefined {
|
|
185
|
-
for (const err of errors) {
|
|
186
|
-
if (typeof err === 'string') return err
|
|
187
|
-
if (err && typeof err === 'object' && 'message' in err) {
|
|
188
|
-
const msg = (err as { message?: unknown }).message
|
|
189
|
-
if (typeof msg === 'string') return msg
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return undefined
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function getErrorCode(err: unknown): string | null {
|
|
196
|
-
return typeof (err as { code?: unknown })?.code === 'string'
|
|
197
|
-
? (err as { code: string }).code
|
|
198
|
-
: null
|
|
199
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UpdateAdminUser — drawer form for editing an admin user.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-user-update-wrap — outer container
|
|
6
|
-
* .byline-user-update-form — vertical-stack form element
|
|
7
|
-
* .byline-user-update-flags — checkbox stack (enabled / verified / super)
|
|
8
|
-
* .byline-user-update-actions — Cancel/Save row
|
|
9
|
-
* .byline-user-update-action — buttons in the actions row
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
.wrap,
|
|
13
|
-
:global(.byline-user-update-wrap) {
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
gap: var(--spacing-8);
|
|
17
|
-
padding: var(--spacing-4);
|
|
18
|
-
margin-top: var(--spacing-4);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.form,
|
|
22
|
-
:global(.byline-user-update-form) {
|
|
23
|
-
display: flex;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
gap: var(--spacing-16);
|
|
26
|
-
padding-top: var(--spacing-8);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.flags,
|
|
30
|
-
:global(.byline-user-update-flags) {
|
|
31
|
-
display: flex;
|
|
32
|
-
flex-direction: column;
|
|
33
|
-
gap: var(--spacing-8);
|
|
34
|
-
padding: var(--spacing-4);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.actions,
|
|
38
|
-
:global(.byline-user-update-actions) {
|
|
39
|
-
display: flex;
|
|
40
|
-
align-items: center;
|
|
41
|
-
justify-content: flex-end;
|
|
42
|
-
gap: var(--spacing-8);
|
|
43
|
-
margin-top: var(--spacing-16);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.action,
|
|
47
|
-
:global(.byline-user-update-action) {
|
|
48
|
-
min-width: 4rem;
|
|
49
|
-
}
|