passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
433 lines (403 loc) • 14.6 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 { withAdministrationWorkspace } from "../../../contexts/AdministrationWorkspaceContext";
import { Trans, withTranslation } from "react-i18next";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import { v4 as uuidv4 } from "uuid";
import DomainUtil from "../../../lib/Domain/DomainUtil";
import MapObject from "../../../lib/Map/MapObject";
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";
import { createSafePortal } from "../../../../shared/utils/portals";
import FileTextSVG from "../../../../img/svg/file_text.svg";
import DisplayAdministrationSelfRegistrationActions from "../DisplayAdministrationWorkspaceActions/DisplayAdministrationSelfRegistrationActions/DisplayAdministrationSelfRegistrationActions";
import AddSVG from "../../../../img/svg/add.svg";
import DeleteSVG from "../../../../img/svg/delete.svg";
/**
* 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.assertNotProfessionalDomains(this.allowedDomains), 300);
this.bindCallbacks();
}
/**
* ComponentDidMount
* Invoked immediately after component is inserted into the tree
* @return {void}
*/
async componentDidMount() {
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() {
const settings = await this.props.adminSelfRegistrationContext.findSettings();
this.setState({ isEnabled: settings.allowedDomains?.size > 0 });
this.assertNotProfessionalDomains(settings.allowedDomains);
this.validateForm();
}
/**
* We check for warnings and errors into the form
* @param {Map} allowedDomains
*/
assertNotProfessionalDomains(allowedDomains) {
const warnings = new Map();
allowedDomains?.forEach((value, key) => {
if (!DomainUtil.isProfessional(value)) {
warnings.set(key, "This is not a safe professional domain");
}
});
this.setState({ warnings });
}
/**
* setup settings for the first time
*/
setupSettings() {
// When disable we remove domains from UI, so if the use enable again we should populate existing setting to UI again
const currentSettings = new SelfRegistrationDomainsViewModel(
this.props.adminSelfRegistrationContext.getCurrentSettings(),
);
this.props.adminSelfRegistrationContext.setDomains(currentSettings);
this.assertNotProfessionalDomains(currentSettings.allowedDomains);
if (currentSettings.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
*/
shouldCheckWarnings() {
const isSaved = this.props.adminSelfRegistrationContext.isSaved();
if (isSaved) {
this.props.adminSelfRegistrationContext.setSaved(false);
this.assertNotProfessionalDomains(this.allowedDomains);
}
}
/**
* 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.assertNotProfessionalDomains(domains);
}
}
/**
* 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
*/
handleToggleClicked() {
const isEnabled = !this.state.isEnabled;
if (isEnabled) {
this.setupSettings();
} else {
this.props.adminSelfRegistrationContext.setDomains({ allowedDomains: new Map() });
this.props.adminSelfRegistrationContext.setErrors(new Map());
}
this.setState({ isEnabled });
}
/**
* Handle the click on the add button
*/
handleAddRowClick() {
this.addRow();
}
/**
* 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}
*/
validateForm() {
this.props.adminSelfRegistrationContext.validateForm();
}
/**
* we cannot delete a row if we have only one domaine
* @returns {boolean}
*/
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 main-column">
<div className="main-content">
<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.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 className="self-registration-form">
<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")}
/>
<button
type="button"
disabled={!this.canDelete()}
className="button-icon"
id={`delete-${key}`}
onClick={() => this.handleDeleteRow(key)}
>
<DeleteSVG />
</button>
</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">
<button type="button" onClick={this.handleAddRowClick}>
<AddSVG />
<span>
<Trans>Add</Trans>
</span>
</button>
</div>
</div>
)}
</div>
{this.props.adminSelfRegistrationContext.hasSettingsChanges() && (
<div className="warning message" id="self-registration-setting-overridden-banner">
<div>
<p>
<Trans>Don't forget to save your settings to apply your modification.</Trans>
</p>
</div>
</div>
)}
</div>
<DisplayAdministrationSelfRegistrationActions />
</>
{createSafePortal(
<div className="sidebar-help-section">
<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://passbolt.com/docs/admin/user-provisioning/self-registration/"
target="_blank"
rel="noopener noreferrer"
>
<FileTextSVG />
<span>
<Trans>Read the documentation</Trans>
</span>
</a>
</div>,
document.getElementById("administration-help-panel"),
)}
</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)),
),
),
);