Merge branch 'master' into feature/advanced-selector-v2

# Conflicts:
#	web/ASC.Web.Components/src/components/help-button/index.js
This commit is contained in:
Alexey Safronov 2019-11-29 12:22:14 +03:00
commit d44f0ea5f0
27 changed files with 676 additions and 860 deletions

View File

@ -2,14 +2,13 @@ import React, { Suspense } from "react";
import { connect } from "react-redux";
import { Router, Switch, Redirect } from "react-router-dom";
import { Loader } from "asc-web-components";
import PeopleLayout from "./components/Layout/index";
import Home from "./components/pages/Home";
import Profile from './components/pages/Profile';
import ProfileAction from './components/pages/ProfileAction';
import GroupAction from './components/pages/GroupAction';
import Reassign from './components/pages/Reassign';
import Import from './components/pages/Import';
import { history, PrivateRoute, PublicRoute, Login, Error404 } from "asc-web-common";
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout } from "asc-web-common";
/*const Profile = lazy(() => import("./components/pages/Profile"));
const ProfileAction = lazy(() => import("./components/pages/ProfileAction"));
@ -19,7 +18,7 @@ const App = ({ settings }) => {
const { homepage } = settings;
return (
<Router history={history}>
<PeopleLayout>
<StudioLayout>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
>
@ -65,7 +64,7 @@ const App = ({ settings }) => {
<PrivateRoute component={Error404} />
</Switch>
</Suspense>
</PeopleLayout>
</StudioLayout>
</Router>
);
};

View File

@ -1,61 +0,0 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import config from "../../../package.json";
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === 'lowercase') return value.toLowerCase();
return value;
}
},
react: {
useSuspense: false
},
backend: {
loadPath: `${config.homepage}/locales/Layout/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
}
};
newInstance.init({
resources: resources,
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === 'lowercase') return value.toLowerCase();
return value;
}
},
react: {
useSuspense: false
}
});
}
export default newInstance;

View File

@ -1,126 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from "react-router";
import { Layout, Toast } from 'asc-web-components';
import { withTranslation, I18nextProvider } from 'react-i18next';
import i18n from "./i18n";
import { store } from 'asc-web-common';
const { logout } = store.auth.actions;
class PurePeopleLayout extends React.Component {
shouldComponentUpdate(nextProps) {
if (this.props.hasChanges !== nextProps.hasChanges) {
return true;
}
return false;
}
onProfileClick = () => {
console.log('ProfileBtn');
const { history, settings } = this.props;
history.push(`${settings.homepage}/view/@self`);
}
onAboutClick = () => {
window.location.href = "/about";
}
onLogoutClick = () => {
this.props.logout();
}
onLogoClick = () => window.open("/", '_self');
render() {
const { hasChanges, children, t } = this.props;
const currentUserActions = [
{
key: 'ProfileBtn', label: t('Profile'), onClick: this.onProfileClick
},
{
key: 'AboutBtn', label: t('AboutCompanyTitle'), onClick: this.onAboutClick
},
{
key: 'LogoutBtn', label: t('LogoutButton'), onClick: this.onLogoutClick
},
];
const newProps = hasChanges
? {
currentUserActions: currentUserActions,
onLogoClick: this.onLogoClick,
...this.props
}
: {};
console.log("PeopleLayout render", newProps);
return (
<>
<Toast />
<Layout key="1" {...newProps}>{children}</Layout>
</>
);
}
};
const getAvailableModules = (modules, currentUser) => {
const isUserAdmin = currentUser.isAdmin;
const customModules = isUserAdmin ? [
{
separator: true,
id: "nav-separator-2"
},
{
id: 'settings',
title: 'Settings',
iconName: "SettingsIcon",
notifications: 0,
url: '/settings',
onClick: () => window.open('/settings', "_self"),
onBadgeClick: e => console.log("SettingsIconBadge Clicked", e)
}] : [];
const separator = { separator: true, id: 'nav-separator-1' };
const products = modules.map(product => {
return {
id: product.id,
title: product.title,
iconName: 'PeopleIcon',
notifications: 0,
url: product.link,
onClick: () => window.open(product.link, '_self'),
onBadgeClick: e => console.log('PeopleIconBadge Clicked', e)
};
}) || [];
return products.length ? [separator, ...products, ...customModules] : products;
};
function mapStateToProps(state) {
return {
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
availableModules: getAvailableModules(state.auth.modules, state.auth.user),
currentUser: state.auth.user,
currentModuleId: state.auth.settings.currentProductId,
settings: state.auth.settings,
language: state.auth.user.cultureName || state.auth.settings.culture,
};
}
const PeopleLayoutContainer = withTranslation()(PurePeopleLayout);
const PeopleLayout = (props) => {
const { language } = props;
i18n.changeLanguage(language);
return (<I18nextProvider i18n={i18n}><PeopleLayoutContainer {...props} /></I18nextProvider>);
};
PeopleLayout.propTypes = {
logout: PropTypes.func.isRequired
};
export default connect(mapStateToProps, { logout })(withRouter((PeopleLayout)));

View File

@ -1,8 +1,7 @@
import React, { Suspense, lazy } from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Loader } from "asc-web-components";
import StudioLayout from "./components/Layout/index";
import { history, PrivateRoute, PublicRoute, Login, Error404 } from "asc-web-common";
import { history, PrivateRoute, PublicRoute, Login, Error404, StudioLayout} from "asc-web-common";
const Home = lazy(() => import("./components/pages/Home"));
const About = lazy(() => import("./components/pages/About"));

View File

@ -1,60 +0,0 @@
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
const newInstance = i18n.createInstance();
if (process.env.NODE_ENV === "production") {
newInstance
.use(Backend)
.init({
lng: 'en',
fallbackLng: "en",
debug: false,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === 'lowercase') return value.toLowerCase();
return value;
}
},
react: {
useSuspense: false
},
backend: {
loadPath: `/locales/Layout/{{lng}}/{{ns}}.json`
}
});
} else if (process.env.NODE_ENV === "development") {
const resources = {
en: {
translation: require("./locales/en/translation.json")
},
ru: {
translation: require("./locales/ru/translation.json")
}
};
newInstance.init({
resources: resources,
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === 'lowercase') return value.toLowerCase();
return value;
}
},
react: {
useSuspense: false
}
});
}
export default newInstance;

View File

@ -1,124 +0,0 @@
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import { Layout, Toast } from "asc-web-components";
import { withTranslation, I18nextProvider } from 'react-i18next';
import i18n from "./i18n";
import isEqual from "lodash/isEqual";
import { store } from 'asc-web-common';
const { logout } = store.auth.actions;
class PureStudioLayout extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
}
onProfileClick = () => {
window.location.href = "/products/people/view/@self";
}
onAboutClick = () => {
this.props.history.push("/about");
}
onLogoutClick = () => {
this.props.logout();
this.props.history.push("/");
}
onLogoClick = () => this.props.history.push("/");
render() {
const { hasChanges, children, t } = this.props;
const currentUserActions = [
{
key: 'ProfileBtn', label: t("Profile"), onClick: this.onProfileClick
},
{
key: 'AboutBtn', label: t("AboutCompanyTitle"), onClick: this.onAboutClick
},
{
key: 'LogoutBtn', label: t("LogoutButton"), onClick: this.onLogoutClick
},
];
const newProps = hasChanges
? {
currentUserActions: currentUserActions,
onLogoClick: this.onLogoClick,
...this.props
}
: {};
console.log("StudioLayout render", newProps);
return (
<>
<Toast />
<Layout key="1" {...newProps}>{children}</Layout>
</>
);
}
};
const getAvailableModules = (modules, currentUser) => {
const isUserAdmin = currentUser.isAdmin;
const separator = { separator: true, id: "nav-separator-1" };
const customModules = isUserAdmin ? [
{
separator: true,
id: "nav-separator-2"
},
{
id: 'settings',
title: 'Settings',
iconName: "SettingsIcon",
notifications: 0,
url: '/settings',
onClick: () => window.open('/settings', "_self"),
onBadgeClick: e => console.log("SettingsIconBadge Clicked", e)
}] : [];
const products =
modules.map(product => {
return {
id: product.id,
title: product.title,
iconName: "PeopleIcon",
notifications: 0,
url: product.link,
onClick: () => window.open(product.link, "_self"),
onBadgeClick: e => console.log("PeopleIconBadge Clicked", e)
};
}) || [];
return products.length ? [separator, ...products, ...customModules] : products;
};
function mapStateToProps(state) {
let availableModules = getAvailableModules(state.auth.modules, state.auth.user);
return {
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
availableModules: availableModules,
currentUser: state.auth.user,
currentModuleId: state.auth.settings.currentProductId,
language: state.auth.user.cultureName || state.auth.settings.culture,
};
};
const StudioLayoutContainer = withTranslation()(PureStudioLayout);
const StudioLayout = (props) => {
const { language } = props;
i18n.changeLanguage(language);
return (<I18nextProvider i18n={i18n}><StudioLayoutContainer {...props} /></I18nextProvider>);
};
StudioLayout.propTypes = {
logout: PropTypes.func.isRequired
};
export default connect(
mapStateToProps,
{ logout }
)(withRouter(StudioLayout));

View File

@ -1,5 +0,0 @@
{
"Profile": "Profile",
"AboutCompanyTitle": "About this program",
"LogoutButton": "Sign Out"
}

View File

@ -1,5 +0,0 @@
{
"Profile": "Профиль",
"AboutCompanyTitle": "О программе",
"LogoutButton": "Выйти"
}

View File

@ -3,7 +3,6 @@ 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 PropTypes from 'prop-types';
import { store, constants } from 'asc-web-common';
@ -298,10 +297,9 @@ class Confirm extends React.PureComponent {
</Row>
*/}
<Collapse className='confirm-row'
isOpen={!!this.state.errorText}>
<div className="alert alert-danger">{this.state.errorText}</div>
</Collapse>
<Text.Body className="confirm-row" fontSize={14} color="#c30">
{this.state.errorText}
</Text.Body>
</div>
</ConfirmContainer>
)

