feat: configure edit permissions for user profiles & change password (#5422)

* feat: configure edit permissions for user profiles

* fix: bug

* fix: bug

* refactor: locale improve

* refactor: currentUser
This commit is contained in:
Katherine 2024-10-18 09:09:32 +08:00 committed by GitHub
parent d158585efd
commit f2e6e02577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 218 additions and 12 deletions

View File

@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next';
import { ActionContextProvider, DropdownVisibleContext, SchemaComponent, useActionContext } from '../'; import { ActionContextProvider, DropdownVisibleContext, SchemaComponent, useActionContext } from '../';
import { useAPIClient } from '../api-client'; import { useAPIClient } from '../api-client';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useSystemSettings } from '../';
const useCloseAction = () => { const useCloseAction = () => {
const { setVisible } = useActionContext(); const { setVisible } = useActionContext();
@ -132,8 +133,10 @@ export const useChangePassword = () => {
const ctx = useContext(DropdownVisibleContext); const ctx = useContext(DropdownVisibleContext);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { data } = useSystemSettings();
const { enableChangePassword } = data?.data || {};
return useMemo<MenuProps['items'][0]>(() => { const result = useMemo<MenuProps['items'][0]>(() => {
return { return {
key: 'password', key: 'password',
eventKey: 'ChangePassword', eventKey: 'ChangePassword',
@ -153,4 +156,9 @@ export const useChangePassword = () => {
), ),
}; };
}, [visible]); }, [visible]);
if (enableChangePassword === false) {
return null;
}
return result;
}; };

View File

@ -125,7 +125,7 @@ export const SettingsMenu: React.FC<{
}, },
editProfile, editProfile,
changePassword, changePassword,
{ (editProfile || changePassword) && {
key: 'divider_2', key: 'divider_2',
type: 'divider', type: 'divider',
}, },

View File

