@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
|
@@ -27,15 +27,18 @@
|
|
|
27
27
|
import type React from 'react'
|
|
28
28
|
import { useState } from 'react'
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import { LocalDateTime } from '@byline/admin/react'
|
|
31
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
32
|
+
import { Button, CloseIcon, Drawer, EditIcon, IconButton } from '@byline/ui/react'
|
|
31
33
|
import cx from 'classnames'
|
|
32
34
|
|
|
33
35
|
import { ChangeAccountPassword } from './change-password.js'
|
|
34
36
|
import styles from './container.module.css'
|
|
37
|
+
import { Preferences } from './preferences.js'
|
|
35
38
|
import { UpdateAccount } from './update.js'
|
|
36
39
|
import type { AccountResponse } from '../index.js'
|
|
37
40
|
|
|
38
|
-
type ComponentKey = 'update' | 'change_password' | 'empty'
|
|
41
|
+
type ComponentKey = 'update' | 'change_password' | 'preferences' | 'empty'
|
|
39
42
|
|
|
40
43
|
interface PanelProps {
|
|
41
44
|
account: AccountResponse
|
|
@@ -43,20 +46,22 @@ interface PanelProps {
|
|
|
43
46
|
onSuccess?: (account: AccountResponse) => void
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const panelComponents: Record<ComponentKey, React.ComponentType<PanelProps>> = {
|
|
50
|
+
update: UpdateAccount,
|
|
51
|
+
change_password: ChangeAccountPassword,
|
|
52
|
+
preferences: Preferences,
|
|
53
|
+
empty: () => null,
|
|
54
|
+
}
|
|
52
55
|
|
|
53
56
|
function ContainerSection({
|
|
54
57
|
title,
|
|
55
58
|
onEdit,
|
|
59
|
+
editAriaLabel,
|
|
56
60
|
children,
|
|
57
61
|
}: {
|
|
58
62
|
title: string
|
|
59
63
|
onEdit?: () => void
|
|
64
|
+
editAriaLabel?: string
|
|
60
65
|
children: React.ReactNode
|
|
61
66
|
}) {
|
|
62
67
|
return (
|
|
@@ -64,7 +69,7 @@ function ContainerSection({
|
|
|
64
69
|
<div className={cx('byline-account-section-head', styles['section-head'])}>
|
|
65
70
|
<h2>{title}</h2>
|
|
66
71
|
{onEdit ? (
|
|
67
|
-
<IconButton variant="text" onClick={onEdit} aria-label={
|
|
72
|
+
<IconButton variant="text" onClick={onEdit} aria-label={editAriaLabel ?? title}>
|
|
68
73
|
<EditIcon width="20px" height="20px" />
|
|
69
74
|
</IconButton>
|
|
70
75
|
) : null}
|
|
@@ -79,6 +84,7 @@ interface AccountSelfContainerProps {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
export function AccountSelfContainer({ account }: AccountSelfContainerProps) {
|
|
87
|
+
const { t } = useTranslation('byline-admin')
|
|
82
88
|
const [currentAccount, setCurrentAccount] = useState<AccountResponse>(account)
|
|
83
89
|
const [current, setCurrent] = useState<ComponentKey>('empty')
|
|
84
90
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
|
@@ -95,82 +101,120 @@ export function AccountSelfContainer({ account }: AccountSelfContainerProps) {
|
|
|
95
101
|
setCurrentAccount(updated)
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
const Panel =
|
|
104
|
+
const Panel = panelComponents[current]
|
|
105
|
+
const panelTitles: Record<ComponentKey, string> = {
|
|
106
|
+
update: t('account.sections.profile'),
|
|
107
|
+
change_password: t('account.sections.password'),
|
|
108
|
+
preferences: t('account.sections.preferences'),
|
|
109
|
+
empty: '',
|
|
110
|
+
}
|
|
111
|
+
const editAriaFor = (section: string) => t('account.editAriaLabel', { section })
|
|
99
112
|
|
|
100
113
|
return (
|
|
101
114
|
<>
|
|
102
115
|
<div className={cx('byline-account-grid', styles.grid)}>
|
|
103
116
|
<div className={cx('byline-account-column', styles.column)}>
|
|
104
|
-
<ContainerSection
|
|
117
|
+
<ContainerSection
|
|
118
|
+
title={t('account.sections.profile')}
|
|
119
|
+
onEdit={openDrawer('update')}
|
|
120
|
+
editAriaLabel={editAriaFor(t('account.sections.profile'))}
|
|
121
|
+
>
|
|
105
122
|
<p className={cx('byline-account-line', styles.line)}>
|
|
106
|
-
<span className="muted">
|
|
123
|
+
<span className="muted">{t('account.profile.emailColon')}</span>{' '}
|
|
124
|
+
{currentAccount.email}
|
|
107
125
|
</p>
|
|
108
126
|
<p className={cx('byline-account-line', styles.line)}>
|
|
109
|
-
<span className="muted">
|
|
127
|
+
<span className="muted">{t('account.profile.givenName')}</span>{' '}
|
|
110
128
|
{currentAccount.given_name ?? (
|
|
111
129
|
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
112
|
-
|
|
130
|
+
{t('common.notSet')}
|
|
113
131
|
</span>
|
|
114
132
|
)}
|
|
115
133
|
</p>
|
|
116
134
|
<p className={cx('byline-account-line', styles.line)}>
|
|
117
|
-
<span className="muted">
|
|
135
|
+
<span className="muted">{t('account.profile.familyName')}</span>{' '}
|
|
118
136
|
{currentAccount.family_name ?? (
|
|
119
137
|
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
120
|
-
|
|
138
|
+
{t('common.notSet')}
|
|
121
139
|
</span>
|
|
122
140
|
)}
|
|
123
141
|
</p>
|
|
124
142
|
<p className={cx('byline-account-cta-line', styles['cta-line'])}>
|
|
125
|
-
<span className="muted">
|
|
143
|
+
<span className="muted">{t('account.profile.username')}</span>{' '}
|
|
126
144
|
{currentAccount.username ?? (
|
|
127
145
|
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
128
|
-
|
|
146
|
+
{t('common.notSet')}
|
|
129
147
|
</span>
|
|
130
148
|
)}
|
|
131
149
|
</p>
|
|
132
150
|
<Button size="sm" onClick={openDrawer('update')}>
|
|
133
|
-
|
|
151
|
+
{t('account.profile.editButton')}
|
|
134
152
|
</Button>
|
|
135
153
|
<div className={cx('muted', 'byline-account-meta', styles.meta)}>
|
|
136
154
|
<p>
|
|
137
|
-
<span className="font-bold">
|
|
155
|
+
<span className="font-bold">{t('account.profile.created')} </span>
|
|
138
156
|
<LocalDateTime value={currentAccount.created_at} />
|
|
139
157
|
</p>
|
|
140
158
|
<p>
|
|
141
|
-
<span className="font-bold">
|
|
159
|
+
<span className="font-bold">{t('account.profile.updated')} </span>
|
|
142
160
|
<LocalDateTime value={currentAccount.updated_at} />
|
|
143
161
|
</p>
|
|
144
162
|
<p className={cx('byline-account-line', styles.line)}>
|
|
145
|
-
<span className="font-bold">
|
|
146
|
-
<LocalDateTime value={currentAccount.last_login} fallback=
|
|
163
|
+
<span className="font-bold">{t('account.profile.lastLogin')} </span>
|
|
164
|
+
<LocalDateTime value={currentAccount.last_login} fallback={t('common.never')} />
|
|
147
165
|
</p>
|
|
148
166
|
</div>
|
|
149
167
|
</ContainerSection>
|
|
168
|
+
|
|
169
|
+
<ContainerSection
|
|
170
|
+
title={t('account.sections.preferences')}
|
|
171
|
+
onEdit={openDrawer('preferences')}
|
|
172
|
+
editAriaLabel={editAriaFor(t('account.sections.preferences'))}
|
|
173
|
+
>
|
|
174
|
+
<p className={cx('byline-account-line', styles.line)}>
|
|
175
|
+
<span className="muted">{t('account.preferences.interfaceLanguage')}</span>{' '}
|
|
176
|
+
{currentAccount.preferred_locale ?? (
|
|
177
|
+
<span className={cx('muted', 'byline-account-not-set', styles['not-set'])}>
|
|
178
|
+
{t('language.useBrowserDefault')}
|
|
179
|
+
</span>
|
|
180
|
+
)}
|
|
181
|
+
</p>
|
|
182
|
+
<p className={cx('byline-account-cta-line', styles['cta-line'])}>
|
|
183
|
+
<Button size="sm" onClick={openDrawer('preferences')}>
|
|
184
|
+
{t('account.preferences.editButton')}
|
|
185
|
+
</Button>
|
|
186
|
+
</p>
|
|
187
|
+
<p className={cx('muted', 'byline-account-status-help', styles['status-help'])}>
|
|
188
|
+
{t('account.preferences.help')}
|
|
189
|
+
</p>
|
|
190
|
+
</ContainerSection>
|
|
150
191
|
</div>
|
|
151
192
|
|
|
152
193
|
<div className={cx('byline-account-column', styles.column)}>
|
|
153
|
-
<ContainerSection
|
|
194
|
+
<ContainerSection
|
|
195
|
+
title={t('account.sections.password')}
|
|
196
|
+
onEdit={openDrawer('change_password')}
|
|
197
|
+
editAriaLabel={editAriaFor(t('account.sections.password'))}
|
|
198
|
+
>
|
|
154
199
|
<p className={cx('byline-account-cta-line', styles['cta-line'])}>
|
|
155
|
-
|
|
156
|
-
password to confirm the change.
|
|
200
|
+
{t('account.password.intro')}
|
|
157
201
|
</p>
|
|
158
202
|
<Button size="sm" onClick={openDrawer('change_password')}>
|
|
159
|
-
|
|
203
|
+
{t('account.password.editButton')}
|
|
160
204
|
</Button>
|
|
161
205
|
</ContainerSection>
|
|
162
206
|
|
|
163
|
-
<ContainerSection title=
|
|
207
|
+
<ContainerSection title={t('account.sections.status')}>
|
|
164
208
|
<p className={cx('byline-account-line', styles.line)}>
|
|
165
|
-
<span className="muted">
|
|
166
|
-
{currentAccount.is_super_admin ? '
|
|
209
|
+
<span className="muted">{t('account.status.superAdmin')}</span>{' '}
|
|
210
|
+
{currentAccount.is_super_admin ? t('common.boolean.yes') : t('common.boolean.no')}
|
|
167
211
|
</p>
|
|
168
212
|
<p className={cx('byline-account-line', styles.line)}>
|
|
169
|
-
<span className="muted">
|
|
170
|
-
{currentAccount.is_email_verified ? '
|
|
213
|
+
<span className="muted">{t('account.status.emailVerified')}</span>{' '}
|
|
214
|
+
{currentAccount.is_email_verified ? t('common.boolean.yes') : t('common.boolean.no')}
|
|
171
215
|
</p>
|
|
172
216
|
<p className={cx('byline-account-line', styles.line)}>
|
|
173
|
-
<span className="muted">
|
|
217
|
+
<span className="muted">{t('account.status.status')}</span>{' '}
|
|
174
218
|
<span
|
|
175
219
|
className={
|
|
176
220
|
currentAccount.is_enabled
|
|
@@ -178,12 +222,13 @@ export function AccountSelfContainer({ account }: AccountSelfContainerProps) {
|
|
|
178
222
|
: cx('byline-account-status-off', styles['status-off'])
|
|
179
223
|
}
|
|
180
224
|
>
|
|
181
|
-
{currentAccount.is_enabled
|
|
225
|
+
{currentAccount.is_enabled
|
|
226
|
+
? t('account.status.enabled')
|
|
227
|
+
: t('account.status.disabled')}
|
|
182
228
|
</span>
|
|
183
229
|
</p>
|
|
184
230
|
<p className={cx('muted', 'byline-account-status-help', styles['status-help'])}>
|
|
185
|
-
|
|
186
|
-
self-editable.
|
|
231
|
+
{t('account.status.help')}
|
|
187
232
|
</p>
|
|
188
233
|
</ContainerSection>
|
|
189
234
|
</div>
|
|
@@ -210,12 +255,12 @@ export function AccountSelfContainer({ account }: AccountSelfContainerProps) {
|
|
|
210
255
|
>
|
|
211
256
|
no action
|
|
212
257
|
</button>
|
|
213
|
-
<IconButton aria-label=
|
|
258
|
+
<IconButton aria-label={t('common.actions.close')} size="sm" onClick={closeDrawer}>
|
|
214
259
|
<CloseIcon width="14px" height="14px" svgClassName="white-icon stroke-white" />
|
|
215
260
|
</IconButton>
|
|
216
261
|
</Drawer.TopActions>
|
|
217
262
|
<Drawer.Header>
|
|
218
|
-
<h2>{
|
|
263
|
+
<h2>{panelTitles[current]}</h2>
|
|
219
264
|
</Drawer.Header>
|
|
220
265
|
<Drawer.Content>
|
|
221
266
|
<div className={cx('byline-account-drawer-scroll', styles['drawer-scroll'])}>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preferences — self-service account-preferences form (drawer body).
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-account-preferences-wrap — outer container
|
|
6
|
+
* .byline-account-preferences-form — vertical-stack form element
|
|
7
|
+
* .byline-account-preferences-row — label + control row (per field)
|
|
8
|
+
* .byline-account-preferences-label — text label above the control
|
|
9
|
+
* .byline-account-preferences-help — muted help-text line
|
|
10
|
+
* .byline-account-preferences-actions — Cancel/Save row
|
|
11
|
+
* .byline-account-preferences-action — buttons in the actions row
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.wrap,
|
|
15
|
+
:global(.byline-account-preferences-wrap) {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--spacing-8);
|
|
19
|
+
padding: var(--spacing-4);
|
|
20
|
+
margin-top: var(--spacing-4);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.form,
|
|
24
|
+
:global(.byline-account-preferences-form) {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--spacing-16);
|
|
28
|
+
padding-top: var(--spacing-8);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.row,
|
|
32
|
+
:global(.byline-account-preferences-row) {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: var(--spacing-4);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.label,
|
|
39
|
+
:global(.byline-account-preferences-label) {
|
|
40
|
+
font-weight: 500;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.help,
|
|
44
|
+
:global(.byline-account-preferences-help) {
|
|
45
|
+
font-size: 0.875rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.actions,
|
|
49
|
+
:global(.byline-account-preferences-actions) {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: flex-end;
|
|
53
|
+
gap: var(--spacing-8);
|
|
54
|
+
margin-top: var(--spacing-16);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.action,
|
|
58
|
+
:global(.byline-account-preferences-action) {
|
|
59
|
+
min-width: 4rem;
|
|
60
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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 Preferences form.
|
|
13
|
+
*
|
|
14
|
+
* General-purpose container for the signed-in admin's UI preferences.
|
|
15
|
+
* Today the only setting is the interface locale; future preferences
|
|
16
|
+
* (theme, default landing page, density, etc.) land here as additional
|
|
17
|
+
* `<form.Field>` blocks rather than spinning up a new drawer per
|
|
18
|
+
* setting.
|
|
19
|
+
*
|
|
20
|
+
* Locale write surface mirrors the chrome-bar `<LanguageMenu>` — both
|
|
21
|
+
* route through `setInterfaceLocale` (the service) →
|
|
22
|
+
* `setInterfaceLocaleFn` (the host server fn) → the shared
|
|
23
|
+
* cookie + `admin_users.preferred_locale` writes. The dropdown carries
|
|
24
|
+
* an explicit "Use browser default" entry that maps to `null`
|
|
25
|
+
* server-side, clearing the column and re-engaging the detection
|
|
26
|
+
* cascade (cookie → Accept-Language → defaultLocale). The sentinel
|
|
27
|
+
* value `__auto__` is the wire form for that option; the service
|
|
28
|
+
* signature uses `string | null`.
|
|
29
|
+
*
|
|
30
|
+
* On save, the freshened `AccountResponse` returned by the server fn
|
|
31
|
+
* is lifted into the parent container so the read-only Preferences
|
|
32
|
+
* section re-renders without a route reload. Mirrors how
|
|
33
|
+
* `UpdateAccount` threads its result through `onSuccess`.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { useMemo, useState } from 'react'
|
|
37
|
+
import { revalidateLogic, useForm } from '@tanstack/react-form-start'
|
|
38
|
+
|
|
39
|
+
import { getClientConfig } from '@byline/core'
|
|
40
|
+
import { Alert, Button, LoaderEllipsis, Select } from '@byline/ui/react'
|
|
41
|
+
import cx from 'classnames'
|
|
42
|
+
import { z } from 'zod'
|
|
43
|
+
|
|
44
|
+
import { useBylineAdminServices } from '../../../services/admin-services-context.js'
|
|
45
|
+
import styles from './preferences.module.css'
|
|
46
|
+
import type { AccountResponse } from '../index.js'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sentinel for the "Use browser default" entry in the locale select.
|
|
50
|
+
* The widget's value type is `string`; we map this sentinel to `null`
|
|
51
|
+
* at submit time. Prefixed with double-underscore to make collision
|
|
52
|
+
* with a real BCP 47 tag structurally impossible (no real locale code
|
|
53
|
+
* contains underscores).
|
|
54
|
+
*/
|
|
55
|
+
const AUTO_VALUE = '__auto__'
|
|
56
|
+
|
|
57
|
+
const preferencesSchema = z.object({
|
|
58
|
+
// Validation against the configured locale set happens server-side
|
|
59
|
+
// (it's the single source of truth). The form schema just enforces
|
|
60
|
+
// non-emptiness — `AUTO_VALUE` is a valid selection.
|
|
61
|
+
locale: z.string().min(1, { message: 'Select a language' }),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
type PreferencesValues = z.infer<typeof preferencesSchema>
|
|
65
|
+
|
|
66
|
+
function defaultsFrom(account: AccountResponse): PreferencesValues {
|
|
67
|
+
return { locale: account.preferred_locale ?? AUTO_VALUE }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface PreferencesProps {
|
|
71
|
+
account: AccountResponse
|
|
72
|
+
onClose?: () => void
|
|
73
|
+
onSuccess?: (account: AccountResponse) => void
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function Preferences({ account, onClose, onSuccess }: PreferencesProps) {
|
|
77
|
+
const { setInterfaceLocale } = useBylineAdminServices()
|
|
78
|
+
const [formError, setFormError] = useState<string | null>(null)
|
|
79
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null)
|
|
80
|
+
|
|
81
|
+
// Build the dropdown's items from the host's configured interface
|
|
82
|
+
// locales. `localeDefinitions` carries host-authored display names
|
|
83
|
+
// ("Français" vs CLDR's lowercase "français"); when it's not set,
|
|
84
|
+
// fall back to the raw code so the form still functions.
|
|
85
|
+
const localeItems = useMemo(() => {
|
|
86
|
+
const { i18n } = getClientConfig()
|
|
87
|
+
const definitionByCode = new Map(
|
|
88
|
+
(i18n.interface.localeDefinitions ?? []).map((d) => [d.code, d.nativeName])
|
|
89
|
+
)
|
|
90
|
+
const items = i18n.interface.locales.map((code) => ({
|
|
91
|
+
value: code,
|
|
92
|
+
label: definitionByCode.get(code) ?? code,
|
|
93
|
+
}))
|
|
94
|
+
return [{ value: AUTO_VALUE, label: 'Use browser default' }, ...items]
|
|
95
|
+
}, [])
|
|
96
|
+
|
|
97
|
+
const form = useForm({
|
|
98
|
+
defaultValues: defaultsFrom(account),
|
|
99
|
+
validationLogic: revalidateLogic({
|
|
100
|
+
mode: 'blur',
|
|
101
|
+
modeAfterSubmission: 'change',
|
|
102
|
+
}),
|
|
103
|
+
validators: {
|
|
104
|
+
onDynamic: preferencesSchema,
|
|
105
|
+
},
|
|
106
|
+
onSubmit: async ({ value }) => {
|
|
107
|
+
setFormError(null)
|
|
108
|
+
setSuccessMessage(null)
|
|
109
|
+
const nextLocale = value.locale === AUTO_VALUE ? null : value.locale
|
|
110
|
+
if (nextLocale === (account.preferred_locale ?? null)) {
|
|
111
|
+
setSuccessMessage('No changes to save.')
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const result = await setInterfaceLocale({ data: { locale: nextLocale } })
|
|
116
|
+
// Pre-auth path returns `account: null`; the form is only
|
|
117
|
+
// reachable behind an admin session, so `account` should be
|
|
118
|
+
// populated. Treat absence as a defensive no-op.
|
|
119
|
+
if (result.account != null) {
|
|
120
|
+
setSuccessMessage('Saved.')
|
|
121
|
+
onSuccess?.(result.account)
|
|
122
|
+
} else {
|
|
123
|
+
setSuccessMessage('Saved.')
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
setFormError('Could not save changes. Please try again.')
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className={cx('byline-account-preferences-wrap', styles.wrap)}>
|
|
133
|
+
<form
|
|
134
|
+
noValidate
|
|
135
|
+
onSubmit={(event) => {
|
|
136
|
+
event.preventDefault()
|
|
137
|
+
event.stopPropagation()
|
|
138
|
+
void form.handleSubmit()
|
|
139
|
+
}}
|
|
140
|
+
className={cx('byline-account-preferences-form', styles.form)}
|
|
141
|
+
>
|
|
142
|
+
{formError ? <Alert intent="danger">{formError}</Alert> : null}
|
|
143
|
+
{successMessage ? <Alert intent="success">{successMessage}</Alert> : null}
|
|
144
|
+
|
|
145
|
+
<form.Field name="locale">
|
|
146
|
+
{(field) => (
|
|
147
|
+
<div className={cx('byline-account-preferences-row', styles.row)}>
|
|
148
|
+
<label
|
|
149
|
+
htmlFor="preferences-locale-select"
|
|
150
|
+
className={cx('byline-account-preferences-label', styles.label)}
|
|
151
|
+
>
|
|
152
|
+
Interface language
|
|
153
|
+
</label>
|
|
154
|
+
<Select<string>
|
|
155
|
+
id="preferences-locale-select"
|
|
156
|
+
size="sm"
|
|
157
|
+
ariaLabel="Interface language"
|
|
158
|
+
value={field.state.value}
|
|
159
|
+
items={localeItems}
|
|
160
|
+
onValueChange={(value) => {
|
|
161
|
+
if (value != null) field.handleChange(value)
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
<p className={cx('muted', 'byline-account-preferences-help', styles.help)}>
|
|
165
|
+
Choose "Use browser default" to let your browser's language settings decide.
|
|
166
|
+
</p>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</form.Field>
|
|
170
|
+
|
|
171
|
+
<div className={cx('byline-account-preferences-actions', styles.actions)}>
|
|
172
|
+
<Button
|
|
173
|
+
type="button"
|
|
174
|
+
intent="secondary"
|
|
175
|
+
size="sm"
|
|
176
|
+
onClick={onClose}
|
|
177
|
+
className={cx('byline-account-preferences-action', styles.action)}
|
|
178
|
+
>
|
|
179
|
+
{successMessage ? 'Close' : 'Cancel'}
|
|
180
|
+
</Button>
|
|
181
|
+
<form.Subscribe
|
|
182
|
+
selector={(state) => ({
|
|
183
|
+
canSubmit: state.canSubmit,
|
|
184
|
+
isSubmitting: state.isSubmitting,
|
|
185
|
+
})}
|
|
186
|
+
>
|
|
187
|
+
{({ canSubmit, isSubmitting }) => (
|
|
188
|
+
<Button
|
|
189
|
+
size="sm"
|
|
190
|
+
intent="primary"
|
|
191
|
+
type="submit"
|
|
192
|
+
disabled={!canSubmit || isSubmitting}
|
|
193
|
+
className={cx('byline-account-preferences-action', styles.action)}
|
|
194
|
+
>
|
|
195
|
+
{isSubmitting === true ? <LoaderEllipsis size={42} /> : 'Save'}
|
|
196
|
+
</Button>
|
|
197
|
+
)}
|
|
198
|
+
</form.Subscribe>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
203
|
+
}
|