View File

@ -4,44 +4,44 @@ import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Container, Row, Col, Card, CardTitle, CardImg } from "reactstrap";
import { Button, PageLayout, Text, toastr } from "asc-web-components";
//import { } from "../../../../../src/store/auth/actions";
const BodyStyle = styled(Container)`
const BodyStyle = styled.div`
margin-top: 70px;
.buttons-style {
margin-top: 20px;
min-width: 110px;
}
.button-style {
margin-right: 8px;
}
.confirm_text {
margin: 12px 0;
}
.owner-container {
display: grid;
.password-card {
border: none;
.card-img {
max-width: 216px;
max-height: 35px;
}
.card-title {
word-wrap: break-word;
margin: 8px 0;
text-align: left;
font-size: 24px;
color: #116d9d;
}
}
.owner-wrapper {
align-self: center;
justify-self: center;
.owner-img {
max-width: 216px;
max-height: 35px;
}
.owner-title {
word-wrap: break-word;
margin: 8px 0;
text-align: left;
font-size: 24px;
color: #116d9d;
}
.owner-confirm_text {
margin: 20px 0 12px 0;
}
.owner-buttons {
margin-top: 20px;
min-width: 110px;
}
.owner-button {
margin-right: 8px;
}
.row_display {
display: flex;
}
.confirm-text {
margin-top: 32px;
.owner-confirm-message {
margin-top: 32px;
}
}
}
`;
@ -68,61 +68,47 @@ class Form extends React.PureComponent {
render() {
const { t, greetingTitle } = this.props;
const mdOptions = { size: 6, offset: 2 };
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
/>
<CardTitle className="card-title">{greetingTitle}</CardTitle>
</Card>
</Col>
</Row>
<Row>
<Col sm="12" md={{ size: 12, offset: 2 }}>
<Text.Body className="confirm_text" fontSize={18}>
<div className="owner-container">
<div className="owner-wrapper">
<img
className="owner-img"
src="images/dark_general.png"
alt="Logo"
/>
<Text.Body className="owner-title">{greetingTitle}</Text.Body>
<Text.Body className="owner-confirm_text" fontSize={18}>
{t("ConfirmOwnerPortalTitle", { newOwner: "NEW OWNER" })}
</Text.Body>
</Col>
</Row>
{this.state.showButtons ? (
<Row>
<Col className="row_display" sm="12" md={mdOptions}>
<Button
className="button-style buttons-style"
primary
size="big"
label={t("SaveButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onAcceptClick}
/>
<Button
className="buttons-style"
size="big"
label={t("CancelButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onCancelClick}
/>
</Col>
</Row>
) : (
<Row>
<Col sm="12" md={{ size: 12, offset: 2 }}>
<Text.Body className="confirm-text" fontSize={12}>
{this.state.showButtons ? (
<>
<Button
className="owner-button owner-buttons"
primary
size="big"
label={t("SaveButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onAcceptClick}
/>
<Button
className="owner-buttons"
size="big"
label={t("CancelButton")}
tabIndex={2}
isDisabled={false}
onClick={this.onCancelClick}
/>
</>
) : (
<Text.Body className="owner-confirm-message" fontSize={12}>
{t("ConfirmOwnerPortalSuccessMessage")}
</Text.Body>
</Col>
</Row>
)}
)}
</div>
</div>
</BodyStyle>
);
}

View File

@ -4,7 +4,6 @@ import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import styled from "styled-components";
import { Container, Row, Col, Card, CardTitle, CardImg } from "reactstrap";
import {
Button,
PageLayout,
@ -13,34 +12,32 @@ import {
Loader,
toastr
} from "asc-web-components";
import { store } from 'asc-web-common';
const { changePassword, getConfirmationInfo, logout } = store.auth.actions;
import { store } from "asc-web-common";
const { changePassword, getConfirmationInfo, logout } = store.auth.actions;
const BodyStyle = styled(Container)`
margin-top: 70px;
p {
const BodyStyle = styled.form`
margin: 70px auto 0 auto;
max-width: 500px;
.password-header {
margin-bottom: 24px;
.password-logo {
max-width: 216px;
max-height: 35px;
}
.password-title {
margin: 8px 0;
}
}
.password-text {
margin-bottom: 5px;
}
.button-style {
margin-top: 20px;
}
.password-row {
margin: 23px 0 0;
.password-card {
border: none;
.card-img {
max-width: 216px;
max-height: 35px;
}
.card-title {
word-wrap: break-word;
margin: 8px 0;
text-align: left;
font-size: 24px;
color: #116d9d;
}
}
.password-button {
margin-top: 20px;
}
`;
@ -106,8 +103,7 @@ class Form extends React.PureComponent {
componentDidMount() {
const { getConfirmationInfo, history } = this.props;
getConfirmationInfo(this.state.key)
.catch(error => {
getConfirmationInfo(this.state.key).catch(error => {
toastr.error(this.props.t(`${error}`));
history.push("/");
});
@ -126,69 +122,65 @@ class Form extends React.PureComponent {
render() {
const { settings, isConfirmLoaded, t, greetingTitle } = this.props;
const { isLoading, password, passwordEmpty } = 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">
{greetingTitle}
</CardTitle>
</Card>
<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}
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"
/>
<Button
className="button-style"
primary
size="big"
tabIndex={2}
label={
isLoading ? t("LoadingProcessing") : t("ImportContactsOkButton")
}
isDisabled={isLoading}
isLoading={isLoading}
onClick={this.onSubmit}
/>
</Col>
</Row>
<div className="password-header">
<img
className="password-logo"
src="images/dark_general.png"
alt="Logo"
/>
<Text.Headline className="password-title" color="#116d9d">
{greetingTitle}
</Text.Headline>
</div>
<Text.Body className="password-text" 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}
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"
/>
<Button
id="button"
className="password-button"
primary
size="big"
tabIndex={2}
label={
isLoading ? t("LoadingProcessing") : t("ImportContactsOkButton")
}
isDisabled={isLoading}
isLoading={isLoading}
onClick={this.onSubmit}
/>
</BodyStyle>
);
}
@ -215,11 +207,12 @@ function mapStateToProps(state) {
isConfirmLoaded: state.auth.isConfirmLoaded,
settings: state.auth.settings.passwordSettings,
isAuthenticated: state.auth.isAuthenticated,
greetingTitle: state.auth.settings.greetingSettings,
greetingTitle: state.auth.settings.greetingSettings
};
}
export default connect(
mapStateToProps,
{ changePassword, getConfirmationInfo, logout }
)(withRouter(withTranslation()(ChangePasswordForm)));
export default connect(mapStateToProps, {
changePassword,
getConfirmationInfo,
logout
})(withRouter(withTranslation()(ChangePasswordForm)));

View File

@ -3,7 +3,6 @@ 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 PropTypes from 'prop-types';
import { store } from 'asc-web-common';
@ -318,10 +317,9 @@ class Confirm extends React.PureComponent {
</Row>
*/}
<Collapse className='confirm-row'
isOpen={!!this.state.errorText}>
<div className="alert alert-danger">{this.state.errorText}</div>
</Collapse>
<Text.Body className='confirm-row' fontSize={14} color="#c30">
{this.state.errorText}
</Text.Body>
</div>
</ConfirmContainer>
)

View File

@ -1,6 +1,6 @@
import React, { Suspense, useEffect } from "react";
import React, { useEffect } from "react";
import { connect } from 'react-redux';
import { Loader, PageLayout } from "asc-web-components";
import { PageLayout } from "asc-web-components";
import i18n from "../i18n";
import { I18nextProvider } from "react-i18next";
import {
@ -20,18 +20,13 @@ const Layout = ({ currentProductId, setCurrentProductId, language, children }) =
return (
<I18nextProvider i18n={i18n}>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
>
<PageLayout
withBodyScroll={true}
articleHeaderContent={<ArticleHeaderContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent />}
sectionBodyContent={children}
/>
</Suspense>
<PageLayout
withBodyScroll={true}
articleHeaderContent={<ArticleHeaderContent />}
articleBodyContent={<ArticleBodyContent />}
sectionHeaderContent={<SectionHeaderContent />}
sectionBodyContent={children}
/>
</I18nextProvider >
);
};

View File

@ -1,6 +1,6 @@
{
"name": "asc-web-common",
"version": "1.0.1",
"version": "1.0.4",
"description": "Ascensio System SIA common components and solutions library",
"license": "AGPL-3.0",
"files": [
@ -122,4 +122,4 @@
"node": ">=8",
"npm": ">=5"
}
}
}

View File

@ -1,3 +1,4 @@
export { default as PrivateRoute } from "./routing/privateRoute";
export { default as PublicRoute } from "./routing/publicRoute";
export { default as Login } from "./login";
export { default as Login } from "./login";
export { default as StudioLayout } from "./layout";

View File

@ -0,0 +1,35 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en//require("./locales/en/translation.json")
},
ru: {
translation: ru//require("./locales/ru/translation.json")
}
};
newInstance.init({
resources: resources,
lng: 'en',
fallbackLng: "en",
debug: true,
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
format: function (value, format) {
if (format === 'lowercase') return value.toLowerCase();
return value;
}
},
react: {
useSuspense: false
}
});
export default newInstance;