@ -19,6 +19,7 @@ import {
useActionContext, useActionContext,
useCurrentUserContext, useCurrentUserContext,
useRequest, useRequest,
useSystemSettings,
} from '../'; } from '../';
import { useAPIClient } from '../api-client'; import { useAPIClient } from '../api-client';
@ -82,6 +83,11 @@ const schema: ISchema = {
title: "{{t('Nickname')}}", title: "{{t('Nickname')}}",
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-reactions': (field) => {
if (field.initialValue) {
field.disabled = true;
}
},
}, },
username: { username: {
type: 'string', type: 'string',
@ -90,6 +96,11 @@ const schema: ISchema = {
'x-component': 'Input', 'x-component': 'Input',
'x-validator': { username: true }, 'x-validator': { username: true },
required: true, required: true,
'x-reactions': (field) => {
if (field.initialValue) {
field.disabled = true;
}
},
}, },
email: { email: {
type: 'string', type: 'string',
@ -97,12 +108,22 @@ const schema: ISchema = {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-validator': 'email', 'x-validator': 'email',
'x-reactions': (field) => {
if (field.initialValue) {
field.disabled = true;
}
},
}, },
phone: { phone: {
type: 'string', type: 'string',
title: '{{t("Phone")}}', title: '{{t("Phone")}}',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
'x-reactions': (field) => {
if (field.initialValue) {
field.disabled = true;
}
},
}, },
footer: { footer: {
'x-component': 'Action.Drawer.Footer', 'x-component': 'Action.Drawer.Footer',
@ -134,8 +155,9 @@ export const useEditProfile = () => {
const ctx = useContext(DropdownVisibleContext); const ctx = useContext(DropdownVisibleContext);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const { data } = useSystemSettings();
return useMemo<MenuProps['items'][0]>(() => { const { enableEditProfile } = data?.data || {};
const result = useMemo<MenuProps['items'][0]>(() => {
return { return {
key: 'profile', key: 'profile',
eventKey: 'EditProfile', eventKey: 'EditProfile',
@ -158,4 +180,8 @@ export const useEditProfile = () => {
), ),
}; };
}, [visible]); }, [visible]);
if (enableEditProfile === false) {
return null;
}
return result;
}; };

View File

@ -16,14 +16,17 @@ import {
useDataBlockRequest, useDataBlockRequest,
useDataBlockResource, useDataBlockResource,
useSchemaComponentContext, useSchemaComponentContext,
useRequest,
useAPIClient,
} from '@nocobase/client'; } from '@nocobase/client';
import React, { useEffect, useMemo } from 'react'; import React, { createContext, useEffect, useMemo, useContext } from 'react';
import { usersSchema } from './schemas/users'; import { App, Tabs, message } from 'antd';
import { useUsersTranslation } from './locale';
import { PasswordField } from './PasswordField';
import { App } from 'antd';
import { useForm } from '@formily/react'; import { useForm } from '@formily/react';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { css } from '@emotion/css';
import { usersSchema, usersSettingsSchema } from './schemas/users';
import { useUsersTranslation } from './locale';
import { PasswordField } from './PasswordField';
const useCancelActionProps = () => { const useCancelActionProps = () => {
const { setVisible } = useActionContext(); const { setVisible } = useActionContext();
@ -49,7 +52,6 @@ const useSubmitActionProps = () => {
async onClick() { async onClick() {
await form.submit(); await form.submit();
const values = form.values; const values = form.values;
console.log('values:', values);
if (values[collection.filterTargetKey]) { if (values[collection.filterTargetKey]) {
await resource.update({ await resource.update({
values, values,
@ -79,7 +81,7 @@ const useEditFormProps = () => {
}; };
}; };
export const UsersManagement: React.FC = () => { const UsersManagementTab: React.FC = () => {
const { t } = useUsersTranslation(); const { t } = useUsersTranslation();
const scCtx = useSchemaComponentContext(); const scCtx = useSchemaComponentContext();
return ( return (
@ -92,3 +94,81 @@ export const UsersManagement: React.FC = () => {
</SchemaComponentContext.Provider> </SchemaComponentContext.Provider>
); );
}; };
const UsersSettingsContext = createContext<any>({});
const UsersSettingsProvider = (props) => {
const result = useRequest({
url: 'systemSettings:get/1',
});
return <UsersSettingsContext.Provider value={result}>{props.children}</UsersSettingsContext.Provider>;
};
const UsersSettingsTab: React.FC = () => {
const { t } = useUsersTranslation();
const scCtx = useSchemaComponentContext();
const form = useForm();
const useFormBlockProps = () => {
const result = useContext(UsersSettingsContext);
const { enableChangePassword, enableEditProfile } = result?.data?.data || {};
useEffect(() => {
form?.setValues({
enableChangePassword: enableChangePassword !== false,
enableEditProfile: enableEditProfile !== false,
});
}, [result]);
return {
form: form,
};
};
const useSubmitActionProps = () => {
const api = useAPIClient();
const form = useForm();
return {
type: 'primary',
async onClick() {
await form.submit();
const values = form.values;
await api.request({ url: 'systemSettings:update/1', data: values, method: 'POST' });
message.success(t('Saved successfully'));
window.location.reload();
},
};
};
return (
<SchemaComponentContext.Provider value={{ ...scCtx, designable: false }}>
<SchemaComponent
schema={usersSettingsSchema}
scope={{ t, useFormBlockProps, useSubmitActionProps }}
components={{ UsersSettingsProvider }}
/>
</SchemaComponentContext.Provider>
);
};
export const UsersManagement: React.FC = () => {
const { t } = useUsersTranslation();
return (
<Tabs
defaultActiveKey="usersManager"
type="card"
className={css`
.ant-tabs-nav {
margin-bottom: 0px;
}
`}
items={[
{
label: t('Users manager'),
key: 'usersManager',
children: <UsersManagementTab />,
},
{
label: t('Settings'),
key: 'usersSettings',
children: <UsersSettingsTab />,
},
]}
/>
);
};

View File

@ -482,6 +482,57 @@ export const usersSchema: ISchema = {
}, },
}; };
export const usersSettingsSchema: ISchema = {
type: 'object',
'x-decorator': 'UsersSettingsProvider',
properties: {
usersSettings: {
type: 'void',
'x-component': 'CardItem',
'x-decorator': 'UsersSettingsProvider',
properties: {
form: {
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useFormBlockProps',
properties: {
enableEditProfile: {
type: 'string',
'x-component': 'Checkbox',
'x-decorator': 'FormItem',
default: true,
'x-content': '{{t("Allow edit profile")}}',
},
enableChangePassword: {
type: 'string',
'x-component': 'Checkbox',
'x-decorator': 'FormItem',
default: true,
'x-content': '{{t("Allow change password")}}',
},
},
},
footer: {
type: 'void',
'x-component': 'div',
'x-component-props': {
style: {
float: 'right',
},
},
properties: {
submit: {
title: 'Submit',
'x-component': 'Action',
'x-use-component-props': 'useSubmitActionProps',
},
},
},
},
},
},
};
export const getRoleUsersSchema = (): ISchema => ({ export const getRoleUsersSchema = (): ISchema => ({
type: 'void', type: 'void',
properties: { properties: {

View File

@ -3,5 +3,8 @@
"Add users": "添加用户", "Add users": "添加用户",
"Remove user": "移除用户", "Remove user": "移除用户",
"Are you sure you want to remove it?": "你确定要移除吗?", "Are you sure you want to remove it?": "你确定要移除吗?",
"Random password": "随机密码" "Random password": "随机密码",
"Users manager": "用户管理",
"Allow edit profile": "允许修改个人资料",
"Allow change password": "允许修改密码"
} }

View File

@ -0,0 +1,38 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { extendCollection } from '@nocobase/database';
export default extendCollection({
name: 'systemSettings',
fields: [
{
interface: 'checkbox',
type: 'boolean',
name: 'enableEditProfile',
uiSchema: {
type: 'boolean',
'x-component': 'Checkbox',
title: '{{t("Allow edit profile")}}',
default: true,
},
},
{
interface: 'checkbox',
type: 'boolean',
name: 'enableChangePassword',
uiSchema: {
type: 'boolean',
'x-component': 'Checkbox',
title: '{{t("Allow Change Password")}}',
default: true,
},
},
],
});