Merge branch 'master' of github.com:ONLYOFFICE/CommunityServer-AspNetCore

This commit is contained in:
Alexey Safronov 2019-09-13 10:13:56 +03:00
commit 9c3cc65bba
11 changed files with 367 additions and 174 deletions

View File

@ -19,7 +19,7 @@ const App = () => {
fallback={<Loader className="pageLoader" type="rombs" size={40} />} fallback={<Loader className="pageLoader" type="rombs" size={40} />}
> >
<Switch> <Switch>
<PublicRoute exact path="/login" component={Login} /> <PublicRoute exact path={["/login","/login/:error"]} component={Login} />
<PublicRoute path="/confirm" component={Confirm} /> <PublicRoute path="/confirm" component={Confirm} />
<PrivateRoute exact path="/" component={Home} /> <PrivateRoute exact path="/" component={Home} />
<PrivateRoute exact path="/about" component={About} /> <PrivateRoute exact path="/about" component={About} />

View File

@ -5,6 +5,9 @@ import i18n from './i18n';
import { Button, TextInput, PageLayout, Text, PasswordInput, FieldContainer, toastr, Loader } from 'asc-web-components'; import { Button, TextInput, PageLayout, Text, PasswordInput, FieldContainer, toastr, Loader } from 'asc-web-components';
import styled from 'styled-components'; import styled from 'styled-components';
import { welcomePageTitle } from './../../../helpers/customNames'; import { welcomePageTitle } from './../../../helpers/customNames';
import { Collapse } from 'reactstrap';
import { connect } from 'react-redux';
import { getPasswordSettings, createConfirmUser } from '../../../store/auth/actions';
const inputWidth = '400px'; const inputWidth = '400px';
@ -12,51 +15,38 @@ const ConfirmContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin-left: 200px;
.confirm-block-title { @media (max-width: 830px) {
margin: 20px 0px; margin-left: 40px;
}
.start-basis {
align-items: flex-start;
}
.margin-left {
margin-left: 20px;
} }
.login-row { .full-width {
width: ${inputWidth}
}
.confirm-row {
margin: 23px 0 0; margin: 23px 0 0;
} }
.input-container { .break-word {
word-break: break-word;
margin-left: 13%;
@media (max-width: 1500px) {
margin-left: 22%;
}
@media (max-width: 768px) {
margin-left: 5%;
}
input {
width: ${inputWidth};
}
} }
.join-button {
align-self: baseline;
}
`; `;
const passwordSettings = {
minLength: 6,
upperCase: true,
digits: true,
specSymbols: true
};
const emailInputName = 'email'; const emailInputName = 'email';
const passwordInputName = 'password'; const passwordInputName = 'password';
const isLoaded = true;
const Confirm = (props) => { const Confirm = (props) => {
const { t } = useTranslation('translation', { i18n }); const { t } = useTranslation('translation', { i18n });
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@ -69,16 +59,17 @@ const Confirm = (props) => {
const [passwordValid, setPasswordValid] = useState(true); const [passwordValid, setPasswordValid] = useState(true);
const [errorText, setErrorText] = useState(""); const [errorText, setErrorText] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { location, history, isLoaded, getPasswordSettings, settings, createConfirmUser } = props;
const queryString = window.location.search.slice(1); const queryString = location.search.slice(1);
const queryParams = queryString.split('&'); const queryParams = queryString.split('&');
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('=')); const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
// const linkParams = Object.fromEntries(arrayOfQueryParams); const linkParams = Object.fromEntries(arrayOfQueryParams);
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; const isVisitor = parseInt(linkParams.emplType) === 2;
const validationEmail = new RegExp(emailRegex);
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9]{2,}$';
const validationEmail = new RegExp(emailRegex);
const onSubmit = useCallback((e) => { const onSubmit = useCallback((e) => {
//e.preventDefault();
errorText && setErrorText(""); errorText && setErrorText("");
@ -98,8 +89,8 @@ const Confirm = (props) => {
hasError = true; hasError = true;
setEmailValid(!hasError); setEmailValid(!hasError);
} }
const passwordValue = (document.getElementsByName(passwordInputName)[0].value);
if (!passwordValue || !passwordValid) { if (!passwordValid) {
hasError = true; hasError = true;
setPasswordValid(!hasError); setPasswordValid(!hasError);
} }
@ -109,8 +100,26 @@ const Confirm = (props) => {
setIsLoading(true); setIsLoading(true);
const loginData = {
userName: email,
password: password
}
const registerData = {
firstname: firstName,
lastname: lastName,
email: email,
isVisitor: isVisitor
};
}, [errorText, email, firstName, lastName, validationEmail, passwordValid]); createConfirmUser(registerData, loginData, queryString)
.then(() => history.push('/'))
.catch(e => {
console.error("confirm error", e);
setErrorText(e.message);
setIsLoading(false)
});
}, [errorText, email, firstName, lastName, validationEmail, passwordValid, createConfirmUser, history, queryString, isVisitor, password]);
const onKeyPress = useCallback((target) => { const onKeyPress = useCallback((target) => {
if (target.code === "Enter") { if (target.code === "Enter") {
@ -120,14 +129,29 @@ const Confirm = (props) => {
useEffect(() => { useEffect(() => {
if (!isLoaded && !settings) {
getPasswordSettings(queryString)
.then(
function () {
console.log("get settings success");
}
)
.catch(e => {
console.error("get settings error", e);
history.push(`/login/${e}`);
})
};
window.addEventListener('keydown', onKeyPress); window.addEventListener('keydown', onKeyPress);
window.addEventListener('keyup', onKeyPress); window.addEventListener('keyup', onKeyPress);
// Remove event listeners on cleanup // Remove event listeners on cleanup
return () => { return () => {
window.removeEventListener('keydown', onKeyPress); window.removeEventListener('keydown', onKeyPress);
window.removeEventListener('keyup', onKeyPress); window.removeEventListener('keyup', onKeyPress);
}; };
}, [onKeyPress]); }, [onKeyPress, getPasswordSettings, isLoaded, settings, queryString, history]);
const onCopyToClipboard = () => toastr.success(t('EmailAndPasswordCopiedToClipboard')); const onCopyToClipboard = () => toastr.success(t('EmailAndPasswordCopiedToClipboard'));
const validatePassword = (value) => setPasswordValid(value); const validatePassword = (value) => setPasswordValid(value);
@ -139,121 +163,123 @@ const Confirm = (props) => {
) )
: ( : (
<ConfirmContainer> <ConfirmContainer>
<div className='start-basis'>
<div className='margin-left'>
<Text.Body className='confirm-row' as='p' fontSize={18}>{t('InviteTitle')}</Text.Body>
<Text.Body className='confirm-block-title' as='p' fontSize={18}>{t('InviteTitle')}</Text.Body> <div className='confirm-row full-width break-word'>
<a href='/login'>
<img src="images/dark_general.png" alt="Logo" />
</a>
<Text.Body as='p' fontSize={24} color='#116d9d'>{t('CustomWelcomePageTitle', { welcomePageTitle })}</Text.Body>
</div>
</div>
<div className='login-row'> <div className='confirm-row'>
<a href='/login'> <div className='full-width'>
<img src="images/dark_general.png" alt="Logo" /> <FieldContainer isVertical={true} className=''>
</a> <TextInput
<Text.Body as='p' fontSize={24} color='#116d9d'>{t('CustomWelcomePageTitle', { welcomePageTitle })}</Text.Body> id='name'
</div> name='name'
value={firstName}
placeholder={t('FirstName')}
size='huge'
scale={true}
tabIndex={1}
isAutoFocussed={true}
autoComplete='given-name'
isDisabled={isLoading}
hasError={!firstNameValid}
onChange={event => {
setFirstName(event.target.value);
!firstNameValid && setFirstNameValid(true);
errorText && setErrorText("");
}}
onKeyDown={event => onKeyPress(event.target)}
/>
<div className='input-container login-row'> </FieldContainer>
<FieldContainer isVertical={true} className=''> <FieldContainer isVertical={true} className=''>
<TextInput
id='name'
name='name'
value={firstName}
placeholder={t('FirstName')}
size='huge'
scale={true}
tabIndex={1}
isAutoFocussed={true}
autoComplete='given-name'
isDisabled={isLoading}
hasError={!firstNameValid}
onChange={event => {
setFirstName(event.target.value);
!firstNameValid && setFirstNameValid(true);
errorText && setErrorText("");
}}
onKeyDown={event => onKeyPress(event.target)}
/>
</FieldContainer> <TextInput
id='surname'
name='surname'
value={lastName}
placeholder={t('LastName')}
size='huge'
scale={true}
tabIndex={2}
autoComplete='family-name'
isDisabled={isLoading}
hasError={!lastNameValid}
onChange={event => {
setLastName(event.target.value);
!lastNameValid && setLastNameValid(true);
errorText && setErrorText("");
}}
onKeyDown={event => onKeyPress(event.target)}
/>
<FieldContainer isVertical={true} className=''> </FieldContainer>
<TextInput <FieldContainer isVertical={true} className=''>
id='surname' <TextInput
name='surname' id='email'
value={lastName} name={emailInputName}
placeholder={t('LastName')} value={email}
size='huge' placeholder={t('Email')}
scale={true} size='huge'
tabIndex={2} scale={true}
autoComplete='family-name' tabIndex={3}
isDisabled={isLoading} autoComplete='email'
hasError={!lastNameValid} isDisabled={isLoading}
onChange={event => { hasError={!emailValid}
setLastName(event.target.value); onChange={event => {
!lastNameValid && setLastNameValid(true); setEmail(event.target.value);
errorText && setErrorText(""); !emailValid && setEmailValid(true);
}} errorText && setErrorText("");
onKeyDown={event => onKeyPress(event.target)} }}
/> onKeyDown={event => onKeyPress(event.target)}
/>
</FieldContainer> </FieldContainer>
</div>
<FieldContainer isVertical={true} className=''> <FieldContainer isVertical={true} className=''>
<TextInput <PasswordInput
id='email' inputName={passwordInputName}
name={emailInputName} emailInputName={emailInputName}
value={email} inputValue={password}
placeholder={t('Email')} placeholder={t('InvitePassword')}
size='huge' size='huge'
scale={true} scale={true}
tabIndex={3} tabIndex={4}
autoComplete='email' maxLength={30}
isDisabled={isLoading} inputWidth={inputWidth}
hasError={!emailValid} hasError={!passwordValid && !password.trim()}
onChange={event => { onChange={event => {
setEmail(event.target.value); setPassword(event.target.value);
!emailValid && setEmailValid(true); !passwordValid && setPasswordValid(true);
errorText && setErrorText(""); errorText && setErrorText("");
}} onKeyPress(event.target);
onKeyDown={event => onKeyPress(event.target)} }}
/> onCopyToClipboard={onCopyToClipboard}
onValidateInput={validatePassword}
clipActionResource={t('CopyEmailAndPassword')}
clipEmailResource={`${t('Email')}: `}
clipPasswordResource={`${t('InvitePassword')}: `}
tooltipPasswordTitle={`${t('ErrorPasswordMessage')}:`}
tooltipPasswordLength={`${t('ErrorPasswordLength', { fromNumber: 6, toNumber: 30 })}:`}
tooltipPasswordDigits={t('ErrorPasswordNoDigits')}
tooltipPasswordCapital={t('ErrorPasswordNoUpperCase')}
tooltipPasswordSpecial={`${t('ErrorPasswordNoSpecialSymbols')} (!@#$%^&*)`}
generatorSpecial="!@#$%^&*"
passwordSettings={settings}
isDisabled={isLoading}
/>
</FieldContainer>
</FieldContainer>
<FieldContainer isVertical={true} className=''>
<PasswordInput
inputName={passwordInputName}
emailInputName={emailInputName}
inputValue={password}
placeholder={t('InvitePassword')}
size='huge'
scale={true}
tabIndex={4}
maxLength={30}
inputWidth={inputWidth}
hasError={!passwordValid}
onChange={event => {
setPassword(event.target.value);
!passwordValid && setPasswordValid(true);
errorText && setErrorText("");
onKeyPress(event.target);
}}
onCopyToClipboard={onCopyToClipboard}
onValidateInput={validatePassword}
clipActionResource={t('CopyEmailAndPassword')}
clipEmailResource={`${t('Email')}: `}
clipPasswordResource={`${t('InvitePassword')}: `}
tooltipPasswordTitle={`${t('ErrorPasswordMessage')}:`}
tooltipPasswordLength={`${t('ErrorPasswordLength', { fromNumber: 6, toNumber: 30 })}:`}
tooltipPasswordDigits={t('ErrorPasswordNoDigits')}
tooltipPasswordCapital={t('ErrorPasswordNoUpperCase')}
tooltipPasswordSpecial={`${t('ErrorPasswordNoSpecialSymbols')} (!@#$%^&*)`}
generatorSpecial="!@#$%^&*"
passwordSettings={passwordSettings}
isDisabled={isLoading}
/>
</FieldContainer>
<div className='login-row join-button'>
<Button <Button
primary primary
size='big' size='big'
@ -263,16 +289,20 @@ const Confirm = (props) => {
isLoading={isLoading} isLoading={isLoading}
onClick={onSubmit} onClick={onSubmit}
/> />
</div> </div>
</div> {/* <Row className='confirm-row'>
{/* <Row className='login-row'>
<Text.Body as='p' fontSize={14}>{t('LoginWithAccount')}</Text.Body> <Text.Body as='p' fontSize={14}>{t('LoginWithAccount')}</Text.Body>
</Row> </Row>
*/} */}
<Collapse className='confirm-row'
isOpen={!!errorText}>
<div className="alert alert-danger">{errorText}</div>
</Collapse>
</div>
</ConfirmContainer> </ConfirmContainer>
) )
); );
@ -280,4 +310,11 @@ const Confirm = (props) => {
const ConfirmForm = (props) => (<PageLayout sectionBodyContent={<Confirm {...props} />} />); const ConfirmForm = (props) => (<PageLayout sectionBodyContent={<Confirm {...props} />} />);
export default withRouter(ConfirmForm); function mapStateToProps(state) {
return {
isLoaded: state.auth.isLoaded,
settings: state.auth.password
};
}
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser })(withRouter(ConfirmForm));

View File

@ -45,6 +45,7 @@ const Form = props => {
const [errorText, setErrorText] = useState(""); const [errorText, setErrorText] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { login, match, location, history } = props; const { login, match, location, history } = props;
const { params } = match;
const onSubmit = useCallback((e) => { const onSubmit = useCallback((e) => {
//e.preventDefault(); //e.preventDefault();
@ -93,6 +94,7 @@ const Form = props => {
}, [onSubmit]); }, [onSubmit]);
useEffect(() => { useEffect(() => {
params.error && setErrorText(params.error);
window.addEventListener('keydown', onKeyPress); window.addEventListener('keydown', onKeyPress);
window.addEventListener('keyup', onKeyPress); window.addEventListener('keyup', onKeyPress);
// Remove event listeners on cleanup // Remove event listeners on cleanup
@ -100,7 +102,7 @@ const Form = props => {
window.removeEventListener('keydown', onKeyPress); window.removeEventListener('keydown', onKeyPress);
window.removeEventListener('keyup', onKeyPress); window.removeEventListener('keyup', onKeyPress);
}; };
}, [onKeyPress]); }, [onKeyPress, params.error]);
return ( return (
<FormContainer> <FormContainer>

View File

@ -7,6 +7,7 @@ export const SET_MODULES = 'SET_MODULES';
export const SET_SETTINGS = 'SET_SETTINGS'; export const SET_SETTINGS = 'SET_SETTINGS';
export const SET_IS_LOADED = 'SET_IS_LOADED'; export const SET_IS_LOADED = 'SET_IS_LOADED';
export const LOGOUT = 'LOGOUT'; export const LOGOUT = 'LOGOUT';
export const SET_PASSWORD_SETTINGS = 'SET_PASSWORD_SETTINGS';
export function setCurrentUser(user) { export function setCurrentUser(user) {
return { return {
@ -43,6 +44,14 @@ export function setLogout() {
}; };
}; };
export function setPasswordSettings(password) {
return {
type: SET_PASSWORD_SETTINGS,
password
};
};
export function getUserInfo(dispatch) { export function getUserInfo(dispatch) {
return api.getUser() return api.getUser()
.then((res) => dispatch(setCurrentUser(res.data.response))) .then((res) => dispatch(setCurrentUser(res.data.response)))
@ -70,4 +79,39 @@ export function logout() {
setAuthorizationToken(); setAuthorizationToken();
return dispatch(setLogout()); return dispatch(setLogout());
}; };
}; };
export function getPasswordSettings(token) {
return dispatch => {
return api.getPasswordSettings(token)
.then((res) => dispatch(setPasswordSettings(res.data.response)))
.then(() => dispatch(setIsLoaded(true)));
}
};
export function createConfirmUser(registerData, loginData, key) {
const data = Object.assign({}, registerData, loginData);
return dispatch => {
return api.createUser(data, key)
.then(res => {
checkResponseError(res);
console.log('register success:', res.data.response);
return api.login(data);
})
.then(res => {
console.error("log in, result:", res);
checkResponseError(res);
const token = res.data.response.token;
setAuthorizationToken(token);
return getUserInfo(dispatch);
});
};
};
export function checkResponseError(res) {
if (res && res.data && res.data.error) {
console.error(res.data.error);
throw new Error(res.data.error.message);
}
}

View File

@ -1,4 +1,4 @@
import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT } from './actions'; import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS } from './actions';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import config from "../../../package.json"; import config from "../../../package.json";
@ -20,11 +20,12 @@ const initialState = {
datePatternJQ: "00/00/0000", datePatternJQ: "00/00/0000",
dateTimePattern: "dddd, MMMM d, yyyy h:mm:ss tt", dateTimePattern: "dddd, MMMM d, yyyy h:mm:ss tt",
datepicker: { datepicker: {
datePattern: "mm/dd/yy", datePattern: "mm/dd/yy",
dateTimePattern: "DD, mm dd, yy h:mm:ss tt", dateTimePattern: "DD, mm dd, yy h:mm:ss tt",
timePattern: "h:mm tt" timePattern: "h:mm tt"
} },
} },
password: null
} }
const authReducer = (state = initialState, action) => { const authReducer = (state = initialState, action) => {
@ -39,9 +40,13 @@ const authReducer = (state = initialState, action) => {
modules: action.modules modules: action.modules
}); });
case SET_SETTINGS: case SET_SETTINGS:
return Object.assign({}, state, { return Object.assign({}, state, {
settings: { ...state.settings, ...action.settings } settings: { ...state.settings, ...action.settings }
}); });
case SET_PASSWORD_SETTINGS:
return Object.assign({}, state, {
password: { ...state.password, ...action.password }
});
case SET_IS_LOADED: case SET_IS_LOADED:
return Object.assign({}, state, { return Object.assign({}, state, {
isLoaded: action.isLoaded isLoaded: action.isLoaded

View File

@ -31,6 +31,19 @@ export function getUser() {
export function getSettings() { export function getSettings() {
return IS_FAKE return IS_FAKE
? fakeApi.getSettings() ? fakeApi.getSettings()
: axios.get(`${API_URL}/settings.json`); : axios.get(`${API_URL}/settings.json`);
}; };
export function getPasswordSettings(key) {
return IS_FAKE
? fakeApi.getPasswordSettings()
: axios.get(`${API_URL}/settings/security/password`, { headers: { 'confirm' : key } });
};
export function createUser(data, key) {
return IS_FAKE
? fakeApi.createUser()
: axios.post(`${API_URL}/people`, data, { headers: { 'confirm' : key } });
}

View File

@ -13,7 +13,7 @@ function fakeResponse(data) {
response: data response: data
} }
}); });
} }
export function login(data) { export function login(data) {
return axios.post(`${API_URL}/authentication`, data); return axios.post(`${API_URL}/authentication`, data);
@ -98,7 +98,25 @@ export function getSettings() {
"culture": "ru-RU", "culture": "ru-RU",
"utcOffset": "03:00:00", "utcOffset": "03:00:00",
"utcHoursOffset": 3 "utcHoursOffset": 3
}; };
return fakeResponse(data); return fakeResponse(data);
}; };
export function getPasswordSettings() {
const data = {
"minLength": 12,
"upperCase": true,
"digits": true,
"specSymbols": true
};
return fakeResponse(data);
};
export function createUser() {
const data = {
"id": "00000000-0000-0000-0000-000000000000"
};
return fakeResponse(data);
}