View File

@ -0,0 +1,89 @@
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import { withTranslation, I18nextProvider } from "react-i18next";
import i18n from "./i18n";
import { logout } from "../../store/auth/actions";
import PureStudioLayout from "./pureStudioLayout";
const getSeparator = id => {
return {
separator: true,
id: id
};
};
const toModuleWrapper = (item, iconName) => {
return {
id: item.id,
title: item.title,
iconName: iconName || "PeopleIcon",
notifications: 0,
url: item.link,
onClick: () => window.open(item.link, "_self"),
onBadgeClick: e => console.log(iconName + " Badge Clicked", e)
};
};
const getCustomModules = isAdmin => {
if (!isAdmin) {
return [];
}
const separator = getSeparator("nav-modules-separator");
const settingsModuleWrapper = toModuleWrapper(
{
id: "settings",
title: "Settings",
link: "/settings"
},
"SettingsIcon"
);
return [separator, settingsModuleWrapper];
};
const getAvailableModules = (modules, currentUser) => {
if (!modules.length) {
return [];
}
const isUserAdmin = currentUser.isAdmin;
const customModules = getCustomModules(isUserAdmin);
const separator = getSeparator("nav-products-separator");
const products = modules.map(toModuleWrapper);
return [separator, ...products, ...customModules];
};
function mapStateToProps(state) {
return {
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
availableModules: getAvailableModules(state.auth.modules, state.auth.user),
currentUser: state.auth.user,
currentModuleId: state.auth.settings.currentProductId,
settings: state.auth.settings,
language: state.auth.user.cultureName || state.auth.settings.culture
};
}
const StudioLayoutContainer = withTranslation()(PureStudioLayout);
const StudioLayout = props => {
const { language } = props;
i18n.changeLanguage(language);
return (
<I18nextProvider i18n={i18n}>
<StudioLayoutContainer {...props} />
</I18nextProvider>
);
};
StudioLayout.propTypes = {
logout: PropTypes.func.isRequired,
language: PropTypes.string
};
export default connect(mapStateToProps, { logout })(withRouter(StudioLayout));

