passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
375 lines (343 loc) • 13 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 3.9.0
*/
import React from "react";
import PropTypes from "prop-types";
import Icon from "../../../../shared/components/Icons/Icon";
import {withAdministrationWorkspace} from "../../../contexts/AdministrationWorkspaceContext";
import {Trans, withTranslation} from "react-i18next";
import {withAppContext} from "../../../contexts/AppContext";
import {v4 as uuidv4} from "uuid";
import DomainUtil from "../../../lib/Domain/DomainUtil";
import MapObject from '../../../lib/Map/MapObject';
import DisplayAdministrationSelfRegistrationActions from "../DisplayAdministrationWorkspaceActions/DisplayAdministrationSelfRegistrationActions/DisplayAdministrationSelfRegistrationActions";
import {withAdminSelfRegistration} from "../../../contexts/Administration/AdministrationSelfRegistration/AdministrationSelfRegistrationContext";
import useDynamicRefs from "../../../lib/Map/DynamicRef";
import {withDialog} from "../../../contexts/DialogContext";
import SelfRegistrationDomainsViewModel from "../../../../shared/models/selfRegistration/SelfRegistrationDomainsViewModel";
import debounce from "debounce-promise";
/**
* This component allows to display the Self registration for the administration
*/
class DisplaySelfRegistrationAdministration extends React.Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.dynamicRefs = useDynamicRefs();
this.checkForPublicDomainDebounce = debounce(this.checkForWarnings, 300);
this.bindCallbacks();
}
/**
* ComponentDidMount
* Invoked immediately after component is inserted into the tree
* @return {void}
*/
async componentDidMount() {
this.props.administrationWorkspaceContext.setDisplayAdministrationWorkspaceAction(DisplayAdministrationSelfRegistrationActions);
await this.findSettings();
}
/**
* componentDidUpdate
* Invoked immediately after state or props is updated.
* It is used to focus on a field if needed or to show the advanced settings panel if needed.
*/
componentDidUpdate() {
this.shouldFocusOnError();
this.shouldCheckWarnings();
}
/**
* componentWillUnmount
* Use to clear the data from the form in case the user put something that needs to be cleared.
*/
componentWillUnmount() {
this.props.administrationWorkspaceContext.resetDisplayAdministrationWorkspaceAction();
this.props.adminSelfRegistrationContext.clearContext();
}
/**
* Get default state
* @returns {*}
*/
get defaultState() {
return {
// toggle state
isEnabled: false,
warnings: new Map()
};
}
/**
* Bind callbacks methods
*/
bindCallbacks() {
this.handleToggleClicked = this.handleToggleClicked.bind(this);
this.handleAddRowClick = this.handleAddRowClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleDeleteRow = this.handleDeleteRow.bind(this);
}
/**
* return the current user
*/
get currentUser() {
return this.props.context.loggedInUser;
}
/**
* return the allowed domains
*/
get allowedDomains() {
return this.props.adminSelfRegistrationContext.getAllowedDomains();
}
/**
* Bind callbacks methods
*/
async findSettings() {
await this.props.adminSelfRegistrationContext.findSettings();
this.setState({isEnabled: this.allowedDomains.size > 0});
await this.checkForWarnings();
await this.validateForm();
}
/**
* We check for warnings and errors into the form
*/
async checkForWarnings(callback = () => {}) {
this.setState({warnings: new Map()}, async() => {
this.allowedDomains.forEach((value, key) => this.checkDomainIsProfessional(key, value));
callback();
});
}
/**
* setup settings for the first time
*/
async setupSettings() {
// When disable we remove domains from UI, so if the use enable again we should populate existing setting to UI again
this.props.adminSelfRegistrationContext.setDomains(new SelfRegistrationDomainsViewModel(this.props.adminSelfRegistrationContext.getCurrentSettings()));
await this.checkForWarnings();
if (this.allowedDomains.size === 0) {
const domain = DomainUtil.extractDomainFromEmail(this.currentUser?.username);
DomainUtil.checkDomainValidity(domain);
this.populateUserDomain(domain);
}
}
/**
* set focus to the first input error
*/
shouldFocusOnError() {
const onFocus = this.props.adminSelfRegistrationContext.shouldFocus();
const [error] = this.props.adminSelfRegistrationContext.getErrors().keys();
if (error && onFocus) {
const inputRef = this.dynamicRefs.getRef(error);
inputRef.current.focus();
this.props.adminSelfRegistrationContext.setFocus(false);
}
}
/**
* in case of saved settings we should check warnings again
*/
async shouldCheckWarnings() {
const isSaved = this.props.adminSelfRegistrationContext.isSaved();
if (isSaved) {
this.props.adminSelfRegistrationContext.setSaved(false);
await this.checkForWarnings();
}
}
/**
* Check domain and populate it if it is a professional
* @param {string} domain
*/
populateUserDomain(domain) {
const row = DomainUtil.isProfessional(domain) ? domain : "";
this.addRow(row);
}
/**
* Check domain and populate it if is a professional domain
* @param {string} domain
*/
addRow(value = "") {
const uuid = uuidv4();
this.props.adminSelfRegistrationContext.setAllowedDomains(uuid, value, () => {
const inputRef = this.dynamicRefs.getRef(uuid);
inputRef?.current.focus();
});
}
/**
* Remove a domain row
* @param {string} key
*/
handleDeleteRow(key) {
if (this.canDelete()) {
const domains = this.allowedDomains;
domains.delete(key);
this.props.adminSelfRegistrationContext.setDomains({allowedDomains: domains});
this.validateForm();
this.checkForWarnings();
}
}
/**
* Check if inputs has warnings
*/
hasWarnings() {
return this.state.warnings.size > 0;
}
/**
* Should input be disabled? True if state is loading or processing
* @returns {boolean}
*/
hasAllInputDisabled() {
return this.props.adminSelfRegistrationContext.isProcessing();
}
/**
* Handle the click on the self registration title
* @param {UserDirectory} userDirectory state
*/
handleToggleClicked() {
this.setState({isEnabled: !this.state.isEnabled}, () => {
if (this.state.isEnabled) {
this.setupSettings();
} else {
this.props.adminSelfRegistrationContext.setDomains({allowedDomains: new Map()});
this.props.adminSelfRegistrationContext.setErrors(new Map());
}
});
}
/**
* Handle the click on the add button
*/
handleAddRowClick() {
this.addRow();
}
/**
* check if domain is a professional one
*/
checkDomainIsProfessional(uuid, value) {
this.setState(prevState => {
const warnings = MapObject.clone(prevState.warnings);
if (!DomainUtil.isProfessional(value)) {
warnings.set(uuid, "This is not a safe professional domain");
} else {
warnings.delete(uuid);
}
return {warnings};
});
}
/**
* Handle input change
* @param event
*/
handleInputChange(event) {
const value = event.target.value;
const uuid = event.target.name;
this.props.adminSelfRegistrationContext.setAllowedDomains(uuid, value, () => this.validateForm());
this.checkForPublicDomainDebounce();
}
/**
* validate the form
* @returns {void}
*/
async validateForm() {
await this.props.adminSelfRegistrationContext.validateForm();
}
/**
* we cannot delete a row if we have only one domaine
* @returns {void}
*/
canDelete() {
return this.allowedDomains.size > 1;
}
/**
* Render the component
* @returns {JSX}
*/
render() {
const isSubmitted = this.props.adminSelfRegistrationContext.isSubmitted();
const errors = this.props.adminSelfRegistrationContext.getErrors();
return (
<div className="row">
<div className="self-registration col7 main-column">
<h3>
<span className="input toggle-switch form-element">
<input type="checkbox" className="toggle-switch-checkbox checkbox" name="settings-toggle"
onChange={this.handleToggleClicked} checked={this.state.isEnabled} disabled={this.hasAllInputDisabled()}
id="settings-toggle"/>
<label htmlFor="settings-toggle"><Trans>Self Registration</Trans></label>
</span>
</h3>
{this.props.adminSelfRegistrationContext.hasSettingsChanges() &&
<div className="warning message" id="self-registration-setting-overridden-banner">
<p>
<Trans>Don't forget to save your settings to apply your modification.</Trans>
</p>
</div>
}
{!this.state.isEnabled &&
<p className="description" id="disabled-description">
<Trans>User self registration is disabled.</Trans> <Trans>Only administrators can invite users to register.</Trans>
</p>
}
{this.state.isEnabled && <>
<div id="self-registration-subtitle" className={`input ${this.hasWarnings() && "warning"} ${isSubmitted && errors.size > 0 && "error"}`}>
<label id="enabled-label">
<Trans>Email domain safe list</Trans>
</label>
</div>
<p className="description" id="enabled-description">
<Trans>All the users with an email address ending with the domain in the safe list are allowed to register on passbolt.</Trans>
</p>
{
MapObject.iterators(this.allowedDomains).map(key => (
<div key={key} className="input">
<div className="domain-row">
<input type="text" className="full-width" onChange={this.handleInputChange} id={`input-${key}`} name={key} value={this.allowedDomains.get(key)}
disabled={!this.hasAllInputDisabled} ref={this.dynamicRefs.setRef(key)} placeholder={this.props.t("domain")} />
<a className={`button button-icon ${this.canDelete() || 'disabled'}`} id={`delete-${key}`} onClick={() => this.handleDeleteRow(key)}><Icon name="trash"/></a>
</div>
{this.hasWarnings() && this.state.warnings.get(key) &&
<div id="domain-name-input-feedback" className="warning-message"><Trans>{this.state.warnings.get(key)}</Trans></div>
}
{(errors.get(key) && isSubmitted) &&
<div className="error-message"><Trans>{errors.get(key)}</Trans></div>
}
</div>
))
}
<div className="domain-add">
<a className="button" onClick={this.handleAddRowClick}>
<Icon name="add"/>
<span><Trans>Add</Trans></span>
</a>
</div>
</>}
</div>
<div className="col4 last">
<div className="sidebar-help">
<h3><Trans>What is user self registration?</Trans></h3>
<p><Trans>User self registration enables users with an email from a whitelisted domain to create their passbolt account without prior admin invitation.</Trans></p>
<a className="button" href="https://help.passbolt.com/configure/self-registration" target="_blank" rel="noopener noreferrer">
<Icon name="document"/>
<span><Trans>Read the documentation</Trans></span>
</a>
</div>
</div>
</div>
);
}
}
DisplaySelfRegistrationAdministration.propTypes = {
dialogContext: PropTypes.any, // The dialog context
context: PropTypes.any, // The application context
adminSelfRegistrationContext: PropTypes.object, // The user directory workspace context
administrationWorkspaceContext: PropTypes.object, // The administration workspace context
t: PropTypes.func, // The translation function
};
export default withAppContext(withDialog(withAdminSelfRegistration(withAdministrationWorkspace(withTranslation('common')(DisplaySelfRegistrationAdministration)))));