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} />}
>
<Switch>
<PublicRoute exact path="/login" component={Login} />
<PublicRoute exact path={["/login","/login/:error"]} component={Login} />
<PublicRoute path="/confirm" component={Confirm} />
<PrivateRoute exact path="/" component={Home} />
<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 styled from 'styled-components';
import { welcomePageTitle } from './../../../helpers/customNames';
import { Collapse } from 'reactstrap';
import { connect } from 'react-redux';
import { getPasswordSettings, createConfirmUser } from '../../../store/auth/actions';
const inputWidth = '400px';
@ -12,51 +15,38 @@ const ConfirmContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-left: 200px;
.confirm-block-title {
margin: 20px 0px;
@media (max-width: 830px) {
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;
}
.input-container {
margin-left: 13%;
@media (max-width: 1500px) {
margin-left: 22%;
}
@media (max-width: 768px) {
margin-left: 5%;
}
input {
width: ${inputWidth};
}
.break-word {
word-break: break-word;
}
.join-button {
align-self: baseline;
}
`;
const passwordSettings = {
minLength: 6,
upperCase: true,
digits: true,
specSymbols: true
};
const emailInputName = 'email';
const passwordInputName = 'password';
const isLoaded = true;
const Confirm = (props) => {
const { t } = useTranslation('translation', { i18n });
const [email, setEmail] = useState('');
@ -69,16 +59,17 @@ const Confirm = (props) => {
const [passwordValid, setPasswordValid] = useState(true);
const [errorText, setErrorText] = useState("");
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 arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
// const linkParams = Object.fromEntries(arrayOfQueryParams);
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
const validationEmail = new RegExp(emailRegex);
const linkParams = Object.fromEntries(arrayOfQueryParams);
const isVisitor = parseInt(linkParams.emplType) === 2;
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9]{2,}$';
const validationEmail = new RegExp(emailRegex);
const onSubmit = useCallback((e) => {
//e.preventDefault();
errorText && setErrorText("");
@ -98,8 +89,8 @@ const Confirm = (props) => {
hasError = true;
setEmailValid(!hasError);
}
const passwordValue = (document.getElementsByName(passwordInputName)[0].value);
if (!passwordValue || !passwordValid) {
if (!passwordValid) {
hasError = true;
setPasswordValid(!hasError);
}
@ -109,8 +100,26 @@ const Confirm = (props) => {
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) => {
if (target.code === "Enter") {
@ -120,14 +129,29 @@ const Confirm = (props) => {
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('keyup', onKeyPress);
// Remove event listeners on cleanup
return () => {
window.removeEventListener('keydown', onKeyPress);
window.removeEventListener('keyup', onKeyPress);
};
}, [onKeyPress]);
}, [onKeyPress, getPasswordSettings, isLoaded, settings, queryString, history]);
const onCopyToClipboard = () => toastr.success(t('EmailAndPasswordCopiedToClipboard'));
const validatePassword = (value) => setPasswordValid(value);
@ -139,121 +163,123 @@ const Confirm = (props) => {
)
: (
<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'>
<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 className='confirm-row'>
<div className='full-width'>
<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)}
/>
<div className='input-container login-row'>
</FieldContainer>
<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 isVertical={true} className=''>
</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
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=''>
<TextInput
id='email'
name={emailInputName}
value={email}
placeholder={t('Email')}
size='huge'
scale={true}
tabIndex={3}
autoComplete='email'
isDisabled={isLoading}
hasError={!emailValid}
onChange={event => {
setEmail(event.target.value);
!emailValid && setEmailValid(true);
errorText && setErrorText("");
}}
onKeyDown={event => onKeyPress(event.target)}
/>
</FieldContainer>
</FieldContainer>
</div>
<FieldContainer isVertical={true} className=''>
<TextInput
id='email'
name={emailInputName}
value={email}
placeholder={t('Email')}
size='huge'
scale={true}
tabIndex={3}
autoComplete='email'
isDisabled={isLoading}
hasError={!emailValid}
onChange={event => {
setEmail(event.target.value);
!emailValid && setEmailValid(true);
errorText && setErrorText("");
}}
onKeyDown={event => onKeyPress(event.target)}
/>
<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 && !password.trim()}
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={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
primary
size='big'
@ -263,16 +289,20 @@ const Confirm = (props) => {
isLoading={isLoading}
onClick={onSubmit}
/>
</div>
</div>
{/* <Row className='login-row'>
{/* <Row className='confirm-row'>
<Text.Body as='p' fontSize={14}>{t('LoginWithAccount')}</Text.Body>
</Row>
*/}
<Collapse className='confirm-row'
isOpen={!!errorText}>
<div className="alert alert-danger">{errorText}</div>
</Collapse>
</div>
</ConfirmContainer>
)
);
@ -280,4 +310,11 @@ const 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 [isLoading, setIsLoading] = useState(false);
const { login, match, location, history } = props;
const { params } = match;
const onSubmit = useCallback((e) => {
//e.preventDefault();
@ -93,6 +94,7 @@ const Form = props => {
}, [onSubmit]);
useEffect(() => {
params.error && setErrorText(params.error);
window.addEventListener('keydown', onKeyPress);
window.addEventListener('keyup', onKeyPress);
// Remove event listeners on cleanup
@ -100,7 +102,7 @@ const Form = props => {
window.removeEventListener('keydown', onKeyPress);
window.removeEventListener('keyup', onKeyPress);
};
}, [onKeyPress]);
}, [onKeyPress, params.error]);
return (
<FormContainer>

View File

@ -7,6 +7,7 @@ export const SET_MODULES = 'SET_MODULES';
export const SET_SETTINGS = 'SET_SETTINGS';
export const SET_IS_LOADED = 'SET_IS_LOADED';
export const LOGOUT = 'LOGOUT';
export const SET_PASSWORD_SETTINGS = 'SET_PASSWORD_SETTINGS';
export function setCurrentUser(user) {
return {
@ -43,6 +44,14 @@ export function setLogout() {
};
};
export function setPasswordSettings(password) {
return {
type: SET_PASSWORD_SETTINGS,
password
};
};
export function getUserInfo(dispatch) {
return api.getUser()
.then((res) => dispatch(setCurrentUser(res.data.response)))
@ -70,4 +79,39 @@ export function logout() {
setAuthorizationToken();
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 config from "../../../package.json";
@ -20,11 +20,12 @@ const initialState = {
datePatternJQ: "00/00/0000",
dateTimePattern: "dddd, MMMM d, yyyy h:mm:ss tt",
datepicker: {
datePattern: "mm/dd/yy",
dateTimePattern: "DD, mm dd, yy h:mm:ss tt",
timePattern: "h:mm tt"
}
}
datePattern: "mm/dd/yy",
dateTimePattern: "DD, mm dd, yy h:mm:ss tt",
timePattern: "h:mm tt"
},
},
password: null
}
const authReducer = (state = initialState, action) => {
@ -39,9 +40,13 @@ const authReducer = (state = initialState, action) => {
modules: action.modules
});
case SET_SETTINGS:
return Object.assign({}, state, {
settings: { ...state.settings, ...action.settings }
});
return Object.assign({}, state, {
settings: { ...state.settings, ...action.settings }
});
case SET_PASSWORD_SETTINGS:
return Object.assign({}, state, {
password: { ...state.password, ...action.password }
});
case SET_IS_LOADED:
return Object.assign({}, state, {
isLoaded: action.isLoaded

View File

@ -31,6 +31,19 @@ export function getUser() {
export function getSettings() {
return IS_FAKE
? fakeApi.getSettings()
? fakeApi.getSettings()
: 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
}
});
}
}
export function login(data) {
return axios.post(`${API_URL}/authentication`, data);
@ -98,7 +98,25 @@ export function getSettings() {
"culture": "ru-RU",
"utcOffset": "03:00:00",
"utcHoursOffset": 3
};
};
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",
"version": "1.0.72",
"version": "1.0.74",
"description": "Ascensio System SIA component library",
"license": "AGPL-3.0",
"main": "dist/asc-web-components.js",

View File

@ -21,6 +21,81 @@ describe('<Avatar />', () => {
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', () => {
const wrapper = shallow(<Avatar {...baseProps} />).instance();

View File

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