passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
887 lines (850 loc) • 36.6 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) 2022 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) 2022 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.8.0
*/
import React from "react";
import PropTypes from "prop-types";
import { Trans, withTranslation } from "react-i18next";
import { withAppContext } from "../../../../shared/context/AppContext/AppContext";
import { withAdministrationWorkspace } from "../../../contexts/AdministrationWorkspaceContext";
import { withDialog } from "../../../contexts/DialogContext";
import SmtpProviders from "./SmtpProviders.data";
import Password from "../../../../shared/components/Password/Password";
import Select from "../../Common/Select/Select";
import { withAdminSmtpSettings } from "../../../contexts/AdminSmtpSettingsContext";
import SmtpSettingsFormEntity from "../../../../shared/models/entity/smtpSettings/smtpSettingsFormEntity";
import DisplayAdministrationSmtpSettingsActions from "../DisplayAdministrationWorkspaceActions/DisplayAdministrationSmtpSettingsActions/DisplayAdministrationSmtpSettingsActions";
import { createSafePortal } from "../../../../shared/utils/portals";
import CaretDownSVG from "../../../../img/svg/caret_down.svg";
import CaretRightSVG from "../../../../img/svg/caret_right.svg";
import EmailSVG from "../../../../img/svg/email.svg";
import FileTextSVG from "../../../../img/svg/file_text.svg";
import LinkSVG from "../../../../img/svg/link.svg";
/*
* Supported authentication methods.
*/
const AUTHENTICATION_METHOD_NONE = SmtpSettingsFormEntity.AUTHENTICATION_METHOD_NONE;
const AUTHENTICATION_METHOD_USERNAME = SmtpSettingsFormEntity.AUTHENTICATION_METHOD_USERNAME;
const AUTHENTICATION_METHOD_USERNAME_PASSWORD = SmtpSettingsFormEntity.AUTHENTICATION_METHOD_USERNAME_PASSWORD;
const AUTHENTICATION_METHOD_OAUTH = SmtpSettingsFormEntity.AUTHENTICATION_METHOD_OAUTH;
export class ManageSmtpAdministrationSettings extends React.Component {
/**
* The no authentication method.
* @returns {string}
*/
static get AUTHENTICATION_METHOD_NONE() {
return AUTHENTICATION_METHOD_NONE;
}
/**
* The authentication method username only
* @returns {string}
*/
static get AUTHENTICATION_METHOD_USERNAME() {
return AUTHENTICATION_METHOD_USERNAME;
}
/**
* The authentication method username and password
* @returns {string}
*/
static get AUTHENTICATION_METHOD_USERNAME_PASSWORD() {
return AUTHENTICATION_METHOD_USERNAME_PASSWORD;
}
/**
* The authentication method OAuth2 Client Credentials
* @returns {string}
*/
static get AUTHENTICATION_METHOD_OAUTH() {
return AUTHENTICATION_METHOD_OAUTH;
}
/**
* Constructor
* @param {Object} props
* @constructor
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.bindCallbacks();
this.createRefs();
}
/**
* Get default state
* @returns {Object}
*/
get defaultState() {
return {
showAdvancedSettings: false,
initialized: false,
source: "db",
};
}
/**
* Creates the React ref for the forms to be able to focus fields having errors
*/
createRefs() {
this.usernameFieldRef = React.createRef();
this.passwordFieldRef = React.createRef();
this.hostFieldRef = React.createRef();
this.portFieldRef = React.createRef();
this.clientFieldRef = React.createRef();
this.senderEmailFieldRef = React.createRef();
this.senderNameFieldRef = React.createRef();
this.oauth_usernameFieldRef = React.createRef();
this.tenant_idFieldRef = React.createRef();
this.client_idFieldRef = React.createRef();
this.client_secretFieldRef = React.createRef();
}
/**
* ComponentDidMount
* Invoked immediately after component is inserted into the tree
* @return {void}
*/
componentDidMount() {
this.props.administrationWorkspaceContext.setDisplayAdministrationWorkspaceAction(
DisplayAdministrationSmtpSettingsActions,
);
this.props.adminSmtpSettingsContext.findSmtpSettings();
}
/**
* 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.adminSmtpSettingsContext.clearContext();
}
/**
* 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() {
const smtpContext = this.props.adminSmtpSettingsContext;
const settings = smtpContext.getCurrentSmtpSettings();
// When data becomes loaded for the first time, set showAdvancedSettings based on provider
if (smtpContext.isDataReady() && !this.state.initialized) {
this.setState({
initialized: true,
showAdvancedSettings: settings?.provider === "other",
});
}
const fieldToFocus = smtpContext.getFieldToFocus();
if (fieldToFocus) {
this[`${fieldToFocus}FieldRef`]?.current?.focus();
}
if (smtpContext.hasProviderChanged()) {
this.setState({ showAdvancedSettings: settings?.provider === "other" });
}
}
/**
* Bind callbacks
*/
bindCallbacks() {
this.handleAdvancedSettingsToggle = this.handleAdvancedSettingsToggle.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleProviderChange = this.handleProviderChange.bind(this);
this.handleAuthenticationMethodChange = this.handleAuthenticationMethodChange.bind(this);
}
/**
* Handle provider change from select field.
* @params {ReactEvent} The react event
*/
handleProviderChange(event) {
const providerId = event.target.value;
const provider = SmtpProviders.find((item) => item.id === providerId);
this.props.adminSmtpSettingsContext.changeProvider(provider);
}
/**
* Handle provider change from select field.
* @params {ReactEvent} The react event
*/
handleAuthenticationMethodChange(event) {
this.props.adminSmtpSettingsContext.changeAuthenticationMethod(event.target.value);
}
/**
* Handle form input changes.
* @params {ReactEvent} The react event
* @returns {void}
*/
handleInputChange(event) {
const target = event.target;
this.props.adminSmtpSettingsContext.setData({ [target.name]: target.value });
}
/**
* Handles the click on the advanced settings toggle.
*/
handleAdvancedSettingsToggle() {
this.setState({ showAdvancedSettings: !this.state.showAdvancedSettings });
}
/**
* Returns true if there is a processing happening with the SMTP settings.
* @returns {boolean}
*/
isProcessing() {
return this.props.adminSmtpSettingsContext.isProcessing();
}
/**
* Return the list of available providers.
* @returns {Array<object>}
*/
get providerList() {
return SmtpProviders.map((item) => ({
value: item.id,
label: item.name,
}));
}
/**
* Return the list of available authentication method.
* @returns {Array<object>}
*/
get authenticationMethodList() {
const settings = this.props.adminSmtpSettingsContext.getCurrentSmtpSettings();
const list = [
{ value: AUTHENTICATION_METHOD_NONE, label: this.translate("None") },
{ value: AUTHENTICATION_METHOD_USERNAME, label: this.translate("Username only") },
{ value: AUTHENTICATION_METHOD_USERNAME_PASSWORD, label: this.translate("Username & password") },
];
if (settings?.provider === "office-365") {
list.push({ value: AUTHENTICATION_METHOD_OAUTH, label: this.translate("OAuth (Client Credentials Grant)") });
}
return list;
}
/**
* Returns a list of available choice for the 'TLS' select field.
* @returns {Array<object>}
*/
get tlsSelectList() {
return [
{
value: true,
label: this.translate("Yes"),
},
{
value: false,
label: this.translate("No"),
},
];
}
/**
* Return the selected authentication method.
* @return {string}
*/
get authenticationMethod() {
return this.props.adminSmtpSettingsContext.getAuthenticationMethod();
}
/**
* Return true if the username field should be displayed
* @return {boolean}
*/
shouldDisplayUsername() {
return (
this.authenticationMethod === AUTHENTICATION_METHOD_USERNAME ||
this.authenticationMethod === AUTHENTICATION_METHOD_USERNAME_PASSWORD
);
}
/**
* Return true if the password field should be displayed
* @return {boolean}
*/
shouldDisplayPassword() {
return this.authenticationMethod === AUTHENTICATION_METHOD_USERNAME_PASSWORD;
}
/**
* Return true if the OAuth fields should be displayed
* @return {boolean}
*/
shouldDisplayOAuth() {
return this.authenticationMethod === AUTHENTICATION_METHOD_OAUTH;
}
/**
* Returns true if the source of the settings is from a config file instead of DB and the user has modified a field.
* @returns {boolean}
*/
shouldShowSourceWarningMessage() {
const smtpContext = this.props.adminSmtpSettingsContext;
return smtpContext.getCurrentSmtpSettings().source !== "db" && smtpContext.isSettingsModified();
}
/**
* Returns true if the data is loaded.
* Useful to avoid UI blinking during data loading.
* @returns {boolean}
*/
isReady() {
return this.props.adminSmtpSettingsContext.isDataReady();
}
/**
* Returns the source of the settings
* @returns {string}
*/
get settingsSource() {
return this.props.adminSmtpSettingsContext?.getCurrentSmtpSettings()?.source;
}
/**
* Returns the source of the current configuration
* @returns {string}
*/
get configurationSource() {
return (
{
env: this.props.t("environment variables"),
file: this.props.t("file"),
db: this.props.t("database"),
}[this.settingsSource] || this.props.t("unknown")
);
}
/**
* Get the translate function
* @returns {function(...[*]=)}
*/
get translate() {
return this.props.t;
}
/**
* Render the component
* @returns {JSX}
*/
render() {
const settings = this.props.adminSmtpSettingsContext.getCurrentSmtpSettings();
const errors = this.props.adminSmtpSettingsContext.getErrors();
const providerObject = SmtpProviders.find((p) => p.id === settings?.provider);
const smtpProviderName = providerObject?.name;
const hasChanges = this.props.adminSmtpSettingsContext.isSettingsModified();
const hasWarnings = hasChanges || this.shouldShowSourceWarningMessage();
return (
<div className="row">
<>
<div className="third-party-provider-settings smtp-settings main-column">
<div className="main-content">
<h3 className="title">
<Trans>Email server</Trans>
</h3>
{this.isReady() && !settings?.provider && (
<>
<h4 className="no-border">
<Trans>Select a provider</Trans>
</h4>
<div className="provider-list">
{SmtpProviders.map((provider) => (
<div
key={provider.id}
className="provider button"
id={provider.id}
onClick={() => this.props.adminSmtpSettingsContext.changeProvider(provider)}
>
<div className="provider-logo">
{provider.id === "other" && <EmailSVG />}
{provider.id !== "other" && (
<img src={`${this.props.context.trustedDomain}/img/third_party/${provider.icon}`} />
)}
</div>
<p className="provider-name">{provider.name}</p>
</div>
))}
</div>
</>
)}
{this.isReady() && settings?.provider && (
<>
<form className="form">
<h4 className="no-border">
<Trans>SMTP server configuration</Trans>
</h4>
<div className={`select-wrapper input required ${this.isProcessing() ? "disabled" : ""}`}>
<label htmlFor="smtp-settings-form-provider">
<Trans>Email provider</Trans>
</label>
<Select
id="smtp-settings-form-provider"
name="provider"
items={this.providerList}
value={settings.provider}
onChange={this.handleProviderChange}
disabled={this.isProcessing()}
/>
</div>
<div className={`select-wrapper input required ${this.isProcessing() ? "disabled" : ""}`}>
<label htmlFor="smtp-settings-form-authentication-method">
<Trans>Authentication method</Trans>
</label>
<Select
id="smtp-settings-form-authentication-method"
name="authentication-method"
items={this.authenticationMethodList}
value={this.authenticationMethod}
onChange={this.handleAuthenticationMethodChange}
disabled={this.isProcessing()}
/>
</div>
{this.shouldDisplayUsername() && (
<div
className={`input text ${errors?.hasError("username") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-username">
<Trans>Username</Trans>
</label>
<input
id="smtp-settings-form-username"
ref={this.usernameFieldRef}
name="username"
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.username}
onChange={this.handleInputChange}
placeholder={this.translate("Username")}
disabled={this.isProcessing()}
/>
{errors?.hasError("username") && (
<div className="error-message">
<Trans>
This is the maximum size for this field, make sure your data was not truncated.
</Trans>
</div>
)}
</div>
)}
{this.shouldDisplayPassword() && (
<div
className={`input-password-wrapper input ${errors?.hasError("password") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-password">
<Trans>Password</Trans>
</label>
<Password
id="smtp-settings-form-password"
name="password"
autoComplete="new-password"
placeholder={this.translate("Password")}
preview={true}
value={settings.password}
onChange={this.handleInputChange}
disabled={this.isProcessing()}
inputRef={this.passwordFieldRef}
/>
{errors?.hasError("password") && (
<div className="password error-message">
<Trans>
This is the maximum size for this field, make sure your data was not truncated.
</Trans>
</div>
)}
</div>
)}
{this.shouldDisplayOAuth() && (
<>
<div
className={`input text required ${errors?.hasError("oauth_username") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-oauth-username">
<Trans>OAuth Username</Trans>
</label>
<input
id="smtp-settings-form-oauth-username"
ref={this.oauth_usernameFieldRef}
name="oauth_username"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.oauth_username}
onChange={this.handleInputChange}
placeholder={this.translate("OAuth Username")}
disabled={this.isProcessing()}
/>
{errors?.hasError("oauth_username") && (
<div className="error-message">
{!settings.oauth_username || errors.hasError("oauth_username", "required") ? (
<Trans>OAuth Username is required.</Trans>
) : (
<Trans>OAuth Username must be a valid email.</Trans>
)}
</div>
)}
</div>
<div
className={`input text required ${errors?.hasError("tenant_id") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-tenant-id">
<Trans>Tenant ID</Trans>
</label>
<input
id="smtp-settings-form-tenant-id"
ref={this.tenant_idFieldRef}
name="tenant_id"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.tenant_id}
onChange={this.handleInputChange}
placeholder={this.translate("Tenant ID")}
disabled={this.isProcessing()}
/>
{errors?.hasError("tenant_id") && (
<div className="error-message">
<Trans>Tenant ID is required.</Trans>
</div>
)}
</div>
<div
className={`input text required ${errors?.hasError("client_id") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-client-id">
<Trans>Client ID</Trans>
</label>
<input
id="smtp-settings-form-client-id"
ref={this.client_idFieldRef}
name="client_id"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.client_id}
onChange={this.handleInputChange}
placeholder={this.translate("Client ID")}
disabled={this.isProcessing()}
/>
{errors?.hasError("client_id") && (
<div className="error-message">
<Trans>Client ID is required.</Trans>
</div>
)}
</div>
<div
className={`input-password-wrapper input required ${errors?.hasError("client_secret") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-client-secret">
<Trans>Client Secret</Trans>
</label>
<Password
id="smtp-settings-form-client-secret"
name="client_secret"
autoComplete="new-password"
placeholder={this.translate("Client Secret")}
preview={true}
value={settings.client_secret}
onChange={this.handleInputChange}
disabled={this.isProcessing()}
inputRef={this.client_secretFieldRef}
/>
{errors?.hasError("client_secret") && (
<div className="password error-message">
<Trans>Client Secret is required.</Trans>
</div>
)}
</div>
</>
)}
<div className="accordion-header">
<button type="button" className="link no-border" onClick={this.handleAdvancedSettingsToggle}>
{this.state.showAdvancedSettings ? <CaretDownSVG /> : <CaretRightSVG />}
<Trans>Advanced settings</Trans>
</button>
</div>
{this.state.showAdvancedSettings && (
<div className="advanced-settings">
<div
className={`input text required ${errors?.hasError("host") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-host">
<Trans>SMTP host</Trans>
</label>
<input
id="smtp-settings-form-host"
ref={this.hostFieldRef}
name="host"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.host}
onChange={this.handleInputChange}
placeholder={this.translate("SMTP server address")}
disabled={this.isProcessing()}
/>
{errors?.hasError("host") && (
<div className="error-message">
<Trans>SMTP Host is required</Trans>
</div>
)}
</div>
<div
className={`input text required ${errors?.hasError("tls") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-tls">
<Trans>Use TLS</Trans>
</label>
<Select
id="smtp-settings-form-tls"
name="tls"
items={this.tlsSelectList}
value={settings.tls}
onChange={this.handleInputChange}
disabled={this.isProcessing()}
/>
{errors?.hasError("tls") && (
<div className="error-message">
<Trans>TLS must be set to 'Yes' or 'No'</Trans>
</div>
)}
</div>
<div
className={`input text required ${errors?.hasError("port") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-port">
<Trans>Port</Trans>
</label>
<input
id="smtp-settings-form-port"
aria-required={true}
ref={this.portFieldRef}
name="port"
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.port}
onChange={this.handleInputChange}
placeholder={this.translate("Port number")}
disabled={this.isProcessing()}
/>
{errors?.hasError("port") && (
<div className="error-message">
{errors.hasError("port", "minimum") || errors.hasError("port", "maximum") ? (
<Trans>Port must be a number between 1 and 65535</Trans>
) : (
<Trans>Port must be a valid number</Trans>
)}
</div>
)}
</div>
<div
className={`input text ${errors?.hasError("client") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-client">
<Trans>SMTP client</Trans>
</label>
<input
id="smtp-settings-form-client"
ref={this.clientFieldRef}
name="client"
maxLength="2048"
type="text"
autoComplete="off"
value={settings.client}
onChange={this.handleInputChange}
placeholder={this.translate("SMTP client address")}
disabled={this.isProcessing()}
/>
{errors?.hasError("client") && (
<div className="error-message">
<Trans>SMTP client should be a valid domain or IP address</Trans>
</div>
)}
</div>
</div>
)}
<h4>
<Trans>Sender configuration</Trans>
</h4>
<div
className={`input text required ${errors?.hasError("sender_name") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-sender-name">
<Trans>Sender name</Trans>
</label>
<input
id="smtp-settings-form-sender-name"
ref={this.senderNameFieldRef}
name="sender_name"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.sender_name}
onChange={this.handleInputChange}
placeholder={this.translate("Sender name")}
disabled={this.isProcessing()}
/>
{errors?.hasError("sender_name") && (
<div className="error-message">
<Trans>Sender name is required</Trans>
</div>
)}
<p>
<Trans>
This is the name users will see in their mailbox when passbolt sends a notification.
</Trans>
</p>
</div>
<div
className={`input text required ${errors?.hasError("sender_email") ? "error" : ""} ${this.isProcessing() ? "disabled" : ""}`}
>
<label htmlFor="smtp-settings-form-sender-name">
<Trans>Sender email</Trans>
</label>
<input
id="smtp-settings-form-sender-email"
ref={this.senderEmailFieldRef}
name="sender_email"
aria-required={true}
className="fluid"
maxLength="256"
type="text"
autoComplete="off"
value={settings.sender_email}
onChange={this.handleInputChange}
placeholder={this.translate("Sender email")}
disabled={this.isProcessing()}
/>
{errors?.hasError("sender_email") && (
<div className="error-message">
{!settings.sender_email || errors.hasError("sender_email", "required") ? (
<Trans>Sender email is required</Trans>
) : (
<Trans>Sender email must be a valid email</Trans>
)}
</div>
)}
<p>
<Trans>
This is the email address users will see in their mail box when passbolt sends a notification.
<br />
It's a good practice to provide a working email address that users can reply to.
</Trans>
</p>
</div>
</form>
</>
)}
</div>
{hasWarnings && (
<div className="warning message">
{this.shouldShowSourceWarningMessage() && (
<div id="smtp-settings-source-warning">
<Trans>
These are the settings provided by a configuration file. If you save it, will ignore the settings
on file and use the ones from the database.
</Trans>
</div>
)}
{hasChanges && (
<div>
<p>
<Trans>Don't forget to save your settings to apply your modification.</Trans>
</p>
</div>
)}
</div>
)}
</div>
<DisplayAdministrationSmtpSettingsActions />
</>
{createSafePortal(
<>
<div className="sidebar-help-section" id="smtp-settings-source">
<h3>
<Trans>Configuration source</Trans>
</h3>
<p>
<Trans>This current configuration source is: </Trans>
{this.configurationSource}.
</p>
</div>
<div className="sidebar-help-section">
<h3>
<Trans>Why do I need an SMTP server?</Trans>
</h3>
<p>
<Trans>
Passbolt needs an smtp server in order to send invitation emails after an account creation and to send
email notifications.
</Trans>
</p>
<a
className="button"
href="https://passbolt.com/docs/admin/emails/email-server/"
target="_blank"
rel="noopener noreferrer"
>
<FileTextSVG />
<span>
<Trans>Read the documentation</Trans>
</span>
</a>
</div>
{providerObject && providerObject.id !== "other" && (
<div className="sidebar-help-section">
<h3>
<Trans>How do I configure a {{ smtpProviderName }} SMTP server?</Trans>
</h3>
<a className="button" href={providerObject.help_page} target="_blank" rel="noopener noreferrer">
<LinkSVG />
<span>
<Trans>See the {{ smtpProviderName }} documentation</Trans>
</span>
</a>
</div>
)}
{settings?.provider &&
(settings.provider === "google-mail" || settings.provider === "google-workspace") && (
<div className="sidebar-help-section">
<h3>
<Trans>Why shouldn't I use my login password ?</Trans>
</h3>
<p>
<Trans>
In order to use the "Username & Password" authentication method with Google, you will
need to enable MFA on your Google Account. The password should not be your login password, you
have to create an "App Password" generated by Google.. However, the email remain the
same.
</Trans>
</p>
<a
className="button"
href="https://support.google.com/mail/answer/185833"
target="_blank"
rel="noopener noreferrer"
>
<FileTextSVG />
<span>
<Trans>More informations</Trans>
</span>
</a>
</div>
)}
</>,
document.getElementById("administration-help-panel"),
)}
</div>
);
}
}
ManageSmtpAdministrationSettings.propTypes = {
context: PropTypes.object, // Application context
dialogContext: PropTypes.any, // The dialog context
administrationWorkspaceContext: PropTypes.object, // The administration workspace context
adminSmtpSettingsContext: PropTypes.object, // The administration SMTP settings context
t: PropTypes.func, // The translation function
};
export default withAppContext(
withAdminSmtpSettings(
withDialog(withAdministrationWorkspace(withTranslation("common")(ManageSmtpAdministrationSettings))),
),
);