244 lines
7.3 KiB
React
244 lines
7.3 KiB
React
/*!
|
|
* Copyright (c) 2024 PLANKA Software GmbH
|
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
|
*/
|
|
|
|
import { dequal } from 'dequal';
|
|
import omit from 'lodash/omit';
|
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
import classNames from 'classnames';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { useTranslation } from 'react-i18next';
|
|
import TextareaAutosize from 'react-textarea-autosize';
|
|
import { Button, Checkbox, Divider, Form, Header, Tab, TextArea } from 'semantic-ui-react';
|
|
import { Input } from '../../../lib/custom-ui';
|
|
|
|
import selectors from '../../../selectors';
|
|
import entryActions from '../../../entry-actions';
|
|
import { useForm, useNestedRef } from '../../../hooks';
|
|
|
|
import styles from './SmtpPane.module.scss';
|
|
|
|
const SmtpPane = React.memo(() => {
|
|
const config = useSelector(selectors.selectConfig);
|
|
const smtpTestState = useSelector(selectors.selectSmtpTestState);
|
|
|
|
const dispatch = useDispatch();
|
|
const [t] = useTranslation();
|
|
|
|
const [passwordFieldRef, handlePasswordFieldRef] = useNestedRef('inputRef');
|
|
const isPasswordTouchedRef = useRef(false);
|
|
|
|
const defaultData = useMemo(
|
|
() => ({
|
|
smtpHost: config.smtpHost,
|
|
smtpPort: config.smtpPort,
|
|
smtpName: config.smtpName,
|
|
smtpSecure: config.smtpSecure,
|
|
smtpTlsRejectUnauthorized: config.smtpTlsRejectUnauthorized,
|
|
smtpUser: config.smtpUser,
|
|
smtpPassword: config.smtpPassword,
|
|
smtpFrom: config.smtpFrom,
|
|
}),
|
|
[config],
|
|
);
|
|
|
|
const [data, handleFieldChange] = useForm(() => ({
|
|
...defaultData,
|
|
smtpHost: defaultData.smtpHost || '',
|
|
smtpPort: defaultData.smtpPort === null ? '' : `${defaultData.smtpPort}`,
|
|
smtpName: defaultData.smtpName || '',
|
|
smtpSecure: defaultData.smtpSecure,
|
|
smtpTlsRejectUnauthorized: defaultData.smtpTlsRejectUnauthorized,
|
|
smtpUser: defaultData.smtpUser || '',
|
|
smtpPassword: defaultData.smtpPassword || '',
|
|
smtpFrom: defaultData.smtpFrom || '',
|
|
}));
|
|
|
|
const isPasswordSet = defaultData.smtpPassword === undefined;
|
|
|
|
const cleanData = useMemo(
|
|
() => ({
|
|
...data,
|
|
smtpHost: data.smtpHost.trim() || null,
|
|
smtpPort: parseInt(data.smtpPort, 10) || null,
|
|
smtpName: data.smtpName.trim() || null,
|
|
smtpUser: data.smtpUser.trim() || null,
|
|
smtpPassword: data.smtpPassword || (isPasswordSet ? undefined : null),
|
|
smtpFrom: data.smtpFrom.trim() || null,
|
|
}),
|
|
[data, isPasswordSet],
|
|
);
|
|
|
|
const isModified = useMemo(() => {
|
|
const cleanDataToCheck = omit(cleanData, 'smtpPassword');
|
|
const defaultDataToCheck = omit(defaultData, 'smtpPassword');
|
|
|
|
return !dequal(cleanDataToCheck, defaultDataToCheck) || isPasswordTouchedRef.current;
|
|
}, [defaultData, cleanData]);
|
|
|
|
const handleSubmit = useCallback(() => {
|
|
isPasswordTouchedRef.current = false;
|
|
dispatch(entryActions.updateConfig(cleanData));
|
|
}, [dispatch, cleanData]);
|
|
|
|
const handlePasswordClear = useCallback(() => {
|
|
dispatch(
|
|
entryActions.updateConfig({
|
|
smtpPassword: null,
|
|
}),
|
|
);
|
|
|
|
passwordFieldRef.current.focus();
|
|
}, [dispatch, passwordFieldRef]);
|
|
|
|
const handleTestClick = useCallback(() => {
|
|
dispatch(entryActions.testSmtpConfig());
|
|
}, [dispatch]);
|
|
|
|
const handlePasswordChange = useCallback(
|
|
(event, { value, ...props }) => {
|
|
isPasswordTouchedRef.current = value !== '';
|
|
handleFieldChange(event, { value, ...props });
|
|
},
|
|
[handleFieldChange],
|
|
);
|
|
|
|
return (
|
|
<Tab.Pane attached={false} className={styles.wrapper}>
|
|
<Form onSubmit={handleSubmit}>
|
|
<div className={styles.text}>{t('common.host')}</div>
|
|
<Input
|
|
fluid
|
|
name="smtpHost"
|
|
value={data.smtpHost}
|
|
maxLength={256}
|
|
className={styles.field}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<div className={styles.text}>{t('common.port')}</div>
|
|
<Input
|
|
fluid
|
|
type="number"
|
|
name="smtpPort"
|
|
value={data.smtpPort}
|
|
placeholder={data.smtpSecure ? '465' : '587'}
|
|
min={0}
|
|
max={65535}
|
|
step={1}
|
|
className={styles.field}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<div className={styles.text}>
|
|
{t('common.clientHostnameInEhlo')} (
|
|
{t('common.optional', {
|
|
context: 'inline',
|
|
})}
|
|
)
|
|
</div>
|
|
<Input
|
|
fluid
|
|
name="smtpName"
|
|
value={data.smtpName}
|
|
maxLength={256}
|
|
className={styles.field}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<Checkbox
|
|
name="smtpSecure"
|
|
checked={data.smtpSecure}
|
|
label={t('common.useSecureConnection')}
|
|
className={styles.checkbox}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<Checkbox
|
|
name="smtpTlsRejectUnauthorized"
|
|
checked={data.smtpTlsRejectUnauthorized}
|
|
label={t('common.rejectUnauthorizedTlsCertificates')}
|
|
className={classNames(styles.field, styles.checkbox)}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<div className={styles.text}>
|
|
{t('common.username')} (
|
|
{t('common.optional', {
|
|
context: 'inline',
|
|
})}
|
|
)
|
|
</div>
|
|
<Input
|
|
fluid
|
|
name="smtpUser"
|
|
value={data.smtpUser}
|
|
maxLength={256}
|
|
className={styles.field}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<div className={styles.text}>
|
|
{t('common.password')} (
|
|
{t('common.optional', {
|
|
context: 'inline',
|
|
})}
|
|
)
|
|
</div>
|
|
<Input.Password
|
|
fluid
|
|
ref={handlePasswordFieldRef}
|
|
name="smtpPassword"
|
|
value={data.smtpPassword}
|
|
placeholder={isPasswordSet ? t('common.passwordIsSet') : undefined}
|
|
maxLength={256}
|
|
className={styles.field}
|
|
onClear={!data.smtpPassword && isPasswordSet ? handlePasswordClear : undefined}
|
|
onChange={handlePasswordChange}
|
|
/>
|
|
<div className={styles.text}>
|
|
{t('common.defaultFrom')} (
|
|
{t('common.optional', {
|
|
context: 'inline',
|
|
})}
|
|
)
|
|
</div>
|
|
<Input
|
|
fluid
|
|
name="smtpFrom"
|
|
value={data.smtpFrom}
|
|
maxLength={256}
|
|
className={styles.field}
|
|
onChange={handleFieldChange}
|
|
/>
|
|
<div className={styles.controls}>
|
|
<Button positive disabled={!isModified} content={t('action.save')} />
|
|
{config.smtpHost && !isModified && (
|
|
<Button
|
|
type="button"
|
|
content={t('action.sendTestEmail')}
|
|
loading={smtpTestState.isLoading}
|
|
disabled={smtpTestState.isLoading}
|
|
onClick={handleTestClick}
|
|
/>
|
|
)}
|
|
</div>
|
|
</Form>
|
|
{smtpTestState.logs && (
|
|
<>
|
|
<Divider horizontal>
|
|
<Header as="h4">
|
|
{t('common.testLog', {
|
|
context: 'title',
|
|
})}
|
|
</Header>
|
|
</Divider>
|
|
<TextArea
|
|
readOnly
|
|
as={TextareaAutosize}
|
|
value={smtpTestState.logs.join('\n')}
|
|
className={styles.testLog}
|
|
/>
|
|
</>
|
|
)}
|
|
</Tab.Pane>
|
|
);
|
|
});
|
|
|
|
export default SmtpPane;
|