@byline/admin 2.3.3 → 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/abilities.js +5 -24
- package/dist/index.js +8 -30
- package/dist/lib/assert-admin-actor.js +13 -74
- package/dist/lib/create-command.js +6 -16
- package/dist/modules/admin-account/commands.js +35 -24
- package/dist/modules/admin-account/components/change-password.d.ts +8 -0
- package/dist/modules/admin-account/components/change-password.js +192 -0
- package/dist/modules/admin-account/components/change-password.module.js +8 -0
- package/dist/modules/admin-account/components/change-password_module.css +27 -0
- package/dist/modules/admin-account/components/container.d.ts +29 -0
- package/dist/modules/admin-account/components/container.js +298 -0
- package/dist/modules/admin-account/components/container.module.js +28 -0
- package/dist/modules/admin-account/components/container_module.css +106 -0
- package/dist/modules/admin-account/components/update.d.ts +8 -0
- package/dist/modules/admin-account/components/update.js +207 -0
- package/dist/modules/admin-account/components/update.module.js +8 -0
- package/dist/modules/admin-account/components/update_module.css +27 -0
- package/dist/modules/admin-account/errors.js +14 -45
- package/dist/modules/admin-account/index.js +4 -34
- package/dist/modules/admin-account/schemas.js +25 -59
- package/dist/modules/admin-account/service.js +56 -61
- package/dist/modules/admin-permissions/abilities.js +6 -24
- package/dist/modules/admin-permissions/commands.js +42 -28
- package/dist/modules/admin-permissions/components/inspector.d.ts +4 -0
- package/dist/modules/admin-permissions/components/inspector.js +284 -0
- package/dist/modules/admin-permissions/components/inspector.module.js +56 -0
- package/dist/modules/admin-permissions/components/inspector_module.css +238 -0
- package/dist/modules/admin-permissions/dto.js +3 -16
- package/dist/modules/admin-permissions/errors.js +14 -27
- package/dist/modules/admin-permissions/index.js +6 -26
- package/dist/modules/admin-permissions/repository.js +1 -8
- package/dist/modules/admin-permissions/schemas.js +33 -70
- package/dist/modules/admin-permissions/service.js +88 -92
- package/dist/modules/admin-roles/abilities.js +8 -30
- package/dist/modules/admin-roles/commands.js +89 -55
- package/dist/modules/admin-roles/components/create.d.ts +7 -0
- package/dist/modules/admin-roles/components/create.js +177 -0
- package/dist/modules/admin-roles/components/create.module.js +8 -0
- package/dist/modules/admin-roles/components/create_module.css +27 -0
- package/dist/modules/admin-roles/components/permissions.d.ts +10 -0
- package/dist/modules/admin-roles/components/permissions.js +303 -0
- package/dist/modules/admin-roles/components/permissions.module.js +44 -0
- package/dist/modules/admin-roles/components/permissions_module.css +192 -0
- package/dist/modules/admin-roles/components/update.d.ts +8 -0
- package/dist/modules/admin-roles/components/update.js +166 -0
- package/dist/modules/admin-roles/components/update.module.js +8 -0
- package/dist/modules/admin-roles/components/update_module.css +27 -0
- package/dist/modules/admin-roles/dto.js +3 -16
- package/dist/modules/admin-roles/errors.js +16 -40
- package/dist/modules/admin-roles/index.js +6 -26
- package/dist/modules/admin-roles/repository.js +1 -8
- package/dist/modules/admin-roles/schemas.js +41 -71
- package/dist/modules/admin-roles/service.js +79 -82
- package/dist/modules/admin-users/abilities.js +9 -38
- package/dist/modules/admin-users/commands.js +92 -50
- package/dist/modules/admin-users/components/create.d.ts +8 -0
- package/dist/modules/admin-users/components/create.js +268 -0
- package/dist/modules/admin-users/components/create.module.js +10 -0
- package/dist/modules/admin-users/components/create_module.css +45 -0
- package/dist/modules/admin-users/components/roles.d.ts +11 -0
- package/dist/modules/admin-users/components/roles.js +148 -0
- package/dist/modules/admin-users/components/roles.module.js +18 -0
- package/dist/modules/admin-users/components/roles_module.css +75 -0
- package/dist/modules/admin-users/components/set-password.d.ts +8 -0
- package/dist/modules/admin-users/components/set-password.js +170 -0
- package/dist/modules/admin-users/components/set-password.module.js +9 -0
- package/dist/modules/admin-users/components/set-password_module.css +31 -0
- package/dist/modules/admin-users/components/update.d.ts +8 -0
- package/dist/modules/admin-users/components/update.js +254 -0
- package/dist/modules/admin-users/components/update.module.js +9 -0
- package/dist/modules/admin-users/components/update_module.css +34 -0
- package/dist/modules/admin-users/dto.js +3 -18
- package/dist/modules/admin-users/errors.js +17 -43
- package/dist/modules/admin-users/index.js +7 -27
- package/dist/modules/admin-users/repository.js +1 -8
- package/dist/modules/admin-users/schemas.js +44 -75
- package/dist/modules/admin-users/seed-super-admin.js +9 -34
- package/dist/modules/admin-users/service.js +76 -91
- package/dist/modules/auth/components/sign-in-form.d.ts +12 -0
- package/dist/modules/auth/components/sign-in-form.js +115 -0
- package/dist/modules/auth/components/sign-in-form.module.js +12 -0
- package/dist/modules/auth/components/sign-in-form_module.css +41 -0
- package/dist/modules/auth/index.js +3 -24
- package/dist/modules/auth/jwt-session-provider.js +179 -149
- package/dist/modules/auth/password.js +11 -53
- package/dist/modules/auth/phc.js +21 -54
- package/dist/modules/auth/refresh-tokens-repository.js +1 -8
- package/dist/modules/auth/resolve-actor.js +6 -28
- package/dist/services/admin-services-context.d.ts +16 -0
- package/dist/services/admin-services-context.js +13 -0
- package/dist/services/admin-services-types.d.ts +129 -0
- package/dist/services/admin-services-types.js +1 -0
- package/dist/store.js +1 -8
- package/dist/vendor/noble-argon2/_blake.js +277 -45
- package/dist/vendor/noble-argon2/_md.js +81 -136
- package/dist/vendor/noble-argon2/_u64.js +65 -67
- package/dist/vendor/noble-argon2/argon2.js +181 -342
- package/dist/vendor/noble-argon2/blake2.js +252 -327
- package/dist/vendor/noble-argon2/utils.js +110 -490
- package/dist/vendor/noble-argon2/utils.js.LICENSE.txt +1 -0
- package/package.json +89 -10
- package/src/abilities.ts +32 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +39 -0
- package/src/lib/assert-admin-actor.ts +90 -0
- package/src/lib/create-command.ts +109 -0
- package/src/modules/admin-account/commands.ts +76 -0
- package/src/modules/admin-account/components/change-password.module.css +40 -0
- package/src/modules/admin-account/components/change-password.tsx +232 -0
- package/src/modules/admin-account/components/container.module.css +158 -0
- package/src/modules/admin-account/components/container.tsx +229 -0
- package/src/modules/admin-account/components/update.module.css +40 -0
- package/src/modules/admin-account/components/update.tsx +263 -0
- package/src/modules/admin-account/errors.ts +75 -0
- package/src/modules/admin-account/index.ts +60 -0
- package/src/modules/admin-account/schemas.ts +84 -0
- package/src/modules/admin-account/service.ts +92 -0
- package/src/modules/admin-permissions/abilities.ts +46 -0
- package/src/modules/admin-permissions/commands.ts +103 -0
- package/src/modules/admin-permissions/components/inspector.module.css +326 -0
- package/src/modules/admin-permissions/components/inspector.tsx +298 -0
- package/src/modules/admin-permissions/dto.ts +28 -0
- package/src/modules/admin-permissions/errors.ts +57 -0
- package/src/modules/admin-permissions/index.ts +72 -0
- package/src/modules/admin-permissions/repository.ts +49 -0
- package/src/modules/admin-permissions/schemas.ts +128 -0
- package/src/modules/admin-permissions/service.ts +137 -0
- package/src/modules/admin-roles/abilities.ts +62 -0
- package/src/modules/admin-roles/commands.ts +161 -0
- package/src/modules/admin-roles/components/create.module.css +40 -0
- package/src/modules/admin-roles/components/create.tsx +218 -0
- package/src/modules/admin-roles/components/permissions.module.css +279 -0
- package/src/modules/admin-roles/components/permissions.tsx +396 -0
- package/src/modules/admin-roles/components/update.module.css +40 -0
- package/src/modules/admin-roles/components/update.tsx +218 -0
- package/src/modules/admin-roles/dto.ts +30 -0
- package/src/modules/admin-roles/errors.ts +76 -0
- package/src/modules/admin-roles/index.ts +81 -0
- package/src/modules/admin-roles/repository.ts +96 -0
- package/src/modules/admin-roles/schemas.ts +139 -0
- package/src/modules/admin-roles/service.ts +136 -0
- package/src/modules/admin-users/abilities.ts +76 -0
- package/src/modules/admin-users/commands.ts +157 -0
- package/src/modules/admin-users/components/create.module.css +63 -0
- package/src/modules/admin-users/components/create.tsx +323 -0
- package/src/modules/admin-users/components/roles.module.css +119 -0
- package/src/modules/admin-users/components/roles.tsx +172 -0
- package/src/modules/admin-users/components/set-password.module.css +46 -0
- package/src/modules/admin-users/components/set-password.tsx +199 -0
- package/src/modules/admin-users/components/update.module.css +49 -0
- package/src/modules/admin-users/components/update.tsx +328 -0
- package/src/modules/admin-users/dto.ts +39 -0
- package/src/modules/admin-users/errors.ts +84 -0
- package/src/modules/admin-users/index.ts +91 -0
- package/src/modules/admin-users/repository.ts +161 -0
- package/src/modules/admin-users/schemas.ts +168 -0
- package/src/modules/admin-users/seed-super-admin.ts +102 -0
- package/src/modules/admin-users/service.ts +166 -0
- package/src/modules/auth/components/sign-in-form.module.css +62 -0
- package/src/modules/auth/components/sign-in-form.tsx +132 -0
- package/src/modules/auth/index.ts +31 -0
- package/src/modules/auth/jwt-session-provider.ts +301 -0
- package/src/modules/auth/password.ts +94 -0
- package/src/modules/auth/phc.ts +121 -0
- package/src/modules/auth/refresh-tokens-repository.ts +74 -0
- package/src/modules/auth/resolve-actor.ts +42 -0
- package/src/services/admin-services-context.tsx +52 -0
- package/src/services/admin-services-types.ts +177 -0
- package/src/store.ts +32 -0
- package/src/vendor/noble-argon2/LICENSE +21 -0
- package/src/vendor/noble-argon2/README.md +87 -0
- package/src/vendor/noble-argon2/_blake.ts +58 -0
- package/src/vendor/noble-argon2/_md.ts +223 -0
- package/src/vendor/noble-argon2/_u64.ts +118 -0
- package/src/vendor/noble-argon2/argon2.ts +668 -0
- package/src/vendor/noble-argon2/blake2.ts +583 -0
- package/src/vendor/noble-argon2/utils.ts +849 -0
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
* Self-service change-password drawer form.
|
|
13
|
+
*
|
|
14
|
+
* Distinct from the admin-users `set-password.tsx`:
|
|
15
|
+
*
|
|
16
|
+
* - Requires the current password as defence against
|
|
17
|
+
* session-hijack abuse. The server verifies it against the
|
|
18
|
+
* stored hash before swapping in the new one — wrong current
|
|
19
|
+
* password surfaces as `admin.account.invalidCurrentPassword`.
|
|
20
|
+
* - Confirmation field catches typos before round-trip.
|
|
21
|
+
*
|
|
22
|
+
* Caveat: changing the password here does not revoke other active
|
|
23
|
+
* sessions today. Existing access tokens stay valid until expiry
|
|
24
|
+
* (~15 min); a "sign out everywhere on password change" follow-up
|
|
25
|
+
* will close that gap.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { useState } from 'react'
|
|
29
|
+
import { revalidateLogic, useForm } from '@tanstack/react-form-start'
|
|
30
|
+
|
|
31
|
+
import { passwordSchema } from '@byline/core/validation'
|
|
32
|
+
import { Alert, Button, InputPassword, LoaderEllipsis } from '@byline/ui/react'
|
|
33
|
+
import cx from 'classnames'
|
|
34
|
+
import { z } from 'zod'
|
|
35
|
+
|
|
36
|
+
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
37
|
+
import styles from './change-password.module.css'
|
|
38
|
+
import type { AccountResponse } from '../index.js'
|
|
39
|
+
|
|
40
|
+
const changePasswordFormSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
currentPassword: z.string().min(1, { message: 'Please enter your current password' }),
|
|
43
|
+
newPassword: passwordSchema,
|
|
44
|
+
confirm: z.string({ message: 'Please confirm the new password' }),
|
|
45
|
+
})
|
|
46
|
+
.refine((v) => v.newPassword === v.confirm, {
|
|
47
|
+
message: 'New passwords do not match',
|
|
48
|
+
path: ['confirm'],
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
type ChangePasswordValues = z.infer<typeof changePasswordFormSchema>
|
|
52
|
+
|
|
53
|
+
interface ChangePasswordProps {
|
|
54
|
+
account: AccountResponse
|
|
55
|
+
onClose?: () => void
|
|
56
|
+
onSuccess?: (account: AccountResponse) => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ChangeAccountPassword({ account, onClose, onSuccess }: ChangePasswordProps) {
|
|
60
|
+
const { changeAccountPassword } = useBylineAdminServices()
|
|
61
|
+
const [formError, setFormError] = useState<string | null>(null)
|
|
62
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
63
|
+
|
|
64
|
+
const form = useForm({
|
|
65
|
+
defaultValues: { currentPassword: '', newPassword: '', confirm: '' } as ChangePasswordValues,
|
|
66
|
+
validationLogic: revalidateLogic({
|
|
67
|
+
mode: 'blur',
|
|
68
|
+
modeAfterSubmission: 'change',
|
|
69
|
+
}),
|
|
70
|
+
validators: {
|
|
71
|
+
onDynamic: changePasswordFormSchema,
|
|
72
|
+
},
|
|
73
|
+
onSubmit: async ({ value }) => {
|
|
74
|
+
setFormError(null)
|
|
75
|
+
setSuccessMessage(null)
|
|
76
|
+
try {
|
|
77
|
+
const updated = await changeAccountPassword({
|
|
78
|
+
data: {
|
|
79
|
+
vid: account.vid,
|
|
80
|
+
currentPassword: value.currentPassword,
|
|
81
|
+
newPassword: value.newPassword,
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
setSuccessMessage('Password updated.')
|
|
85
|
+
form.reset({ currentPassword: '', newPassword: '', confirm: '' })
|
|
86
|
+
onSuccess?.(updated)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const code = getErrorCode(err)
|
|
89
|
+
if (code === 'admin.account.invalidCurrentPassword') {
|
|
90
|
+
form.setFieldMeta('currentPassword', (meta) => ({
|
|
91
|
+
...meta,
|
|
92
|
+
errorMap: { ...meta.errorMap, onServer: 'Current password is incorrect.' },
|
|
93
|
+
errors: ['Current password is incorrect.'],
|
|
94
|
+
}))
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
if (code === 'admin.users.versionConflict') {
|
|
98
|
+
setFormError(
|
|
99
|
+
'Your account has been modified elsewhere since you opened this form. Reload to refresh and try again.'
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
if (code === 'admin.account.notFound') {
|
|
104
|
+
setFormError('Your admin account could not be found. Please sign in again.')
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
setFormError('Could not change the password. Please try again.')
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className={cx('byline-account-change-password-wrap', styles.wrap)}>
|
|
114
|
+
<form
|
|
115
|
+
noValidate
|
|
116
|
+
onSubmit={(event) => {
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
event.stopPropagation()
|
|
119
|
+
void form.handleSubmit()
|
|
120
|
+
}}
|
|
121
|
+
className={cx('byline-account-change-password-form', styles.form)}
|
|
122
|
+
>
|
|
123
|
+
{formError ? <Alert intent="danger">{formError}</Alert> : null}
|
|
124
|
+
{successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
|
|
125
|
+
|
|
126
|
+
<p className="muted">
|
|
127
|
+
Other active sessions will continue to work until their tokens expire. Sign out elsewhere
|
|
128
|
+
if you suspect another device has been compromised.
|
|
129
|
+
</p>
|
|
130
|
+
|
|
131
|
+
<form.Field name="currentPassword">
|
|
132
|
+
{(field) => (
|
|
133
|
+
<InputPassword
|
|
134
|
+
label="Current password"
|
|
135
|
+
id="currentPassword"
|
|
136
|
+
name={field.name}
|
|
137
|
+
value={field.state.value}
|
|
138
|
+
onBlur={field.handleBlur}
|
|
139
|
+
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
140
|
+
error={field.state.meta.errors.length > 0}
|
|
141
|
+
errorText={firstError(field.state.meta.errors)}
|
|
142
|
+
autoComplete="current-password"
|
|
143
|
+
required
|
|
144
|
+
/>
|
|
145
|
+
)}
|
|
146
|
+
</form.Field>
|
|
147
|
+
|
|
148
|
+
<form.Field name="newPassword">
|
|
149
|
+
{(field) => (
|
|
150
|
+
<InputPassword
|
|
151
|
+
label="New password"
|
|
152
|
+
id="newPassword"
|
|
153
|
+
name={field.name}
|
|
154
|
+
value={field.state.value}
|
|
155
|
+
onBlur={field.handleBlur}
|
|
156
|
+
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
157
|
+
error={field.state.meta.errors.length > 0}
|
|
158
|
+
errorText={firstError(field.state.meta.errors)}
|
|
159
|
+
autoComplete="new-password"
|
|
160
|
+
required
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
</form.Field>
|
|
164
|
+
|
|
165
|
+
<form.Field name="confirm">
|
|
166
|
+
{(field) => (
|
|
167
|
+
<InputPassword
|
|
168
|
+
label="Confirm new password"
|
|
169
|
+
id="confirm"
|
|
170
|
+
name={field.name}
|
|
171
|
+
value={field.state.value}
|
|
172
|
+
onBlur={field.handleBlur}
|
|
173
|
+
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
174
|
+
error={field.state.meta.errors.length > 0}
|
|
175
|
+
errorText={firstError(field.state.meta.errors)}
|
|
176
|
+
autoComplete="new-password"
|
|
177
|
+
required
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
</form.Field>
|
|
181
|
+
|
|
182
|
+
<div className={cx('byline-account-change-password-actions', styles.actions)}>
|
|
183
|
+
<Button
|
|
184
|
+
type="button"
|
|
185
|
+
intent="secondary"
|
|
186
|
+
size="sm"
|
|
187
|
+
onClick={onClose}
|
|
188
|
+
className={cx('byline-account-change-password-action', styles.action)}
|
|
189
|
+
>
|
|
190
|
+
{successMessage ? 'Close' : 'Cancel'}
|
|
191
|
+
</Button>
|
|
192
|
+
<form.Subscribe
|
|
193
|
+
selector={(state) => ({
|
|
194
|
+
canSubmit: state.canSubmit,
|
|
195
|
+
isSubmitting: state.isSubmitting,
|
|
196
|
+
isDirty: state.isDirty,
|
|
197
|
+
})}
|
|
198
|
+
>
|
|
199
|
+
{({ canSubmit, isSubmitting }) => (
|
|
200
|
+
<Button
|
|
201
|
+
size="sm"
|
|
202
|
+
intent="primary"
|
|
203
|
+
type="submit"
|
|
204
|
+
disabled={!canSubmit || isSubmitting}
|
|
205
|
+
className={cx('byline-account-change-password-action', styles.action)}
|
|
206
|
+
>
|
|
207
|
+
{isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
|
|
208
|
+
</Button>
|
|
209
|
+
)}
|
|
210
|
+
</form.Subscribe>
|
|
211
|
+
</div>
|
|
212
|
+
</form>
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function firstError(errors: readonly unknown[]): string | undefined {
|
|
218
|
+
for (const err of errors) {
|
|
219
|
+
if (typeof err === 'string') return err
|
|
220
|
+
if (err && typeof err === 'object' && 'message' in err) {
|
|
221
|
+
const msg = (err as { message?: unknown }).message
|
|
222
|
+
if (typeof msg === 'string') return msg
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return undefined
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getErrorCode(err: unknown): string | null {
|
|
229
|
+
return typeof (err as { code?: unknown })?.code === 'string'
|
|
230
|
+
? (err as { code: string }).code
|
|
231
|
+
: null
|
|
232
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountSelfContainer — self-service account dashboard with edit drawers.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-account-grid — outer two-column grid
|
|
6
|
+
* .byline-account-column — column wrapper (vertical card stack)
|
|
7
|
+
* .byline-account-section — single bordered section card
|
|
8
|
+
* .byline-account-section-head — section title + edit-icon row
|
|
9
|
+
* .byline-account-line — single key/value text line
|
|
10
|
+
* .byline-account-cta-line — paragraph above the in-card primary CTA
|
|
11
|
+
* .byline-account-meta — small metadata block (created/updated/last login)
|
|
12
|
+
* .byline-account-status — status colour modifier (enabled / disabled)
|
|
13
|
+
* .byline-account-status-on
|
|
14
|
+
* .byline-account-status-off
|
|
15
|
+
* .byline-account-status-help — italic helper line on Account Status card
|
|
16
|
+
* .byline-account-drawer — drawer responsive width override
|
|
17
|
+
* .byline-account-drawer-body — drawer content container
|
|
18
|
+
* .byline-account-drawer-scroll — drawer scrollable inner viewport
|
|
19
|
+
* .byline-account-drawer-skip — sr-only "no action" focus shim
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
.grid,
|
|
23
|
+
:global(.byline-account-grid) {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: var(--spacing-16);
|
|
27
|
+
margin-bottom: var(--spacing-48);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (min-width: 40rem) {
|
|
31
|
+
.grid,
|
|
32
|
+
:global(.byline-account-grid) {
|
|
33
|
+
display: grid;
|
|
34
|
+
grid-template-columns: 1fr 1fr;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.column,
|
|
39
|
+
:global(.byline-account-column) {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: var(--spacing-16);
|
|
43
|
+
margin-bottom: var(--spacing-16);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.section,
|
|
47
|
+
:global(.byline-account-section) {
|
|
48
|
+
padding: var(--spacing-16);
|
|
49
|
+
border: var(--border-width-thin) var(--border-style-solid) var(--gray-100);
|
|
50
|
+
background-color: var(--canvas-25);
|
|
51
|
+
border-radius: var(--border-radius-sm);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.section-head,
|
|
55
|
+
:global(.byline-account-section-head) {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
margin-bottom: var(--spacing-8);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.line,
|
|
63
|
+
:global(.byline-account-line) {
|
|
64
|
+
margin-bottom: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.cta-line,
|
|
68
|
+
:global(.byline-account-cta-line) {
|
|
69
|
+
margin-bottom: var(--spacing-12);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.meta,
|
|
73
|
+
:global(.byline-account-meta) {
|
|
74
|
+
margin-top: var(--spacing-16);
|
|
75
|
+
font-size: var(--font-size-xs);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.status-on,
|
|
79
|
+
:global(.byline-account-status-on) {
|
|
80
|
+
color: var(--green-600);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.status-off,
|
|
84
|
+
:global(.byline-account-status-off) {
|
|
85
|
+
color: var(--red-600);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.status-help,
|
|
89
|
+
:global(.byline-account-status-help) {
|
|
90
|
+
margin-top: var(--spacing-12);
|
|
91
|
+
margin-bottom: 0;
|
|
92
|
+
font-size: var(--font-size-xs);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Italic helper for "Not set" placeholders. Replaces the Tailwind
|
|
97
|
+
* `italic` utility we used pre-lift; with the component now in a
|
|
98
|
+
* published package, Tailwind's source scanner doesn't see the class
|
|
99
|
+
* string in the host so the rule was never generated.
|
|
100
|
+
*/
|
|
101
|
+
.not-set,
|
|
102
|
+
:global(.byline-account-not-set) {
|
|
103
|
+
font-style: italic;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.drawer,
|
|
107
|
+
:global(.byline-account-drawer) {
|
|
108
|
+
/* Width handled by uikit Drawer prop; no additional class needed today. */
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (min-width: 48rem) {
|
|
112
|
+
.drawer,
|
|
113
|
+
:global(.byline-account-drawer) {
|
|
114
|
+
width: 500px;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.drawer-body,
|
|
119
|
+
:global(.byline-account-drawer-body) {
|
|
120
|
+
padding: var(--spacing-8);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.drawer-scroll,
|
|
124
|
+
:global(.byline-account-drawer-scroll) {
|
|
125
|
+
max-height: calc(100vh - 160px);
|
|
126
|
+
overflow-y: auto;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.drawer-skip,
|
|
130
|
+
:global(.byline-account-drawer-skip) {
|
|
131
|
+
position: absolute;
|
|
132
|
+
width: 1px;
|
|
133
|
+
height: 1px;
|
|
134
|
+
padding: 0;
|
|
135
|
+
margin: -1px;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
clip: rect(0, 0, 0, 0);
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
border: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
143
|
+
.section,
|
|
144
|
+
:global(.byline-account-section) {
|
|
145
|
+
border-color: var(--gray-700);
|
|
146
|
+
background-color: var(--canvas-800);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.status-on,
|
|
150
|
+
:global(.byline-account-status-on) {
|
|
151
|
+
color: var(--green-400);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.status-off,
|
|
155
|
+
:global(.byline-account-status-off) {
|
|
156
|
+
color: var(--red-400);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
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
|
+
* Self-service account container.
|
|
13
|
+
*
|
|
14
|
+
* Same drawer pattern as `admin-users/ui/container.tsx` but
|
|
15
|
+
* narrower: only Profile and Password sections (no Roles, no
|
|
16
|
+
* Delete) — those are admin-only actions on someone else, not
|
|
17
|
+
* self-service. Each card surfaces the read-only summary plus an
|
|
18
|
+
* "Edit" affordance that opens the matching drawer.
|
|
19
|
+
*
|
|
20
|
+
* Forms lift the fresh `AccountResponse` back into local state on
|
|
21
|
+
* success so the container's bumped `vid` is in hand for any
|
|
22
|
+
* subsequent edit without a refetch.
|
|
23
|
+
*
|
|
24
|
+
* Stable override handles: see `container.module.css`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type React from 'react'
|
|
28
|
+
import { useState } from 'react'
|
|
29
|
+
|
|
30
|
+
import { Button, CloseIcon, Drawer, EditIcon, IconButton, LocalDateTime } from '@byline/ui/react'
|
|
31
|
+
import cx from 'classnames'
|
|
32
|
+
|
|
33
|
+
import { ChangeAccountPassword } from './change-password.js'
|
|
34
|
+
import styles from './container.module.css'
|
|
35
|
+
import { UpdateAccount } from './update.js'
|
|
36
|
+
import type { AccountResponse } from '../index.js'
|
|
37
|
+
|
|
38
|
+
type ComponentKey = 'update' | 'change_password' | 'empty'
|
|
39
|
+
|
|
40
|
+
interface PanelProps {
|
|
41
|
+
account: AccountResponse
|
|
42
|
+
onClose?: () => void
|
|
43
|
+
onSuccess?: (account: AccountResponse) => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const panels: Record<ComponentKey, { title: string; component: React.ComponentType<PanelProps> }> =
|
|
47
|
+
{
|
|
48
|
+
update: { title: 'Profile', component: UpdateAccount },
|
|
49
|
+
change_password: { title: 'Change Password', component: ChangeAccountPassword },
|
|
50
|
+
empty: { title: '', component: () => null },
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ContainerSection({
|
|
54
|
+
title,
|
|
55
|
+
onEdit,
|
|
56
|
+
children,
|
|
57
|
+
}: {
|
|
58
|
+
title: string
|
|
59
|
+
onEdit?: () => void
|
|
60
|
+
children: React.ReactNode
|
|
61
|
+
}) {
|
|
62
|
+
return (
|
|
63
|
+
<div className={cx('byline-account-section', styles.section)}>
|
|
64
|
+
<div className={cx('byline-account-section-head', styles['section-head'])}>
|
|
65
|
+
<h2>{title}</h2>
|
|
66
|
+
{onEdit ? (
|
|
67
|
+
<IconButton variant="text" onClick={onEdit} aria-label={`Edit ${title}`}>
|
|
68
|
+
<EditIcon width="20px" height="20px" />
|
|
69
|
+
</IconButton>
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
<div>{children}</div>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface AccountSelfContainerProps {
|
|
78
|
+
account: AccountResponse
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function AccountSelfContainer({ account }: AccountSelfContainerProps) {
|
|
82
|
+
const [currentAccount, setCurrentAccount] = useState<AccountResponse>(account)
|
|
83
|
+
const [current, setCurrent] = useState<ComponentKey>('empty')
|
|
84
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
|
85
|
+
|
|
86
|
+
const openDrawer = (key: ComponentKey) => () => {
|
|
87
|
+
setCurrent(key)
|
|
88
|
+
setIsDrawerOpen(true)
|
|
89
|
+
}
|
|
90
|
+
const closeDrawer = () => {
|
|
91
|
+
setCurrent('empty')
|
|
92
|
+
setIsDrawerOpen(false)
|
|
93
|
+
}
|
|
94
|
+
const handleSuccess = (updated: AccountResponse) => {
|
|
95
|
+
setCurrentAccount(updated)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const Panel = panels[current].component
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<div className={cx('byline-account-grid', styles.grid)}>
|
|
103
|
+
<div className={cx('byline-account-column', styles.column)}>
|
|
104
|
+
<ContainerSection title="Profile" onEdit={openDrawer('update')}>
|
|
105
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
106
|
+
<span className="muted">Email:</span> {currentAccount.email}
|
|
107
|
+
</p>
|
|
108
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
109
|
+
<span className="muted">Given name:</span>{' '}
|
|
110
|
+
{currentAccount.given_name ?? (
|
|
111
|
+
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
112
|
+
Not set
|
|
113
|
+
</span>
|
|
114
|
+
)}
|
|
115
|
+
</p>
|
|
116
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
117
|
+
<span className="muted">Family name:</span>{' '}
|
|
118
|
+
{currentAccount.family_name ?? (
|
|
119
|
+
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
120
|
+
Not set
|
|
121
|
+
</span>
|
|
122
|
+
)}
|
|
123
|
+
</p>
|
|
124
|
+
<p className={cx('byline-account-cta-line', styles['cta-line'])}>
|
|
125
|
+
<span className="muted">Username:</span>{' '}
|
|
126
|
+
{currentAccount.username ?? (
|
|
127
|
+
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
128
|
+
Not set
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
131
|
+
</p>
|
|
132
|
+
<Button size="sm" onClick={openDrawer('update')}>
|
|
133
|
+
Edit Profile
|
|
134
|
+
</Button>
|
|
135
|
+
<div className={cx('muted', 'byline-account-meta', styles.meta)}>
|
|
136
|
+
<p>
|
|
137
|
+
<span className="font-bold">Created: </span>
|
|
138
|
+
<LocalDateTime value={currentAccount.created_at} />
|
|
139
|
+
</p>
|
|
140
|
+
<p>
|
|
141
|
+
<span className="font-bold">Updated: </span>
|
|
142
|
+
<LocalDateTime value={currentAccount.updated_at} />
|
|
143
|
+
</p>
|
|
144
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
145
|
+
<span className="font-bold">Last login: </span>
|
|
146
|
+
<LocalDateTime value={currentAccount.last_login} fallback="Never" />
|
|
147
|
+
</p>
|
|
148
|
+
</div>
|
|
149
|
+
</ContainerSection>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div className={cx('byline-account-column', styles.column)}>
|
|
153
|
+
<ContainerSection title="Password" onEdit={openDrawer('change_password')}>
|
|
154
|
+
<p className={cx('byline-account-cta-line', styles['cta-line'])}>
|
|
155
|
+
Change the password used to sign in to the admin. You'll need to enter your current
|
|
156
|
+
password to confirm the change.
|
|
157
|
+
</p>
|
|
158
|
+
<Button size="sm" onClick={openDrawer('change_password')}>
|
|
159
|
+
Change Password
|
|
160
|
+
</Button>
|
|
161
|
+
</ContainerSection>
|
|
162
|
+
|
|
163
|
+
<ContainerSection title="Account Status">
|
|
164
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
165
|
+
<span className="muted">Super admin:</span>{' '}
|
|
166
|
+
{currentAccount.is_super_admin ? 'Yes' : 'No'}
|
|
167
|
+
</p>
|
|
168
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
169
|
+
<span className="muted">Email verified:</span>{' '}
|
|
170
|
+
{currentAccount.is_email_verified ? 'Yes' : 'No'}
|
|
171
|
+
</p>
|
|
172
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
173
|
+
<span className="muted">Status:</span>{' '}
|
|
174
|
+
<span
|
|
175
|
+
className={
|
|
176
|
+
currentAccount.is_enabled
|
|
177
|
+
? cx('byline-account-status-on', styles['status-on'])
|
|
178
|
+
: cx('byline-account-status-off', styles['status-off'])
|
|
179
|
+
}
|
|
180
|
+
>
|
|
181
|
+
{currentAccount.is_enabled ? 'Enabled' : 'Disabled'}
|
|
182
|
+
</span>
|
|
183
|
+
</p>
|
|
184
|
+
<p className={cx('muted', 'byline-account-status-help', styles['status-help'])}>
|
|
185
|
+
These flags are managed by an admin with the appropriate permissions and are not
|
|
186
|
+
self-editable.
|
|
187
|
+
</p>
|
|
188
|
+
</ContainerSection>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<Drawer
|
|
193
|
+
id="admin-account-drawer"
|
|
194
|
+
closeOnOverlayClick={false}
|
|
195
|
+
width="medium"
|
|
196
|
+
topOffset="46px"
|
|
197
|
+
isOpen={isDrawerOpen}
|
|
198
|
+
onDismiss={closeDrawer}
|
|
199
|
+
className={cx('byline-account-drawer', styles.drawer)}
|
|
200
|
+
>
|
|
201
|
+
<Drawer.Container
|
|
202
|
+
aria-hidden={!isDrawerOpen}
|
|
203
|
+
className={cx('byline-account-drawer-body', styles['drawer-body'])}
|
|
204
|
+
>
|
|
205
|
+
<Drawer.TopActions>
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
tabIndex={0}
|
|
209
|
+
className={cx('byline-account-drawer-skip', styles['drawer-skip'])}
|
|
210
|
+
>
|
|
211
|
+
no action
|
|
212
|
+
</button>
|
|
213
|
+
<IconButton aria-label="Close" size="sm" onClick={closeDrawer}>
|
|
214
|
+
<CloseIcon width="14px" height="14px" svgClassName="white-icon stroke-white" />
|
|
215
|
+
</IconButton>
|
|
216
|
+
</Drawer.TopActions>
|
|
217
|
+
<Drawer.Header>
|
|
218
|
+
<h2>{panels[current].title}</h2>
|
|
219
|
+
</Drawer.Header>
|
|
220
|
+
<Drawer.Content>
|
|
221
|
+
<div className={cx('byline-account-drawer-scroll', styles['drawer-scroll'])}>
|
|
222
|
+
<Panel account={currentAccount} onClose={closeDrawer} onSuccess={handleSuccess} />
|
|
223
|
+
</div>
|
|
224
|
+
</Drawer.Content>
|
|
225
|
+
</Drawer.Container>
|
|
226
|
+
</Drawer>
|
|
227
|
+
</>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateAccount — self-service profile form (drawer body).
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-account-update-wrap — outer container
|
|
6
|
+
* .byline-account-update-form — vertical-stack form element
|
|
7
|
+
* .byline-account-update-actions — Cancel/Save row
|
|
8
|
+
* .byline-account-update-action — buttons in the actions row
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.wrap,
|
|
12
|
+
:global(.byline-account-update-wrap) {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--spacing-8);
|
|
16
|
+
padding: var(--spacing-4);
|
|
17
|
+
margin-top: var(--spacing-4);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.form,
|
|
21
|
+
:global(.byline-account-update-form) {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: var(--spacing-16);
|
|
25
|
+
padding-top: var(--spacing-8);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.actions,
|
|
29
|
+
:global(.byline-account-update-actions) {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: flex-end;
|
|
33
|
+
gap: var(--spacing-8);
|
|
34
|
+
margin-top: var(--spacing-16);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.action,
|
|
38
|
+
:global(.byline-account-update-action) {
|
|
39
|
+
min-width: 4rem;
|
|
40
|
+
}
|