@byline/admin 2.5.1 → 2.6.0
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/fields/array/array-field.d.ts +14 -0
- package/dist/fields/array/array-field.js +177 -0
- package/dist/fields/array/array-field.module.js +11 -0
- package/dist/fields/array/array-field_module.css +32 -0
- package/dist/fields/blocks/blocks-field.d.ts +13 -0
- package/dist/fields/blocks/blocks-field.js +245 -0
- package/dist/fields/blocks/blocks-field.module.js +26 -0
- package/dist/fields/blocks/blocks-field_module.css +107 -0
- package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
- package/dist/fields/checkbox/checkbox-field.js +28 -0
- package/dist/fields/checkbox/checkbox-field.module.js +6 -0
- package/dist/fields/checkbox/checkbox-field_module.css +4 -0
- package/dist/fields/column-formatter.d.ts +20 -0
- package/dist/fields/column-formatter.js +15 -0
- package/dist/fields/date-time-formatter.d.ts +16 -0
- package/dist/fields/date-time-formatter.js +8 -0
- package/dist/fields/datetime/datetime-field.d.ts +16 -0
- package/dist/fields/datetime/datetime-field.js +37 -0
- package/dist/fields/datetime/datetime-field.module.js +5 -0
- package/dist/fields/datetime/datetime-field_module.css +4 -0
- package/dist/fields/draggable-context-menu.d.ts +6 -0
- package/dist/fields/draggable-context-menu.js +85 -0
- package/dist/fields/draggable-context-menu.module.js +15 -0
- package/dist/fields/draggable-context-menu_module.css +91 -0
- package/dist/fields/field-helpers.d.ts +26 -0
- package/dist/fields/field-helpers.js +50 -0
- package/dist/fields/field-renderer.d.ts +37 -0
- package/dist/fields/field-renderer.js +206 -0
- package/dist/fields/field-renderer.module.js +8 -0
- package/dist/fields/field-renderer_module.css +11 -0
- package/dist/fields/field-services-context.d.ts +16 -0
- package/dist/fields/field-services-context.js +13 -0
- package/dist/fields/field-services-types.d.ts +63 -0
- package/dist/fields/field-services-types.js +1 -0
- package/dist/fields/file/file-field.d.ts +19 -0
- package/dist/fields/file/file-field.js +225 -0
- package/dist/fields/file/file-field.module.js +18 -0
- package/dist/fields/file/file-field_module.css +131 -0
- package/dist/fields/file/file-upload-field.d.ts +21 -0
- package/dist/fields/file/file-upload-field.js +130 -0
- package/dist/fields/file/file-upload-field.module.js +15 -0
- package/dist/fields/file/file-upload-field_module.css +74 -0
- package/dist/fields/group/group-field.d.ts +15 -0
- package/dist/fields/group/group-field.js +59 -0
- package/dist/fields/group/group-field.module.js +9 -0
- package/dist/fields/group/group-field_module.css +27 -0
- package/dist/fields/image/image-field.d.ts +19 -0
- package/dist/fields/image/image-field.js +241 -0
- package/dist/fields/image/image-field.module.js +22 -0
- package/dist/fields/image/image-field_module.css +121 -0
- package/dist/fields/image/image-upload-field.d.ts +21 -0
- package/dist/fields/image/image-upload-field.js +190 -0
- package/dist/fields/image/image-upload-field.module.js +19 -0
- package/dist/fields/image/image-upload-field_module.css +92 -0
- package/dist/fields/local-date-time.d.ts +27 -0
- package/dist/fields/local-date-time.js +49 -0
- package/dist/fields/locale-badge.d.ts +18 -0
- package/dist/fields/locale-badge.js +10 -0
- package/dist/fields/locale-badge.module.js +5 -0
- package/dist/fields/locale-badge_module.css +27 -0
- package/dist/fields/numerical/numerical-field.d.ts +18 -0
- package/dist/fields/numerical/numerical-field.js +74 -0
- package/dist/fields/relation/relation-display.d.ts +40 -0
- package/dist/fields/relation/relation-display.js +58 -0
- package/dist/fields/relation/relation-display.module.js +9 -0
- package/dist/fields/relation/relation-display_module.css +21 -0
- package/dist/fields/relation/relation-field.d.ts +18 -0
- package/dist/fields/relation/relation-field.js +138 -0
- package/dist/fields/relation/relation-field.module.js +13 -0
- package/dist/fields/relation/relation-field_module.css +62 -0
- package/dist/fields/relation/relation-picker.d.ts +49 -0
- package/dist/fields/relation/relation-picker.js +236 -0
- package/dist/fields/relation/relation-picker.module.js +26 -0
- package/dist/fields/relation/relation-picker_module.css +124 -0
- package/dist/fields/relation/relation-summary.d.ts +31 -0
- package/dist/fields/relation/relation-summary.js +50 -0
- package/dist/fields/relation/relation-summary.module.js +11 -0
- package/dist/fields/relation/relation-summary_module.css +37 -0
- package/dist/fields/select/select-field.d.ts +16 -0
- package/dist/fields/select/select-field.js +50 -0
- package/dist/fields/select/select-field.module.js +5 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/sortable-item.d.ts +15 -0
- package/dist/fields/sortable-item.js +81 -0
- package/dist/fields/sortable-item.module.js +22 -0
- package/dist/fields/sortable-item_module.css +124 -0
- package/dist/fields/text/text-field.d.ts +20 -0
- package/dist/fields/text/text-field.js +104 -0
- package/dist/fields/text/text-field.module.js +6 -0
- package/dist/fields/text/text-field_module.css +5 -0
- package/dist/fields/text-area/text-area-field.d.ts +20 -0
- package/dist/fields/text-area/text-area-field.js +105 -0
- package/dist/fields/text-area/text-area-field.module.js +6 -0
- package/dist/fields/text-area/text-area-field_module.css +5 -0
- package/dist/fields/use-field-change-handler.d.ts +23 -0
- package/dist/fields/use-field-change-handler.js +52 -0
- package/dist/forms/document-actions.d.ts +48 -0
- package/dist/forms/document-actions.js +475 -0
- package/dist/forms/document-actions.module.js +34 -0
- package/dist/forms/document-actions_module.css +118 -0
- package/dist/forms/form-context.d.ts +89 -0
- package/dist/forms/form-context.js +466 -0
- package/dist/forms/form-renderer.d.ts +98 -0
- package/dist/forms/form-renderer.js +597 -0
- package/dist/forms/form-renderer.module.js +46 -0
- package/dist/forms/form-renderer_module.css +245 -0
- package/dist/forms/navigation-guard.d.ts +54 -0
- package/dist/forms/navigation-guard.js +22 -0
- package/dist/forms/path-widget.d.ts +36 -0
- package/dist/forms/path-widget.js +116 -0
- package/dist/forms/path-widget.module.js +8 -0
- package/dist/forms/path-widget_module.css +29 -0
- package/dist/forms/upload-executor.d.ts +57 -0
- package/dist/forms/upload-executor.js +94 -0
- package/dist/lib/translate-validation-error.d.ts +36 -0
- package/dist/lib/translate-validation-error.js +11 -0
- package/dist/modules/admin-account/commands.d.ts +2 -1
- package/dist/modules/admin-account/commands.js +13 -2
- package/dist/modules/admin-account/components/change-password.js +45 -36
- package/dist/modules/admin-account/components/container.js +185 -134
- package/dist/modules/admin-account/components/preferences.d.ts +8 -0
- package/dist/modules/admin-account/components/preferences.js +152 -0
- package/dist/modules/admin-account/components/preferences.module.js +11 -0
- package/dist/modules/admin-account/components/preferences_module.css +41 -0
- package/dist/modules/admin-account/components/update.js +50 -31
- package/dist/modules/admin-account/index.d.ts +3 -3
- package/dist/modules/admin-account/index.js +2 -2
- package/dist/modules/admin-account/schemas.d.ts +4 -0
- package/dist/modules/admin-account/schemas.js +4 -1
- package/dist/modules/admin-account/service.d.ts +1 -0
- package/dist/modules/admin-account/service.js +8 -0
- package/dist/modules/admin-permissions/components/inspector.js +31 -41
- package/dist/modules/admin-roles/components/create.js +43 -26
- package/dist/modules/admin-roles/components/permissions.js +26 -35
- package/dist/modules/admin-roles/components/update.js +26 -16
- package/dist/modules/admin-users/components/create.js +60 -40
- package/dist/modules/admin-users/components/roles.js +9 -15
- package/dist/modules/admin-users/components/set-password.js +30 -31
- package/dist/modules/admin-users/components/update.js +58 -39
- package/dist/modules/admin-users/dto.js +1 -0
- package/dist/modules/admin-users/repository.d.ts +17 -0
- package/dist/modules/admin-users/schemas.d.ts +4 -0
- package/dist/modules/admin-users/schemas.js +6 -2
- package/dist/modules/auth/components/sign-in-form.js +10 -8
- package/dist/presentation/group.d.ts +27 -0
- package/dist/presentation/group.js +14 -0
- package/dist/presentation/group.module.js +6 -0
- package/dist/presentation/group_module.css +19 -0
- package/dist/presentation/row.d.ts +25 -0
- package/dist/presentation/row.js +8 -0
- package/dist/presentation/row.module.js +5 -0
- package/dist/presentation/row_module.css +18 -0
- package/dist/presentation/tabs.d.ts +25 -0
- package/dist/presentation/tabs.js +39 -0
- package/dist/presentation/tabs.module.js +10 -0
- package/dist/presentation/tabs_module.css +68 -0
- package/dist/react.d.ts +66 -0
- package/dist/react.js +36 -0
- package/dist/services/admin-services-types.d.ts +16 -0
- package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
- package/dist/widgets/diff-viewer/diff-modal.js +149 -0
- package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
- package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
- package/dist/widgets/status-badge/status-badge.d.ts +25 -0
- package/dist/widgets/status-badge/status-badge.js +37 -0
- package/dist/widgets/status-badge/status-badge.module.js +7 -0
- package/dist/widgets/status-badge/status-badge_module.css +20 -0
- package/package.json +14 -4
- package/src/fields/array/array-field.module.css +48 -0
- package/src/fields/array/array-field.tsx +267 -0
- package/src/fields/blocks/blocks-field.module.css +148 -0
- package/src/fields/blocks/blocks-field.tsx +323 -0
- package/src/fields/checkbox/checkbox-field.module.css +4 -0
- package/src/fields/checkbox/checkbox-field.tsx +54 -0
- package/src/fields/column-formatter.tsx +31 -0
- package/src/fields/date-time-formatter.tsx +22 -0
- package/src/fields/datetime/datetime-field.module.css +13 -0
- package/src/fields/datetime/datetime-field.tsx +54 -0
- package/src/fields/draggable-context-menu.module.css +127 -0
- package/src/fields/draggable-context-menu.tsx +87 -0
- package/src/fields/field-helpers.ts +69 -0
- package/src/fields/field-renderer.module.css +22 -0
- package/src/fields/field-renderer.tsx +288 -0
- package/src/fields/field-services-context.tsx +35 -0
- package/src/fields/field-services-types.ts +68 -0
- package/src/fields/file/file-field.module.css +153 -0
- package/src/fields/file/file-field.tsx +286 -0
- package/src/fields/file/file-upload-field.module.css +101 -0
- package/src/fields/file/file-upload-field.tsx +187 -0
- package/src/fields/group/group-field.module.css +43 -0
- package/src/fields/group/group-field.tsx +84 -0
- package/src/fields/image/image-field.module.css +155 -0
- package/src/fields/image/image-field.tsx +306 -0
- package/src/fields/image/image-upload-field.module.css +123 -0
- package/src/fields/image/image-upload-field.tsx +276 -0
- package/src/fields/local-date-time.tsx +88 -0
- package/src/fields/locale-badge.module.css +37 -0
- package/src/fields/locale-badge.tsx +32 -0
- package/src/fields/numerical/numerical-field.tsx +114 -0
- package/src/fields/relation/relation-display.module.css +36 -0
- package/src/fields/relation/relation-display.tsx +130 -0
- package/src/fields/relation/relation-field.module.css +83 -0
- package/src/fields/relation/relation-field.tsx +211 -0
- package/src/fields/relation/relation-picker.module.css +168 -0
- package/src/fields/relation/relation-picker.tsx +326 -0
- package/src/fields/relation/relation-summary.module.css +55 -0
- package/src/fields/relation/relation-summary.tsx +123 -0
- package/src/fields/select/select-field.module.css +13 -0
- package/src/fields/select/select-field.tsx +61 -0
- package/src/fields/sortable-item.module.css +167 -0
- package/src/fields/sortable-item.tsx +106 -0
- package/src/fields/text/text-field.module.css +13 -0
- package/src/fields/text/text-field.tsx +146 -0
- package/src/fields/text-area/text-area-field.module.css +13 -0
- package/src/fields/text-area/text-area-field.tsx +147 -0
- package/src/fields/use-field-change-handler.ts +112 -0
- package/src/forms/document-actions.module.css +160 -0
- package/src/forms/document-actions.tsx +482 -0
- package/src/forms/form-context.tsx +704 -0
- package/src/forms/form-renderer.module.css +321 -0
- package/src/forms/form-renderer.tsx +891 -0
- package/src/forms/navigation-guard.tsx +98 -0
- package/src/forms/path-widget.module.css +41 -0
- package/src/forms/path-widget.test.tsx +217 -0
- package/src/forms/path-widget.tsx +183 -0
- package/src/forms/upload-executor.ts +192 -0
- package/src/lib/translate-validation-error.ts +56 -0
- package/src/modules/admin-account/commands.ts +13 -0
- package/src/modules/admin-account/components/change-password.tsx +46 -31
- package/src/modules/admin-account/components/container.tsx +83 -38
- package/src/modules/admin-account/components/preferences.module.css +60 -0
- package/src/modules/admin-account/components/preferences.tsx +203 -0
- package/src/modules/admin-account/components/update.tsx +53 -27
- package/src/modules/admin-account/index.ts +3 -0
- package/src/modules/admin-account/schemas.ts +13 -0
- package/src/modules/admin-account/service.ts +12 -0
- package/src/modules/admin-permissions/components/inspector.tsx +22 -14
- package/src/modules/admin-roles/components/create.tsx +51 -23
- package/src/modules/admin-roles/components/permissions.tsx +25 -21
- package/src/modules/admin-roles/components/update.tsx +37 -19
- package/src/modules/admin-users/components/create.tsx +63 -34
- package/src/modules/admin-users/components/roles.tsx +9 -8
- package/src/modules/admin-users/components/set-password.tsx +34 -28
- package/src/modules/admin-users/components/update.tsx +58 -36
- package/src/modules/admin-users/dto.ts +1 -0
- package/src/modules/admin-users/repository.ts +17 -0
- package/src/modules/admin-users/schemas.ts +12 -0
- package/src/modules/auth/components/sign-in-form.tsx +14 -8
- package/src/presentation/group.module.css +41 -0
- package/src/presentation/group.tsx +40 -0
- package/src/presentation/row.module.css +32 -0
- package/src/presentation/row.tsx +33 -0
- package/src/presentation/tabs.module.css +107 -0
- package/src/presentation/tabs.tsx +84 -0
- package/src/react.ts +84 -0
- package/src/services/admin-services-types.ts +18 -0
- package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
- package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
- package/src/widgets/status-badge/status-badge.module.css +31 -0
- package/src/widgets/status-badge/status-badge.tsx +71 -0
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
* a reload prompt.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { useState } from 'react'
|
|
23
|
+
import { useMemo, useState } from 'react'
|
|
24
24
|
import { revalidateLogic, useForm } from '@tanstack/react-form-start'
|
|
25
25
|
|
|
26
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
26
27
|
import { Alert, Button, Checkbox, Input, LoaderEllipsis } from '@byline/ui/react'
|
|
27
28
|
import cx from 'classnames'
|
|
28
29
|
import { z } from 'zod'
|
|
@@ -31,20 +32,19 @@ import { useBylineAdminServices } from '../../../services/admin-services-context
|
|
|
31
32
|
import styles from './update.module.css'
|
|
32
33
|
import type { AdminUserResponse } from '../index.js'
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
})
|
|
35
|
+
const MAX_NAME = 100
|
|
36
|
+
const MAX_USERNAME = 100
|
|
37
|
+
const MAX_EMAIL = 254
|
|
46
38
|
|
|
47
|
-
type UpdateUserValues =
|
|
39
|
+
type UpdateUserValues = {
|
|
40
|
+
given_name: string
|
|
41
|
+
family_name: string
|
|
42
|
+
username: string
|
|
43
|
+
email: string
|
|
44
|
+
is_super_admin: boolean
|
|
45
|
+
is_enabled: boolean
|
|
46
|
+
is_email_verified: boolean
|
|
47
|
+
}
|
|
48
48
|
|
|
49
49
|
function defaultsFrom(user: AdminUserResponse): UpdateUserValues {
|
|
50
50
|
return {
|
|
@@ -95,9 +95,33 @@ interface UpdateUserProps {
|
|
|
95
95
|
|
|
96
96
|
export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
97
97
|
const { updateAdminUser } = useBylineAdminServices()
|
|
98
|
+
const { t } = useTranslation('byline-admin')
|
|
98
99
|
const [formError, setFormError] = useState<string | null>(null)
|
|
99
100
|
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
100
101
|
|
|
102
|
+
const updateUserSchema = useMemo(
|
|
103
|
+
() =>
|
|
104
|
+
z.object({
|
|
105
|
+
given_name: z
|
|
106
|
+
.string()
|
|
107
|
+
.max(MAX_NAME, t('account.update.errors.givenNameTooLong', { max: MAX_NAME })),
|
|
108
|
+
family_name: z
|
|
109
|
+
.string()
|
|
110
|
+
.max(MAX_NAME, t('account.update.errors.familyNameTooLong', { max: MAX_NAME })),
|
|
111
|
+
username: z
|
|
112
|
+
.string()
|
|
113
|
+
.max(MAX_USERNAME, t('account.update.errors.usernameTooLong', { max: MAX_USERNAME })),
|
|
114
|
+
email: z
|
|
115
|
+
.email({ message: t('account.update.errors.invalidEmail') })
|
|
116
|
+
.min(3)
|
|
117
|
+
.max(MAX_EMAIL, t('account.update.errors.emailTooLong', { max: MAX_EMAIL })),
|
|
118
|
+
is_super_admin: z.boolean(),
|
|
119
|
+
is_enabled: z.boolean(),
|
|
120
|
+
is_email_verified: z.boolean(),
|
|
121
|
+
}),
|
|
122
|
+
[t]
|
|
123
|
+
)
|
|
124
|
+
|
|
101
125
|
const form = useForm({
|
|
102
126
|
defaultValues: defaultsFrom(user),
|
|
103
127
|
validationLogic: revalidateLogic({
|
|
@@ -112,7 +136,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
112
136
|
setSuccessMessage(null)
|
|
113
137
|
const patch = buildPatch(value, user)
|
|
114
138
|
if (Object.keys(patch).length === 0) {
|
|
115
|
-
setSuccessMessage('
|
|
139
|
+
setSuccessMessage(t('common.feedback.noChanges'))
|
|
116
140
|
return
|
|
117
141
|
}
|
|
118
142
|
|
|
@@ -120,30 +144,28 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
120
144
|
const updated = await updateAdminUser({
|
|
121
145
|
data: { id: user.id, vid: user.vid, patch },
|
|
122
146
|
})
|
|
123
|
-
setSuccessMessage('
|
|
147
|
+
setSuccessMessage(t('common.feedback.saved'))
|
|
124
148
|
onSuccess?.(updated)
|
|
125
149
|
} catch (err) {
|
|
126
150
|
const code = getErrorCode(err)
|
|
127
151
|
if (code === 'admin.users.emailInUse') {
|
|
128
|
-
|
|
152
|
+
const message = t('account.update.errors.emailInUse')
|
|
129
153
|
form.setFieldMeta('email', (meta) => ({
|
|
130
154
|
...meta,
|
|
131
|
-
errorMap: { ...meta.errorMap, onServer:
|
|
132
|
-
errors: [
|
|
155
|
+
errorMap: { ...meta.errorMap, onServer: message },
|
|
156
|
+
errors: [message],
|
|
133
157
|
}))
|
|
134
158
|
return
|
|
135
159
|
}
|
|
136
160
|
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
|
-
)
|
|
161
|
+
setFormError(t('adminUsers.update.errors.versionConflict'))
|
|
140
162
|
return
|
|
141
163
|
}
|
|
142
164
|
if (code === 'admin.users.notFound') {
|
|
143
|
-
setFormError('
|
|
165
|
+
setFormError(t('adminUsers.update.errors.notFound'))
|
|
144
166
|
return
|
|
145
167
|
}
|
|
146
|
-
setFormError('
|
|
168
|
+
setFormError(t('common.errors.couldNotSave'))
|
|
147
169
|
}
|
|
148
170
|
},
|
|
149
171
|
})
|
|
@@ -165,7 +187,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
165
187
|
<form.Field name="given_name">
|
|
166
188
|
{(field) => (
|
|
167
189
|
<Input
|
|
168
|
-
label=
|
|
190
|
+
label={t('account.update.fields.givenName')}
|
|
169
191
|
id="given_name"
|
|
170
192
|
name={field.name}
|
|
171
193
|
value={field.state.value}
|
|
@@ -181,7 +203,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
181
203
|
<form.Field name="family_name">
|
|
182
204
|
{(field) => (
|
|
183
205
|
<Input
|
|
184
|
-
label=
|
|
206
|
+
label={t('account.update.fields.familyName')}
|
|
185
207
|
id="family_name"
|
|
186
208
|
name={field.name}
|
|
187
209
|
value={field.state.value}
|
|
@@ -197,7 +219,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
197
219
|
<form.Field name="username">
|
|
198
220
|
{(field) => (
|
|
199
221
|
<Input
|
|
200
|
-
label=
|
|
222
|
+
label={t('account.update.fields.username')}
|
|
201
223
|
id="username"
|
|
202
224
|
name={field.name}
|
|
203
225
|
value={field.state.value}
|
|
@@ -205,7 +227,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
205
227
|
onChange={(e) => field.handleChange(e.currentTarget.value)}
|
|
206
228
|
error={field.state.meta.errors.length > 0}
|
|
207
229
|
errorText={firstError(field.state.meta.errors)}
|
|
208
|
-
helpText=
|
|
230
|
+
helpText={t('account.update.fields.usernameHelp')}
|
|
209
231
|
autoComplete="username"
|
|
210
232
|
/>
|
|
211
233
|
)}
|
|
@@ -214,7 +236,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
214
236
|
<form.Field name="email">
|
|
215
237
|
{(field) => (
|
|
216
238
|
<Input
|
|
217
|
-
label=
|
|
239
|
+
label={t('common.fields.email')}
|
|
218
240
|
id="email"
|
|
219
241
|
name={field.name}
|
|
220
242
|
type="email"
|
|
@@ -235,10 +257,10 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
235
257
|
<Checkbox
|
|
236
258
|
id="is_enabled"
|
|
237
259
|
name={field.name}
|
|
238
|
-
label=
|
|
260
|
+
label={t('adminUsers.create.flags.enabledLabel')}
|
|
239
261
|
checked={field.state.value}
|
|
240
262
|
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
241
|
-
helpText=
|
|
263
|
+
helpText={t('adminUsers.create.flags.enabledHelp')}
|
|
242
264
|
/>
|
|
243
265
|
)}
|
|
244
266
|
</form.Field>
|
|
@@ -248,7 +270,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
248
270
|
<Checkbox
|
|
249
271
|
id="is_email_verified"
|
|
250
272
|
name={field.name}
|
|
251
|
-
label=
|
|
273
|
+
label={t('adminUsers.create.flags.emailVerifiedLabel')}
|
|
252
274
|
checked={field.state.value}
|
|
253
275
|
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
254
276
|
/>
|
|
@@ -260,10 +282,10 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
260
282
|
<Checkbox
|
|
261
283
|
id="is_super_admin"
|
|
262
284
|
name={field.name}
|
|
263
|
-
label=
|
|
285
|
+
label={t('adminUsers.create.flags.superAdminLabel')}
|
|
264
286
|
checked={field.state.value}
|
|
265
287
|
onCheckedChange={(checked) => field.handleChange(checked === true)}
|
|
266
|
-
helpText=
|
|
288
|
+
helpText={t('adminUsers.create.flags.superAdminHelp')}
|
|
267
289
|
/>
|
|
268
290
|
)}
|
|
269
291
|
</form.Field>
|
|
@@ -277,7 +299,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
277
299
|
onClick={onClose}
|
|
278
300
|
className={cx('byline-user-update-action', styles.action)}
|
|
279
301
|
>
|
|
280
|
-
{successMessage ? '
|
|
302
|
+
{successMessage ? t('common.actions.close') : t('common.actions.cancel')}
|
|
281
303
|
</Button>
|
|
282
304
|
<form.Subscribe
|
|
283
305
|
selector={(state) => ({
|
|
@@ -294,7 +316,7 @@ export function UpdateUser({ user, onClose, onSuccess }: UpdateUserProps) {
|
|
|
294
316
|
disabled={!canSubmit || isSubmitting}
|
|
295
317
|
className={cx('byline-user-update-action', styles.action)}
|
|
296
318
|
>
|
|
297
|
-
{isSubmitting === true ? <LoaderEllipsis size={42} /> : '
|
|
319
|
+
{isSubmitting === true ? <LoaderEllipsis size={42} /> : t('common.actions.save')}
|
|
298
320
|
</Button>
|
|
299
321
|
)}
|
|
300
322
|
</form.Subscribe>
|
|
@@ -33,6 +33,7 @@ export function toAdminUser(row: AdminUserRow): AdminUserResponse {
|
|
|
33
33
|
is_super_admin: row.is_super_admin,
|
|
34
34
|
is_enabled: row.is_enabled,
|
|
35
35
|
is_email_verified: row.is_email_verified,
|
|
36
|
+
preferred_locale: row.preferred_locale,
|
|
36
37
|
created_at: row.created_at,
|
|
37
38
|
updated_at: row.updated_at,
|
|
38
39
|
}
|
|
@@ -50,6 +50,13 @@ export interface AdminUserRow {
|
|
|
50
50
|
is_super_admin: boolean
|
|
51
51
|
is_enabled: boolean
|
|
52
52
|
is_email_verified: boolean
|
|
53
|
+
/**
|
|
54
|
+
* Admin interface locale preference. `null` means "use the detection
|
|
55
|
+
* cascade" (cookie → Accept-Language → defaultLocale). Stored as a
|
|
56
|
+
* BCP 47 code; validated at the command layer against the host's
|
|
57
|
+
* `i18n.interface.locales`.
|
|
58
|
+
*/
|
|
59
|
+
preferred_locale: string | null
|
|
53
60
|
created_at: Date
|
|
54
61
|
updated_at: Date
|
|
55
62
|
}
|
|
@@ -73,6 +80,8 @@ export interface CreateAdminUserInput {
|
|
|
73
80
|
is_super_admin?: boolean
|
|
74
81
|
is_enabled?: boolean
|
|
75
82
|
is_email_verified?: boolean
|
|
83
|
+
/** Initial locale preference. `null` defers to the detection cascade. */
|
|
84
|
+
preferred_locale?: string | null
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export interface UpdateAdminUserInput {
|
|
@@ -84,6 +93,8 @@ export interface UpdateAdminUserInput {
|
|
|
84
93
|
is_enabled?: boolean
|
|
85
94
|
is_email_verified?: boolean
|
|
86
95
|
remember_me?: boolean
|
|
96
|
+
/** Pass `null` to clear and fall back to the detection cascade. */
|
|
97
|
+
preferred_locale?: string | null
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
export type AdminUserListOrder =
|
|
@@ -151,6 +162,12 @@ export interface AdminUsersRepository {
|
|
|
151
162
|
setPasswordHash(id: string, expectedVid: number, passwordHash: string): Promise<AdminUserRow>
|
|
152
163
|
/** Toggle enabled state. Vid-less — admin intent is independent of other edits. */
|
|
153
164
|
setEnabled(id: string, enabled: boolean): Promise<void>
|
|
165
|
+
/**
|
|
166
|
+
* Set the admin interface locale preference. Vid-less — user preference
|
|
167
|
+
* is independent of content state. Pass `null` to clear and fall back
|
|
168
|
+
* to the detection cascade (cookie → Accept-Language → defaultLocale).
|
|
169
|
+
*/
|
|
170
|
+
setPreferredLocale(id: string, locale: string | null): Promise<void>
|
|
154
171
|
recordLoginSuccess(id: string, ip: string | null): Promise<void>
|
|
155
172
|
recordLoginFailure(id: string): Promise<void>
|
|
156
173
|
/**
|
|
@@ -46,6 +46,15 @@ const emailSchema = z
|
|
|
46
46
|
|
|
47
47
|
const nameSchema = z.string().min(1).max(100)
|
|
48
48
|
|
|
49
|
+
// BCP 47 locale codes — `en`, `pt-BR`, `zh-Hans-CN`, etc. The 16-char
|
|
50
|
+
// ceiling matches the DB column width and is wider than any real-world
|
|
51
|
+
// locale tag.
|
|
52
|
+
const preferredLocaleSchema = z
|
|
53
|
+
.string()
|
|
54
|
+
.min(2)
|
|
55
|
+
.max(16)
|
|
56
|
+
.regex(/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/, 'must be a BCP 47 locale tag')
|
|
57
|
+
|
|
49
58
|
const orderSchema = z.enum([
|
|
50
59
|
'given_name',
|
|
51
60
|
'family_name',
|
|
@@ -82,6 +91,7 @@ export const createAdminUserRequestSchema = z.object({
|
|
|
82
91
|
is_super_admin: z.boolean().optional(),
|
|
83
92
|
is_enabled: z.boolean().optional(),
|
|
84
93
|
is_email_verified: z.boolean().optional(),
|
|
94
|
+
preferred_locale: preferredLocaleSchema.nullish(),
|
|
85
95
|
})
|
|
86
96
|
export type CreateAdminUserRequest = z.infer<typeof createAdminUserRequestSchema>
|
|
87
97
|
|
|
@@ -97,6 +107,7 @@ export const updateAdminUserRequestSchema = z.object({
|
|
|
97
107
|
is_super_admin: z.boolean().optional(),
|
|
98
108
|
is_enabled: z.boolean().optional(),
|
|
99
109
|
is_email_verified: z.boolean().optional(),
|
|
110
|
+
preferred_locale: preferredLocaleSchema.nullish(),
|
|
100
111
|
})
|
|
101
112
|
.refine((p) => Object.keys(p).length > 0, { message: 'patch cannot be empty' }),
|
|
102
113
|
})
|
|
@@ -144,6 +155,7 @@ export const adminUserResponseSchema = z.object({
|
|
|
144
155
|
is_super_admin: z.boolean(),
|
|
145
156
|
is_enabled: z.boolean(),
|
|
146
157
|
is_email_verified: z.boolean(),
|
|
158
|
+
preferred_locale: z.string().nullable(),
|
|
147
159
|
created_at: z.date(),
|
|
148
160
|
updated_at: z.date(),
|
|
149
161
|
})
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { type FormEvent, useState } from 'react'
|
|
27
27
|
|
|
28
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
28
29
|
import { Alert, Button, Card, Input, LoaderEllipsis } from '@byline/ui/react'
|
|
29
30
|
import cx from 'classnames'
|
|
30
31
|
|
|
@@ -44,6 +45,7 @@ interface SignInFormProps {
|
|
|
44
45
|
|
|
45
46
|
export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
46
47
|
const { adminSignIn } = useBylineAdminServices()
|
|
48
|
+
const { t } = useTranslation('byline-admin')
|
|
47
49
|
const [email, setEmail] = useState('')
|
|
48
50
|
const [password, setPassword] = useState('')
|
|
49
51
|
const [pending, setPending] = useState(false)
|
|
@@ -53,7 +55,7 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
53
55
|
event.preventDefault()
|
|
54
56
|
if (pending) return
|
|
55
57
|
if (email.trim().length === 0 || password.length === 0) {
|
|
56
|
-
setError('
|
|
58
|
+
setError(t('auth.signIn.errors.empty'))
|
|
57
59
|
return
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -67,7 +69,7 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
67
69
|
window.location.assign(target)
|
|
68
70
|
} catch (err) {
|
|
69
71
|
console.warn('sign-in failed', err)
|
|
70
|
-
setError('
|
|
72
|
+
setError(t('auth.signIn.errors.invalidCredentials'))
|
|
71
73
|
setPending(false)
|
|
72
74
|
}
|
|
73
75
|
}
|
|
@@ -76,9 +78,9 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
76
78
|
<Card className={cx('byline-sign-in-card', styles.card)}>
|
|
77
79
|
<Card.Header>
|
|
78
80
|
<Card.Title>
|
|
79
|
-
<h2>
|
|
81
|
+
<h2>{t('auth.signIn.title')}</h2>
|
|
80
82
|
</Card.Title>
|
|
81
|
-
<Card.Description>
|
|
83
|
+
<Card.Description>{t('auth.signIn.description')}</Card.Description>
|
|
82
84
|
{error && (
|
|
83
85
|
<Alert intent="danger" className={cx('byline-sign-in-alert', styles.alert)}>
|
|
84
86
|
{error}
|
|
@@ -89,7 +91,7 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
89
91
|
<form onSubmit={handleSubmit} noValidate className={cx('byline-sign-in-form', styles.form)}>
|
|
90
92
|
<div className={cx('byline-sign-in-fields', styles.fields)}>
|
|
91
93
|
<Input
|
|
92
|
-
label=
|
|
94
|
+
label={t('common.fields.email')}
|
|
93
95
|
id="email"
|
|
94
96
|
name="email"
|
|
95
97
|
type="email"
|
|
@@ -100,7 +102,7 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
100
102
|
disabled={pending}
|
|
101
103
|
/>
|
|
102
104
|
<Input
|
|
103
|
-
label=
|
|
105
|
+
label={t('common.fields.password')}
|
|
104
106
|
id="password"
|
|
105
107
|
name="password"
|
|
106
108
|
type="password"
|
|
@@ -114,7 +116,7 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
114
116
|
<div className={cx('byline-sign-in-actions', styles.actions)}>
|
|
115
117
|
{homeUrl && (
|
|
116
118
|
<a href={homeUrl} className={cx('byline-sign-in-home-link', styles['home-link'])}>
|
|
117
|
-
|
|
119
|
+
{t('common.actions.home')}
|
|
118
120
|
</a>
|
|
119
121
|
)}
|
|
120
122
|
<Button
|
|
@@ -122,7 +124,11 @@ export function SignInForm({ callbackUrl, homeUrl }: SignInFormProps) {
|
|
|
122
124
|
disabled={pending}
|
|
123
125
|
className={cx('byline-sign-in-button', styles.button)}
|
|
124
126
|
>
|
|
125
|
-
{pending ?
|
|
127
|
+
{pending ? (
|
|
128
|
+
<LoaderEllipsis size={30} color="#aaaaaa" />
|
|
129
|
+
) : (
|
|
130
|
+
<span>{t('common.actions.signIn')}</span>
|
|
131
|
+
)}
|
|
126
132
|
</Button>
|
|
127
133
|
</div>
|
|
128
134
|
</form>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group — labelled fieldset clustering related fields together.
|
|
3
|
+
*
|
|
4
|
+
* Used by `FormRenderer` when a `CollectionAdminConfig` declares a
|
|
5
|
+
* `groups` primitive. Renders a bordered, padded `<fieldset>` with an
|
|
6
|
+
* optional `<legend>` for the label.
|
|
7
|
+
*
|
|
8
|
+
* Override handles: `.byline-admin-group` (the fieldset) and
|
|
9
|
+
* `.byline-admin-group-legend` (the optional heading) are exposed as
|
|
10
|
+
* stable global classes for host overrides.
|
|
11
|
+
*
|
|
12
|
+
* Note: this is the *admin layout* group primitive, not the schema-level
|
|
13
|
+
* `group` field type. Field widgets use `byline-field-*` handles to keep
|
|
14
|
+
* the namespaces disambiguated.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
.group,
|
|
18
|
+
:global(.byline-admin-group) {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: var(--spacing-16);
|
|
22
|
+
padding: var(--spacing-12);
|
|
23
|
+
border: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
|
|
24
|
+
border-radius: var(--border-radius-md);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.legend,
|
|
28
|
+
:global(.byline-admin-group-legend) {
|
|
29
|
+
padding-inline: var(--spacing-4);
|
|
30
|
+
font-size: var(--font-size-sm);
|
|
31
|
+
font-weight: var(--font-weight-medium);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ─── Dark theme variants ───────────────────────────────────── */
|
|
35
|
+
|
|
36
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
37
|
+
.group,
|
|
38
|
+
:global(.byline-admin-group) {
|
|
39
|
+
border-color: var(--gray-700);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 type { ReactNode } from 'react'
|
|
10
|
+
|
|
11
|
+
import cx from 'classnames'
|
|
12
|
+
|
|
13
|
+
import styles from './group.module.css'
|
|
14
|
+
|
|
15
|
+
interface AdminGroupProps {
|
|
16
|
+
/** Optional heading rendered as a `<legend>` above the cluster. */
|
|
17
|
+
label?: string
|
|
18
|
+
children: ReactNode
|
|
19
|
+
className?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Labelled fieldset clustering related fields together.
|
|
24
|
+
*
|
|
25
|
+
* Used by `FormRenderer` when a `CollectionAdminConfig` declares a `groups`
|
|
26
|
+
* primitive. Renders a bordered, padded `<fieldset>` with an optional
|
|
27
|
+
* `<legend>` for the label.
|
|
28
|
+
*
|
|
29
|
+
* Stable override handles: `.byline-admin-group` on the fieldset and
|
|
30
|
+
* `.byline-admin-group-legend` on the legend (alongside the hashed
|
|
31
|
+
* CSS-modules locals).
|
|
32
|
+
*/
|
|
33
|
+
export const AdminGroup = ({ label, children, className }: AdminGroupProps) => {
|
|
34
|
+
return (
|
|
35
|
+
<fieldset className={cx('byline-admin-group', styles.group, className)}>
|
|
36
|
+
{label && <legend className={cx('byline-admin-group-legend', styles.legend)}>{label}</legend>}
|
|
37
|
+
{children}
|
|
38
|
+
</fieldset>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row — horizontal flex layout for admin form fields.
|
|
3
|
+
*
|
|
4
|
+
* Members render side-by-side at `sm` and above, stack vertically below.
|
|
5
|
+
* `flex: 1` + `min-width: 0` on direct children lets two text inputs
|
|
6
|
+
* share the row evenly without overflowing.
|
|
7
|
+
*
|
|
8
|
+
* Override handle: `.byline-admin-row` is exposed as a stable global
|
|
9
|
+
* class so hosts can target this element from their own stylesheet
|
|
10
|
+
* without referring to the hashed local name.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.row,
|
|
14
|
+
:global(.byline-admin-row) {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
align-items: flex-start;
|
|
18
|
+
gap: var(--spacing-16);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.row > *,
|
|
22
|
+
:global(.byline-admin-row) > * {
|
|
23
|
+
flex: 1;
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (min-width: 40rem) {
|
|
28
|
+
.row,
|
|
29
|
+
:global(.byline-admin-row) {
|
|
30
|
+
flex-direction: row;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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 type { ReactNode } from 'react'
|
|
10
|
+
|
|
11
|
+
import cx from 'classnames'
|
|
12
|
+
|
|
13
|
+
import styles from './row.module.css'
|
|
14
|
+
|
|
15
|
+
interface AdminRowProps {
|
|
16
|
+
children: ReactNode
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Horizontal flex-row layout for admin form fields.
|
|
22
|
+
*
|
|
23
|
+
* Used by `FormRenderer` when a `CollectionAdminConfig` declares a `rows`
|
|
24
|
+
* primitive. Members are rendered side-by-side above the `sm` breakpoint
|
|
25
|
+
* and stack vertically below it. `flex-1` + `min-width: 0` lets two text
|
|
26
|
+
* inputs share the row evenly without overflowing.
|
|
27
|
+
*
|
|
28
|
+
* The element carries `.byline-admin-row` as a stable global class for
|
|
29
|
+
* host overrides (alongside the hashed CSS-modules local).
|
|
30
|
+
*/
|
|
31
|
+
export const AdminRow = ({ children, className }: AdminRowProps) => {
|
|
32
|
+
return <div className={cx('byline-admin-row', styles.row, className)}>{children}</div>
|
|
33
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs — navigation bar for admin form layouts.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-admin-tabs — the tablist container
|
|
6
|
+
* .byline-admin-tab — each tab button
|
|
7
|
+
* .byline-admin-tab-active — added when the tab is the active one
|
|
8
|
+
* .byline-admin-tab-label — the inner label span (with optional badge)
|
|
9
|
+
* .byline-admin-tab-badge — the inline error-count badge
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
.tabs,
|
|
13
|
+
:global(.byline-admin-tabs) {
|
|
14
|
+
display: flex;
|
|
15
|
+
gap: var(--spacing-16);
|
|
16
|
+
border-bottom: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tab,
|
|
20
|
+
:global(.byline-admin-tab) {
|
|
21
|
+
position: relative;
|
|
22
|
+
margin-bottom: -1px;
|
|
23
|
+
padding: 0.625rem 0;
|
|
24
|
+
border-bottom: 2px solid transparent;
|
|
25
|
+
background: none;
|
|
26
|
+
color: var(--gray-500);
|
|
27
|
+
font-size: 1.1rem;
|
|
28
|
+
font-weight: var(--font-weight-medium);
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
transition:
|
|
31
|
+
color 150ms ease,
|
|
32
|
+
border-color 150ms ease;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tab:hover,
|
|
37
|
+
:global(.byline-admin-tab):hover {
|
|
38
|
+
color: var(--gray-800);
|
|
39
|
+
border-bottom-color: var(--gray-300);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.tab:focus-visible,
|
|
43
|
+
:global(.byline-admin-tab):focus-visible {
|
|
44
|
+
box-shadow: 0 0 0 2px var(--blue-500);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tab-active,
|
|
48
|
+
:global(.byline-admin-tab-active) {
|
|
49
|
+
color: var(--primary-600);
|
|
50
|
+
border-bottom-color: var(--primary-400);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tab-active:hover,
|
|
54
|
+
:global(.byline-admin-tab-active):hover {
|
|
55
|
+
color: var(--primary-600);
|
|
56
|
+
border-bottom-color: var(--primary-400);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.label,
|
|
60
|
+
:global(.byline-admin-tab-label) {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 0.375rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.badge,
|
|
67
|
+
:global(.byline-admin-tab-badge) {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
min-width: 1.25rem;
|
|
72
|
+
height: 1.25rem;
|
|
73
|
+
padding: 0 0.375rem;
|
|
74
|
+
font-size: 0.7rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ─── Dark theme variants ───────────────────────────────────── */
|
|
78
|
+
|
|
79
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
80
|
+
.tabs,
|
|
81
|
+
:global(.byline-admin-tabs) {
|
|
82
|
+
border-bottom-color: var(--gray-700);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tab,
|
|
86
|
+
:global(.byline-admin-tab) {
|
|
87
|
+
color: var(--gray-400);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.tab:hover,
|
|
91
|
+
:global(.byline-admin-tab):hover {
|
|
92
|
+
color: var(--gray-200);
|
|
93
|
+
border-bottom-color: var(--gray-600);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.tab-active,
|
|
97
|
+
:global(.byline-admin-tab-active) {
|
|
98
|
+
color: var(--primary-200);
|
|
99
|
+
border-bottom-color: var(--primary-400);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.tab-active:hover,
|
|
103
|
+
:global(.byline-admin-tab-active):hover {
|
|
104
|
+
color: var(--primary-200);
|
|
105
|
+
border-bottom-color: var(--primary-400);
|
|
106
|
+
}
|
|
107
|
+
}
|