View File

@ -0,0 +1,85 @@
import React from "react";
import PropTypes from "prop-types";
import { Layout, Toast } from "asc-web-components";
class PureStudioLayout extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.hasChanges !== nextProps.hasChanges ||
this.props.currentModuleId !== nextProps.currentModuleId;
}
onProfileClick = () => {
const { history, settings } = this.props;
if (settings.homepage == "/products/people") {
history.push("/products/people/view/@self");
} else {
window.open("/products/people/view/@self", "_self")
}
};
onAboutClick = () => {
window.open("/about", "_self")
};
onLogoutClick = () => {
this.props.logout();
};
onLogoClick = () => {
window.open("/", "_self")
};
render() {
const { hasChanges, children, t } = this.props;
const currentUserActions = [
{
key: "ProfileBtn",
label: t("Profile"),
onClick: this.onProfileClick
},
{
key: "AboutBtn",
label: t("AboutCompanyTitle"),
onClick: this.onAboutClick
},
{
key: "LogoutBtn",
label: t("LogoutButton"),
onClick: this.onLogoutClick
}
];
const newProps = hasChanges
? {
currentUserActions: currentUserActions,
onLogoClick: this.onLogoClick,
...this.props
}
: {};
console.log("PureStudioLayout render", newProps);
return (
<>
<Toast />
<Layout key="1" {...newProps}>
{children}
</Layout>
</>
);
}
}
PureStudioLayout.propTypes = {
logout: PropTypes.func.isRequired,
language: PropTypes.string,
hasChanges: PropTypes.bool,
currentModuleId: PropTypes.string,
history: PropTypes.object,
settings: PropTypes.object,
children: PropTypes.any,
t: PropTypes.func
};
export default PureStudioLayout;

