From 6f842ef94a3cccd8db2f1ee8a358071122191bf9 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Thu, 25 Jun 2020 12:03:19 +0300 Subject: [PATCH] Refactor User Profile page and add extension points to it (#4996) * Move components specific to UserProfile page to corresponding folder * Split UserProfile page into components * Rename components, refine code a bit * Add some extension points * Fix margin --- client/app/components/users/UserEdit.jsx | 295 ------------------ client/app/components/users/UserShow.jsx | 63 ---- client/app/pages/users/UserProfile.jsx | 86 +++-- client/app/pages/users/UsersList.jsx | 3 +- .../app/pages/users/components/ApiKeyForm.jsx | 64 ++++ .../users/components}/CreateUserDialog.jsx | 0 .../users/components/EditableUserProfile.jsx | 37 +++ .../PasswordForm}/ChangePasswordDialog.jsx | 4 +- .../PasswordForm/PasswordLinkAlert.jsx | 45 +++ .../PasswordForm/PasswordResetForm.jsx | 37 +++ .../PasswordForm/ResendInvitationForm.jsx | 38 +++ .../users/components/PasswordForm/index.jsx | 37 +++ .../users/components/ReadOnlyUserProfile.jsx | 29 ++ .../components/ReadOnlyUserProfile.test.js} | 4 +- .../pages/users/components/ToggleUserForm.jsx | 53 ++++ .../app/pages/users/components/UserGroups.jsx | 29 ++ .../pages/users/components/UserInfoForm.jsx | 94 ++++++ .../ReadOnlyUserProfile.test.js.snap} | 4 +- client/app/pages/users/hooks/useUserGroups.js | 22 ++ 19 files changed, 547 insertions(+), 397 deletions(-) delete mode 100644 client/app/components/users/UserEdit.jsx delete mode 100644 client/app/components/users/UserShow.jsx create mode 100644 client/app/pages/users/components/ApiKeyForm.jsx rename client/app/{components/users => pages/users/components}/CreateUserDialog.jsx (100%) create mode 100644 client/app/pages/users/components/EditableUserProfile.jsx rename client/app/{components/users => pages/users/components/PasswordForm}/ChangePasswordDialog.jsx (98%) create mode 100644 client/app/pages/users/components/PasswordForm/PasswordLinkAlert.jsx create mode 100644 client/app/pages/users/components/PasswordForm/PasswordResetForm.jsx create mode 100644 client/app/pages/users/components/PasswordForm/ResendInvitationForm.jsx create mode 100644 client/app/pages/users/components/PasswordForm/index.jsx create mode 100644 client/app/pages/users/components/ReadOnlyUserProfile.jsx rename client/app/{components/users/UserShow.test.js => pages/users/components/ReadOnlyUserProfile.test.js} (77%) create mode 100644 client/app/pages/users/components/ToggleUserForm.jsx create mode 100644 client/app/pages/users/components/UserGroups.jsx create mode 100644 client/app/pages/users/components/UserInfoForm.jsx rename client/app/{components/users/__snapshots__/UserShow.test.js.snap => pages/users/components/__snapshots__/ReadOnlyUserProfile.test.js.snap} (93%) create mode 100644 client/app/pages/users/hooks/useUserGroups.js diff --git a/client/app/components/users/UserEdit.jsx b/client/app/components/users/UserEdit.jsx deleted file mode 100644 index 2bc81ae6..00000000 --- a/client/app/components/users/UserEdit.jsx +++ /dev/null @@ -1,295 +0,0 @@ -import React, { Fragment } from "react"; -import { includes, get } from "lodash"; -import Alert from "antd/lib/alert"; -import Button from "antd/lib/button"; -import Form from "antd/lib/form"; -import Modal from "antd/lib/modal"; -import Tag from "antd/lib/tag"; -import User from "@/services/user"; -import Group from "@/services/group"; -import { currentUser } from "@/services/auth"; -import { absoluteUrl } from "@/services/utils"; -import { UserProfile } from "../proptypes"; -import DynamicForm from "../dynamic-form/DynamicForm"; -import ChangePasswordDialog from "./ChangePasswordDialog"; -import InputWithCopy from "../InputWithCopy"; - -export default class UserEdit extends React.Component { - static propTypes = { - user: UserProfile.isRequired, - }; - - constructor(props) { - super(props); - this.state = { - user: this.props.user, - groups: [], - loadingGroups: true, - regeneratingApiKey: false, - sendingPasswordEmail: false, - resendingInvitation: false, - togglingUser: false, - }; - } - - componentDidMount() { - Group.query().then(groups => { - this.setState({ - groups: groups.map(({ id, name }) => ({ value: id, name })), - loadingGroups: false, - }); - }); - } - - changePassword = () => { - ChangePasswordDialog.showModal({ user: this.props.user }); - }; - - sendPasswordReset = () => { - this.setState({ sendingPasswordEmail: true }); - - User.sendPasswordReset(this.state.user) - .then(passwordLink => { - this.setState({ passwordLink }); - }) - .finally(() => { - this.setState({ sendingPasswordEmail: false }); - }); - }; - - resendInvitation = () => { - this.setState({ resendingInvitation: true }); - - User.resendInvitation(this.state.user) - .then(passwordLink => { - this.setState({ passwordLink }); - }) - .finally(() => { - this.setState({ resendingInvitation: false }); - }); - }; - - regenerateApiKey = () => { - const doRegenerate = () => { - this.setState({ regeneratingApiKey: true }); - User.regenerateApiKey(this.state.user) - .then(apiKey => { - if (apiKey) { - const { user } = this.state; - this.setState({ user: { ...user, apiKey } }); - } - }) - .finally(() => { - this.setState({ regeneratingApiKey: false }); - }); - }; - - Modal.confirm({ - title: "Regenerate API Key", - content: "Are you sure you want to regenerate?", - okText: "Regenerate", - onOk: doRegenerate, - maskClosable: true, - autoFocusButton: null, - }); - }; - - toggleUser = () => { - const { user } = this.state; - const toggleUser = user.isDisabled ? User.enableUser : User.disableUser; - - this.setState({ togglingUser: true }); - toggleUser(user) - .then(data => { - if (data) { - this.setState({ user: User.convertUserInfo(data) }); - } - }) - .finally(() => { - this.setState({ togglingUser: false }); - }); - }; - - saveUser = (values, successCallback, errorCallback) => { - const data = { - id: this.props.user.id, - ...values, - }; - - User.save(data) - .then(user => { - successCallback("Saved."); - this.setState({ user: User.convertUserInfo(user) }); - }) - .catch(error => { - errorCallback(get(error, "response.data.message", "Failed saving.")); - }); - }; - - renderUserInfoForm() { - const { user, groups, loadingGroups } = this.state; - - const formFields = [ - { - name: "name", - title: "Name", - type: "text", - initialValue: user.name, - }, - { - name: "email", - title: "Email", - type: "email", - initialValue: user.email, - }, - !user.isDisabled && currentUser.id !== user.id - ? { - name: "group_ids", - title: "Groups", - type: "select", - mode: "multiple", - options: groups, - initialValue: groups.filter(group => includes(user.groupIds, group.value)).map(group => group.value), - loading: loadingGroups, - placeholder: loadingGroups ? "Loading..." : "", - } - : { - name: "group_ids", - title: "Groups", - type: "content", - content: this.renderUserGroups(), - }, - ].map(field => ({ readOnly: user.isDisabled, required: true, ...field })); - - return ; - } - - renderUserGroups() { - const { user, groups, loadingGroups } = this.state; - - return loadingGroups ? ( - "Loading..." - ) : ( -
- {groups - .filter(group => includes(user.groupIds, group.value)) - .map(group => ( - - {group.name} - - ))} -
- ); - } - - renderApiKey() { - const { user, regeneratingApiKey } = this.state; - - return ( -
-
- - - - -
- ); - } - - renderPasswordLinkAlert() { - const { user, passwordLink } = this.state; - - return ( - -

- The mail server is not configured, please send the following link to {user.name}: -

- - - } - type="warning" - className="m-t-20" - afterClose={() => { - this.setState({ passwordLink: null }); - }} - closable - /> - ); - } - - renderResendInvitation() { - return ( - - ); - } - - renderSendPasswordReset() { - const { sendingPasswordEmail } = this.state; - - return ( - - - - ); - } - - rendertoggleUser() { - const { user, togglingUser } = this.state; - - return user.isDisabled ? ( - - ) : ( - - ); - } - - render() { - const { user, passwordLink } = this.state; - - return ( -
- Profile -

{user.name}

-
- {this.renderUserInfoForm()} - {!user.isDisabled && ( - - {this.renderApiKey()} -
-
Password
- {user.id === currentUser.id && ( - - )} - {currentUser.isAdmin && user.id !== currentUser.id && ( - - {user.isInvitationPending ? this.renderResendInvitation() : this.renderSendPasswordReset()} - {passwordLink && this.renderPasswordLinkAlert()} - - )} -
- )} -
- {currentUser.isAdmin && user.id !== currentUser.id && this.rendertoggleUser()} -
- ); - } -} diff --git a/client/app/components/users/UserShow.jsx b/client/app/components/users/UserShow.jsx deleted file mode 100644 index 7a69f445..00000000 --- a/client/app/components/users/UserShow.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; -import { includes } from "lodash"; -import Tag from "antd/lib/tag"; -import Group from "@/services/group"; -import { UserProfile } from "../proptypes"; - -export default class UserShow extends React.Component { - static propTypes = { - user: UserProfile.isRequired, - }; - - constructor(props) { - super(props); - this.state = { groups: [], loadingGroups: true }; - } - - componentDidMount() { - Group.query().then(groups => { - this.setState({ groups, loadingGroups: false }); - }); - } - - renderUserGroups() { - const { groupIds } = this.props.user; - const { groups } = this.state; - - return ( -
- {groups - .filter(group => includes(groupIds, group.id)) - .map(group => ( - - {group.name} - - ))} -
- ); - } - - render() { - const { name, email, profileImageUrl } = this.props.user; - const { loadingGroups } = this.state; - - return ( -
- profile - -

{name}

- -
- -
-
Name:
-
{name}
-
Email:
-
{email}
-
Groups:
-
{loadingGroups ? "Loading..." : this.renderUserGroups()}
-
-
- ); - } -} diff --git a/client/app/pages/users/UserProfile.jsx b/client/app/pages/users/UserProfile.jsx index d1a75545..64109141 100644 --- a/client/app/pages/users/UserProfile.jsx +++ b/client/app/pages/users/UserProfile.jsx @@ -1,52 +1,72 @@ -import React from "react"; +import React, { useState, useRef, useEffect } from "react"; import PropTypes from "prop-types"; import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession"; import EmailSettingsWarning from "@/components/EmailSettingsWarning"; -import UserEdit from "@/components/users/UserEdit"; -import UserShow from "@/components/users/UserShow"; +import DynamicComponent from "@/components/DynamicComponent"; import LoadingState from "@/components/items-list/components/LoadingState"; import wrapSettingsTab from "@/components/SettingsWrapper"; + import User from "@/services/user"; import { currentUser } from "@/services/auth"; + +import EditableUserProfile from "./components/EditableUserProfile"; +import ReadOnlyUserProfile from "./components/ReadOnlyUserProfile"; + import "./settings.less"; -class UserProfile extends React.Component { - static propTypes = { - userId: PropTypes.string, - onError: PropTypes.func, - }; +function UserProfile({ userId, onError }) { + const [user, setUser] = useState(null); - static defaultProps = { - userId: null, // defaults to `currentUser.id` - onError: () => {}, - }; + const onErrorRef = useRef(onError); + onErrorRef.current = onError; - constructor(props) { - super(props); - this.state = { user: null }; - } + useEffect(() => { + let isCancelled = false; + User.get({ id: userId || currentUser.id }) + .then(user => { + if (!isCancelled) { + setUser(User.convertUserInfo(user)); + } + }) + .catch(error => { + if (!isCancelled) { + onErrorRef.current(error); + } + }); - componentDidMount() { - const userId = this.props.userId || currentUser.id; - User.get({ id: userId }) - .then(user => this.setState({ user: User.convertUserInfo(user) })) - .catch(error => this.props.onError(error)); - } + return () => { + isCancelled = true; + }; + }, [userId]); - render() { - const { user } = this.state; - const canEdit = user && (currentUser.isAdmin || currentUser.id === user.id); - const UserComponent = canEdit ? UserEdit : UserShow; - return ( - - -
{user ? : }
-
- ); - } + const canEdit = user && (currentUser.isAdmin || currentUser.id === user.id); + return ( + + +
+ {!user && } + {user && ( + + {!canEdit && } + {canEdit && } + + )} +
+
+ ); } +UserProfile.propTypes = { + userId: PropTypes.string, + onError: PropTypes.func, +}; + +UserProfile.defaultProps = { + userId: null, // defaults to `currentUser.id` + onError: () => {}, +}; + const UserProfilePage = wrapSettingsTab( { title: "Account", diff --git a/client/app/pages/users/UsersList.jsx b/client/app/pages/users/UsersList.jsx index 3bb7e41c..a58f1877 100644 --- a/client/app/pages/users/UsersList.jsx +++ b/client/app/pages/users/UsersList.jsx @@ -20,7 +20,6 @@ import * as Sidebar from "@/components/items-list/components/Sidebar"; import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable"; import Layout from "@/components/layouts/ContentWithSidebar"; -import CreateUserDialog from "@/components/users/CreateUserDialog"; import wrapSettingsTab from "@/components/SettingsWrapper"; import { currentUser } from "@/services/auth"; @@ -30,6 +29,8 @@ import navigateTo from "@/components/ApplicationArea/navigateTo"; import notification from "@/services/notification"; import { absoluteUrl } from "@/services/utils"; +import CreateUserDialog from "./components/CreateUserDialog"; + function UsersListActions({ user, enableUser, disableUser, deleteUser }) { if (user.id === currentUser.id) { return null; diff --git a/client/app/pages/users/components/ApiKeyForm.jsx b/client/app/pages/users/components/ApiKeyForm.jsx new file mode 100644 index 00000000..d93fc71e --- /dev/null +++ b/client/app/pages/users/components/ApiKeyForm.jsx @@ -0,0 +1,64 @@ +import React, { useState, useRef, useCallback } from "react"; +import PropTypes from "prop-types"; +import Button from "antd/lib/button"; +import Form from "antd/lib/form"; +import Modal from "antd/lib/modal"; +import DynamicComponent from "@/components/DynamicComponent"; +import InputWithCopy from "@/components/InputWithCopy"; +import { UserProfile } from "@/components/proptypes"; +import User from "@/services/user"; + +export default function ApiKeyForm(props) { + const { user, onChange } = props; + + const [loading, setLoading] = useState(false); + const onChangeRef = useRef(onChange); + onChangeRef.current = onChange; + + const regenerateApiKey = useCallback(() => { + const doRegenerate = () => { + setLoading(true); + User.regenerateApiKey(user) + .then(apiKey => { + if (apiKey) { + onChangeRef.current({ ...user, apiKey }); + } + }) + .finally(() => { + setLoading(false); + }); + }; + + Modal.confirm({ + title: "Regenerate API Key", + content: "Are you sure you want to regenerate?", + okText: "Regenerate", + onOk: doRegenerate, + maskClosable: true, + autoFocusButton: null, + }); + }, [user]); + + return ( + +
+
+ + + + +
+
+ ); +} + +ApiKeyForm.propTypes = { + user: UserProfile.isRequired, + onChange: PropTypes.func, +}; + +ApiKeyForm.defaultProps = { + onChange: () => {}, +}; diff --git a/client/app/components/users/CreateUserDialog.jsx b/client/app/pages/users/components/CreateUserDialog.jsx similarity index 100% rename from client/app/components/users/CreateUserDialog.jsx rename to client/app/pages/users/components/CreateUserDialog.jsx diff --git a/client/app/pages/users/components/EditableUserProfile.jsx b/client/app/pages/users/components/EditableUserProfile.jsx new file mode 100644 index 00000000..5fa2d9d3 --- /dev/null +++ b/client/app/pages/users/components/EditableUserProfile.jsx @@ -0,0 +1,37 @@ +import React, { useState, useEffect } from "react"; +import { UserProfile } from "@/components/proptypes"; + +import UserInfoForm from "./UserInfoForm"; +import ApiKeyForm from "./ApiKeyForm"; +import PasswordForm from "./PasswordForm"; +import ToggleUserForm from "./ToggleUserForm"; + +export default function EditableUserProfile(props) { + const [user, setUser] = useState(props.user); + + useEffect(() => { + setUser(props.user); + }, [props.user]); + + return ( +
+ Profile +

{user.name}

+
+ + {!user.isDisabled && ( + + +
+ +
+ )} +
+ +
+ ); +} + +EditableUserProfile.propTypes = { + user: UserProfile.isRequired, +}; diff --git a/client/app/components/users/ChangePasswordDialog.jsx b/client/app/pages/users/components/PasswordForm/ChangePasswordDialog.jsx similarity index 98% rename from client/app/components/users/ChangePasswordDialog.jsx rename to client/app/pages/users/components/PasswordForm/ChangePasswordDialog.jsx index c49fccf8..7bd2304b 100644 --- a/client/app/components/users/ChangePasswordDialog.jsx +++ b/client/app/pages/users/components/PasswordForm/ChangePasswordDialog.jsx @@ -3,10 +3,10 @@ import React from "react"; import Form from "antd/lib/form"; import Modal from "antd/lib/modal"; import Input from "antd/lib/input"; +import { UserProfile } from "@/components/proptypes"; +import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import User from "@/services/user"; import notification from "@/services/notification"; -import { UserProfile } from "../proptypes"; -import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; class ChangePasswordDialog extends React.Component { static propTypes = { diff --git a/client/app/pages/users/components/PasswordForm/PasswordLinkAlert.jsx b/client/app/pages/users/components/PasswordForm/PasswordLinkAlert.jsx new file mode 100644 index 00000000..9ac738a1 --- /dev/null +++ b/client/app/pages/users/components/PasswordForm/PasswordLinkAlert.jsx @@ -0,0 +1,45 @@ +import { isString } from "lodash"; +import React from "react"; +import PropTypes from "prop-types"; +import Alert from "antd/lib/alert"; +import DynamicComponent from "@/components/DynamicComponent"; +import InputWithCopy from "@/components/InputWithCopy"; +import { UserProfile } from "@/components/proptypes"; +import { absoluteUrl } from "@/services/utils"; + +export default function PasswordLinkAlert(props) { + const { user, passwordLink, ...restProps } = props; + + if (!isString(passwordLink)) { + return null; + } + + return ( + + +

+ The mail server is not configured, please send the following link to {user.name}: +

+ + + } + type="warning" + className="m-t-20" + closable + {...restProps} + /> +
+ ); +} + +PasswordLinkAlert.propTypes = { + user: UserProfile.isRequired, + passwordLink: PropTypes.string, +}; + +PasswordLinkAlert.defaultProps = { + passwordLink: null, +}; diff --git a/client/app/pages/users/components/PasswordForm/PasswordResetForm.jsx b/client/app/pages/users/components/PasswordForm/PasswordResetForm.jsx new file mode 100644 index 00000000..1d7a04ad --- /dev/null +++ b/client/app/pages/users/components/PasswordForm/PasswordResetForm.jsx @@ -0,0 +1,37 @@ +import React, { useState, useCallback } from "react"; +import Button from "antd/lib/button"; +import DynamicComponent from "@/components/DynamicComponent"; +import { UserProfile } from "@/components/proptypes"; +import User from "@/services/user"; +import PasswordLinkAlert from "./PasswordLinkAlert"; + +export default function PasswordResetForm(props) { + const { user } = props; + + const [loading, setLoading] = useState(false); + const [passwordLink, setPasswordLink] = useState(null); + + const sendPasswordReset = useCallback(() => { + setLoading(true); + User.sendPasswordReset(user) + .then(passwordLink => { + setPasswordLink(passwordLink); + }) + .finally(() => { + setLoading(false); + }); + }, [user]); + + return ( + + + setPasswordLink(null)} /> + + ); +} + +PasswordResetForm.propTypes = { + user: UserProfile.isRequired, +}; diff --git a/client/app/pages/users/components/PasswordForm/ResendInvitationForm.jsx b/client/app/pages/users/components/PasswordForm/ResendInvitationForm.jsx new file mode 100644 index 00000000..45519d8d --- /dev/null +++ b/client/app/pages/users/components/PasswordForm/ResendInvitationForm.jsx @@ -0,0 +1,38 @@ +import React, { useState, useCallback } from "react"; +import Button from "antd/lib/button"; +import DynamicComponent from "@/components/DynamicComponent"; +import { UserProfile } from "@/components/proptypes"; +import User from "@/services/user"; +import PasswordLinkAlert from "./PasswordLinkAlert"; + +export default function ResendInvitationForm(props) { + const { user } = props; + + const [loading, setLoading] = useState(false); + const [passwordLink, setPasswordLink] = useState(null); + + const resendInvitation = useCallback(() => { + setLoading(true); + + User.resendInvitation(user) + .then(passwordLink => { + setPasswordLink(passwordLink); + }) + .finally(() => { + setLoading(false); + }); + }, [user]); + + return ( + + + setPasswordLink(null)} /> + + ); +} + +ResendInvitationForm.propTypes = { + user: UserProfile.isRequired, +}; diff --git a/client/app/pages/users/components/PasswordForm/index.jsx b/client/app/pages/users/components/PasswordForm/index.jsx new file mode 100644 index 00000000..8ed0cb8b --- /dev/null +++ b/client/app/pages/users/components/PasswordForm/index.jsx @@ -0,0 +1,37 @@ +import React, { useCallback } from "react"; +import Button from "antd/lib/button"; +import DynamicComponent from "@/components/DynamicComponent"; +import { UserProfile } from "@/components/proptypes"; +import { currentUser } from "@/services/auth"; + +import ChangePasswordDialog from "./ChangePasswordDialog"; +import PasswordResetForm from "./PasswordResetForm"; +import ResendInvitationForm from "./ResendInvitationForm"; + +export default function PasswordForm(props) { + const { user } = props; + + const changePassword = useCallback(() => { + ChangePasswordDialog.showModal({ user }); + }, [user]); + + return ( + +
Password
+ {user.id === currentUser.id && ( + + )} + {user.id !== currentUser.id && currentUser.isAdmin && ( + + {user.isInvitationPending ? : } + + )} +
+ ); +} + +PasswordForm.propTypes = { + user: UserProfile.isRequired, +}; diff --git a/client/app/pages/users/components/ReadOnlyUserProfile.jsx b/client/app/pages/users/components/ReadOnlyUserProfile.jsx new file mode 100644 index 00000000..27aa468d --- /dev/null +++ b/client/app/pages/users/components/ReadOnlyUserProfile.jsx @@ -0,0 +1,29 @@ +import React from "react"; +import { UserProfile } from "@/components/proptypes"; + +import UserGroups from "./UserGroups"; +import useUserGroups from "../hooks/useUserGroups"; + +export default function ReadOnlyUserProfile({ user }) { + const { groups, isLoading: isLoadingGroups } = useUserGroups(user); + + return ( +
+ profile +

{user.name}

+
+
+
Name:
+
{user.name}
+
Email:
+
{user.email}
+
Groups:
+
{isLoadingGroups ? "Loading..." : }
+
+
+ ); +} + +ReadOnlyUserProfile.propTypes = { + user: UserProfile.isRequired, +}; diff --git a/client/app/components/users/UserShow.test.js b/client/app/pages/users/components/ReadOnlyUserProfile.test.js similarity index 77% rename from client/app/components/users/UserShow.test.js rename to client/app/pages/users/components/ReadOnlyUserProfile.test.js index 0cd517bb..c2118bef 100644 --- a/client/app/components/users/UserShow.test.js +++ b/client/app/pages/users/components/ReadOnlyUserProfile.test.js @@ -1,7 +1,7 @@ import React from "react"; import renderer from "react-test-renderer"; import Group from "@/services/group"; -import UserShow from "./UserShow"; +import ReadOnlyUserProfile from "./ReadOnlyUserProfile"; beforeEach(() => { Group.query = jest.fn().mockResolvedValue([]); @@ -16,7 +16,7 @@ test("renders correctly", () => { profileImageUrl: "http://www.images.com/llama.jpg", }; - const component = renderer.create(); + const component = renderer.create(); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); diff --git a/client/app/pages/users/components/ToggleUserForm.jsx b/client/app/pages/users/components/ToggleUserForm.jsx new file mode 100644 index 00000000..63cee9e6 --- /dev/null +++ b/client/app/pages/users/components/ToggleUserForm.jsx @@ -0,0 +1,53 @@ +import React, { useState, useRef, useCallback } from "react"; +import PropTypes from "prop-types"; +import Button from "antd/lib/button"; +import DynamicComponent from "@/components/DynamicComponent"; +import { UserProfile } from "@/components/proptypes"; +import { currentUser } from "@/services/auth"; +import User from "@/services/user"; + +export default function ToggleUserForm(props) { + const { user, onChange } = props; + + const [loading, setLoading] = useState(false); + const onChangeRef = useRef(onChange); + onChangeRef.current = onChange; + + const toggleUser = useCallback(() => { + const action = user.isDisabled ? User.enableUser : User.disableUser; + setLoading(true); + action(user) + .then(data => { + if (data) { + onChangeRef.current(User.convertUserInfo(data)); + } + }) + .finally(() => { + setLoading(false); + }); + }, [user]); + + if (!currentUser.isAdmin || user.id === currentUser.id) { + return null; + } + + const buttonProps = { + type: user.isDisabled ? "primary" : "danger", + children: user.isDisabled ? "Enable User" : "Disable User", + }; + + return ( + +