UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

510 lines (464 loc) 15.7 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 2.13.0 */ import React from "react"; import PropTypes from "prop-types"; import {withAppContext} from "../../../contexts/AppContext"; import Icon from "../../../../shared/components/Icons/Icon"; import {withUserWorkspace} from "../../../contexts/UserWorkspaceContext"; import EditUser from "../EditUser/EditUser"; import {withDialog} from "../../../contexts/DialogContext"; import DeleteUser from "../DeleteUser/DeleteUser"; import DeleteUserWithConflicts from "../DeleteUser/DeleteUserWithConflicts"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import {withActionFeedback} from "../../../contexts/ActionFeedbackContext"; import ConfirmDisableUserMFA from "../ConfirmDisableUserMFA/ConfirmDisableUserMFA"; import {Trans, withTranslation} from "react-i18next"; import HandleReviewAccountRecoveryRequestWorkflow from "../../AccountRecovery/HandleReviewAccountRecoveryRequestWorkflow/HandleReviewAccountRecoveryRequestWorkflow"; import {withWorkflow} from "../../../contexts/WorkflowContext"; import ClipBoard from '../../../../shared/lib/Browser/clipBoard'; /** * This component is a container of multiple actions applicable on user */ class DisplayUserWorkspaceActions extends React.Component { /** * Constructor * @param {Object} props */ constructor(props) { super(props); this.state = this.defaultState; this.bindCallbacks(); this.createRefs(); } /** * Returns the component default state */ get defaultState() { return { moreMenuOpen: false // Display flag for the more button menu }; } /** * Bind callbacks methods */ bindCallbacks() { this.handleDocumentClickEvent = this.handleDocumentClickEvent.bind(this); this.handleDetailsLockedEvent = this.handleDetailsLockedEvent.bind(this); this.handleEditClickEvent = this.handleEditClickEvent.bind(this); this.handleDeleteClickEvent = this.handleDeleteClickEvent.bind(this); this.handleMoreClickEvent = this.handleMoreClickEvent.bind(this); this.handleDisableMfaEvent = this.handleDisableMfaEvent.bind(this); this.handleCopyPermalinkEvent = this.handleCopyPermalinkEvent.bind(this); this.handleResendInviteClickEvent = this.handleResendInviteClickEvent.bind(this); this.handleReviewRecoveryRequestEvent = this.handleReviewRecoveryRequestEvent.bind(this); } /** * Create DOM nodes or React elements references in order to be able to access them programmatically. */ createRefs() { this.moreMenuRef = React.createRef(); } /** * Whenever the component is mounted */ componentDidMount() { document.addEventListener('click', this.handleDocumentClickEvent, {capture: true}); } /** * Whenever the component will unmount */ componentWillUnmount() { document.removeEventListener('click', this.handleDocumentClickEvent, {capture: true}); } /** * Returns true if the current user can delete the current selected user */ get canDelete() { const isNotCurrentUser = this.selectedUser && this.props.context.loggedInUser.id !== this.selectedUser.id; return !this.isButtonDisabled() && isNotCurrentUser; } /** * Handle view detail click event */ handleDetailsLockedEvent() { // lock or unlock the detail resource or folder this.props.userWorkspaceContext.onDetailsLocked(); } /** * Handle the will of copying the user permalink */ handleCopyPermalinkEvent() { this.copyPermalink(); } /** * Handle the will of */ handleResendInviteClickEvent() { this.resendInvite(); } /** * Has lock for the detail display * @returns {boolean} */ hasDetailsLocked() { return this.props.userWorkspaceContext.details.locked; } /** * Handle edit click event */ handleEditClickEvent() { const editUserDialogProps = { id: this.selectedUser.id }; this.props.context.setContext({editUserDialogProps}); this.props.dialogContext.open(EditUser); } /** * Handle delete click event */ async handleDeleteClickEvent() { try { await this.props.context.port.request("passbolt.users.delete-dry-run", this.selectedUser.id); this.displayDeleteUserDialog(); } catch (error) { if (error.name === "DeleteDryRunError") { this.displayDeleteUserWithConflictsDialog(error.errors); } else { this.handleError(error); } } } /** * Display delete user dialog when there is not conflict to solve */ displayDeleteUserDialog() { const deleteUserDialogProps = { user: this.selectedUser }; this.props.context.setContext({deleteUserDialogProps}); this.props.dialogContext.open(DeleteUser); } /** * Display delete user dialog when there is conflict to solve. */ displayDeleteUserWithConflictsDialog(errors) { const deleteUserWithConflictsDialogProps = { user: this.selectedUser, errors: errors, }; this.props.context.setContext({deleteUserWithConflictsDialogProps}); this.props.dialogContext.open(DeleteUserWithConflicts); } /** * Display error dialog * @param error */ handleError(error) { const errorDialogProps = { message: error.message }; this.props.context.setContext({errorDialogProps}); this.props.dialogContext.open(NotifyError); } /** * Handle the open or close the more menu */ handleMoreClickEvent() { this.toggleMoreMenu(); } /** * Handle the will of disable MFA for a user */ handleDisableMfaEvent() { this.disableMFA(); } /** * Handle click events on document. Hide the component if the click occurred outside of the component. * @param {ReactEvent} event The event */ handleDocumentClickEvent(event) { // Prevent closing when the user click on an element of the menu if (this.moreMenuRef.current && this.moreMenuRef.current.contains(event.target)) { return; } this.closeMoreMenu(); } /** * Handle review recovery request click event */ handleReviewRecoveryRequestEvent() { this.closeMoreMenu(); const accountRecoveryRequestId = this.selectedUser.pending_account_recovery_request.id; this.props.workflowContext.start(HandleReviewAccountRecoveryRequestWorkflow, {accountRecoveryRequestId}); } /** * Get selected user * @returns {user|null} */ get selectedUser() { return this.props.userWorkspaceContext.selectedUsers[0]; } /** * Returns true if the more actions are available */ get hasMoreActionAllowed() { return this.hasOneUserSelected(); } /** * Returns true if the current user can copy a user permalink */ get canCopyPermalink() { return Boolean(this.selectedUser); } /** * Can the logged in user use the mfa capability. */ get canIUseMfa() { return this.props.context.siteSettings.canIUse("multiFactorAuthentication"); } /** * Returns true if the current user can disable the MFA of a user */ get canDisableMfaForUser() { return this.selectedUser && this.selectedUser.is_mfa_enabled; } /** * Returns true if the logged in user can use the resend capability. */ get canIUseResend() { return this.isLoggedInUserAdmin(); } /** * Returns true if the logged in user can resend an invite to the user */ get canResendInviteToUser() { return this.selectedUser && !this.selectedUser.active; } /** * Check if the user can use the review recovery request capability. */ canIReviewAccountRecoveryRequest() { return this.props.context.siteSettings.canIUse("accountRecovery") && this.isLoggedInUserAdmin(); } /** * Has a pending account recovery for the user. * @returns {boolean} */ hasPendingAccountRecoveryRequest() { return this.selectedUser && Boolean(this.selectedUser.pending_account_recovery_request); } /** * Check if the users workspace has one user selected. * @return {boolean} */ hasOneUserSelected() { return this.props.userWorkspaceContext.selectedUsers.length === 1; } /** * Check if the button is disabled. * @returns {boolean} */ isButtonDisabled() { return !this.hasOneUserSelected(); } /** * Can update the resource * @returns {boolean} */ isLoggedInUserAdmin() { return this.props.context.loggedInUser && this.props.context.loggedInUser.role.name === "admin"; } /** * Disable the selected user's MFA */ disableMFA() { this.closeMoreMenu(); this.props.dialogContext.open(ConfirmDisableUserMFA); } /** * Toggles the more menu */ toggleMoreMenu() { const moreMenuOpen = !this.state.moreMenuOpen; this.setState({moreMenuOpen}); } /** * Close the more menu */ closeMoreMenu() { this.setState({moreMenuOpen: false}); } /** * Copy the user permalink */ async copyPermalink() { this.closeMoreMenu(); const baseUrl = this.props.context.userSettings.getTrustedDomain(); const permalink = `${baseUrl}/app/users/view/${this.selectedUser.id}`; await ClipBoard.copy(permalink, this.props.context.port); this.props.actionFeedbackContext.displaySuccess(this.translate("The permalink has been copied to clipboard")); } /** * Resend an invite to the given user */ resendInvite() { this.props.context.port.request('passbolt.users.resend-invite', this.selectedUser.username) .then(this.onResendInviteSuccess.bind(this)) .catch(this.onResendInviteFailure.bind(this)); } /** * Whenever the resend invite succeeds */ onResendInviteSuccess() { this.props.actionFeedbackContext.displaySuccess(this.translate("The invite has been resent successfully")); this.toggleMoreMenu(); } /** * Whenever the resend invite fails * @param error An error */ onResendInviteFailure(error) { const errorDialogProps = { error: error }; this.toggleMoreMenu(); this.props.dialogContext.open(NotifyError, errorDialogProps); } /** * Get the translate function * @returns {function(...[*]=)} */ get translate() { return this.props.t; } /** * Render the component * @returns {JSX} */ render() { return ( <div className="col2_3 actions-wrapper"> <div className="actions"> {this.isLoggedInUserAdmin() && <ul> <li> <a className={`button ${this.isButtonDisabled() ? "disabled" : ""}`} onClick={this.handleEditClickEvent}> <Icon name="edit"/> <span><Trans>Edit</Trans></span> </a> </li> <li> <a className={`button ${!this.canDelete ? "disabled" : ""}`} onClick={this.handleDeleteClickEvent}> <Icon name="trash"/> <span><Trans>Delete</Trans></span> </a> </li> <div className="dropdown" ref={this.moreMenuRef}> <a className={`button more ${this.state.moreMenuOpen ? "open" : ""} ${this.hasMoreActionAllowed ? "" : "disabled"}`} onClick={this.handleMoreClickEvent}> <span><Trans>More</Trans></span> <Icon name="caret-down"/> </a> <ul className={`dropdown-content menu right ${this.state.moreMenuOpen ? "visible" : ""}`}> <li id="copy-user-permalink" className="separator-after"> <div className="row"> <div className="main-cell-wrapper"> <div className="main-cell"> <a onClick={this.handleCopyPermalinkEvent} className={`${this.canCopyPermalink ? "" : "disabled"}`}> <span><Trans>Copy permalink to clipboard</Trans></span> </a> </div> </div> </div> </li> {this.canIUseResend && <li id="resend-invite-user" className="separator-after"> <div className="row"> <div className="main-cell-wrapper"> <div className="main-cell"> <a onClick={this.handleResendInviteClickEvent} className={`${this.canResendInviteToUser ? "" : "disabled"}`}> <span><Trans>Resend invite</Trans></span> </a> </div> </div> </div> </li> } {this.canIUseMfa && <li id="disable-mfa-action" className=""> <div className="row"> <div className="main-cell-wrapper"> <div className="main-cell"> <a id="disable-mfa" onClick={this.handleDisableMfaEvent} className={this.canDisableMfaForUser ? '' : 'disabled'}> <span><Trans>Disable MFA</Trans></span> </a> </div> </div> </div> </li> } {this.canIReviewAccountRecoveryRequest() && <li key="review-recovery-user"> <div className="row"> <div className="main-cell-wrapper"> <div className="main-cell"> <a id="review-recovery" onClick={this.handleReviewRecoveryRequestEvent} className={`${!this.hasPendingAccountRecoveryRequest() ? "disabled" : ""}`}> <span><Trans>Review recovery request</Trans></span> </a> </div> </div> </div> </li> } </ul> </div> </ul> } </div> <div className="actions secondary"> <ul> <li> <a className={`button button-toggle info ${this.hasDetailsLocked() ? "selected" : ""}`} onClick={this.handleDetailsLockedEvent}> <Icon name="info-circle" big={true}/> <span className="visuallyhidden"><Trans>View detail</Trans></span> </a> </li> </ul> </div> </div> ); } } DisplayUserWorkspaceActions.propTypes = { context: PropTypes.any, // The application context userWorkspaceContext: PropTypes.any, // the user workspace context workflowContext: PropTypes.any, // the workflow context dialogContext: PropTypes.any, // the dialog context actionFeedbackContext: PropTypes.object, // the action feeedback context t: PropTypes.func, // The translation function }; export default withAppContext(withActionFeedback(withWorkflow(withDialog(withUserWorkspace(withTranslation('common')(DisplayUserWorkspaceActions))))));