View File

@ -1,6 +1,6 @@
{ {
"name": "asc-web-components", "name": "asc-web-components",
"version": "1.0.72", "version": "1.0.74",
"description": "Ascensio System SIA component library", "description": "Ascensio System SIA component library",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"main": "dist/asc-web-components.js", "main": "dist/asc-web-components.js",

View File

@ -21,6 +21,81 @@ describe('<Avatar />', () => {
expect(wrapper).toExist(); expect(wrapper).toExist();
}); });
it('render owner avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} role='owner' />
);
expect(wrapper.prop('role')).toEqual('owner');
});
it('render guest avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} role='guest' />
);
expect(wrapper.prop('role')).toEqual('guest');
});
it('render big avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} size='big' />
);
expect(wrapper.prop('size')).toEqual('big');
});
it('render medium avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} size='medium' />
);
expect(wrapper.prop('size')).toEqual('medium');
});
it('render small avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} size='small' />
);
expect(wrapper.prop('size')).toEqual('small');
});
it('render empty avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} userName='' source='' />
);
expect(wrapper.prop('userName')).toEqual('');
expect(wrapper.prop('source')).toEqual('');
});
it('render source avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} userName='Demo User' source='demo' />
);
expect(wrapper.prop('userName')).toEqual('Demo User');
expect(wrapper.prop('source')).toEqual('demo');
});
it('render initials avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} userName='Demo User' source='' />
);
expect(wrapper.prop('userName')).toEqual('Demo User');
expect(wrapper.prop('source')).toEqual('');
});
it('render editing avatar', () => {
const wrapper = mount(
<Avatar {...baseProps} editing />
);
expect(wrapper.prop('editing')).toEqual(true);
});
it('not re-render test', () => { it('not re-render test', () => {
const wrapper = shallow(<Avatar {...baseProps} />).instance(); const wrapper = shallow(<Avatar {...baseProps} />).instance();

View File

@ -145,12 +145,10 @@ const getRoleIcon = role => {
}; };
const getInitials = userName => const getInitials = userName =>
typeof userName === 'string' userName
? userName .split(/\s/)
.split(/\s/) .reduce((response, word) => response += word.slice(0, 1), '')
.reduce((response, word) => response += word.slice(0, 1), '') .substring(0, 2);
.substring(0, 2)
: '';
const Initials = props => ( const Initials = props => (
<NamedAvatar {...props}>{getInitials(props.userName)}</NamedAvatar> <NamedAvatar {...props}>{getInitials(props.userName)}</NamedAvatar>
@ -163,7 +161,7 @@ Initials.propTypes = {
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
class Avatar extends React.Component { class Avatar extends React.Component {
shouldComponentUpdate(nextProps,nextState) { shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState); return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
} }

View File

@ -174,6 +174,7 @@ class PasswordInput extends React.Component {
const newPassword = this.getNewPassword(); const newPassword = this.getNewPassword();
this.checkPassword(newPassword); this.checkPassword(newPassword);
this.props.onChange && this.props.onChange({ target: { value: newPassword } });
} }
getNewPassword = () => { getNewPassword = () => {