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

This commit is contained in:
Alexey Safronov 2019-10-01 11:37:59 +03:00
commit 99ba2483c8
15 changed files with 715 additions and 203 deletions

View File

@ -1,6 +1,6 @@
import React, { useCallback } from "react";
import { connect } from "react-redux";
import { Text, IconButton, ContextMenuButton, toastr } from "asc-web-components";
import { Text, IconButton, ContextMenuButton, toastr, utils } from "asc-web-components";
import { withRouter } from "react-router";
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
import { getUserStatus } from "../../../../../store/people/selectors";
@ -8,16 +8,21 @@ import { useTranslation } from 'react-i18next';
import { resendUserInvites } from "../../../../../store/services/api";
import { EmployeeStatus } from "../../../../../helpers/constants";
import { updateUserStatus } from "../../../../../store/people/actions";
import { callbackify } from "util";
import styled from 'styled-components';
const wrapperStyle = {
display: "flex",
alignItems: "center"
};
const textStyle = {
marginLeft: "16px",
marginRight: "16px"
};
const Header = styled(Text.ContentHeader)`
margin-left: 16px;
margin-right: 16px;
max-width: calc(100vw - 430px);
@media ${utils.device.tablet} {
max-width: calc(100vw - 96px);
}
`;
const SectionHeaderContent = props => {
const { profile, history, settings, isAdmin, viewer, updateUserStatus } = props;
@ -188,10 +193,10 @@ const SectionHeaderContent = props => {
onClick={onClick}
/>
</div>
<Text.ContentHeader truncate={true} style={textStyle}>
<Header truncate={true}>
{profile.displayName}
{profile.isLDAP && ` (${t('LDAPLbl')})`}
</Text.ContentHeader>
</Header>
{(isAdmin || isMe(viewer, profile.userName)) && (
<ContextMenuButton
directionX="right"

View File

@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux';
import { withRouter } from "react-router";
import { IconButton, Text } from 'asc-web-components';
import { IconButton, Text, utils } from 'asc-web-components';
import { useTranslation } from 'react-i18next';
import {typeUser, typeGuest } from './../../../../../helpers/customNames';
@ -13,6 +13,10 @@ const Wrapper = styled.div`
const Header = styled(Text.ContentHeader)`
margin-left: 16px;
max-width: calc(100vw - 430px);
@media ${utils.device.tablet} {
max-width: calc(100vw - 64px);
}
`;
const SectionHeaderContent = (props) => {
@ -35,7 +39,7 @@ const SectionHeaderContent = (props) => {
return (
<Wrapper>
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={onClick}/>
<Header>{headerText}</Header>
<Header truncate={true}>{headerText}</Header>
</Wrapper>
);
};

View File

@ -5,11 +5,11 @@
"AboutCompanyAddressTitle": "address",
"AboutCompanyEmailTitle": "email",
"AboutCompanyTelTitle": "tel.",
"LicensedUnder": "This software is licensed under", "_comment": "{0}GNU GPL v.3{1}",
"LicensedUnder": "This software is licensed under",
"SourceCode": "Source code is available on", "_comment": "{0}GNU GPL v.3{1}","_comment":"SYNTAX ERROR"
"SourceCode": "Source code is available on"
}

View File

@ -5,6 +5,6 @@
"AboutCompanyAddressTitle": "адрес",
"AboutCompanyEmailTitle": "email",
"AboutCompanyTelTitle": "тел.",
"LicensedUnder": "Это программное обеспечение лицензируется под", "_comment": "{0}GNU GPL v.3{1}",
"SourceCode": "Исходный код программы доступен по cсылке", "_comment": "{0}GNU GPL v.3{1}","_comment":"SYNTAX ERROR"
"LicensedUnder": "Это программное обеспечение лицензируется под",
"SourceCode": "Исходный код программы доступен по cсылке"
}

View File

@ -7,6 +7,7 @@ import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import ChangeEmailForm from "./sub-components/changeEmail";
import CreateUserForm from "./sub-components/createUser";
import ActivateUserForm from "./sub-components/activateUser";
// const CreateUserForm = lazy(() => import("./sub-components/createUser"));
const ChangePasswordForm = lazy(() => import("./sub-components/changePassword"));
@ -26,9 +27,13 @@ const Confirm = ({ match, language }) => {
>
<Switch>
<PublicRoute
path={[`${match.path}/LinkInvite`, `${match.path}/Activation`]}
path={`${match.path}/LinkInvite`}
component={CreateUserForm}
/>
<PublicRoute
path={`${match.path}/Activation`}
component={ActivateUserForm}
/>
<Route
exact
path={`${match.path}/EmailActivation`}

View File

@ -13,7 +13,7 @@
"ErrorPasswordNoUpperCase": "capital letters",
"ErrorPasswordNoSpecialSymbols": "special characters",
"EmailAndPasswordCopiedToClipboard": "Email and password copied to clipboard",
"PassworResetTitle": "Now you can create a new password.", "_comment":"SYNTAX ERROR 'Passwor' Reset Title",
"PassworResetTitle": "Now you can create a new password.",
"PasswordCustomMode": "Password",
"ImportContactsOkButton": "OK",
"LoadingProcessing": "Loading...",

View File

@ -0,0 +1,321 @@
import React from 'react';
import { withRouter } from "react-router";
import { withTranslation } from 'react-i18next';
import { Button, TextInput, PageLayout, Text, PasswordInput, toastr, Loader } from 'asc-web-components';
import styled from 'styled-components';
import { Collapse } from 'reactstrap';
import { connect } from 'react-redux';
import { welcomePageTitle } from './../../../../helpers/customNames';
import { EmployeeActivationStatus } from './../../../../helpers/constants';
import { getPasswordSettings, activateConfirmUser } from '../../../../store/auth/actions';
import PropTypes from 'prop-types';
const inputWidth = '400px';
const ConfirmContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-left: 200px;
@media (max-width: 830px) {
margin-left: 40px;
}
.start-basis {
align-items: flex-start;
}
.margin-left {
margin-left: 20px;
}
.full-width {
width: ${inputWidth}
}
.confirm-row {
margin: 23px 0 0;
}
.break-word {
word-break: break-word;
}
`;
const emailInputName = 'email';
const passwordInputName = 'password';
class Confirm extends React.PureComponent {
constructor(props) {
super(props);
const queryParams = props.location.search.slice(1).split('&');
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
const linkParams = Object.fromEntries(arrayOfQueryParams);
const indexOfSlash = this.props.match.path.lastIndexOf('/');
const typeLink = this.props.match.path.slice(indexOfSlash + 1);
this.state = {
email: decodeURIComponent(linkParams.email),
emailValid: true,
firstName: decodeURIComponent(linkParams.firstname),
firstNameValid: true,
lastName: decodeURIComponent(linkParams.lastname),
lastNameValid: true,
password: '',
passwordValid: true,
errorText: '',
isLoading: false,
passwordEmpty: false,
queryString: `type=${typeLink}&${props.location.search.slice(1)}`,
type: typeLink,
userId: linkParams.uid
};
}
onSubmit = (e) => {
this.setState({ isLoading: true }, function () {
const { history, activateConfirmUser } = this.props;
this.setState({ errorText: "" });
let hasError = false;
if (!this.state.firstName.trim()) {
hasError = true;
this.setState({ firstNameValid: !hasError });
}
if (!this.state.lastName.trim()) {
hasError = true;
this.setState({ lastNameValid: !hasError });
}
if (!this.state.passwordValid) {
hasError = true;
this.setState({ passwordValid: !hasError });
}
!this.state.password.trim() && this.setState({ passwordEmpty: true });
if (hasError) {
this.setState({ isLoading: false });
return false;
}
const loginData = {
userName: this.state.email,
password: this.state.password
};
const personalData = {
firstname: this.state.firstName,
lastname: this.state.lastName,
email: this.state.email
};
activateConfirmUser(personalData, loginData, this.state.queryString, this.state.userId, EmployeeActivationStatus.Activated)
.then(() => history.push('/'))
.catch(e => {
console.error("activate error", e);
this.setState({ errorText: e.message });
this.setState({ isLoading: false });
});
});
};
onKeyPress = (event) => {
if (event.key === "Enter") {
this.onSubmit();
}
};
onCopyToClipboard = () => toastr.success(this.props.t('EmailAndPasswordCopiedToClipboard'));
validatePassword = (value) => this.setState({ passwordValid: value });
componentDidMount() {
const { getPasswordSettings, history } = this.props;
getPasswordSettings(this.state.queryString)
.then(
function () {
console.log("get settings success");
}
)
.catch(e => {
console.error("get settings error", e);
history.push(`/login/error=${e}`);
});
window.addEventListener('keydown', this.onKeyPress);
window.addEventListener('keyup', this.onKeyPress);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.onKeyPress);
window.removeEventListener('keyup', this.onKeyPress);
}
onChangeName = event => {
this.setState({ firstName: event.target.value });
!this.state.firstNameValid && this.setState({ firstNameValid: event.target.value });
this.state.errorText && this.setState({ errorText: "" });
}
onChangeSurname = event => {
this.setState({ lastName: event.target.value });
!this.state.lastNameValid && this.setState({ lastNameValid: true });
this.state.errorText && this.setState({ errorText: "" });;
}
onChangePassword = event => {
this.setState({ password: event.target.value });
!this.state.passwordValid && this.setState({ passwordValid: true });
(event.target.value.trim()) && this.setState({ passwordEmpty: false });
this.state.errorText && this.setState({ errorText: "" });
this.onKeyPress(event);
}
render() {
console.log('ActivateUser render');
const { settings, isConfirmLoaded, t } = this.props;
return (
!isConfirmLoaded
? (
<Loader className="pageLoader" type="rombs" size={40} />
)
: (
<ConfirmContainer>
<div className='start-basis'>
<div className='margin-left'>
<Text.Body className='confirm-row' 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>
<div className='full-width'>
<TextInput
className='confirm-row'
id='name'
name='name'
value={this.state.firstName}
placeholder={t('FirstName')}
size='huge'
scale={true}
tabIndex={1}
isAutoFocussed={true}
autoComplete='given-name'
isDisabled={this.state.isLoading}
hasError={!this.state.firstNameValid}
onChange={this.onChangeName}
onKeyDown={this.onKeyPress}
/>
<TextInput
className='confirm-row'
id='surname'
name='surname'
value={this.state.lastName}
placeholder={t('LastName')}
size='huge'
scale={true}
tabIndex={2}
autoComplete='family-name'
isDisabled={this.state.isLoading}
hasError={!this.state.lastNameValid}
onChange={this.onChangeSurname}
onKeyDown={this.onKeyPress}
/>
</div>
<PasswordInput
className='confirm-row'
id='password'
inputName={passwordInputName}
emailInputName={emailInputName}
inputValue={this.state.password}
placeholder={t('InvitePassword')}
size='huge'
scale={true}
tabIndex={4}
maxLength={30}
inputWidth={inputWidth}
hasError={this.state.passwordEmpty}
onChange={this.onChangePassword}
onCopyToClipboard={this.onCopyToClipboard}
onValidateInput={this.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={this.state.isLoading}
onKeyDown={this.onKeyPress}
/>
<Button
className='confirm-row'
primary
size='big'
label={t('LoginRegistryButton')}
tabIndex={5}
isLoading={this.state.isLoading}
onClick={this.onSubmit}
/>
</div>
{/* <Row className='confirm-row'>
<Text.Body as='p' fontSize={14}>{t('LoginWithAccount')}</Text.Body>
</Row>
*/}
<Collapse className='confirm-row'
isOpen={!!this.state.errorText}>
<div className="alert alert-danger">{this.state.errorText}</div>
</Collapse>
</div>
</ConfirmContainer>
)
);
}
}
Confirm.propTypes = {
getPasswordSettings: PropTypes.func.isRequired,
activateConfirmUser: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
const ActivateUserForm = (props) => (<PageLayout sectionBodyContent={<Confirm {...props} />} />);
function mapStateToProps(state) {
return {
isConfirmLoaded: state.auth.isConfirmLoaded,
settings: state.auth.password
};
}
export default connect(mapStateToProps, { getPasswordSettings, activateConfirmUser })(withRouter(withTranslation()(ActivateUserForm)));

View File

@ -1,32 +1,41 @@
import React, { useState, useEffect, useCallback } from "react";
import React from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Row, Col, Card, CardImg, CardTitle } from "reactstrap";
import { Button, PageLayout, Text, PasswordInput } from "asc-web-components";
import { useTranslation } from "react-i18next";
import i18n from "../i18n";
import {
Container,
Collapse,
Row,
Col,
Card,
CardTitle,
CardImg
} from "reactstrap";
import {
Button,
PageLayout,
Text,
PasswordInput,
Loader,
toastr
} from "asc-web-components";
import { welcomePageTitle } from "../../../../helpers/customNames";
import {
changePassword,
getPasswordSettings
} from "../../../../../src/store/auth/actions";
const BodyStyle = styled.div`
const BodyStyle = styled(Container)`
margin-top: 70px;
p {
margin-bottom: 5px;
}
.password-row {
margin: 23px 0 0;
.password-card {
border: none;
.card-img {
max-width: 216px;
max-height: 35px;
@ -42,179 +51,214 @@ const BodyStyle = styled.div`
}
`;
const Form = props => {
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [errorText, setErrorText] = useState("");
const [passwordValid, setPasswordValid] = useState(true);
const {
match,
location,
history,
changePassword,
getPasswordSettings
} = props;
const { params } = match;
const { t } = useTranslation("translation", { i18n });
class Form extends React.PureComponent {
constructor(props) {
super(props);
const indexOfSlash = match.path.lastIndexOf("/");
const typeLink = match.path.slice(indexOfSlash + 1);
const queryString = `type=${typeLink}&${location.search.slice(1)}`;
const { match, location } = props;
getPasswordSettings(queryString)
.then(function() {
console.log("GET PASSWORD SETTINGS SUCCESS");
})
.catch(e => {
console.log("ERROR GET PASSWORD SETTINGS", e);
history.push(`/login/error=${e}`);
});
const str = location.search.split("&");
const userId = str[2].slice(4);
const indexOfSlash = match.path.lastIndexOf("/");
const typeLink = match.path.slice(indexOfSlash + 1);
const queryString = `type=${typeLink}&${location.search.slice(1)}`;
const onSubmit = useCallback(
e => {
errorText && setErrorText("");
this.state = {
password: "",
passwordValid: true,
isValidConfirmLink: false,
errorText: "",
isLoading: false,
passwordEmpty: false,
queryString: queryString,
type: typeLink,
userId: userId
};
}
onKeyPress = target => {
if (target.key === "Enter") {
this.onSubmit();
}
};
onChange = event => {
this.setState({ password: event.target.value });
!this.state.passwordValid && this.setState({ passwordValid: true });
event.target.value.trim() && this.setState({ passwordEmpty: false });
this.state.errorText && this.setState({ errorText: "" });
this.onKeyPress(event);
};
onSubmit = e => {
this.setState({ isLoading: true }, function() {
const { userId, password, queryString } = this.state;
const { history, changePassword } = this.props;
this.setState({ errorText: "" });
let hasError = false;
if (!password.trim()) {
if (!this.state.passwordValid) {
hasError = true;
setPasswordValid(!hasError);
this.setState({ passwordValid: !hasError });
}
if (hasError) return false;
!this.state.password.trim() && this.setState({ passwordEmpty: true });
setIsLoading(true);
console.log("changePassword onSubmit", match, location, history);
if (hasError) {
this.setState({ isLoading: false });
return false;
}
const str = location.search.split("&");
const userId = str[2].slice(4);
const key = `type=PasswordChange&${location.search.slice(1)}`;
changePassword(userId, { password }, key)
changePassword(userId, { password }, queryString)
.then(() => {
console.log("UPDATE PASSWORD");
history.push("/");
})
.catch(e => {
history.push("/");
console.log("ERROR UPDATE PASSWORD", e);
console.log("ERROR UPDATE PASSWORD:", e.message);
this.setState({ errorText: e.message });
this.setState({ isLoading: false });
});
},
[errorText, history, location, changePassword, match, password]
);
const onKeyPress = useCallback(
target => {
if (target.key === "Enter") {
onSubmit();
}
},
[onSubmit]
);
useEffect(() => {
params.error && setErrorText(params.error);
window.addEventListener("keydown", onKeyPress);
window.addEventListener("keyup", onKeyPress);
// Remove event listeners on cleanup
return () => {
window.removeEventListener("keydown", onKeyPress);
window.removeEventListener("keyup", onKeyPress);
};
}, [onKeyPress, params.error]);
const settings = {
minLength: 6,
upperCase: false,
digits: false,
specSymbols: false
});
};
const tooltipPasswordLength =
"from " + settings.minLength + " to 30 characters";
componentDidMount() {
const { getPasswordSettings, history } = this.props;
getPasswordSettings(this.state.queryString)
.then(() => {
console.log("GET PASSWORD SETTINGS SUCCESS");
})
.catch(e => {
console.log("ERROR GET PASSWORD SETTINGS", e);
history.push(`/login/error=${e}`);
});
const mdOptions = { size: 6, offset: 3 };
return (
<BodyStyle>
<Row className="password-row">
<Col sm="12" md={mdOptions}>
<Card className="password-card">
<CardImg
className="card-img"
src="images/dark_general.png"
alt="Logo"
top
window.addEventListener("keydown", this.onKeyPress);
window.addEventListener("keyup", this.onKeyPress);
}
componentWillUnmount() {
window.removeEventListener("keydown", this.onKeyPress);
window.removeEventListener("keyup", this.onKeyPress);
}
onCopyToClipboard = () =>
toastr.success(this.props.t("EmailAndPasswordCopiedToClipboard"));
validatePassword = value => this.setState({ passwordValid: value });
render() {
const { settings, isConfirmLoaded, t } = this.props;
const { isLoading, password, passwordEmpty, errorText } = this.state;
const mdOptions = { size: 6, offset: 3 };
return !isConfirmLoaded ? (
<Loader className="pageLoader" type="rombs" size={40} />
) : (
<BodyStyle>
<Row className="password-row">
<Col sm="12" md={mdOptions}>
<Card className="password-card">
<CardImg
className="card-img"
src="images/dark_general.png"
alt="Logo"
top
/>
<CardTitle className="card-title">
{t("CustomWelcomePageTitle", { welcomePageTitle })}
</CardTitle>
</Card>
</Col>
</Row>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<Text.Body fontSize={14}>{t("PassworResetTitle")}</Text.Body>
<PasswordInput
id="password"
name="password"
inputName="password"
inputValue={password}
size="huge"
scale={true}
type="password"
isDisabled={isLoading}
hasError={passwordEmpty}
onCopyToClipboard={this.onCopyToClipboard}
onValidateInput={this.validatePassword}
generatorSpecial="!@#$%^&*"
tabIndex={1}
value={password}
onChange={this.onChange}
emailInputName="E-mail"
passwordSettings={settings}
tooltipPasswordTitle="Password must contain:"
tooltipPasswordLength={`${t("ErrorPasswordLength", {
fromNumber: 6,
toNumber: 30
})}:`}
placeholder={t("PasswordCustomMode")}
maxLength={30}
onKeyDown={this.onKeyPress}
isAutoFocussed={true}
inputWidth="490px"
/>
<CardTitle className="card-title">
{t("CustomWelcomePageTitle", { welcomePageTitle })}
</CardTitle>
</Card>
</Col>
</Row>
<Row className="password-row">
<Col sm="12" md={mdOptions}>
<Button
primary
size="big"
tabIndex={3}
label={
isLoading ? t("LoadingProcessing") : t("ImportContactsOkButton")
}
isDisabled={isLoading}
isLoading={isLoading}
onClick={this.onSubmit}
/>
</Col>
</Row>
<Collapse isOpen={!!errorText}>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<div className="alert alert-danger">{errorText}</div>
</Col>
</Row>
</Collapse>
</BodyStyle>
);
}
}
<Text.Body fontSize={14}>{t("PassworResetTitle")}</Text.Body>
<PasswordInput
id="password"
name="password"
size="huge"
scale={true}
type="password"
isDisabled={isLoading}
hasError={!passwordValid}
tabIndex={1}
value={password}
onChange={event => {
setPassword(event.target.value);
!passwordValid && setPasswordValid(true);
errorText && setErrorText("");
onKeyPress(event.target);
}}
emailInputName="E-mail"
passwordSettings={settings}
tooltipPasswordTitle="Password must contain:"
tooltipPasswordLength={tooltipPasswordLength}
placeholder={t("PasswordCustomMode")}
maxLength={30}
//isAutoFocussed={true}
//autocomple="current-password"
//onKeyDown={event => onKeyPress(event.target)}
/>
</Col>
</Row>
<Row className="password-row">
<Col sm="12" md={mdOptions}>
<Button
primary
size="big"
tabIndex={3}
label={
isLoading ? t("LoadingProcessing") : t("ImportContactsOkButton")
}
isDisabled={isLoading}
isLoading={isLoading}
onClick={onSubmit}
/>
</Col>
</Row>
</BodyStyle>
);
};
const ChangePasswordForm = props => {
return <PageLayout sectionBodyContent={<Form {...props} />} />;
};
ChangePasswordForm.propTypes = {
Form.propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
changePassword: PropTypes.func.isRequired
};
ChangePasswordForm.defaultProps = {
Form.defaultProps = {
password: ""
};
const ChangePasswordForm = props => (
<PageLayout sectionBodyContent={<Form {...props} />} />
);
function mapStateToProps(state) {
return {
isValidConfirmLink: state.auth.isValidConfirmLink,
isConfirmLoaded: state.auth.isConfirmLoaded,
settings: state.auth.password
};
}
export default connect(
null,
mapStateToProps,
{ changePassword, getPasswordSettings }
)(withRouter(withTranslation()(ChangePasswordForm)));

View File

@ -6,8 +6,7 @@ import styled from 'styled-components';
import { Collapse } from 'reactstrap';
import { connect } from 'react-redux';
import { welcomePageTitle } from './../../../../helpers/customNames';
import { EmployeeActivationStatus } from './../../../../helpers/constants';
import { getPasswordSettings, createConfirmUser, activateConfirmUser } from '../../../../store/auth/actions';
import { getPasswordSettings, createConfirmUser } from '../../../../store/auth/actions';
import PropTypes from 'prop-types';
const inputWidth = '400px';
@ -64,9 +63,9 @@ class Confirm extends React.PureComponent {
this.state = {
email: '',
emailValid: true,
firstName: linkParams.firstname === undefined ? '' : decodeURIComponent(linkParams.firstname),
firstName: '',
firstNameValid: true,
lastName: linkParams.lastname === undefined ? '' : decodeURIComponent(linkParams.lastname),
lastName: '',
lastNameValid: true,
password: '',
passwordValid: true,
@ -82,7 +81,7 @@ class Confirm extends React.PureComponent {
onSubmit = (e) => {
this.setState({ isLoading: true }, function () {
const { history, createConfirmUser, activateConfirmUser } = this.props;
const { history, createConfirmUser } = this.props;
const queryParams = this.state.queryString.split('&');
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
const linkParams = Object.fromEntries(arrayOfQueryParams);
@ -102,7 +101,7 @@ class Confirm extends React.PureComponent {
this.setState({ lastNameValid: !hasError });
}
if (!this.state.queryEmail && !validationEmail.test(this.state.email.trim())) {
if (!validationEmail.test(this.state.email.trim())) {
hasError = true;
this.setState({ emailValid: !hasError });
}
@ -118,40 +117,24 @@ class Confirm extends React.PureComponent {
this.setState({ isLoading: false });
return false;
}
const emailData = this.state.type === 'LinkInvite' ? this.state.email : this.state.queryEmail;
const loginData = {
userName: emailData,
userName: this.state.email,
password: this.state.password
};
const personalData = {
firstname: this.state.firstName,
lastname: this.state.lastName,
email: emailData
email: this.state.email
};
const registerData = Object.assign(personalData, { isVisitor: isVisitor })
switch (this.state.type) {
case 'LinkInvite':
createConfirmUser(registerData, loginData, this.state.queryString)
.then(() => history.push('/'))
.catch(e => {
console.error("confirm error", e);
this.setState({ errorText: e.message });
this.setState({ isLoading: false });
});
break;
case 'Activation':
activateConfirmUser(personalData, loginData, this.state.queryString, this.state.userId, EmployeeActivationStatus.Activated)
.then(() => history.push('/'))
.catch(e => {
console.error("activate error", e);
this.setState({ errorText: e.message });
this.setState({ isLoading: false });
});
break;
default: return;
}
createConfirmUser(registerData, loginData, this.state.queryString)
.then(() => history.push('/'))
.catch(e => {
console.error("confirm error", e);
this.setState({ errorText: e.message });
this.setState({ isLoading: false });
})
});
};
@ -271,7 +254,7 @@ class Confirm extends React.PureComponent {
onKeyDown={this.onKeyPress}
/>
{this.state.type === 'LinkInvite' && <TextInput
<TextInput
className='confirm-row'
id='email'
name={emailInputName}
@ -286,7 +269,7 @@ class Confirm extends React.PureComponent {
onChange={this.onChangeEmail}
onKeyDown={this.onKeyPress}
/>
}
</div>
<PasswordInput
@ -366,4 +349,4 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser, activateConfirmUser })(withRouter(withTranslation()(CreateUserForm)));
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser })(withRouter(withTranslation()(CreateUserForm)));

View File

@ -0,0 +1,89 @@
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { AUTH_KEY } from './constants';
import Cookies from 'universal-cookie';
import { connect } from 'react-redux';
import { checkConfirmLink } from './../store/auth/actions';
import { ValidationResult } from './../helpers/constants';
import decomposeConfirmLink from './../helpers/decomposeConfirmLink';
import { PageLayout, Loader } from "asc-web-components";
class ConfirmRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
type: '',
key: '',
p: '',
emplType: '',
email: '',
uid: '',
firstname: '',
lastname: '',
isReady: false,
componentProps: {}
}
}
componentDidMount() {
const { pathname, search } = this.props.location;
const { checkConfirmLink } = this.props;
const decomposedLink = decomposeConfirmLink(pathname, search);
let validationResult;
checkConfirmLink(decomposedLink)
.then((res) => {
validationResult = res.data.response;
switch (validationResult) {
case ValidationResult.Ok:
const confirmHeader = `type=${decomposedLink.type}&${search.slice(1)}`;
const componentProps = Object.assign({}, decomposedLink, { confirmHeader });
this.setState({
isReady: true,
componentProps
});
break;
case ValidationResult.Invalid:
window.location.href = '/login/error=Invalid link'
break;
case ValidationResult.Expired:
window.location.href = '/login/error=Expired link'
break;
default:
window.location.href = '/login/error=Unknown error'
break;
}
})
.catch((e) => window.location.href = '/');
}
render() {
const { component: Component, location, path, computedMatch, ...rest } = this.props;
const newProps = Object.assign({}, { location, path, computedMatch }, { linkData: this.state.componentProps });
return (
<Route
{...rest}
render={props =>
!this.state.isReady ? (
<PageLayout
sectionBodyContent={
<Loader className="pageLoader" type="rombs" size={40} />
}
/>
) :
(new Cookies()).get(AUTH_KEY) ? (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
) : (
<Component {...newProps} />
)
}
/>
)
}
};
export default connect(null, { checkConfirmLink })(ConfirmRoute);

View File

@ -10,3 +10,37 @@ export const EmployeeActivationStatus = Object.freeze({
Pending: 2,
AutoGenerated: 4
});
/**
* Enum for type of confirm link.
* @readonly
*/
export const ConfirmType = Object.freeze({
EmpInvite: 0,
LinkInvite: 1,
PortalSuspend: 2,
PortalContinue: 3,
PortalRemove: 4,
DnsChange: 5,
PortalOwnerChange: 6,
Activation: 7,
EmailChange: 8,
EmailActivation: 9,
PasswordChange: 10,
ProfileRemove: 11,
PhoneActivation: 12,
PhoneAuth: 13,
Auth: 14,
TfaActivation: 15,
TfaAuth: 16
});
/**
* Enum for result of validation confirm link.
* @readonly
*/
export const ValidationResult = Object.freeze({
Ok: 0,
Invalid: 1,
Expired: 2
});

View File

@ -0,0 +1,11 @@
const decomposeLink = (url, querySearch) => {
const queryString = querySearch.slice(1).split('&');
const arrayOfQueryString = queryString.map(queryParam => queryParam.split('='));
const queryParams = Object.fromEntries(arrayOfQueryString);
const posSeparator = url.lastIndexOf('/');
const type = url.slice(posSeparator + 1);
const data = Object.assign({ type }, queryParams);
return data;
}
export default decomposeLink;

View File

@ -202,4 +202,10 @@ export function activateConfirmUser(personalData, loginData, key, userId, activa
return getUserInfo(dispatch);
});
};
};
};
export function checkConfirmLink(data) {
return dispatch => {
return api.checkConfirmLink(data);
}
}

View File

@ -81,4 +81,10 @@ export function updateUser(data) {
return IS_FAKE
? fakeApi.updateUser()
: axios.put(`${API_URL}/people/${data.id}`, data);
}
export function checkConfirmLink(data) {
return IS_FAKE
? fakeApi.checkConfirmLink()
: axios.post(`${API_URL}/authentication/confirm.json`, data);
}

View File

@ -163,3 +163,7 @@ export function updateActivationStatus() {
export function updateUser(data) {
return fakeResponse(data);
}
export function checkConfirmLink(data) {
return fakeResponse(data);
}