@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,328 +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
|
-
* Account details drawer form.
|
|
13
|
-
*
|
|
14
|
-
* Client-side validation runs through TanStack Form's `onDynamic` +
|
|
15
|
-
* Zod — same rules the server uses, re-declared here so field errors
|
|
16
|
-
* show up without a network round-trip. On submit the form diffs
|
|
17
|
-
* against the original row and sends only the *changed* fields as a
|
|
18
|
-
* patch, plus the `vid` the form was opened with, so a concurrent edit
|
|
19
|
-
* elsewhere comes back as `admin.users.versionConflict` and we surface
|
|
20
|
-
* a reload prompt.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { useState } from 'react'
|
|
24
|
-
import { revalidateLogic, useForm } from '@tanstack/react-form-start'
|
|
25
|
-
|
|
26
|
-
import type { AdminUserResponse } from '@byline/admin/admin-users'
|
|
27
|
-
import cx from 'classnames'
|
|
28
|
-
import { z } from 'zod'
|
|
29
|
-
|
|
30
|
-
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
31
|
-
import { Alert, Button, Checkbox, Input, LoaderEllipsis } from '../../../uikit.js'
|
|
32
|
-
import styles from './update.module.css'
|
|
33
|
-
|
|
34
|
-
const updateUserSchema = z.object({
|
|
35
|
-
given_name: z.string().max(100, 'Given name must not exceed 100 characters'),
|
|
36
|
-
family_name: z.string().max(100, 'Family name must not exceed 100 characters'),
|
|
37
|
-
username: z.string().max(100, 'Username must not exceed 100 characters'),
|
|
38
|
-
email: z
|
|
39
|
-
.email({ message: 'Enter a valid email address' })
|
|
40
|
-
.min(3)
|
|
41
|
-
.max(254, 'Email must not exceed 254 characters'),
|
|
42
|
-
is_super_admin: z.boolean(),
|
|
43
|
-
is_enabled: z.boolean(),
|
|
44
|
-
is_email_verified: z.boolean(),
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
type UpdateUserValues = z.infer<typeof updateUserSchema>
|
|
48
|
-
|
|
49
|
-
function defaultsFrom(user: AdminUserResponse): UpdateUserValues {
|
|
50
|
-
return {
|
|
51
|
-
given_name: user.given_name ?? '',
|
|
52
|
-
family_name: user.family_name ?? '',
|
|
53
|
-
username: user.username ?? '',
|
|
54
|
-
email: user.email,
|
|
55
|
-
is_super_admin: user.is_super_admin,
|
|
56
|
-
is_enabled: user.is_enabled,
|
|
57
|
-
is_email_verified: user.is_email_verified,
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Build a patch object containing only fields whose values differ from the original user row. */
|
|
62
|
-
function buildPatch(values: UpdateUserValues, user: AdminUserResponse) {
|
|
63
|
-
const patch: {
|
|
64
|
-
given_name?: string | null
|
|
65
|
-
family_name?: string | null
|
|
66
|
-
username?: string | null
|
|
67
|
-
email?: string
|
|
68
|
-
is_super_admin?: boolean
|
|
69
|
-
is_enabled?: boolean
|
|
70
|
-
is_email_verified?: boolean
|
|
71
|
-
} = {}
|
|
72
|
-
// Text fields: treat empty string as null (clear). null === null matches,
|
|
73
|
-
// '' → null ≠ current null stays consistent.
|
|
74
|
-
const normaliseText = (value: string): string | null => (value.trim().length > 0 ? value : null)
|
|
75
|
-
const nextGiven = normaliseText(values.given_name)
|
|
76
|
-
const nextFamily = normaliseText(values.family_name)
|
|
77
|
-
const nextUsername = normaliseText(values.username)
|
|
78
|
-
|
|
79
|
-
if (nextGiven !== user.given_name) patch.given_name = nextGiven
|
|
80
|
-
if (nextFamily !== user.family_name) patch.family_name = nextFamily
|
|
81
|
-
if (nextUsername !== user.username) patch.username = nextUsername
|
|
82
|
-
if (values.email !== user.email) patch.email = values.email
|
|
83
|
-
if (values.is_super_admin !== user.is_super_admin) patch.is_super_admin = values.is_super_admin
|
|
84
|
-
if (values.is_enabled !== user.is_enabled) patch.is_enabled = values.is_enabled
|
|
85
|
-
if (values.is_email_verified !== user.is_email_verified)
|
|
86
|
-
patch.is_email_verified = values.is_email_verified
|
|
87
|
-
return patch
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
interface UpdateUserProps {
|
|
91
|
-
user: AdminUserResponse
|
|
92
|
-
onClose?: () => void
|
|
93
|
-
onSuccess?: (user: AdminUserResponse) => void
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
97
|
-
const { updateAdminUser } = useBylineAdminServices()
|
|
98
|
-
const [formError, setFormError] = useState<string | null>(null)
|
|
99
|
-
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
100
|
-
|
|
101
|
-
const form = useForm({
|
|
102
|
-
defaultValues: defaultsFrom(user),
|
|
103
|
-
validationLogic: revalidateLogic({
|
|
104
|
-
mode: 'blur',
|
|
105
|
-
modeAfterSubmission: 'change',
|
|
106
|
-
}),
|
|
107
|
-
validators: {
|
|
108
|
-
onDynamic: updateUserSchema,
|
|
109
|
-
},
|
|
110
|
-
onSubmit: async ({ value }) => {
|
|
111
|
-
setFormError(null)
|
|
112
|
-
setSuccessMessage(null)
|
|
113
|
-
const patch = buildPatch(value, user)
|
|
114
|
-
if (Object.keys(patch).length === 0) {
|
|
115
|
-
setSuccessMessage('No changes to save.')
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const updated = await updateAdminUser({
|
|
121
|
-
data: { id: user.id, vid: user.vid, patch },
|
|
122
|
-
})
|
|
123
|
-
setSuccessMessage('Saved.')
|
|
124
|
-
onSuccess?.(updated)
|
|
125
|
-
} catch (err) {
|
|
126
|
-
const code = getErrorCode(err)
|
|
127
|
-
if (code === 'admin.users.emailInUse') {
|
|
128
|
-
// Surface on the email field directly.
|
|
129
|
-
form.setFieldMeta('email', (meta) => ({
|
|
130
|
-
...meta,
|
|
131
|
-
errorMap: { ...meta.errorMap, onServer: 'This email is already in use.' },
|
|
132
|
-
errors: ['This email is already in use.'],
|
|
133
|
-
}))
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
if (code === 'admin.users.versionConflict') {
|
|
137
|
-
setFormError(
|
|
138
|
-
'This user has been modified elsewhere since you opened this form. Reload to get the latest values and try again.'
|
|
139
|
-
)
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
if (code === 'admin.users.notFound') {
|
|
143
|
-
setFormError('This user no longer exists.')
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
setFormError('Could not save changes. Please try again.')
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<div className={cx('byline-user-update-wrap', styles.wrap)}>
|
|
153
|
-
<form
|
|
154
|
-
noValidate
|
|
155
|
-
onSubmit={(event) => {
|
|
156
|
-
event.preventDefault()
|
|
157
|
-
event.stopPropagation()
|
|
158
|
-
void form.handleSubmit()
|
|
159
|
-
}}
|
|
160
|
-
className={cx('byline-user-update-form', styles.form)}
|
|
161
|
-
>
|
|
162
|
-
{formError ? <Alert intent="danger">{formError}</Alert> : null}
|
|
163
|
-
{successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
|
|
164
|
-
|
|
165
|
-
<form.Field name="given_name">
|
|
166
|
-
{(field) => (
|
|
167
|
-
<Input
|
|
168
|
-
label="Given name"
|
|
169
|
-
id="given_name"
|
|
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="given-name"
|
|
177
|
-
/>
|
|
178
|
-
)}
|
|
179
|
-
</form.Field>
|
|
180
|
-
|
|
181
|
-
<form.Field name="family_name">
|
|
182
|
-
{(field) => (
|
|
183
|
-
<Input
|
|
184
|
-
label="Family name"
|
|
185
|
-
id="family_name"
|
|
186
|
-
name={field.name}
|
|
187
|
-
value={field.state.value}
|
|
188
|
-
onBlur={field.handleBlur}
|
|
189
|
-
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
190
|
-
error={field.state.meta.errors.length > 0}
|
|
191
|
-
errorText={firstError(field.state.meta.errors)}
|
|
192
|
-
autoComplete="family-name"
|
|
193
|
-
/>
|
|
194
|
-
)}
|
|
195
|
-
</form.Field>
|
|
196
|
-
|
|
197
|
-
<form.Field name="username">
|
|
198
|
-
{(field) => (
|
|
199
|
-
<Input
|
|
200
|
-
label="Username"
|
|
201
|
-
id="username"
|
|
202
|
-
name={field.name}
|
|
203
|
-
value={field.state.value}
|
|
204
|
-
onBlur={field.handleBlur}
|
|
205
|
-
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
206
|
-
error={field.state.meta.errors.length > 0}
|
|
207
|
-
errorText={firstError(field.state.meta.errors)}
|
|
208
|
-
helpText="Optional. Leave blank to clear."
|
|
209
|
-
autoComplete="username"
|
|
210
|
-
/>
|
|
211
|
-
)}
|
|
212
|
-
</form.Field>
|
|
213
|
-
|
|
214
|
-
<form.Field name="email">
|
|
215
|
-
{(field) => (
|
|
216
|
-
<Input
|
|
217
|
-
label="Email"
|
|
218
|
-
id="email"
|
|
219
|
-
name={field.name}
|
|
220
|
-
type="email"
|
|
221
|
-
value={field.state.value}
|
|
222
|
-
onBlur={field.handleBlur}
|
|
223
|
-
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
224
|
-
error={field.state.meta.errors.length > 0}
|
|
225
|
-
errorText={firstError(field.state.meta.errors)}
|
|
226
|
-
autoComplete="email"
|
|
227
|
-
required
|
|
228
|
-
/>
|
|
229
|
-
)}
|
|
230
|
-
</form.Field>
|
|
231
|
-
|
|
232
|
-
<div className={cx('byline-user-update-flags', styles.flags)}>
|
|
233
|
-
<form.Field name="is_enabled">
|
|
234
|
-
{(field) => (
|
|
235
|
-
<Checkbox
|
|
236
|
-
id="is_enabled"
|
|
237
|
-
name={field.name}
|
|
238
|
-
label="Enabled"
|
|
239
|
-
checked={field.state.value}
|
|
240
|
-
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
241
|
-
helpText="Disabled accounts cannot sign in."
|
|
242
|
-
/>
|
|
243
|
-
)}
|
|
244
|
-
</form.Field>
|
|
245
|
-
|
|
246
|
-
<form.Field name="is_email_verified">
|
|
247
|
-
{(field) => (
|
|
248
|
-
<Checkbox
|
|
249
|
-
id="is_email_verified"
|
|
250
|
-
name={field.name}
|
|
251
|
-
label="Email verified"
|
|
252
|
-
checked={field.state.value}
|
|
253
|
-
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
254
|
-
/>
|
|
255
|
-
)}
|
|
256
|
-
</form.Field>
|
|
257
|
-
|
|
258
|
-
<form.Field name="is_super_admin">
|
|
259
|
-
{(field) => (
|
|
260
|
-
<Checkbox
|
|
261
|
-
id="is_super_admin"
|
|
262
|
-
name={field.name}
|
|
263
|
-
label="Super admin"
|
|
264
|
-
checked={field.state.value}
|
|
265
|
-
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
266
|
-
helpText="Super admins bypass every ability check — grant with care."
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
</form.Field>
|
|
270
|
-
</div>
|
|
271
|
-
|
|
272
|
-
<div className={cx('byline-user-update-actions', styles.actions)}>
|
|
273
|
-
<Button
|
|
274
|
-
type="button"
|
|
275
|
-
intent="secondary"
|
|
276
|
-
size="sm"
|
|
277
|
-
onClick={onClose}
|
|
278
|
-
className={cx('byline-user-update-action', styles.action)}
|
|
279
|
-
>
|
|
280
|
-
{successMessage ? 'Close' : 'Cancel'}
|
|
281
|
-
</Button>
|
|
282
|
-
<form.Subscribe
|
|
283
|
-
selector={(state) => ({
|
|
284
|
-
canSubmit: state.canSubmit,
|
|
285
|
-
isSubmitting: state.isSubmitting,
|
|
286
|
-
isDirty: state.isDirty,
|
|
287
|
-
})}
|
|
288
|
-
>
|
|
289
|
-
{({ canSubmit, isSubmitting }) => (
|
|
290
|
-
<Button
|
|
291
|
-
size="sm"
|
|
292
|
-
intent="primary"
|
|
293
|
-
type="submit"
|
|
294
|
-
disabled={!canSubmit || isSubmitting}
|
|
295
|
-
className={cx('byline-user-update-action', styles.action)}
|
|
296
|
-
>
|
|
297
|
-
{isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
|
|
298
|
-
</Button>
|
|
299
|
-
)}
|
|
300
|
-
</form.Subscribe>
|
|
301
|
-
</div>
|
|
302
|
-
</form>
|
|
303
|
-
</div>
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function firstError(errors: readonly unknown[]): string | undefined {
|
|
308
|
-
for (const err of errors) {
|
|
309
|
-
if (typeof err === 'string') return err
|
|
310
|
-
if (err && typeof err === 'object' && 'message' in err) {
|
|
311
|
-
const msg = (err as { message?: unknown }).message
|
|
312
|
-
if (typeof msg === 'string') return msg
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return undefined
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Extract the admin-users error code from a thrown server-fn response.
|
|
320
|
-
* Typed errors (`AdminUsersError`, `AuthError`) survive the server-fn
|
|
321
|
-
* boundary with their `code` intact thanks to the `BylineCodedError`
|
|
322
|
-
* serialization adapter registered in `src/start.ts`.
|
|
323
|
-
*/
|
|
324
|
-
function getErrorCode(err: unknown): string | null {
|
|
325
|
-
return typeof (err as { code?: unknown })?.code === 'string'
|
|
326
|
-
? (err as { code: string }).code
|
|
327
|
-
: null
|
|
328
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SignInForm — admin sign-in card form.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-sign-in-card — outer Card (responsive width)
|
|
6
|
-
* .byline-sign-in-alert — error alert spacing
|
|
7
|
-
* .byline-sign-in-form — the form element
|
|
8
|
-
* .byline-sign-in-fields — vertical stack of inputs
|
|
9
|
-
* .byline-sign-in-actions — action row (optional Home link + submit button)
|
|
10
|
-
* .byline-sign-in-home-link — left-side "Home" link (rendered when homeUrl is set)
|
|
11
|
-
* .byline-sign-in-button — the submit button (min-width)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
.card,
|
|
15
|
-
:global(.byline-sign-in-card) {
|
|
16
|
-
width: 100%;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@media (min-width: 40rem) {
|
|
20
|
-
.card,
|
|
21
|
-
:global(.byline-sign-in-card) {
|
|
22
|
-
max-width: 380px;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.alert,
|
|
27
|
-
:global(.byline-sign-in-alert) {
|
|
28
|
-
margin-top: var(--spacing-12);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.form,
|
|
32
|
-
:global(.byline-sign-in-form) {
|
|
33
|
-
padding-top: var(--spacing-8);
|
|
34
|
-
margin-bottom: var(--spacing-8);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.fields,
|
|
38
|
-
:global(.byline-sign-in-fields) {
|
|
39
|
-
display: flex;
|
|
40
|
-
flex-direction: column;
|
|
41
|
-
gap: var(--spacing-16);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.actions,
|
|
45
|
-
:global(.byline-sign-in-actions) {
|
|
46
|
-
display: flex;
|
|
47
|
-
align-items: center;
|
|
48
|
-
margin-top: var(--spacing-24);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.home-link,
|
|
52
|
-
:global(.byline-sign-in-home-link) {
|
|
53
|
-
font-size: 0.9rem;
|
|
54
|
-
text-decoration: underline;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.button,
|
|
58
|
-
:global(.byline-sign-in-button) {
|
|
59
|
-
min-width: 5rem;
|
|
60
|
-
/* Always floats right whether or not a Home link is present on the left. */
|
|
61
|
-
margin-left: auto;
|
|
62
|
-
}
|
|
@@ -1,132 +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
|
-
* Admin sign-in form.
|
|
13
|
-
*
|
|
14
|
-
* Client component — collects email + password, calls the `adminSignIn`
|
|
15
|
-
* server fn, and on success navigates to the caller-supplied
|
|
16
|
-
* `callbackUrl` (or `/admin`). On failure renders a generic "Invalid
|
|
17
|
-
* credentials" alert; the provider equalises timing between
|
|
18
|
-
* unknown-email and wrong-password so the UI doesn't distinguish the two.
|
|
19
|
-
*
|
|
20
|
-
* Stable override handles: `.byline-sign-in-card`, `.byline-sign-in-alert`,
|
|
21
|
-
* `.byline-sign-in-form`, `.byline-sign-in-fields`,
|
|
22
|
-
* `.byline-sign-in-actions`, `.byline-sign-in-button`,
|
|
23
|
-
* `.byline-sign-in-home-link`.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { type FormEvent, useState } from 'react'
|
|
27
|
-
|
|
28
|
-
import cx from 'classnames'
|
|
29
|
-
|
|
30
|
-
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
31
|
-
import { Alert, Button, Card, Input, LoaderEllipsis } from '../../../uikit.js'
|
|
32
|
-
import styles from './sign-in-form.module.css'
|
|
33
|
-
|
|
34
|
-
interface SignInFormProps {
|
|
35
|
-
/** Destination after successful sign-in. Defaults to `/admin`. */
|
|
36
|
-
callbackUrl?: string
|
|
37
|
-
/**
|
|
38
|
-
* Optional plain "Home" link rendered on the left of the action row.
|
|
39
|
-
* Typically the host's configured `serverURL` so signed-out admins can
|
|
40
|
-
* navigate back to the public site without typing the URL.
|
|
41
|
-
*/
|
|
42
|
-
homeUrl?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
46
|
-
const { adminSignIn } = useBylineAdminServices()
|
|
47
|
-
const [email, setEmail] = useState('')
|
|
48
|
-
const [password, setPassword] = useState('')
|
|
49
|
-
const [pending, setPending] = useState(false)
|
|
50
|
-
const [error, setError] = useState<string | null>(null)
|
|
51
|
-
|
|
52
|
-
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
53
|
-
event.preventDefault()
|
|
54
|
-
if (pending) return
|
|
55
|
-
if (email.trim().length === 0 || password.length === 0) {
|
|
56
|
-
setError('Enter your email and password.')
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
setPending(true)
|
|
61
|
-
setError(null)
|
|
62
|
-
try {
|
|
63
|
-
await adminSignIn({ data: { email: email.trim(), password } })
|
|
64
|
-
const target = callbackUrl && callbackUrl.length > 0 ? callbackUrl : '/admin'
|
|
65
|
-
// Full-page navigation — the admin layout needs to re-run its
|
|
66
|
-
// `beforeLoad` guard against the freshly-set session cookies.
|
|
67
|
-
window.location.assign(target)
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.warn('sign-in failed', err)
|
|
70
|
-
setError('Invalid credentials.')
|
|
71
|
-
setPending(false)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<Card className={cx('byline-sign-in-card', styles.card)}>
|
|
77
|
-
<Card.Header>
|
|
78
|
-
<Card.Title>
|
|
79
|
-
<h2>Sign in</h2>
|
|
80
|
-
</Card.Title>
|
|
81
|
-
<Card.Description>Sign in to the Byline admin.</Card.Description>
|
|
82
|
-
{error && (
|
|
83
|
-
<Alert intent="danger" className={cx('byline-sign-in-alert', styles.alert)}>
|
|
84
|
-
{error}
|
|
85
|
-
</Alert>
|
|
86
|
-
)}
|
|
87
|
-
</Card.Header>
|
|
88
|
-
<Card.Content>
|
|
89
|
-
<form onSubmit={handleSubmit} noValidate className={cx('byline-sign-in-form', styles.form)}>
|
|
90
|
-
<div className={cx('byline-sign-in-fields', styles.fields)}>
|
|
91
|
-
<Input
|
|
92
|
-
label="Email"
|
|
93
|
-
id="email"
|
|
94
|
-
name="email"
|
|
95
|
-
type="email"
|
|
96
|
-
autoComplete="email"
|
|
97
|
-
required
|
|
98
|
-
value={email}
|
|
99
|
-
onChange={(event) => setEmail(event.currentTarget.value)}
|
|
100
|
-
disabled={pending}
|
|
101
|
-
/>
|
|
102
|
-
<Input
|
|
103
|
-
label="Password"
|
|
104
|
-
id="password"
|
|
105
|
-
name="password"
|
|
106
|
-
type="password"
|
|
107
|
-
autoComplete="current-password"
|
|
108
|
-
required
|
|
109
|
-
value={password}
|
|
110
|
-
onChange={(event) => setPassword(event.currentTarget.value)}
|
|
111
|
-
disabled={pending}
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
<div className={cx('byline-sign-in-actions', styles.actions)}>
|
|
115
|
-
{homeUrl && (
|
|
116
|
-
<a href={homeUrl} className={cx('byline-sign-in-home-link', styles['home-link'])}>
|
|
117
|
-
Home
|
|
118
|
-
</a>
|
|
119
|
-
)}
|
|
120
|
-
<Button
|
|
121
|
-
type="submit"
|
|
122
|
-
disabled={pending}
|
|
123
|
-
className={cx('byline-sign-in-button', styles.button)}
|
|
124
|
-
>
|
|
125
|
-
{pending ? <LoaderEllipsis size={30} color="#aaaaaa" /> : <span>Sign In</span>}
|
|
126
|
-
</Button>
|
|
127
|
-
</div>
|
|
128
|
-
</form>
|
|
129
|
-
</Card.Content>
|
|
130
|
-
</Card>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
import { createContext, type ReactNode, useContext } from 'react'
|
|
10
|
-
|
|
11
|
-
import type { BylineAdminServices } from './admin-services-types.js'
|
|
12
|
-
|
|
13
|
-
const AdminServicesContext = createContext<BylineAdminServices | null>(null)
|
|
14
|
-
|
|
15
|
-
interface BylineAdminServicesProviderProps {
|
|
16
|
-
services: BylineAdminServices
|
|
17
|
-
children: ReactNode
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const BylineAdminServicesProvider = ({
|
|
21
|
-
services,
|
|
22
|
-
children,
|
|
23
|
-
}: BylineAdminServicesProviderProps) => (
|
|
24
|
-
<AdminServicesContext.Provider value={services}>{children}</AdminServicesContext.Provider>
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
export const useBylineAdminServices = (): BylineAdminServices => {
|
|
28
|
-
const ctx = useContext(AdminServicesContext)
|
|
29
|
-
if (!ctx) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
'@byline/ui: BylineAdminServicesProvider missing. Wrap your admin tree with <BylineAdminServicesProvider services={…} />.'
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
return ctx
|
|
35
|
-
}
|