passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
563 lines (525 loc) • 17.9 kB
JavaScript
/**
* 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 "../../../../shared/context/AppContext/AppContext";
import { withDialog } from "../../../contexts/DialogContext";
import ContextualMenuWrapper from "../../Common/ContextualMenu/ContextualMenuWrapper";
import { withActionFeedback } from "../../../contexts/ActionFeedbackContext";
import EditUser from "../EditUser/EditUser";
import ConfirmDisableUserMFA from "../ConfirmDisableUserMFA/ConfirmDisableUserMFA";
import DeleteUserWithConflicts from "../DeleteUser/DeleteUserWithConflicts";
import DeleteUser from "../DeleteUser/DeleteUser";
import NotifyError from "../../Common/Error/NotifyError/NotifyError";
import { Trans, withTranslation } from "react-i18next";
import { withWorkflow } from "../../../contexts/WorkflowContext";
import HandleReviewAccountRecoveryRequestWorkflow from "../../AccountRecovery/HandleReviewAccountRecoveryRequestWorkflow/HandleReviewAccountRecoveryRequestWorkflow";
import LinkSVG from "../../../../img/svg/link.svg";
import EmailSVG from "../../../../img/svg/email.svg";
import KeySVG from "../../../../img/svg/key.svg";
import EditSVG from "../../../../img/svg/edit.svg";
import SendSVG from "../../../../img/svg/send.svg";
import FingerprintDisabledSVG from "../../../../img/svg/fingerprint_disabled.svg";
import DeleteSVG from "../../../../img/svg/delete.svg";
import BuoySVG from "../../../../img/svg/buoy.svg";
import MetadataKeySVG from "../../../../img/svg/metadata_key.svg";
import ConfirmShareMissingMetadataKeys from "../ConfirmShareMissingMetadataKeys/ConfirmShareMissingMetadataKeys";
import { withClipboard } from "../../../contexts/Clipboard/ManagedClipboardServiceProvider";
import { actions } from "../../../../shared/services/rbacs/actionEnumeration";
import { withRbac } from "../../../../shared/context/Rbac/RbacContext";
class DisplayUsersContextualMenu extends React.Component {
/**
* Constructor
* Initialize state and bind methods
*/
constructor(props) {
super(props);
this.bindCallbacks();
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handlePermalinkCopy = this.handlePermalinkCopy.bind(this);
this.handlePublicKeyCopy = this.handlePublicKeyCopy.bind(this);
this.handleUsernameCopy = this.handleUsernameCopy.bind(this);
this.handleEditClickEvent = this.handleEditClickEvent.bind(this);
this.handleResendInviteClickEvent = this.handleResendInviteClickEvent.bind(this);
this.handleDeleteClickEvent = this.handleDeleteClickEvent.bind(this);
this.handleDisableMfaEvent = this.handleDisableMfaEvent.bind(this);
this.handleReviewRecoveryRequestClickEvent = this.handleReviewRecoveryRequestClickEvent.bind(this);
this.handleShareMissingMetadataKeysEvent = this.handleShareMissingMetadataKeysEvent.bind(this);
}
/**
* Can the logged in user use the mfa capability.
*/
get canIUseMfa() {
return this.props.context.siteSettings.canIUse("multiFactorAuthentication") && this.isLoggedInUserAdmin();
}
/**
* Returns true if the currrent user can disable the MFA of a user
*/
get canDisableMfaForUser() {
return this.props.user.is_mfa_enabled;
}
/**
* the resource selected
* @returns {*}
*/
get user() {
return this.props.user;
}
/**
* 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.user && !this.user.active;
}
/**
* Handle the copy of user permalink
*/
async handlePermalinkCopy() {
const baseUrl = this.props.context.userSettings.getTrustedDomain();
const permalink = `${baseUrl}/app/users/view/${this.user.id}`;
await this.props.clipboardContext.copy(permalink, this.translate("The permalink has been copied to clipboard."));
this.props.hide();
}
/**
* Handle the copy of the username
*/
async handleUsernameCopy() {
const username = `${this.user.username}`;
await this.props.clipboardContext.copy(username, this.translate("The email has been copied to clipboard."));
}
/**
* Handle the copy of public key
*/
async handlePublicKeyCopy() {
const gpgkeyInfo = await this.props.context.port.request(
"passbolt.keyring.get-public-key-info-by-user",
this.user.id,
);
await this.props.clipboardContext.copy(
gpgkeyInfo.armored_key,
this.translate("The public key has been copied to clipboard."),
);
this.props.hide();
}
/**
* handle edit user
*/
handleEditClickEvent() {
const editUserDialogProps = {
id: this.user.id,
};
this.props.context.setContext({ editUserDialogProps });
this.props.dialogContext.open(EditUser);
this.props.hide();
}
/**
* Handle the will of resending an invite
*/
handleResendInviteClickEvent() {
this.resendInvite();
}
/**
* Handle the will of disable MFA for a user
*/
handleDisableMfaEvent() {
this.disableMFA();
}
/**
* Handle delete click event
*/
async handleDeleteClickEvent() {
try {
await this.props.context.port.request("passbolt.users.delete-dry-run", this.user.id);
this.displayDeleteUserDialog();
} catch (error) {
if (error.name === "DeleteDryRunError") {
this.displayDeleteUserWithConflictsDialog(error.errors);
} else {
this.handleError(error);
}
}
this.props.hide();
}
/**
* Display delete user dialog when there is not conflict to solve
*/
displayDeleteUserDialog() {
const deleteUserDialogProps = {
user: this.user,
};
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.user,
errors: errors,
};
this.props.context.setContext({ deleteUserWithConflictsDialogProps });
this.props.dialogContext.open(DeleteUserWithConflicts);
}
/**
* Handle review recovery request click event
*/
handleReviewRecoveryRequestClickEvent() {
const accountRecoveryRequestId = this.user.pending_account_recovery_request.id;
this.props.workflowContext.start(HandleReviewAccountRecoveryRequestWorkflow, { accountRecoveryRequestId });
this.props.hide();
}
/**
* Handle share missing metadata keys click event
*/
handleShareMissingMetadataKeysEvent() {
const shareMissingMetadataKeysDialogProps = {
user: this.user,
};
this.props.dialogContext.open(ConfirmShareMissingMetadataKeys, shareMissingMetadataKeysDialogProps);
this.props.hide();
}
/**
* Display error dialog
* @param error
*/
handleError(error) {
const errorDialogProps = {
error: error,
};
this.props.dialogContext.open(NotifyError, errorDialogProps);
}
/**
* Check if the user can use the edit capability.
* @returns {boolean}
*/
canIUseEdit() {
return this.isLoggedInUserAdmin();
}
/**
* Check if the user can use the delete capability.
* @param user An user
*/
canIUseDelete() {
return this.isLoggedInUserAdmin();
}
/**
* Check if the user can use the review recovery request capability.
*/
canIReviewAccountRecoveryRequest() {
return (
this.props.context.siteSettings.canIUse("accountRecovery") &&
this.props.rbacContext.canIUseAction(actions.ACCOUNT_RECOVERY_RESPONSE_CREATE) &&
this.props.rbacContext.canIUseAction(actions.ACCOUNT_RECOVERY_REQUEST_INDEX)
);
}
/**
* Check if the user can use the share missing data key capability.
*/
get canIShareMissingMetadataKeys() {
return (
this.props.context.siteSettings.canIUse("metadata") &&
this.isLoggedInUserAdmin() &&
this.props.context.loggedInUser.id !== this.user.id
);
}
/**
* Can delete the user. A user cannot deleted its own account.
* @returns {boolean}
*/
canDeleteUser() {
return this.props.context.loggedInUser.id !== this.user.id;
}
/**
* Check if the public key of the user could be copied.
* It's not the case if users are inactive as they don't have public key at this stage.
* @return {boolean}
*/
canCopyPublicKey() {
return this.user.active;
}
/**
* Has a pending account recovery for the user.
* @returns {boolean}
*/
hasPendingAccountRecoveryRequest() {
return this.user && Boolean(this.user.pending_account_recovery_request);
}
/**
* Has a pending account recovery for the user.
* @returns {boolean}
*/
hasMissingMetadataKeysRequest() {
return this.user && this.user.missing_metadata_key_ids?.length > 0;
}
/**
* 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.props.dialogContext.open(ConfirmDisableUserMFA);
this.props.hide();
}
/**
* Resend an invite to the given user
*/
resendInvite() {
this.props.context.port
.request("passbolt.users.resend-invite", this.user.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.props.hide();
}
/**
* Whenever the resend invite fails
* @param error An error
*/
onResendInviteFailure(error) {
const errorDialogProps = {
error: error,
};
this.props.hide();
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 (
<ContextualMenuWrapper hide={this.props.hide} left={this.props.left} top={this.props.top} className="floating">
<li key="copy-user-permalink" className="opened">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button className="link no-border" type="button" onClick={this.handlePermalinkCopy}>
<LinkSVG />
<span>
<Trans>Copy permalink</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
<li key="copy-public-key" className="opened">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
type="button"
onClick={this.handlePublicKeyCopy}
disabled={!this.canCopyPublicKey()}
className="link no-border"
>
<KeySVG />
<span>
<Trans>Copy public key</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
<li key="copy-username" className="separator-after opened">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button className="link no-border" type="button" onClick={this.handleUsernameCopy}>
<EmailSVG />
<span>
<Trans>Copy email address</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
{this.canIUseEdit() && (
<li key="edit-user" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button className="link no-border" type="button" id="edit" onClick={this.handleEditClickEvent}>
<EditSVG />
<span>
<Trans>Edit</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
{this.canIUseResend && (
<li key="resend-invite-user" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
type="button"
id="resend"
onClick={this.handleResendInviteClickEvent}
disabled={!this.canResendInviteToUser}
className="link no-border"
>
<SendSVG />
<span>
<Trans>Resend invite</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
{this.canIUseMfa && (
<li key="disable-user-mfa" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
type="button"
id="disable-mfa"
onClick={this.handleDisableMfaEvent}
disabled={!this.canDisableMfaForUser}
className="link no-border"
>
<FingerprintDisabledSVG />
<span>
<Trans>Disable MFA</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
{this.canIUseDelete() && (
<li key="delete-user" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
type="button"
id="delete"
onClick={this.handleDeleteClickEvent}
disabled={!this.canDeleteUser()}
className="link no-border"
>
<DeleteSVG />
<span>
<Trans>Delete</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
{this.canIReviewAccountRecoveryRequest() && (
<li key="review-recovery-user" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
type="button"
id="review-recovery"
onClick={this.handleReviewRecoveryRequestClickEvent}
disabled={!this.hasPendingAccountRecoveryRequest()}
className="link no-border"
>
<BuoySVG />
<span>
<Trans>Review recovery request</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
{this.canIShareMissingMetadataKeys && (
<li key="missing-metadata-keys" className="ready">
<div className="row">
<div className="main-cell-wrapper">
<div className="main-cell">
<button
id="share-metadata-keys"
type="button"
onClick={this.handleShareMissingMetadataKeysEvent}
disabled={!this.hasMissingMetadataKeysRequest()}
className="link no-border"
>
<MetadataKeySVG />
<span>
<Trans>Share metadata keys</Trans>
</span>
</button>
</div>
</div>
</div>
</li>
)}
</ContextualMenuWrapper>
);
}
}
DisplayUsersContextualMenu.propTypes = {
context: PropTypes.any, // The application context
hide: PropTypes.func, // Hide the contextual menu
left: PropTypes.number, // left position in px of the page
top: PropTypes.number, // top position in px of the page
workflowContext: PropTypes.any, // the workflow context
dialogContext: PropTypes.any, // the dialog context
rbacContext: PropTypes.any, // the rbac context
user: PropTypes.object, // user selected
actionFeedbackContext: PropTypes.any, // The action feedback context
clipboardContext: PropTypes.object, // the clipboard service
t: PropTypes.func, // The translation function
};
export default withAppContext(
withWorkflow(
withRbac(withDialog(withClipboard(withActionFeedback(withTranslation("common")(DisplayUsersContextualMenu))))),
),
);