View File

@ -1,15 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import {
Collapse,
Container,
Row,
Col,
Card,
CardTitle,
CardImg
} from "reactstrap";
import {
Button,
TextInput,
@ -22,329 +13,348 @@ import {
} from "asc-web-components";
import { connect } from "react-redux";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import { withTranslation, I18nextProvider } from "react-i18next";
import i18n from "./i18n";
import SubModalDialog from "./sub-components/modal-dialog";
import { login, setIsLoaded } from "../../store/auth/actions";
import { sendInstructionsToChangePassword } from "../../api/people";
const FormContainer = styled(Container)`
margin-top: 70px;
const FormContainer = styled.form`
margin: 50px auto 0 auto;
max-width: 432px;
.link-style {
float: right;
line-height: 16px;
.login-header {
margin-bottom: 24px;
.login-logo {
max-width: 216px;
max-height: 35px;
}
.login-title {
margin: 8px 0;
}
}
.text-body {
.login-input {
margin-bottom: 24px;
}
.login-forgot-wrapper {
height: 36px;
.login-checkbox-wrapper {
position: absolute;
display: inline-flex;
.login-checkbox {
float: left;
span {
font-size: 12px;
}
}
.login-tooltip {
margin-left: 3px;
display: inline-flex;
}
}
.login-link {
float: right;
line-height: 16px;
}
}
.login-button {
margin-bottom: 16px;
}
.btn-style {
.login-button-dialog {
margin-right: 8px;
}
.checkbox {
float: left;
span {
font-size: 12px;
}
}
.question-icon {
float: left;
margin-left: 4px;
line-height: 16px;
}
.login-row {
margin: 23px 0 0;
.login-card {
border: none;
.card-img {
max-width: 216px;
max-height: 35px;
}
.card-title {
word-wrap: break-word;
margin: 8px 0;
text-align: left;
font-size: 24px;
color: #116d9d;
}
}
}
.button-row {
margin: 16px 0 0;
}
`;
const TooltipStyle = styled.span`
margin-left: 3px;
position: absolute;
margin-top: 2px;
`;
class Form extends Component {
constructor(props) {
super(props);
const mdOptions = { size: 6, offset: 3 };
this.state = {
identifierValid: true,
identifier: "",
isLoading: false,
isDisabled: false,
passwordValid: true,
password: "",
isChecked: false,
openDialog: false,
email: "",
errorText: ""
};
}
const Form = props => {
const { t } = useTranslation("translation", { i18n });
const { login, setIsLoaded, match, history, language, greetingTitle } = props;
const { params } = match;
const [identifier, setIdentifier] = useState(params.confirmedEmail || "");
const [identifierValid, setIdentifierValid] = useState(true);
const [password, setPassword] = useState("");
const [passwordValid, setPasswordValid] = useState(true);
const [errorText, setErrorText] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
const [email, setEmail] = useState("");
const [isDisabled, setIsDisabled] = useState(false);
const [isChecked, setIsisChecked] = useState(false);
const onClick = () => {
setOpenDialog(true);
setIsDisabled(true);
setEmail(identifier);
onChangeLogin = event => {
this.setState({ identifier: event.target.value });
!this.state.identifierValid && this.setState({ identifierValid: true });
this.state.errorText && this.setState({ errorText: "" });
};
const onDialogClose = () => {
setOpenDialog(false);
setIsDisabled(false);
setIsLoading(false);
setEmail("");
onChangePassword = event => {
this.setState({ password: event.target.value });
!this.state.passwordValid && this.setState({ passwordValid: true });
this.state.errorText && this.setState({ errorText: "" });
};
const onSendPasswordInstructions = useCallback(() => {
setIsLoading(true);
sendInstructionsToChangePassword(email)
.then(res => toastr.success(res), message => toastr.error(message))
.finally(onDialogClose());
}, [email]);
onChangeEmail = event => {
this.setState({ email: event.target.value });
};
const onSubmit = useCallback(() => {
errorText && setErrorText("");
onChangeCheckbox = () => this.setState({ isChecked: !this.state.isChecked });
onClick = () => {
this.setState({
openDialog: true,
isDisabled: true,
email: this.state.identifier
});
};
onKeyPress = event => {
if (event.key === "Enter") {
!this.state.isDisabled
? this.onSubmit()
: this.onSendPasswordInstructions();
}
};
onSendPasswordInstructions = () => {
this.setState({ isLoading: true });
sendInstructionsToChangePassword(this.state.email)
.then(
res => toastr.success(res),
message => toastr.error(message)
)
.finally(this.onDialogClose());
};
onDialogClose = () => {
this.setState({
openDialog: false,
isDisabled: false,
isLoading: false,
email: ""
});
};
onSubmit = () => {
const { errorText, identifier, password } = this.state;
const { login, setIsLoaded, history } = this.props;
errorText && this.setState({ errorText: "" });
let hasError = false;
const userName = identifier.trim();
if (!userName) {
hasError = true;
setIdentifierValid(!hasError);
this.setState({ identifierValid: !hasError });
}
const pass = password.trim();
if (!pass) {
hasError = true;
setPasswordValid(!hasError);
this.setState({ passwordValid: !hasError });
}
if (hasError) return false;
setIsLoading(true);
this.setState({ isLoading: true });
login(userName, pass).then(
() => {
//console.log("auth success", match, location, history);
setIsLoading(false);
login(userName, pass)
.then(() => {
setIsLoaded(true);
history.push("/");
},
error => {
//console.error("auth error", error);
setErrorText(error);
setIsLoading(false);
}
);
}, [errorText, history, identifier, login, setIsLoaded, password]);
})
.catch(error => {
this.setState({ errorText: error, isLoading: false });
});
};
const onKeyPress = useCallback(
event => {
if (event.key === "Enter") {
!isDisabled ? onSubmit() : onSendPasswordInstructions();
}
},
[onSendPasswordInstructions, onSubmit, isDisabled]
);
useEffect(() => {
componentDidMount() {
const { language, match } = this.props;
const { params } = match;
i18n.changeLanguage(language);
params.error && setErrorText(params.error);
window.addEventListener("keyup", onKeyPress);
// Remove event listeners on cleanup
return () => {
window.removeEventListener("keyup", onKeyPress);
};
}, [onKeyPress, params, language]);
params.error && this.setState({ errorText: params.error });
window.addEventListener("keyup", this.onKeyPress);
}
const onChangePassword = event => {
setPassword(event.target.value);
!passwordValid && setPasswordValid(true);
errorText && setErrorText("");
};
componentWillUnmount() {
window.removeEventListener("keyup", this.onKeyPress);
}
const onChangeLogin = event => {
setIdentifier(event.target.value);
!identifierValid && setIdentifierValid(true);
errorText && setErrorText("");
};
render() {
const { greetingTitle, match, t } = this.props;
const onChangeEmail = event => {
setEmail(event.target.value);
};
const {
identifierValid,
identifier,
isLoading,
passwordValid,
password,
isChecked,
openDialog,
email,
errorText
} = this.state;
const { params } = match;
// console.log('Login render');
//console.log("Login render");
return (
<FormContainer>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<Card className="login-card">
<CardImg
className="card-img"
src="images/dark_general.png"
alt="Logo"
top
return (
<FormContainer>
<div className="login-header">
<img
className="login-logo"
src="images/dark_general.png"
alt="Logo"
/>
<Text.Headline className="login-title" color="#116d9d">
{greetingTitle}
</Text.Headline>
</div>
<TextInput
id="login"
name="login"
hasError={!identifierValid}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size="huge"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={this.onChangeLogin}
onKeyDown={this.onKeyPress}
className="login-input"
/>
<TextInput
id="password"
name="password"
type="password"
hasError={!passwordValid}
value={password}
placeholder={t("Password")}
size="huge"
scale={true}
isAutoFocussed={true}
tabIndex={2}
isDisabled={isLoading}
autoComplete="current-password"
onChange={this.onChangePassword}
onKeyDown={this.onKeyPress}
className="login-input"
/>
<div className="login-forgot-wrapper">
<div className="login-checkbox-wrapper">
<Checkbox
className="login-checkbox"
isChecked={isChecked}
onChange={this.onChangeCheckbox}
label={t("Remember")}
/>
<CardTitle className="card-title">
{greetingTitle}
</CardTitle>
</Card>
</Col>
</Row>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<TextInput
id="login"
name="login"
hasError={!identifierValid}
value={identifier}
placeholder={t("RegistrationEmailWatermark")}
size="huge"
scale={true}
isAutoFocussed={true}
tabIndex={1}
isDisabled={isLoading}
autoComplete="username"
onChange={onChangeLogin}
onKeyDown={onKeyPress}
/>
</Col>
</Row>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<TextInput
id="password"
name="password"
type="password"
hasError={!passwordValid}
value={password}
placeholder={t("Password")}
size="huge"
scale={true}
tabIndex={2}
isDisabled={isLoading}
autoComplete="current-password"
onChange={onChangePassword}
onKeyDown={onKeyPress}
/>
</Col>
</Row>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<Link
fontSize={12}
className="link-style"
type="page"
isHovered={true}
onClick={onClick}
>
{t("ForgotPassword")}
</Link>
<Checkbox
className="checkbox"
isChecked={isChecked}
onChange={() => setIsisChecked(!isChecked)}
label={t("Remember")}
/>
<TooltipStyle>
<HelpButton
helpButtonHeaderContent={t('CookieSettingsTitle')}
className="login-tooltip"
helpButtonHeaderContent={t("CookieSettingsTitle")}
tooltipContent={
<Text.Body fontSize={12}>{t("RememberHelper")}</Text.Body>
}
/>
</TooltipStyle>
</Col>
</Row>
</div>
{openDialog ? (
<SubModalDialog
openDialog={openDialog}
isLoading={isLoading}
email={email}
onChangeEmail={onChangeEmail}
onSendPasswordInstructions={onSendPasswordInstructions}
onDialogClose={onDialogClose}
t={t}
/>
) : null}
<Link
fontSize={12}
className="login-link"
type="page"
isHovered={true}
onClick={this.onClick}
>
{t("ForgotPassword")}
</Link>
</div>
<Row className="button-row">
<Col sm="12" md={mdOptions}>
<Button
primary
size="big"
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
tabIndex={3}
isDisabled={isLoading}
{openDialog ? (
<SubModalDialog
openDialog={openDialog}
isLoading={isLoading}
onClick={onSubmit}
email={email}
onChangeEmail={this.onChangeEmail}
onSendPasswordInstructions={this.onSendPasswordInstructions}
onDialogClose={this.onDialogClose}
t={t}
/>
</Col>
</Row>
{params.confirmedEmail && (
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<Text.Body isBold={true} fontSize={16}>
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}
</Text.Body>
</Col>
</Row>
)}
<Collapse isOpen={!!errorText}>
<Row className="login-row">
<Col sm="12" md={mdOptions}>
<div className="alert alert-danger">{errorText}</div>
</Col>
</Row>
</Collapse>
</FormContainer>
) : null}
<Button
id="button"
className="login-button"
primary
size="big"
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
tabIndex={3}
isDisabled={isLoading}
isLoading={isLoading}
onClick={this.onSubmit}
/>
{params.confirmedEmail && (
<Text.Body isBold={true} fontSize={16}>
{t("MessageEmailConfirmed")} {t("MessageAuthorize")}
</Text.Body>
)}
<Text.Body fontSize={14} color="#c30">
{errorText}
</Text.Body>
</FormContainer>
);
}
}
const FormWrapper = withTranslation()(Form);
const LoginForm = props => {
const { language } = props;
i18n.changeLanguage(language);
return (
<I18nextProvider i18n={i18n}>
<PageLayout sectionBodyContent={<FormWrapper {...props} />} />
</I18nextProvider>
);
};
const LoginForm = props => (
<PageLayout sectionBodyContent={<Form {...props} />} />
);
LoginForm.propTypes = {
Form.propTypes = {
login: PropTypes.func.isRequired,
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
history: PropTypes.object.isRequired,
setIsLoaded: PropTypes.func.isRequired,
greetingTitle: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
language: PropTypes.string.isRequired
};
LoginForm.defaultProps = {
Form.defaultProps = {
identifier: "",
password: "",
email: ""
};
LoginForm.propTypes = {
language: PropTypes.string.isRequired
};
function mapStateToProps(state) {
return {
language: state.auth.user.cultureName || state.auth.settings.culture,
@ -352,7 +362,6 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps,
{ login, setIsLoaded }
)(withRouter(LoginForm));
export default connect(mapStateToProps, { login, setIsLoaded })(
withRouter(LoginForm)
);

View File

@ -45,7 +45,7 @@ class SubModalDialog extends React.Component {
]}
footerContent={[
<Button
className="btn-style"
className="login-button-dialog"
key="SendBtn"
label={isLoading ? t("LoadingProcessing") : t("SendButton")}
size="big"

View File

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

View File

@ -53,3 +53,4 @@ import { HelpButton } from "asc-web-components";
| `place` | `string` | - | `top`, `right`, `bottom`, `left` | `top` | Tooltip placement |
| `displayType` | `oneOf` | - | `dropdown`, `aside`, `auto` | `auto` | Tooltip display type |
| `helpButtonHeaderContent` | `string` | - | - | - | Tooltip header content (tooltip opened in aside) |
| `className` | `string` | - | - | - | Set component class |

View File

@ -115,7 +115,7 @@ class HelpButton extends React.Component {
onClick = () => {
this.setState({isOpen: !this.state.isOpen});
}
};
render() {
const { isOpen, displayType } = this.state;
@ -178,8 +178,7 @@ class HelpButton extends React.Component {
<Backdrop onClick={this.onClose} visible={isOpen} zIndex={zIndex} />
<Aside visible={isOpen} scale={false} zIndex={zIndex}>
<Content>
{
helpButtonHeaderContent &&
{helpButtonHeaderContent && (
<Header>
<HeaderText>
<Text.Body isBold={true} fontSize={21}>
@ -187,7 +186,7 @@ class HelpButton extends React.Component {
</Text.Body>
</HeaderText>
</Header>
}
)}
<Body>{tooltipContent}</Body>
</Content>
</Aside>
@ -203,7 +202,8 @@ HelpButton.propTypes = {
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]),
tooltipContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
tooltipContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
.isRequired,
offsetRight: PropTypes.number,
tooltipMaxWidth: PropTypes.number,
tooltipId: PropTypes.string,

View File

@ -31,8 +31,13 @@ const StyledArticle = styled.article`
z-index: 400;
`
: `
display: none;
width: 0px;
width: 240px;
min-width: 240px;
position: fixed;
height: 100%;
top: 0;
left: -240px;
z-index: 400;
`}
}
`;

View File

@ -56,7 +56,7 @@ class ToggleButton extends Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.isChecked
checked: props.isChecked
};
}
@ -104,4 +104,8 @@ ToggleButton.propTypes = {
className: PropTypes.string
};
ToggleIcon.propTypes = {
isChecked: PropTypes.bool
}
export default ToggleButton;