Merge branch 'master' into refactoring/di
# Conflicts: # common/ASC.Api.Core/Auth/ConfirmAuthHandler.cs # common/ASC.Core.Common/Security/EmailValidationKeyProvider.cs # web/ASC.Web.Api/Controllers/PortalController.cs # web/ASC.Web.Api/Controllers/SettingsController.cs # web/ASC.Web.Core/Notify/StudioNotifyService.cs
This commit is contained in:
commit
0bbc383266
127
build/install/docker/Dockerfile
Normal file
127
build/install/docker/Dockerfile
Normal file
@ -0,0 +1,127 @@
|
||||
FROM ubuntu:18.04
|
||||
|
||||
ARG RELEASE_DATE="2016-06-21"
|
||||
ARG RELEASE_DATE_SIGN=""
|
||||
ARG VERSION="8.9.0.190"
|
||||
ARG SOURCE_REPO_URL="deb http://static.teamlab.com.s3.amazonaws.com/repo/debian squeeze main"
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
LABEL onlyoffice.community.release-date="${RELEASE_DATE}" \
|
||||
onlyoffice.community.version="${VERSION}" \
|
||||
maintainer="Ascensio System SIA <support@onlyoffice.com>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en \
|
||||
LC_ALL=en_US.UTF-8
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -yq install gnupg2 ca-certificates && \
|
||||
apt-get install -yq sudo locales && \
|
||||
addgroup --system --gid 107 onlyoffice && \
|
||||
adduser -uid 104 --quiet --home /var/www/onlyoffice --system --gid 107 onlyoffice && \
|
||||
addgroup --system --gid 104 elasticsearch && \
|
||||
adduser -uid 103 --quiet --home /nonexistent --system --gid 104 elasticsearch && \
|
||||
locale-gen en_US.UTF-8 && \
|
||||
apt-get -y update && \
|
||||
apt-get install -yq software-properties-common wget curl cron rsyslog && \
|
||||
wget http://nginx.org/keys/nginx_signing.key && \
|
||||
apt-key add nginx_signing.key && \
|
||||
echo "deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx" >> /etc/apt/sources.list.d/nginx.list && \
|
||||
echo "deb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx" >> /etc/apt/sources.list.d/nginx.list && \
|
||||
apt-get install -yq openjdk-8-jre-headless && \
|
||||
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | apt-key add - && \
|
||||
apt-get install -yq apt-transport-https && \
|
||||
echo "deb https://artifacts.elastic.co/packages/6.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-6.x.list && \
|
||||
apt-get -y update && \
|
||||
apt-get install -yq elasticsearch=6.5.0 && \
|
||||
add-apt-repository -y ppa:certbot/certbot && \
|
||||
add-apt-repository -y ppa:jonathonf/ffmpeg-4 && \
|
||||
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list && \
|
||||
wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \
|
||||
dpkg -i packages-microsoft-prod.deb && \
|
||||
apt-get -y update && \
|
||||
apt-get install -yq nginx && \
|
||||
cd ~ && \
|
||||
wget http://www-us.apache.org/dist/kafka/2.2.1/kafka_2.12-2.2.1.tgz && \
|
||||
tar xzf kafka_2.12-2.2.1.tgz && \
|
||||
rm kafka_2.12-2.2.1.tgz && \
|
||||
echo "#!/bin/sh\nexit 0" > /usr/sbin/policy-rc.d && \
|
||||
apt-get install -yq dumb-init python-certbot-nginx htop nano dnsutils python3-pip multiarch-support iproute2 ffmpeg jq git yarn dotnet-sdk-3.0 supervisor mysql-client mysql-server
|
||||
|
||||
RUN git clone https://github.com/ONLYOFFICE/CommunityServer-AspNetCore.git /app/onlyoffice/src/
|
||||
|
||||
RUN cd /app/onlyoffice/src/ && \
|
||||
yarn install --cwd web/ASC.Web.Components --frozen-lockfile > build/ASC.Web.Components.log && \
|
||||
npm run build --prefix web/ASC.Web.Components && \
|
||||
yarn pack --cwd web/ASC.Web.Components
|
||||
|
||||
RUN cd /app/onlyoffice/src/ && \
|
||||
npm run build:storybook --prefix web/ASC.Web.Components && \
|
||||
mkdir -p /var/www/story/ && \
|
||||
cp -Rf web/ASC.Web.Components/storybook-static/* /var/www/story/
|
||||
|
||||
RUN cd /app/onlyoffice/src/ && \
|
||||
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
|
||||
yarn add ../../$component --cwd web/ASC.Web.Client --cache-folder ../../yarn && \
|
||||
yarn install --cwd web/ASC.Web.Client --frozen-lockfile || (cd web/ASC.Web.Client && npm i && cd ../../) && \
|
||||
npm run build --prefix web/ASC.Web.Client && \
|
||||
rm -rf /var/www/studio/client/* && \
|
||||
mkdir -p /var/www/studio/client && \
|
||||
cp -rf web/ASC.Web.Client/build/* /var/www/studio/client
|
||||
|
||||
RUN cd /app/onlyoffice/src/ && \
|
||||
component=$(ls web/ASC.Web.Components/asc-web-components-v1.*.tgz) && \
|
||||
yarn add ../../../$component --cwd products/ASC.People/Client --cache-folder ../../../yarn && \
|
||||
yarn install --cwd products/ASC.People/Client --frozen-lockfile || (cd products/ASC.People/Client && npm i && cd ../../../) && \
|
||||
npm run build --prefix products/ASC.People/Client && \
|
||||
mkdir -p /var/www/products/ASC.People/client && \
|
||||
cp -Rf products/ASC.People/Client/build/* /var/www/products/ASC.People/client && \
|
||||
mkdir -p /var/www/products/ASC.People/client/products/people
|
||||
|
||||
RUN cd /app/onlyoffice/src/ && \
|
||||
rm -f /etc/nginx/conf.d/* && \
|
||||
cp -rf config/nginx/onlyoffice*.conf /etc/nginx/conf.d/ && \
|
||||
mkdir -p /app/onlyoffice/config/ && cp -rf config/* /app/onlyoffice/config/ && \
|
||||
sed -e 's/#//' -i /etc/nginx/conf.d/onlyoffice.conf && \
|
||||
cd products/ASC.People/Server && \
|
||||
dotnet -d publish -o /var/www/products/ASC.People/server && \
|
||||
cd ../../../ && \
|
||||
cd web/ASC.Web.Api && \
|
||||
dotnet -d publish -o /var/www/studio/api && \
|
||||
cd ../../ && \
|
||||
cd web/ASC.Web.Studio && \
|
||||
dotnet -d publish -o /var/www/studio/server && \
|
||||
cd ../../ && \
|
||||
cd common/services/ASC.Notify && \
|
||||
dotnet -d publish -o /var/www/services/notify && \
|
||||
cd ../../../ && \
|
||||
cd common/services/ASC.Studio.Notify && \
|
||||
dotnet -d publish -o /var/www/services/studio.notify
|
||||
|
||||
COPY config/mysql/conf.d/mysql.cnf /etc/mysql/conf.d/mysql.cnf
|
||||
COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
RUN sed -i 's/172.18.0.5/localhost/' /app/onlyoffice/config/appsettings.test.json
|
||||
|
||||
RUN mkdir -p /var/mysqld/ && \
|
||||
chown -R mysql:mysql /var/lib/mysql /var/run/mysqld /var/mysqld/ && \
|
||||
service mysql start && \
|
||||
mysql -e "CREATE DATABASE IF NOT EXISTS onlyoffice CHARACTER SET utf8 COLLATE 'utf8_general_ci'" && \
|
||||
mysql -D "onlyoffice" < /app/onlyoffice/src/sql/app.sql && \
|
||||
mysql -D "onlyoffice" < /app/onlyoffice/src/sql/app.data.sql && \
|
||||
mysql -D "onlyoffice" -e 'CREATE USER IF NOT EXISTS "onlyoffice_user"@"localhost" IDENTIFIED WITH mysql_native_password BY "onlyoffice_pass";' && \
|
||||
mysql -D "onlyoffice" -e 'GRANT ALL PRIVILEGES ON *.* TO 'onlyoffice_user'@'localhost' IDENTIFIED BY "onlyoffice_pass";' && \
|
||||
mysql -D "onlyoffice" -e 'UPDATE core_user SET email = "paul.bannov@gmail.com";' && \
|
||||
mysql -D "onlyoffice" -e 'UPDATE core_usersecurity SET pwdhash = "vLFfghR5tNV3K9DKhmwArV+SbjWAcgZZzIDTnJ0JgCo=", pwdhashsha512 = "USubvPlB+ogq0Q1trcSupg==";' && \
|
||||
service mysql stop
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
|
||||
VOLUME /var/lib/mysql
|
||||
|
||||
EXPOSE 80 443 8092 8081
|
||||
|
||||
ENTRYPOINT ["/usr/bin/supervisord", "--"]
|
7
build/install/docker/config/mysql/conf.d/mysql.cnf
Normal file
7
build/install/docker/config/mysql/conf.d/mysql.cnf
Normal file
@ -0,0 +1,7 @@
|
||||
[mysqld]
|
||||
sql_mode = 'NO_ENGINE_SUBSTITUTION'
|
||||
max_connections = 1000
|
||||
max_allowed_packet = 1048576000
|
||||
group_concat_max_len = 2048
|
||||
log-error = /var/log/mysql/error.log
|
||||
|
64
build/install/docker/config/supervisor/supervisord.conf
Normal file
64
build/install/docker/config/supervisor/supervisord.conf
Normal file
@ -0,0 +1,64 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
[program:mysqld]
|
||||
command=/usr/bin/pidproxy /var/mysqld/mysqld.pid /usr/bin/mysqld_safe --pid-file=/var/mysqld/mysqld.pid
|
||||
autostart=true
|
||||
autorestart=true
|
||||
environment=PATH=/usr/local/sbin:/usr/bin:/bin:/usr/local/bin
|
||||
user=mysql
|
||||
|
||||
[program:kafka]
|
||||
directory=/root/kafka_2.12-2.2.1/
|
||||
command=/root/kafka_2.12-2.2.1/bin/kafka-server-start.sh /root/kafka_2.12-2.2.1/config/server.properties
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:kafka_zookeeper]
|
||||
directory=/root/kafka_2.12-2.2.1/
|
||||
command=/root/kafka_2.12-2.2.1/bin/zookeeper-server-start.sh /root/kafka_2.12-2.2.1/config/zookeeper.properties
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:api]
|
||||
directory=/var/www/studio/api/
|
||||
command=dotnet ASC.Web.Api.dll --urls=http://0.0.0.0:5000 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:notify]
|
||||
directory=/var/www/services/notify/
|
||||
command=dotnet ASC.Notify.dll --urls=http://0.0.0.0:5005 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:studio_notify]
|
||||
directory=/var/www/services/studio.notify/
|
||||
command=dotnet ASC.Studio.Notify.dll --urls=http://0.0.0.0:5006 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:people]
|
||||
directory=/var/www/products/ASC.People/server/
|
||||
command=dotnet ASC.People.dll --urls=http://0.0.0.0:5004 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:studio]
|
||||
directory=/var/www/studio/server/
|
||||
command=dotnet ASC.Web.Studio.dll --urls=http://0.0.0.0:5003 --pathToConf=/app/onlyoffice/config/ --$STORAGE_ROOT=/app/onlyoffice/data/ --ENVIRONMENT=test
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx -g "daemon off;"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=5
|
||||
numprocs=1
|
||||
startsecs=0
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
stderr_logfile=/var/log/supervisor/%(program_name)s_stderr.log
|
||||
stderr_logfile_maxbytes=10MB
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)s_stdout.log
|
||||
stdout_logfile_maxbytes=10MB
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
@ -8,8 +9,6 @@ using System.Threading.Tasks;
|
||||
using ASC.Core;
|
||||
using ASC.Security.Cryptography;
|
||||
using ASC.Web.Studio.Core;
|
||||
using ASC.Web.Studio.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -56,33 +55,46 @@ namespace ASC.Api.Core.Auth
|
||||
{
|
||||
var emailValidationKeyModel = EmailValidationKeyModel.FromRequest(Context.Request);
|
||||
|
||||
if (SecurityContext.IsAuthenticated && emailValidationKeyModel.Type != ConfirmType.EmailChange)
|
||||
if (!emailValidationKeyModel.Type.HasValue)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
|
||||
return SecurityContext.IsAuthenticated
|
||||
? Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)))
|
||||
: Task.FromResult(AuthenticateResult.Fail(new AuthenticationException(HttpStatusCode.Unauthorized.ToString())));
|
||||
}
|
||||
|
||||
var checkKeyResult = emailValidationKeyModel.Validate(EmailValidationKeyProvider, AuthContext, TenantManager, AuthManager);
|
||||
EmailValidationKeyProvider.ValidationResult checkKeyResult;
|
||||
try
|
||||
{
|
||||
checkKeyResult = emailValidationKeyModel.Validate(EmailValidationKeyProvider, AuthContext, TenantManager, UserManager, AuthManager);
|
||||
}
|
||||
catch (ArgumentNullException)
|
||||
{
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidationResult.Invalid;
|
||||
}
|
||||
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.Role, emailValidationKeyModel.Type.ToString())
|
||||
};
|
||||
|
||||
if (!SecurityContext.IsAuthenticated)
|
||||
if (checkKeyResult == EmailValidationKeyProvider.ValidationResult.Ok)
|
||||
{
|
||||
if (emailValidationKeyModel.UiD.HasValue)
|
||||
if (!SecurityContext.IsAuthenticated)
|
||||
{
|
||||
SecurityContext.AuthenticateMe(emailValidationKeyModel.UiD.Value, claims);
|
||||
if (emailValidationKeyModel.UiD.HasValue && !emailValidationKeyModel.UiD.Equals(Guid.Empty))
|
||||
{
|
||||
SecurityContext.AuthenticateMe(emailValidationKeyModel.UiD.Value, claims);
|
||||
}
|
||||
else
|
||||
{
|
||||
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem, claims);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem, claims);
|
||||
SecurityContext.AuthenticateMe(SecurityContext.CurrentAccount, claims);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SecurityContext.AuthenticateMe(SecurityContext.CurrentAccount, claims);
|
||||
}
|
||||
|
||||
var result = checkKeyResult switch
|
||||
{
|
||||
|
@ -151,20 +151,20 @@ namespace ASC.Security.Cryptography
|
||||
public EmployeeType? EmplType { get; set; }
|
||||
public string Email { get; set; }
|
||||
public Guid? UiD { get; set; }
|
||||
public ConfirmType Type { get; set; }
|
||||
public ConfirmType? Type { get; set; }
|
||||
public int? P { get; set; }
|
||||
|
||||
public ValidationResult Validate(EmailValidationKeyProvider provider, AuthContext authContext, TenantManager tenantManager, AuthManager authentication)
|
||||
public ValidationResult Validate(EmailValidationKeyProvider provider, AuthContext authContext, TenantManager tenantManager, UserManager userManager, AuthManager authentication)
|
||||
{
|
||||
ValidationResult checkKeyResult;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case ConfirmType.EmpInvite:
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + EmplType, Key, provider.ValidInterval);
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + (int)EmplType, Key, provider.ValidInterval);
|
||||
break;
|
||||
case ConfirmType.LinkInvite:
|
||||
checkKeyResult = provider.ValidateEmailKey(Type.ToString() + EmplType.ToString(), Key, provider.ValidInterval);
|
||||
checkKeyResult = provider.ValidateEmailKey(Type.ToString() + (int)EmplType, Key, provider.ValidInterval);
|
||||
break;
|
||||
case ConfirmType.EmailChange:
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + authContext.CurrentAccount.ID, Key, provider.ValidInterval);
|
||||
@ -181,6 +181,17 @@ namespace ASC.Security.Cryptography
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)) + UiD, Key, provider.ValidInterval);
|
||||
break;
|
||||
case ConfirmType.Activation:
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + UiD, Key, provider.ValidInterval);
|
||||
break;
|
||||
case ConfirmType.ProfileRemove:
|
||||
// validate UiD
|
||||
if (P == 1)
|
||||
{
|
||||
var user = userManager.GetUsers(UiD.GetValueOrDefault());
|
||||
if (user == null || user.Status == EmployeeStatus.Terminated || authContext.IsAuthenticated && authContext.CurrentAccount.ID != UiD)
|
||||
return ValidationResult.Invalid;
|
||||
}
|
||||
|
||||
checkKeyResult = provider.ValidateEmailKey(Email + Type + UiD, Key, provider.ValidInterval);
|
||||
break;
|
||||
default:
|
||||
@ -196,7 +207,12 @@ namespace ASC.Security.Cryptography
|
||||
var Request = QueryHelpers.ParseQuery(httpRequest.Headers["confirm"]);
|
||||
|
||||
_ = Request.TryGetValue("type", out var type);
|
||||
_ = Enum.TryParse<ConfirmType>(type, out var confirmType);
|
||||
|
||||
ConfirmType? cType = null;
|
||||
if (Enum.TryParse<ConfirmType>(type, out var confirmType))
|
||||
{
|
||||
cType = confirmType;
|
||||
}
|
||||
|
||||
_ = Request.TryGetValue("key", out var key);
|
||||
|
||||
@ -213,7 +229,7 @@ namespace ASC.Security.Cryptography
|
||||
return new EmailValidationKeyModel
|
||||
{
|
||||
Key = key,
|
||||
Type = confirmType,
|
||||
Type = cType,
|
||||
Email = _email,
|
||||
EmplType = employeeType,
|
||||
UiD = userId,
|
||||
@ -221,7 +237,7 @@ namespace ASC.Security.Cryptography
|
||||
};
|
||||
}
|
||||
|
||||
public void Deconstruct(out string key, out string email, out EmployeeType? employeeType, out Guid? userId, out ConfirmType confirmType, out int? p)
|
||||
public void Deconstruct(out string key, out string email, out EmployeeType? employeeType, out Guid? userId, out ConfirmType? confirmType, out int? p)
|
||||
=> (key, email, employeeType, userId, confirmType, p) = (Key, Email, EmplType, UiD, Type, P);
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/wrappers.proto";
|
||||
package ASC.Core;
|
||||
|
||||
message AzRecordCache {
|
||||
string SubjectId = 1;
|
||||
google.protobuf.StringValue SubjectId = 1;
|
||||
|
||||
string ActionId = 2;
|
||||
google.protobuf.StringValue ActionId = 2;
|
||||
|
||||
string ObjectId = 3;
|
||||
google.protobuf.StringValue ObjectId = 3;
|
||||
|
||||
string Reaction = 4;
|
||||
google.protobuf.StringValue Reaction = 4;
|
||||
|
||||
int32 Tenant = 5;
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
"ConnectionStrings": {
|
||||
"default": {
|
||||
"name": "default",
|
||||
"connectionString": "Server=172.18.0.3;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none",
|
||||
"connectionString": "Server=172.18.0.5;Port=3306;Database=onlyoffice;User ID=onlyoffice_user;Password=onlyoffice_pass;Pooling=true;Character Set=utf8;AutoEnlist=false;SSL Mode=none",
|
||||
"providerName": "MySql.Data.MySqlClient"
|
||||
}
|
||||
},
|
||||
|
@ -88,10 +88,11 @@ class ArticleBodyContent extends React.Component {
|
||||
render() {
|
||||
const { data, selectedKeys } = this.props;
|
||||
|
||||
console.log("PeopleTreeMenu", this.props);
|
||||
//console.log("PeopleTreeMenu", this.props);
|
||||
|
||||
return (
|
||||
<TreeMenu
|
||||
className="people-tree-menu"
|
||||
checkable={false}
|
||||
draggable={false}
|
||||
disabled={false}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Text } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { getCurrentModule } from '../../../store/auth/selectors';
|
||||
|
||||
const ArticleHeaderContent = () => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
return <Text.MenuHeader>{t('People')}</Text.MenuHeader>;
|
||||
const ArticleHeaderContent = ({currentModuleName}) => {
|
||||
return <Text.MenuHeader>{currentModuleName}</Text.MenuHeader>;
|
||||
}
|
||||
|
||||
export default ArticleHeaderContent;
|
||||
const mapStateToProps = (state) => {
|
||||
const currentModule = getCurrentModule(state.auth.modules, state.auth.settings.currentProductId);
|
||||
return {
|
||||
currentModuleName: (currentModule && currentModule.title) || ""
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ArticleHeaderContent);
|
@ -86,21 +86,25 @@ class PureArticleMainButtonContent extends React.Component {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
isAdmin: isAdmin(state.auth.user),
|
||||
settings: state.auth.settings
|
||||
}
|
||||
}
|
||||
|
||||
const ArticleMainButtonContentContainer = withTranslation()(PureArticleMainButtonContent);
|
||||
|
||||
const ArticleMainButtonContent = (props) => <I18nextProvider i18n={i18n}><ArticleMainButtonContentContainer {...props} /></I18nextProvider>;
|
||||
const ArticleMainButtonContent = (props) => {
|
||||
const { language } = props;
|
||||
i18n.changeLanguage(language);
|
||||
return (<I18nextProvider i18n={i18n}><ArticleMainButtonContentContainer {...props} /></I18nextProvider>);
|
||||
};
|
||||
|
||||
ArticleMainButtonContent.propTypes = {
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
isAdmin: isAdmin(state.auth.user),
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
settings: state.auth.settings
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(ArticleMainButtonContent));
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,7 +52,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,10 +2,7 @@
|
||||
"InviteLinkTitle": "Invitation link",
|
||||
"ImportPeople": "Import people",
|
||||
"Actions": "Actions",
|
||||
"People": "People",
|
||||
|
||||
"LblInviteAgain": "Invite again",
|
||||
|
||||
"CustomNewEmployee": "New {{typeUser, lowercase}}",
|
||||
"CustomNewGuest": "New {{typeGuest, lowercase}}",
|
||||
"CustomNewDepartment": "New {{department, lowercase}}"
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"InviteLinkTitle": "Пригласительная ссылка",
|
||||
"ImportPeople": "Импортировать людей",
|
||||
"Actions": "Действия",
|
||||
"LblInviteAgain": "Отправить приглашение ещё раз",
|
||||
"CustomNewEmployee": "Новый {{typeUser, lowercase}}",
|
||||
"CustomNewGuest": "Новый {{typeGuest, lowercase}}",
|
||||
"CustomNewDepartment": "Новый {{department, lowercase}}"
|
||||
}
|
@ -32,6 +32,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { Layout, Toast } from 'asc-web-components';
|
||||
import { logout } from '../../store/auth/actions';
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from "./i18n";
|
||||
import { isAdmin } from "../../store/auth/selectors";
|
||||
|
||||
class PurePeopleLayout extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@ -66,7 +67,23 @@ class PurePeopleLayout extends React.Component {
|
||||
};
|
||||
|
||||
|
||||
const getAvailableModules = (modules) => {
|
||||
const getAvailableModules = (modules, currentUser) => {
|
||||
const isUserAdmin = isAdmin(currentUser);
|
||||
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 {
|
||||
@ -80,22 +97,27 @@ const getAvailableModules = (modules) => {
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return products.length ? [separator, ...products] : products;
|
||||
return products.length ? [separator, ...products, ...customModules] : products;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
hasChanges: state.auth.isAuthenticated && state.auth.isLoaded,
|
||||
availableModules: getAvailableModules(state.auth.modules),
|
||||
availableModules: getAvailableModules(state.auth.modules, state.auth.user),
|
||||
currentUser: state.auth.user,
|
||||
currentModuleId: state.auth.settings.currentProductId,
|
||||
settings: state.auth.settings
|
||||
settings: state.auth.settings,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
const PeopleLayoutContainer = withTranslation()(PurePeopleLayout);
|
||||
|
||||
const PeopleLayout = (props) => <I18nextProvider i18n={i18n}><PeopleLayoutContainer {...props} /></I18nextProvider>;
|
||||
const PeopleLayout = (props) => {
|
||||
const { language } = props;
|
||||
i18n.changeLanguage(language);
|
||||
return (<I18nextProvider i18n={i18n}><PeopleLayoutContainer {...props} /></I18nextProvider>);
|
||||
};
|
||||
|
||||
PeopleLayout.propTypes = {
|
||||
logout: PropTypes.func.isRequired
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Profile": "Профиль",
|
||||
"AboutCompanyTitle": "О программе",
|
||||
"LogoutButton": "Выйти"
|
||||
}
|
@ -1,201 +1,208 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router';
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { withRouter } from "react-router";
|
||||
import {
|
||||
toastr,
|
||||
ModalDialog,
|
||||
Link,
|
||||
Checkbox,
|
||||
Button,
|
||||
Textarea,
|
||||
Text
|
||||
toastr,
|
||||
ModalDialog,
|
||||
Link,
|
||||
Checkbox,
|
||||
Button,
|
||||
Textarea,
|
||||
Text
|
||||
} from "asc-web-components";
|
||||
import { getInvitationLink, getShortenedLink } from '../../../store/profile/actions';
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
import { typeGuests } from './../../../helpers/customNames';
|
||||
import styled from 'styled-components'
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { getShortenedLink } from "../../../store/services/api";
|
||||
import { withTranslation, I18nextProvider } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import { typeGuests } from "./../../../helpers/customNames";
|
||||
import styled from "styled-components";
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
const ModalDialogContainer = styled.div`
|
||||
.margin-text {
|
||||
margin: 12px 0;
|
||||
}
|
||||
.margin-text {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.margin-link {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.margin-link {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.margin-textarea {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.margin-textarea {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.flex{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
const textAreaName = 'link-textarea';
|
||||
const textAreaName = "link-textarea";
|
||||
|
||||
class PureInviteDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isGuest: false,
|
||||
userInvitationLink: this.props.userInvitationLink,
|
||||
guestInvitationLink: this.props.guestInvitationLink,
|
||||
isLoading: false,
|
||||
isLinkShort: false,
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
onCopyLinkToClipboard = () => {
|
||||
// console.log("COPY");
|
||||
const { t } = this.props;
|
||||
copy(this.state.isGuest ? this.state.guestInvitationLink : this.state.userInvitationLink);
|
||||
toastr.success(t('LinkCopySuccess'));
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isGuest: false,
|
||||
userInvitationLink: this.props.userInvitationLink,
|
||||
guestInvitationLink: this.props.guestInvitationLink,
|
||||
isLoading: false,
|
||||
isLinkShort: false,
|
||||
visible: false
|
||||
};
|
||||
}
|
||||
|
||||
onCheckedGuest = () => this.setState({ isGuest: !this.state.isGuest });
|
||||
onCopyLinkToClipboard = () => {
|
||||
// console.log("COPY");
|
||||
const { t } = this.props;
|
||||
copy(
|
||||
this.state.isGuest
|
||||
? this.state.guestInvitationLink
|
||||
: this.state.userInvitationLink
|
||||
);
|
||||
toastr.success(t("LinkCopySuccess"));
|
||||
};
|
||||
|
||||
onGetShortenedLink = () => {
|
||||
this.setState({ isLoading: true });
|
||||
const { getShortenedLink, userInvitationLink, guestInvitationLink } = this.props;
|
||||
onCheckedGuest = () => this.setState({ isGuest: !this.state.isGuest });
|
||||
|
||||
getShortenedLink(userInvitationLink)
|
||||
.then((res) => {
|
||||
// console.log("getShortInvitationLinkuser success", res.data.response);
|
||||
this.setState({ userInvitationLink: res.data.response });
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("getShortInvitationLink error", e);
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
onGetShortenedLink = () => {
|
||||
this.setState({ isLoading: true });
|
||||
const {
|
||||
userInvitationLink,
|
||||
guestInvitationLink
|
||||
} = this.props;
|
||||
|
||||
getShortenedLink(guestInvitationLink)
|
||||
.then((res) => {
|
||||
// console.log("getShortInvitationLinkGuest success", res.data.response);
|
||||
this.setState({
|
||||
guestInvitationLink: res.data.response,
|
||||
isLoading: false,
|
||||
isLinkShort: true
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("getShortInvitationLink error", e);
|
||||
});
|
||||
getShortenedLink(userInvitationLink)
|
||||
.then(link => this.setState({ userInvitationLink: link }))
|
||||
.catch(e => {
|
||||
console.error("getShortInvitationLink error", e);
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
||||
};
|
||||
getShortenedLink(guestInvitationLink)
|
||||
.then(link =>
|
||||
this.setState({
|
||||
guestInvitationLink: link,
|
||||
isLoading: false,
|
||||
isLinkShort: true
|
||||
})
|
||||
)
|
||||
.catch(e => {
|
||||
console.error("getShortInvitationLink error", e);
|
||||
});
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
console.log('invitelink did UPDATE')
|
||||
if (this.props.visible && !prevProps.visible) {
|
||||
this.onCopyLinkToClipboard();
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
console.log("invitelink did UPDATE");
|
||||
if (this.props.visible && !prevProps.visible) {
|
||||
this.onCopyLinkToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
onClickToCloseButton = () => this.props.onCloseButton && this.props.onCloseButton();
|
||||
onClose = () => this.props.onClose && this.props.onClose();
|
||||
onClickToCloseButton = () =>
|
||||
this.props.onCloseButton && this.props.onCloseButton();
|
||||
onClose = () => this.props.onClose && this.props.onClose();
|
||||
|
||||
render() {
|
||||
console.log("InviteDialog render");
|
||||
const { t, visible, settings } = this.props;
|
||||
render() {
|
||||
console.log("InviteDialog render");
|
||||
const { t, visible, settings } = this.props;
|
||||
|
||||
return (
|
||||
<ModalDialogContainer>
|
||||
<ModalDialog
|
||||
visible={visible}
|
||||
onClose={this.onClose}
|
||||
|
||||
headerContent={t('InviteLinkTitle')}
|
||||
|
||||
bodyContent={(
|
||||
<>
|
||||
<Text.Body
|
||||
className='margin-text'
|
||||
as='p'>
|
||||
{t('HelpAnswerLinkInviteSettings')}
|
||||
</Text.Body>
|
||||
<Text.Body
|
||||
className='margin-text'
|
||||
as='p'>
|
||||
{t('InviteLinkValidInterval', { count: 7 })}
|
||||
</Text.Body>
|
||||
<div className='flex'>
|
||||
<div>
|
||||
<Link
|
||||
className='margin-link'
|
||||
type='action'
|
||||
isHovered={true}
|
||||
onClick={this.onCopyLinkToClipboard}
|
||||
>
|
||||
{t('CopyToClipboard')}
|
||||
</Link>
|
||||
{
|
||||
settings && !this.state.isLinkShort &&
|
||||
<Link type='action'
|
||||
isHovered={true}
|
||||
onClick={this.onGetShortenedLink}
|
||||
>
|
||||
{t('GetShortenLink')}
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
<Checkbox
|
||||
label={t('InviteUsersAsCollaborators', { typeGuests })}
|
||||
isChecked={this.state.isGuest}
|
||||
onChange={this.onCheckedGuest}
|
||||
isDisabled={this.state.isLoading}
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
className='margin-textarea'
|
||||
isReadOnly={true}
|
||||
isDisabled={this.state.isLoading}
|
||||
name={textAreaName}
|
||||
value={this.state.isGuest ? this.state.guestInvitationLink : this.state.userInvitationLink}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
footerContent={(
|
||||
<>
|
||||
<Button
|
||||
key="CloseBtn"
|
||||
label={this.state.isLoading ? t('LoadingProcessing') : t('CloseButton')}
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={this.onClickToCloseButton}
|
||||
isLoading={this.state.isLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
return (
|
||||
<ModalDialogContainer>
|
||||
<ModalDialog
|
||||
visible={visible}
|
||||
onClose={this.onClose}
|
||||
headerContent={t("InviteLinkTitle")}
|
||||
bodyContent={
|
||||
<>
|
||||
<Text.Body className="margin-text" as="p">
|
||||
{t("HelpAnswerLinkInviteSettings")}
|
||||
</Text.Body>
|
||||
<Text.Body className="margin-text" as="p">
|
||||
{t("InviteLinkValidInterval", { count: 7 })}
|
||||
</Text.Body>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<Link
|
||||
className="margin-link"
|
||||
type="action"
|
||||
isHovered={true}
|
||||
onClick={this.onCopyLinkToClipboard}
|
||||
>
|
||||
{t("CopyToClipboard")}
|
||||
</Link>
|
||||
{settings && !this.state.isLinkShort && (
|
||||
<Link
|
||||
type="action"
|
||||
isHovered={true}
|
||||
onClick={this.onGetShortenedLink}
|
||||
>
|
||||
{t("GetShortenLink")}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<Checkbox
|
||||
label={t("InviteUsersAsCollaborators", { typeGuests })}
|
||||
isChecked={this.state.isGuest}
|
||||
onChange={this.onCheckedGuest}
|
||||
isDisabled={this.state.isLoading}
|
||||
/>
|
||||
</ModalDialogContainer>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
settings: state.auth.settings.hasShortenService,
|
||||
userInvitationLink: state.auth.settings.inviteLinks.userLink,
|
||||
guestInvitationLink: state.auth.settings.inviteLinks.guestLink
|
||||
}
|
||||
</div>
|
||||
<Textarea
|
||||
className="margin-textarea"
|
||||
isReadOnly={true}
|
||||
isDisabled={this.state.isLoading}
|
||||
name={textAreaName}
|
||||
value={
|
||||
this.state.isGuest
|
||||
? this.state.guestInvitationLink
|
||||
: this.state.userInvitationLink
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
footerContent={
|
||||
<>
|
||||
<Button
|
||||
key="CloseBtn"
|
||||
label={
|
||||
this.state.isLoading
|
||||
? t("LoadingProcessing")
|
||||
: t("CloseButton")
|
||||
}
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={this.onClickToCloseButton}
|
||||
isLoading={this.state.isLoading}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalDialogContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
settings: state.auth.settings.hasShortenService,
|
||||
userInvitationLink: state.auth.settings.inviteLinks.userLink,
|
||||
guestInvitationLink: state.auth.settings.inviteLinks.guestLink
|
||||
};
|
||||
};
|
||||
|
||||
const InviteDialogContainer = withTranslation()(PureInviteDialog);
|
||||
|
||||
const InviteDialog = (props) => <I18nextProvider i18n={i18n}><InviteDialogContainer {...props} /></I18nextProvider>;
|
||||
const InviteDialog = props => (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<InviteDialogContainer {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
InviteDialog.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onCloseButton: PropTypes.func.isRequired
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onCloseButton: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getInvitationLink, getShortenedLink })(withRouter(InviteDialog));
|
||||
export default connect(mapStateToProps)(withRouter(InviteDialog));
|
||||
|
@ -32,6 +32,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,23 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import { ErrorContainer } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
|
||||
export const Error404 = () => {
|
||||
const Error404Container = ({language}) => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return <ErrorContainer>{t("Error404Text")}</ErrorContainer>;
|
||||
};
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export const Error404 = connect(mapStateToProps)(Error404Container);
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Error404Text": "Извините, страница не найдена."
|
||||
}
|
@ -28,7 +28,7 @@ import {
|
||||
updateGroup
|
||||
} from "../../../../../store/group/actions";
|
||||
import styled from "styled-components";
|
||||
import { fetchSelectorUsers } from "../../../../../store/people/actions";
|
||||
import { fetchSelectorUsers, fetchPeople, fetchGroups } from "../../../../../store/people/actions";
|
||||
import { GUID_EMPTY } from "../../../../../helpers/constants";
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
@ -110,34 +110,40 @@ class SectionBodyContent extends React.Component {
|
||||
key: 0,
|
||||
label: t("CustomAddEmployee", { typeUser })
|
||||
},
|
||||
groupMembers: group && group.members ? group.members.map(m => {
|
||||
return {
|
||||
key: m.id,
|
||||
label: m.displayName
|
||||
}
|
||||
}) : [],
|
||||
groupManager: group && group.manager ? {
|
||||
key: group.manager.id,
|
||||
label: group.manager.displayName
|
||||
} : {
|
||||
key: GUID_EMPTY,
|
||||
label: t("CustomAddEmployee", { typeUser })
|
||||
}
|
||||
groupMembers:
|
||||
group && group.members
|
||||
? group.members.map(m => {
|
||||
return {
|
||||
key: m.id,
|
||||
label: m.displayName
|
||||
};
|
||||
})
|
||||
: [],
|
||||
groupManager:
|
||||
group && group.manager
|
||||
? {
|
||||
key: group.manager.id,
|
||||
label: group.manager.displayName
|
||||
}
|
||||
: {
|
||||
key: GUID_EMPTY,
|
||||
label: t("CustomAddEmployee", { typeUser })
|
||||
}
|
||||
};
|
||||
|
||||
return newState;
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { users, fetchSelectorUsers } = this.props;
|
||||
if(!users || !users.length) {
|
||||
if (!users || !users.length) {
|
||||
fetchSelectorUsers();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
//const { users, group } = this.props;
|
||||
if(!isEqual(this.props, prevProps)) {
|
||||
if (!isEqual(this.props, prevProps)) {
|
||||
this.setState(this.mapPropsToState());
|
||||
}
|
||||
}
|
||||
@ -163,12 +169,12 @@ class SectionBodyContent extends React.Component {
|
||||
};
|
||||
|
||||
onHeadSelectorSelect = option => {
|
||||
this.setState({
|
||||
this.setState({
|
||||
groupManager: {
|
||||
key: option.key,
|
||||
label: option.label
|
||||
},
|
||||
isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen
|
||||
isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
@ -178,24 +184,24 @@ class SectionBodyContent extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
onUsersSelectorSearch = (value) => {
|
||||
onUsersSelectorSearch = value => {
|
||||
/*setOptions(
|
||||
options.filter(option => {
|
||||
return option.label.indexOf(value) > -1;
|
||||
})
|
||||
);*/
|
||||
};
|
||||
onUsersSelectorSelect = (selectedOptions) => {
|
||||
onUsersSelectorSelect = selectedOptions => {
|
||||
//console.log("onSelect", selectedOptions);
|
||||
//this.onUsersSelectorClick();
|
||||
this.setState({
|
||||
this.setState({
|
||||
groupMembers: selectedOptions.map(option => {
|
||||
return {
|
||||
key: option.key,
|
||||
label: option.label
|
||||
};
|
||||
}),
|
||||
isUsersSelectorOpen: !this.state.isUsersSelectorOpen
|
||||
isUsersSelectorOpen: !this.state.isUsersSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
@ -211,31 +217,36 @@ class SectionBodyContent extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
save = (group) => {
|
||||
const { createGroup, updateGroup } = this.props;
|
||||
return group.id
|
||||
? updateGroup(group.id, group.name, group.managerKey, group.members)
|
||||
: createGroup(group.name, group.managerKey, group.members);
|
||||
};
|
||||
|
||||
onSave = () => {
|
||||
const { history, group, createGroup, updateGroup, resetGroup } = this.props;
|
||||
const { group } = this.props;
|
||||
const { groupName, groupManager, groupMembers } = this.state;
|
||||
|
||||
if (!groupName || !groupName.trim().length) return false;
|
||||
|
||||
this.setState({ inLoading: true });
|
||||
|
||||
(group && group.id
|
||||
? updateGroup(
|
||||
group.id,
|
||||
groupName,
|
||||
groupManager.key,
|
||||
groupMembers.map(u => u.key)
|
||||
)
|
||||
: createGroup(groupName, groupManager.key, groupMembers.map(u => u.key))
|
||||
)
|
||||
.then(() => {
|
||||
toastr.success("Success");
|
||||
//this.setState({ inLoading: true });
|
||||
history.goBack();
|
||||
resetGroup();
|
||||
const newGroup = {
|
||||
name: groupName,
|
||||
managerKey: groupManager.key,
|
||||
members: groupMembers.map(u => u.key)
|
||||
};
|
||||
|
||||
if(group && group.id)
|
||||
newGroup.id = group.id;
|
||||
|
||||
this.save(newGroup)
|
||||
.then(group => {
|
||||
toastr.success(`Group '${group.name}' has been saved successfully`);
|
||||
})
|
||||
.catch(error => {
|
||||
toastr.error(error.message);
|
||||
toastr.error(error);
|
||||
this.setState({ inLoading: false });
|
||||
});
|
||||
};
|
||||
@ -247,11 +258,11 @@ class SectionBodyContent extends React.Component {
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
onSelectedItemClose = (member) => {
|
||||
this.setState({
|
||||
onSelectedItemClose = member => {
|
||||
this.setState({
|
||||
groupMembers: this.state.groupMembers.filter(g => g.key !== member.key)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderModal = () => {
|
||||
const { groups, modalVisible } = this.state;
|
||||
@ -413,7 +424,7 @@ class SectionBodyContent extends React.Component {
|
||||
<Icons.CatalogGuestIcon size="medium" />
|
||||
</ComboButton>
|
||||
<AdvancedSelector
|
||||
isDropDown={true}
|
||||
displayType="dropdown"
|
||||
isOpen={isHeadSelectorOpen}
|
||||
size="full"
|
||||
placeholder={"Search"}
|
||||
@ -435,7 +446,7 @@ class SectionBodyContent extends React.Component {
|
||||
isRequired={false}
|
||||
hasError={false}
|
||||
isVertical={true}
|
||||
labelText="Members"
|
||||
labelText={t("Members")}
|
||||
>
|
||||
<ComboButton
|
||||
id="users-selector"
|
||||
@ -455,7 +466,7 @@ class SectionBodyContent extends React.Component {
|
||||
<Icons.CatalogGuestIcon size="medium" />
|
||||
</ComboButton>
|
||||
<AdvancedSelector
|
||||
isDropDown={true}
|
||||
displayType="dropdown"
|
||||
isOpen={isUsersSelectorOpen}
|
||||
size="full"
|
||||
placeholder={"Search"}
|
||||
@ -559,11 +570,11 @@ function mapStateToProps(state) {
|
||||
settings: state.auth.settings,
|
||||
group: state.group.targetGroup,
|
||||
groups: convertGroups(state.people.groups),
|
||||
users: convertUsers(state.people.selector.users) //TODO: replace to api requests with search
|
||||
users: convertUsers(state.people.selector.users), //TODO: replace to api requests with search
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ resetGroup, createGroup, updateGroup, fetchSelectorUsers }
|
||||
{ resetGroup, createGroup, updateGroup, fetchSelectorUsers, fetchPeople, fetchGroups }
|
||||
)(withRouter(withTranslation()(SectionBodyContent)));
|
||||
|
@ -32,6 +32,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -50,7 +53,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ class GroupAction extends React.Component {
|
||||
render() {
|
||||
console.log("GroupAction render")
|
||||
|
||||
const { group, match } = this.props;
|
||||
const { group, match, language } = this.props;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
@ -59,6 +61,7 @@ class GroupAction extends React.Component {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
settings: state.auth.settings,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
group: state.group.targetGroup
|
||||
};
|
||||
}
|
||||
|
@ -8,5 +8,6 @@
|
||||
"CustomAddEmployee": "Add {{typeUser, lowercase}}",
|
||||
"CustomNewDepartment": "New {{department, lowercase}}",
|
||||
"CustomEditDepartment": "Edit {{department, lowercase}}",
|
||||
"CustomDepartmentName": "{{department}} name"
|
||||
"CustomDepartmentName": "{{department}} name",
|
||||
"Members": "Members"
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"SaveButton": "Сохранить",
|
||||
"CancelButton": "Отмена",
|
||||
|
||||
|
||||
|
||||
"CustomHeadOfDepartment": "{{headOfDepartment}}",
|
||||
"CustomAddEmployee": "Добавить {{typeUser, lowercase}}",
|
||||
"CustomNewDepartment": "Новый {{department, lowercase}}",
|
||||
"CustomEditDepartment": "Редактирование {{department, lowercase}}",
|
||||
"CustomDepartmentName": "Имя {{department}}",
|
||||
"Members": "Участники"
|
||||
}
|
@ -100,7 +100,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
</Text.Body>
|
||||
)
|
||||
)
|
||||
.catch(e => toastr.error("ERROR"))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
this.onDialogClose();
|
||||
}}
|
||||
@ -172,7 +172,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Disabled, [user.id])
|
||||
.then(() => toastr.success("SUCCESS Context action: Disable"))
|
||||
.catch(e => toastr.error("FAILED Context action: Disable", e))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
};
|
||||
|
||||
@ -182,7 +182,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
onLoading(true);
|
||||
updateUserStatus(EmployeeStatus.Active, [user.id])
|
||||
.then(() => toastr.success("SUCCESS Context action: Enable"))
|
||||
.catch(e => toastr.error("FAILED Context action: Enable", e))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
};
|
||||
|
||||
@ -230,7 +230,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
toastr.success("User has been removed successfully");
|
||||
return fetchPeople(filter);
|
||||
})
|
||||
.catch(e => toastr.error("ERROR"))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
this.onDialogClose();
|
||||
}}
|
||||
@ -290,7 +290,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
</Text.Body>
|
||||
)
|
||||
)
|
||||
.catch(e => toastr.error("ERROR"))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
this.onDialogClose();
|
||||
}}
|
||||
@ -320,7 +320,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
</Text.Body>
|
||||
)
|
||||
)
|
||||
.catch(e => toastr.error("ERROR"))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
};
|
||||
getUserContextOptions = (user, viewer) => {
|
||||
|
@ -1,38 +1,75 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { withRouter } from "react-router";
|
||||
import { RowContent, Link, Icons } from "asc-web-components";
|
||||
import { RowContent, Link, LinkWithDropdown, Icons, toastr } from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import { getUserStatus } from "../../../../../store/people/selectors";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { headOfDepartment } from './../../../../../helpers/customNames';
|
||||
import history from "../../../../../history";
|
||||
|
||||
const getFormatedGroups = groups => {
|
||||
let temp = [];
|
||||
|
||||
if (!groups) temp.push({ key: 0, label: '' });
|
||||
|
||||
groups && groups.map(group =>
|
||||
temp.push(
|
||||
{
|
||||
key: group.id,
|
||||
label: group.name,
|
||||
onClick: () => history.push(`/products/people/filter?group=${group.id}`)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (temp.length <= 1) {
|
||||
return (
|
||||
<Link
|
||||
containerWidth='160px'
|
||||
type='action'
|
||||
title={temp[0].label}
|
||||
fontSize={12}
|
||||
onClick={temp[0].onClick}
|
||||
>
|
||||
{temp[0].label}
|
||||
</Link>);
|
||||
} else {
|
||||
return (
|
||||
<LinkWithDropdown
|
||||
isTextOverflow={true}
|
||||
containerWidth='160px'
|
||||
type='action'
|
||||
title={temp[0].label}
|
||||
fontSize={12}
|
||||
data={temp}
|
||||
>
|
||||
{temp[0].label}
|
||||
</LinkWithDropdown>);
|
||||
}
|
||||
};
|
||||
|
||||
const UserContent = ({ user, history, settings }) => {
|
||||
const { userName, displayName, headDepartment, department, mobilePhone, email } = user;
|
||||
const { userName, displayName, title, mobilePhone, email } = user;
|
||||
const status = getUserStatus(user);
|
||||
const groups = getFormatedGroups(user.groups);
|
||||
|
||||
const onUserNameClick = useCallback(() => {
|
||||
console.log("User name action");
|
||||
history.push(`${settings.homepage}/view/${userName}`);
|
||||
}, [history, settings.homepage, userName]);
|
||||
|
||||
const onHeadDepartmentClick = useCallback(
|
||||
() => console.log("Head of department action"),
|
||||
[]
|
||||
const onUserTitleClick = useCallback(
|
||||
() => toastr.success(`Filter action by user title: ${title}`),
|
||||
[title]
|
||||
);
|
||||
|
||||
const onDepartmentClick = useCallback(
|
||||
() => console.log("Department action"),
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
const onPhoneClick = useCallback(
|
||||
() => console.log("Phone action"),
|
||||
[]
|
||||
() => window.open(`sms:${mobilePhone}`),
|
||||
[mobilePhone]
|
||||
);
|
||||
|
||||
const onEmailClick = useCallback(
|
||||
() => console.log("Email action"),
|
||||
[]
|
||||
() => window.open(`mailto:${email}`),
|
||||
[email]
|
||||
);
|
||||
|
||||
const nameColor = status === 'pending' ? '#A3A9AE' : '#333333';
|
||||
@ -40,7 +77,7 @@ const UserContent = ({ user, history, settings }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headDepartmentStyle = {
|
||||
width: '80px'
|
||||
width: '110px'
|
||||
}
|
||||
|
||||
return (
|
||||
@ -50,20 +87,20 @@ const UserContent = ({ user, history, settings }) => {
|
||||
{status === 'pending' && <Icons.SendClockIcon size='small' isfill={true} color='#3B72A7' />}
|
||||
{status === 'disabled' && <Icons.CatalogSpamIcon size='small' isfill={true} color='#3B72A7' />}
|
||||
</>
|
||||
{headDepartment
|
||||
{title
|
||||
? <Link
|
||||
containerWidth='80px'
|
||||
containerWidth='110px'
|
||||
type='page'
|
||||
title={t('CustomHeadOfDepartment', { headOfDepartment })}
|
||||
title={title}
|
||||
fontSize={12}
|
||||
color={sideInfoColor}
|
||||
onClick={onHeadDepartmentClick}
|
||||
onClick={onUserTitleClick}
|
||||
>
|
||||
{t('CustomHeadOfDepartment', { headOfDepartment })}
|
||||
{title}
|
||||
</Link>
|
||||
: <div style={headDepartmentStyle}></div>
|
||||
}
|
||||
<Link containerWidth='160px' type='action' title={department} fontSize={12} color={sideInfoColor} onClick={onDepartmentClick} >{department}</Link>
|
||||
{groups}
|
||||
<Link type='page' title={mobilePhone} fontSize={12} color={sideInfoColor} onClick={onPhoneClick} >{mobilePhone}</Link>
|
||||
<Link containerWidth='220px' type='page' title={email} fontSize={12} color={sideInfoColor} onClick={onEmailClick} >{email}</Link>
|
||||
</RowContent>
|
||||
|
@ -12,18 +12,6 @@ import {
|
||||
department
|
||||
} from "./../../../../../helpers/customNames";
|
||||
import { withRouter } from "react-router";
|
||||
import Filter from "../../../../../store/people/filter";
|
||||
import {
|
||||
EMPLOYEE_STATUS,
|
||||
ACTIVATION_STATUS,
|
||||
ROLE,
|
||||
GROUP,
|
||||
SEARCH,
|
||||
SORT_BY,
|
||||
SORT_ORDER,
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
} from "../../../../../helpers/constants";
|
||||
import { getFilterByLocation } from "../../../../../helpers/converters";
|
||||
|
||||
const getEmployeeStatus = filterValues => {
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React, { useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import { withRouter } from "react-router";
|
||||
import {
|
||||
GroupButtonsMenu,
|
||||
DropDownItem,
|
||||
Text,
|
||||
toastr,
|
||||
ContextMenuButton
|
||||
ContextMenuButton,
|
||||
IconButton
|
||||
} from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
@ -28,13 +30,25 @@ import {
|
||||
resendUserInvites,
|
||||
deleteUsers
|
||||
} from "../../../../../store/services/api";
|
||||
import { deleteGroup } from '../../../../../store/group/actions';
|
||||
import { deleteGroup } from "../../../../../store/group/actions";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
.group-button-menu-container {
|
||||
margin: 0 -16px;
|
||||
}
|
||||
|
||||
const wrapperStyle = {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
};
|
||||
.header-container {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: calc(100vw - 32px);
|
||||
|
||||
.add-group-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const SectionHeaderContent = props => {
|
||||
const {
|
||||
@ -50,7 +64,7 @@ const SectionHeaderContent = props => {
|
||||
selection,
|
||||
updateUserStatus,
|
||||
updateUserType,
|
||||
onLoading,
|
||||
onLoading,
|
||||
filter,
|
||||
history,
|
||||
settings,
|
||||
@ -83,7 +97,7 @@ const SectionHeaderContent = props => {
|
||||
const onSentInviteAgain = useCallback(() => {
|
||||
resendUserInvites(selectedUserIds)
|
||||
.then(() => toastr.success("The invitation was successfully sent"))
|
||||
.catch(e => toastr.error("ERROR"));
|
||||
.catch(error => toastr.error(error));
|
||||
}, [selectedUserIds]);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
@ -93,7 +107,7 @@ const SectionHeaderContent = props => {
|
||||
toastr.success("Users have been removed successfully");
|
||||
return fetchPeople(filter);
|
||||
})
|
||||
.catch(e => toastr.error("ERROR"))
|
||||
.catch(error => toastr.error(error))
|
||||
.finally(() => onLoading(false));
|
||||
}, [selectedUserIds, onLoading, filter]);
|
||||
|
||||
@ -148,11 +162,15 @@ const SectionHeaderContent = props => {
|
||||
}
|
||||
];
|
||||
|
||||
const onEditGroup = useCallback(() => history.push(`${settings.homepage}/group/edit/${group.id}`), [history, settings, group]);
|
||||
const onEditGroup = useCallback(
|
||||
() => history.push(`${settings.homepage}/group/edit/${group.id}`),
|
||||
[history, settings, group]
|
||||
);
|
||||
|
||||
const onDeleteGroup = useCallback(() => {
|
||||
deleteGroup(group.id)
|
||||
.then(() => toastr.success("Group has been removed successfully"));
|
||||
deleteGroup(group.id).then(() =>
|
||||
toastr.success("Group has been removed successfully")
|
||||
);
|
||||
}, [deleteGroup, group]);
|
||||
|
||||
const getContextOptions = useCallback(() => {
|
||||
@ -170,37 +188,60 @@ const SectionHeaderContent = props => {
|
||||
];
|
||||
}, [t, onEditGroup, onDeleteGroup]);
|
||||
|
||||
return isHeaderVisible ? (
|
||||
<div style={{ margin: "0 -16px" }}>
|
||||
<GroupButtonsMenu
|
||||
checked={isHeaderChecked}
|
||||
isIndeterminate={isHeaderIndeterminate}
|
||||
onChange={onCheck}
|
||||
menuItems={menuItems}
|
||||
visible={isHeaderVisible}
|
||||
moreLabel={t("More")}
|
||||
closeTitle={t("CloseButton")}
|
||||
onClose={onClose}
|
||||
selected={menuItems[0].label}
|
||||
/>
|
||||
</div>
|
||||
) : group ? (
|
||||
<div style={wrapperStyle}>
|
||||
<Text.ContentHeader>{group.name}</Text.ContentHeader>
|
||||
{isAdmin && (
|
||||
<ContextMenuButton
|
||||
directionX="right"
|
||||
title={t("Actions")}
|
||||
iconName="VerticalDotsIcon"
|
||||
size={16}
|
||||
color="#A3A9AE"
|
||||
getData={getContextOptions.bind(this, t)}
|
||||
isDisabled={false}
|
||||
/>
|
||||
const onAddDepartmentsClick = useCallback(() => {
|
||||
history.push(`${settings.homepage}/group/create`);
|
||||
}, [history, settings]);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
{isHeaderVisible ? (
|
||||
<div className="group-button-menu-container">
|
||||
<GroupButtonsMenu
|
||||
checked={isHeaderChecked}
|
||||
isIndeterminate={isHeaderIndeterminate}
|
||||
onChange={onCheck}
|
||||
menuItems={menuItems}
|
||||
visible={isHeaderVisible}
|
||||
moreLabel={t("More")}
|
||||
closeTitle={t("CloseButton")}
|
||||
onClose={onClose}
|
||||
selected={menuItems[0].label}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="header-container">
|
||||
{group ? (
|
||||
<>
|
||||
<Text.ContentHeader truncate={true}>{group.name}</Text.ContentHeader>
|
||||
{isAdmin && (
|
||||
<ContextMenuButton
|
||||
directionX="right"
|
||||
title={t("Actions")}
|
||||
iconName="VerticalDotsIcon"
|
||||
size={16}
|
||||
color="#A3A9AE"
|
||||
getData={getContextOptions.bind(this, t)}
|
||||
isDisabled={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text.ContentHeader>Departments</Text.ContentHeader>
|
||||
{isAdmin && (
|
||||
<IconButton
|
||||
className="add-group-button"
|
||||
size={16}
|
||||
iconName="PlusIcon"
|
||||
isFill={false}
|
||||
onClick={onAddDepartmentsClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Text.ContentHeader>{t("People")}</Text.ContentHeader>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -32,6 +32,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -50,7 +53,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -39,13 +39,13 @@ class PureHome extends React.Component {
|
||||
headerVisible && selection.length > 0 && selection.length < users.length;
|
||||
const headerChecked = headerVisible && selection.length === users.length;
|
||||
|
||||
console.log(`renderGroupButtonMenu()
|
||||
/*console.log(`renderGroupButtonMenu()
|
||||
headerVisible=${headerVisible}
|
||||
headerIndeterminate=${headerIndeterminate}
|
||||
headerChecked=${headerChecked}
|
||||
selection.length=${selection.length}
|
||||
users.length=${users.length}
|
||||
selected=${selected}`);
|
||||
selected=${selected}`);*/
|
||||
|
||||
let newState = {};
|
||||
|
||||
@ -143,18 +143,13 @@ class PureHome extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
users: state.people.users,
|
||||
selection: state.people.selection,
|
||||
selected: state.people.selected,
|
||||
isLoaded: state.auth.isLoaded
|
||||
};
|
||||
}
|
||||
|
||||
const HomeContainer = withTranslation()(PureHome);
|
||||
|
||||
const Home = (props) => <I18nextProvider i18n={i18n}><HomeContainer {...props}/></I18nextProvider>;
|
||||
const Home = (props) => {
|
||||
const {language} = props;
|
||||
i18n.changeLanguage(language);
|
||||
return (<I18nextProvider i18n={i18n}><HomeContainer {...props}/></I18nextProvider>);
|
||||
}
|
||||
|
||||
Home.propTypes = {
|
||||
users: PropTypes.array.isRequired,
|
||||
@ -162,6 +157,16 @@ Home.propTypes = {
|
||||
isLoaded: PropTypes.bool
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
users: state.people.users,
|
||||
selection: state.people.selection,
|
||||
selected: state.people.selected,
|
||||
isLoaded: state.auth.isLoaded,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ setSelected }
|
||||
|
@ -0,0 +1,56 @@
|
||||
{
|
||||
"LoadingProcessing": "Загрузка...",
|
||||
"LblSendEmail": "Отправить email",
|
||||
"LblSendMessage": "Отправить сообщение",
|
||||
"EditButton": "Редактировать",
|
||||
"PasswordChangeButton": "Изменить пароль",
|
||||
"EmailChangeButton": "Изменить email",
|
||||
"DisableUserButton": "Заблокировать",
|
||||
"EnableUserButton": "Разблокировать",
|
||||
"ReassignData": "Передать данные",
|
||||
"RemoveData": "Удалить личные данные",
|
||||
"DeleteSelfProfile": "Удалить профиль",
|
||||
"LoadingDescription": "Пожалуйста подождите...",
|
||||
"NotFoundDescription": "В данном разделе нет людей, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы просмотреть всех людей в этом разделе.",
|
||||
"NotFoundTitle": "Результатов, соответствующих заданным критериям, не найдено",
|
||||
"ClearButton": "Сбросить фильтр",
|
||||
"UserStatus": "Статус",
|
||||
"LblActive": "Активный",
|
||||
"LblTerminated": "Заблокирован",
|
||||
"Email": "Email",
|
||||
"LblPending": "Ожидание",
|
||||
"UserType": "Тип",
|
||||
"Administrator": "Администратор",
|
||||
"LblOther": "Другое",
|
||||
"DeleteButton": "Удалить",
|
||||
"SuccessChangeUserStatus": "Статус пользователя успешно изменен",
|
||||
"SuccessChangeUserType": "Тип пользователя успешно изменен",
|
||||
"LblSelect": "Выберите",
|
||||
"More": "Больше",
|
||||
"CloseButton": "Закрыть",
|
||||
"Actions": "Действия",
|
||||
"People": "Люди",
|
||||
"PreviousPage": "Предыдущая",
|
||||
"NextPage": "Следующая",
|
||||
|
||||
"LblInviteAgain": "Выслать прилашение ещё раз",
|
||||
"ByFirstNameSorting": "По имени",
|
||||
"ByLastNameSorting": "По фамилии",
|
||||
"LblInvited": "Invited",
|
||||
"LblSetActive": "Разблокировать",
|
||||
"LblSetDisabled": "Заблокировать",
|
||||
|
||||
"CustomHeadOfDepartment": " {{headOfDepartment}}",
|
||||
"CustomTypeGuest": "{{typeGuest}}",
|
||||
"CustomTypeUser": "{{typeUser}}",
|
||||
"CustomMakeUser": "Сделать {{typeUser, lowercase}}",
|
||||
"CustomMakeGuest": "Сделать {{typeGuest, lowercase}}",
|
||||
"CustomDepartment": "{{department}}",
|
||||
"CountPerPage": "{{count}} на странице",
|
||||
"PageOfTotalPage": "{{page}} из {{totalPage}}",
|
||||
|
||||
"DirectionAscLabel":"А-Я",
|
||||
"DirectionDescLabel":"Я-А",
|
||||
|
||||
"DefaultSelectLabel": "Выберите"
|
||||
}
|
@ -0,0 +1,389 @@
|
||||
import React from "react";
|
||||
import { Trans } from 'react-i18next';
|
||||
import { department as departmentName, position, employedSinceDate } from '../../../../../../helpers/customNames';
|
||||
import { resendUserInvites, sendInstructionsToChangeEmail } from "../../../../../../store/services/api";
|
||||
import {
|
||||
Text,
|
||||
TextInput,
|
||||
Button,
|
||||
IconButton,
|
||||
Link,
|
||||
toastr,
|
||||
ModalDialog,
|
||||
ComboBox,
|
||||
HelpButton
|
||||
} from "asc-web-components";
|
||||
import styled from 'styled-components';
|
||||
import history from "../../../../../../history";
|
||||
|
||||
const InfoContainer = styled.div`
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
const InfoItem = styled.div`
|
||||
font-family: Open Sans;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
width: 400px;
|
||||
`;
|
||||
|
||||
const InfoItemLabel = styled.div`
|
||||
width: 140px;
|
||||
white-space: nowrap;
|
||||
color: #A3A9AE;
|
||||
`;
|
||||
|
||||
const InfoItemValue = styled.div`
|
||||
width: 240px;
|
||||
|
||||
.language-combo {
|
||||
padding-top: 4px;
|
||||
float: left;
|
||||
|
||||
& > div {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TooltipIcon = styled.span`
|
||||
display: inline-flex;
|
||||
padding-top: 6px;
|
||||
`;
|
||||
|
||||
const IconButtonWrapper = styled.div`
|
||||
${props => props.isBefore
|
||||
? `margin-right: 8px;`
|
||||
: `margin-left: 8px;`
|
||||
}
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
:hover {
|
||||
& > div > svg > path {
|
||||
fill: #3B72A7;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const onGroupClick = (department) => {
|
||||
history.push(`/products/people/filter?group=${department.id}`)
|
||||
};
|
||||
|
||||
const getFormattedDepartments = departments => {
|
||||
const formattedDepartments = departments.map((department, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
<Link type="page" fontSize={13} isHovered={true} onClick={onGroupClick.bind(this, department)}>
|
||||
{department.name}
|
||||
</Link>
|
||||
{departments.length - 1 !== index ? ", " : ""}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
return formattedDepartments;
|
||||
};
|
||||
|
||||
const capitalizeFirstLetter = string => {
|
||||
return string && string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
class ProfileInfo extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.mapPropsToState(props);
|
||||
}
|
||||
|
||||
mapPropsToState = (props) => {
|
||||
const newState = {
|
||||
profile: props.profile,
|
||||
dialog: {
|
||||
visible: false,
|
||||
header: "",
|
||||
body: "",
|
||||
buttons: [],
|
||||
newEmail: props.profile.email,
|
||||
}
|
||||
};
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
onEmailChange = e => {
|
||||
const emailRegex = /.+@.+\..+/;
|
||||
const newEmail = e.target.value || this.state.dialog.newEmail || this.props.profile.email;
|
||||
const hasError = !emailRegex.test(newEmail);
|
||||
|
||||
const dialog = {
|
||||
visible: true,
|
||||
header: "Change email",
|
||||
body: (
|
||||
<Text.Body>
|
||||
<span style={{ display: "block", marginBottom: "8px" }}>The activation instructions will be sent to the entered email</span>
|
||||
<TextInput
|
||||
id="new-email"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
value={newEmail}
|
||||
onChange={this.onEmailChange}
|
||||
hasError={hasError}
|
||||
/>
|
||||
</Text.Body>
|
||||
),
|
||||
buttons: [
|
||||
<Button
|
||||
key="SendBtn"
|
||||
label="Send"
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={this.onSendEmailChangeInstructions}
|
||||
isDisabled={hasError}
|
||||
/>
|
||||
],
|
||||
value: newEmail
|
||||
};
|
||||
this.setState({ dialog: dialog })
|
||||
}
|
||||
|
||||
onSendEmailChangeInstructions = () => {
|
||||
sendInstructionsToChangeEmail(this.state.profile.id, this.state.dialog.value)
|
||||
.then((res) => {
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => toastr.error(error))
|
||||
.finally(this.onDialogClose);
|
||||
}
|
||||
|
||||
onSentInviteAgain = id => {
|
||||
resendUserInvites(new Array(id))
|
||||
.then(() => toastr.success("The invitation was successfully sent"))
|
||||
.catch(error => toastr.error(error));
|
||||
};
|
||||
|
||||
onDialogClose = value => {
|
||||
const dialog = { visible: false, value: value };
|
||||
this.setState({ dialog: dialog })
|
||||
}
|
||||
|
||||
onEmailClick = (e, email) => {
|
||||
if (e.target.title)
|
||||
window.open("mailto:" + email);
|
||||
}
|
||||
|
||||
onLanguageSelect = (language) => {
|
||||
console.log("onLanguageSelect", language);
|
||||
const { profile, updateProfileCulture } = this.props;
|
||||
|
||||
if (profile.cultureName === language.key) return;
|
||||
|
||||
updateProfileCulture(profile.id, language.key);
|
||||
}
|
||||
|
||||
getLanguages = () => {
|
||||
const { cultures, t } = this.props;
|
||||
|
||||
return cultures.map((culture) => {
|
||||
return { key: culture, label: t(`Culture_${culture}`) };
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dialog } = this.state;
|
||||
const { isVisitor, email, activationStatus, department, groups, title, mobilePhone, sex, workFrom, birthday, location, cultureName, currentCulture, id } = this.props.profile;
|
||||
const isAdmin = this.props.isAdmin;
|
||||
const isSelf = this.props.isSelf;
|
||||
const { t, i18n } = this.props;
|
||||
const type = isVisitor ? "Guest" : "Employee";
|
||||
const language = cultureName || currentCulture || this.props.culture;
|
||||
const languages = this.getLanguages();
|
||||
const selectedLanguage = languages.find(item => item.key === language);
|
||||
const workFromDate = new Date(workFrom).toLocaleDateString(language);
|
||||
const birthDayDate = new Date(birthday).toLocaleDateString(language);
|
||||
const formatedSex = capitalizeFirstLetter(sex);
|
||||
const formatedDepartments = department && getFormattedDepartments(groups);
|
||||
const supportEmail = "documentation@onlyoffice.com";
|
||||
const tooltipLanguage =
|
||||
<Text.Body fontSize={13}>
|
||||
<Trans i18nKey="NotFoundLanguage" i18n={i18n}>
|
||||
"In case you cannot find your language in the list of the
|
||||
available ones, feel free to write to us at
|
||||
<Link href="mailto:documentation@onlyoffice.com" isHovered={true}>
|
||||
{{ supportEmail }}
|
||||
</Link> to take part in the translation and get up to 1 year free of
|
||||
charge."
|
||||
</Trans>
|
||||
{" "}
|
||||
<Link isHovered={true} href="https://helpcenter.onlyoffice.com/ru/guides/become-translator.aspx">{t("LearnMore")}</Link>
|
||||
</Text.Body>
|
||||
|
||||
return (
|
||||
<InfoContainer>
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('UserType')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{type}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
{email &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Email')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
<>
|
||||
{activationStatus === 2 && (isAdmin || isSelf) &&
|
||||
<IconButtonWrapper isBefore={true} title={t('PendingTitle')}>
|
||||
<IconButton
|
||||
color='#C96C27'
|
||||
size={16}
|
||||
iconName='DangerIcon'
|
||||
isFill={true} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
<Link
|
||||
type="page"
|
||||
fontSize={13}
|
||||
isHovered={true}
|
||||
title={email}
|
||||
onClick={this.onEmailClick.bind(email)}
|
||||
>
|
||||
{email}
|
||||
</Link>
|
||||
{(isAdmin || isSelf) &&
|
||||
<IconButtonWrapper title={t('EmailChangeButton')} >
|
||||
<IconButton
|
||||
color="#A3A9AE"
|
||||
size={16}
|
||||
iconName='AccessEditIcon'
|
||||
isFill={true}
|
||||
onClick={this.onEmailChange} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
{activationStatus === 2 && (isAdmin || isSelf) &&
|
||||
<IconButtonWrapper title={t('SendInviteAgain')}>
|
||||
<IconButton
|
||||
color="#A3A9AE"
|
||||
size={16}
|
||||
iconName='FileActionsConvertIcon'
|
||||
isFill={true}
|
||||
onClick={this.onSentInviteAgain.bind(this, id)} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
</>
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{department &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomDepartment", { department: departmentName })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{formatedDepartments}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{title &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomPosition", { position })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{title}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{(mobilePhone) &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('PhoneLbl')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{mobilePhone}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{sex &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Sex')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{formatedSex}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{workFrom &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomEmployedSinceDate", { employedSinceDate })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{workFromDate}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{birthday &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Birthdate')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{birthDayDate}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{location &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Location')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{location}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{isSelf &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Language')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
<ComboBox
|
||||
options={languages}
|
||||
selectedOption={selectedLanguage}
|
||||
onSelect={this.onLanguageSelect}
|
||||
isDisabled={false}
|
||||
noBorder={true}
|
||||
scaled={false}
|
||||
scaledOptions={false}
|
||||
size='content'
|
||||
className='language-combo'
|
||||
/>
|
||||
<TooltipIcon>
|
||||
<HelpButton place="bottom" offsetLeft={50} offsetRight={0} tooltipContent={tooltipLanguage} />
|
||||
</TooltipIcon>
|
||||
|
||||
</InfoItemValue>
|
||||
|
||||
</InfoItem>
|
||||
}
|
||||
<ModalDialog
|
||||
visible={dialog.visible}
|
||||
headerContent={dialog.header}
|
||||
bodyContent={dialog.body}
|
||||
footerContent={dialog.buttons}
|
||||
onClose={this.onDialogClose.bind(this, email)}
|
||||
/>
|
||||
</InfoContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ProfileInfo;
|
@ -1,24 +1,19 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { withRouter } from "react-router";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { department as departmentName, position, employedSinceDate } from '../../../../../helpers/customNames';
|
||||
import { resendUserInvites, sendInstructionsToChangeEmail } from "../../../../../store/services/api";
|
||||
import {
|
||||
Text,
|
||||
TextInput,
|
||||
Avatar,
|
||||
Button,
|
||||
ToggleContent,
|
||||
IconButton,
|
||||
Link,
|
||||
toastr,
|
||||
ModalDialog,
|
||||
ComboBox
|
||||
IconButton
|
||||
} from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import styled from 'styled-components';
|
||||
import { getUserRole, getUserContacts } from "../../../../../store/people/selectors";
|
||||
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
|
||||
import { updateProfileCulture } from "../../../../../store/profile/actions";
|
||||
import ProfileInfo from "./ProfileInfo/ProfileInfo"
|
||||
|
||||
const ProfileWrapper = styled.div`
|
||||
display: flex;
|
||||
@ -35,6 +30,10 @@ const AvatarWrapper = styled.div`
|
||||
const EditButtonWrapper = styled.div`
|
||||
margin-top: 16px;
|
||||
width: 160px;
|
||||
|
||||
& > button {
|
||||
padding: 8px 20px 9px 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ContactTextTruncate = styled.div`
|
||||
@ -55,78 +54,6 @@ const ContactWrapper = styled.div`
|
||||
width: 300px;
|
||||
`;
|
||||
|
||||
const InfoContainer = styled.div`
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
const InfoItem = styled.div`
|
||||
font-family: Open Sans;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
width: 400px;
|
||||
`;
|
||||
|
||||
const InfoItemLabel = styled.div`
|
||||
width: 120px;
|
||||
white-space: nowrap;
|
||||
color: #A3A9AE;
|
||||
`;
|
||||
|
||||
const InfoItemValue = styled.div`
|
||||
width: 220px;
|
||||
|
||||
.language-combo {
|
||||
padding-top: 4px;
|
||||
|
||||
& > div {
|
||||
padding-left: 0px;
|
||||
|
||||
& > div {
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const IconButtonWrapper = styled.div`
|
||||
${props => props.isBefore
|
||||
? `margin-right: 8px;`
|
||||
: `margin-left: 8px;`
|
||||
}
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
:hover {
|
||||
& > div > svg > path {
|
||||
fill: #3B72A7;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const getFormattedDepartments = departments => {
|
||||
const splittedDepartments = departments.split(",");
|
||||
const departmentsLength = splittedDepartments.length - 1;
|
||||
const formattedDepartments = splittedDepartments.map((department, index) => {
|
||||
return (
|
||||
<span key={index}>
|
||||
<Link type="page" fontSize={13} isHovered={true}>
|
||||
{department.trim()}
|
||||
</Link>
|
||||
{departmentsLength !== index ? ", " : ""}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
return formattedDepartments;
|
||||
};
|
||||
|
||||
const capitalizeFirstLetter = string => {
|
||||
return string && string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
const createContacts = contacts => {
|
||||
const styledContacts = contacts.map((contact, index) => {
|
||||
return (
|
||||
@ -140,278 +67,13 @@ const createContacts = contacts => {
|
||||
return styledContacts;
|
||||
};
|
||||
|
||||
class ProfileInfo extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.mapPropsToState(props);
|
||||
}
|
||||
|
||||
mapPropsToState = (props) => {
|
||||
const newState = {
|
||||
profile: props.profile,
|
||||
dialog: {
|
||||
visible: false,
|
||||
header: "",
|
||||
body: "",
|
||||
buttons: [],
|
||||
newEmail: props.profile.email,
|
||||
}
|
||||
};
|
||||
|
||||
return newState;
|
||||
};
|
||||
|
||||
onEmailChange = e => {
|
||||
const emailRegex = /.+@.+\..+/;
|
||||
const newEmail = e.target.value || this.state.dialog.newEmail || this.props.profile.email;
|
||||
const hasError = !emailRegex.test(newEmail);
|
||||
|
||||
const dialog = {
|
||||
visible: true,
|
||||
header: "Change email",
|
||||
body: (
|
||||
<Text.Body>
|
||||
<span style={{ display: "block", marginBottom: "8px" }}>The activation instructions will be sent to the entered email</span>
|
||||
<TextInput
|
||||
id="new-email"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
value={newEmail}
|
||||
onChange={this.onEmailChange}
|
||||
hasError={hasError}
|
||||
/>
|
||||
</Text.Body>
|
||||
),
|
||||
buttons: [
|
||||
<Button
|
||||
key="SendBtn"
|
||||
label="Send"
|
||||
size="medium"
|
||||
primary={true}
|
||||
onClick={this.onSendEmailChangeInstructions}
|
||||
isDisabled={hasError}
|
||||
/>
|
||||
],
|
||||
value: newEmail
|
||||
};
|
||||
this.setState({ dialog: dialog })
|
||||
}
|
||||
|
||||
onSendEmailChangeInstructions = () => {
|
||||
sendInstructionsToChangeEmail(this.state.profile.id, this.state.dialog.value)
|
||||
.then((res) => {
|
||||
res.data.error ? toastr.error(res.data.error.message) : toastr.success(res.data.response)
|
||||
})
|
||||
.catch((error) => toastr.error(error.message))
|
||||
.finally(this.onDialogClose);
|
||||
}
|
||||
|
||||
onSentInviteAgain = id => {
|
||||
resendUserInvites(new Array(id))
|
||||
.then(() => toastr.success("The invitation was successfully sent"))
|
||||
.catch(e => toastr.error("ERROR"));
|
||||
};
|
||||
|
||||
|
||||
|
||||
onDialogClose = value => {
|
||||
const dialog = { visible: false, value: value };
|
||||
this.setState({ dialog: dialog })
|
||||
}
|
||||
|
||||
onEmailClick = (e, email) => {
|
||||
if (e.target.title)
|
||||
window.open("mailto:" + email);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dialog } = this.state;
|
||||
const { isVisitor, email, activationStatus, department, title, mobilePhone, sex, workFrom, birthday, location, cultureName, currentCulture, id } = this.props.profile;
|
||||
const isAdmin = this.props.isAdmin;
|
||||
const isSelf = this.props.isSelf;
|
||||
const t = this.props.t;
|
||||
const type = isVisitor ? "Guest" : "Employee";
|
||||
const fakeLanguage = [{
|
||||
key: "en-US",
|
||||
label: "English (United States)"
|
||||
},
|
||||
{
|
||||
key: "ru-RU",
|
||||
label: "Russian (Russia)"
|
||||
}];
|
||||
const language = cultureName || currentCulture;
|
||||
const workFromDate = new Date(workFrom).toLocaleDateString(language);
|
||||
const birthDayDate = new Date(birthday).toLocaleDateString(language);
|
||||
const formatedSex = capitalizeFirstLetter(sex);
|
||||
const formatedDepartments = getFormattedDepartments(department);
|
||||
|
||||
return (
|
||||
<InfoContainer>
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('UserType')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{type}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
{email &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Email')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
<Link
|
||||
type="page"
|
||||
fontSize={13}
|
||||
isHovered={true}
|
||||
title={email}
|
||||
onClick={this.onEmailClick.bind(email)}
|
||||
>
|
||||
{activationStatus === 2 && (isAdmin || isSelf) &&
|
||||
<IconButtonWrapper isBefore={true} title={t('PendingTitle')}>
|
||||
<IconButton
|
||||
color='#C96C27'
|
||||
size={16}
|
||||
iconName='DangerIcon'
|
||||
isFill={true} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
{email}
|
||||
{(isAdmin || isSelf) &&
|
||||
<IconButtonWrapper title={t('EmailChangeButton')} >
|
||||
<IconButton
|
||||
color="#A3A9AE"
|
||||
size={16}
|
||||
iconName='AccessEditIcon'
|
||||
isFill={true}
|
||||
onClick={this.onEmailChange} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
{activationStatus === 2 && (isAdmin || isSelf) &&
|
||||
<IconButtonWrapper title={t('SendInviteAgain')}>
|
||||
<IconButton
|
||||
color="#A3A9AE"
|
||||
size={16}
|
||||
iconName='FileActionsConvertIcon'
|
||||
isFill={true}
|
||||
onClick={this.onSentInviteAgain.bind(this, id)} />
|
||||
</IconButtonWrapper>
|
||||
}
|
||||
</Link>
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{department &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomDepartment", { department: departmentName })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{formatedDepartments}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{title &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomPosition", { position })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{title}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{(mobilePhone || isSelf) &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('PhoneLbl')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{mobilePhone}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{sex &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Sex')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{formatedSex}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{workFrom &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t("CustomEmployedSinceDate", { employedSinceDate })}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{workFromDate}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{birthday &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Birthdate')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{birthDayDate}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{location &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Location')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
{location}
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
{isSelf &&
|
||||
<InfoItem>
|
||||
<InfoItemLabel>
|
||||
{t('Language')}:
|
||||
</InfoItemLabel>
|
||||
<InfoItemValue>
|
||||
<ComboBox
|
||||
options={fakeLanguage}
|
||||
onSelect={() => { }}
|
||||
selectedOption={fakeLanguage.find(item => item.key === language)}
|
||||
isDisabled={false}
|
||||
noBorder={true}
|
||||
dropDownMaxHeight={250}
|
||||
scaled={false}
|
||||
scaledOptions={true}
|
||||
size='content'
|
||||
className='language-combo'
|
||||
/>
|
||||
</InfoItemValue>
|
||||
</InfoItem>
|
||||
}
|
||||
<ModalDialog
|
||||
visible={dialog.visible}
|
||||
headerContent={dialog.header}
|
||||
bodyContent={dialog.body}
|
||||
footerContent={dialog.buttons}
|
||||
onClose={this.onDialogClose.bind(this, email)}
|
||||
/>
|
||||
</InfoContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const SectionBodyContent = props => {
|
||||
const { t } = useTranslation();
|
||||
const { profile, history, settings, isAdmin, viewer } = props;
|
||||
const { profile, updateProfileCulture, history, settings, isAdmin, viewer } = props;
|
||||
|
||||
const contacts = profile.contacts && getUserContacts(profile.contacts);
|
||||
const role = getUserRole(profile);
|
||||
const socialContacts = contacts && createContacts(contacts.social);
|
||||
const socialContacts = (contacts && contacts.social && contacts.social.length > 0 && createContacts(contacts.social)) || null;
|
||||
const infoContacts = contacts && createContacts(contacts.contact);
|
||||
const isSelf = isMe(viewer, profile.userName);
|
||||
|
||||
@ -440,12 +102,13 @@ const SectionBodyContent = props => {
|
||||
size="big"
|
||||
scale={true}
|
||||
label={t("EditUserDialogTitle")}
|
||||
title={t("EditUserDialogTitle")}
|
||||
onClick={onEditProfileClick}
|
||||
/>
|
||||
</EditButtonWrapper>
|
||||
)}
|
||||
</AvatarWrapper>
|
||||
<ProfileInfo profile={profile} isSelf={isSelf} isAdmin={isAdmin} t={t} />
|
||||
<ProfileInfo profile={profile} updateProfileCulture={updateProfileCulture} isSelf={isSelf} isAdmin={isAdmin} t={t} cultures={settings.cultures} culture={settings.culture} />
|
||||
{isSelf && (
|
||||
<ToggleWrapper isSelf={true} >
|
||||
<ToggleContent label={t('Subscriptions')} isOpen={true} >
|
||||
@ -474,7 +137,7 @@ const SectionBodyContent = props => {
|
||||
</ToggleContent>
|
||||
</ToggleWrapper>
|
||||
)}
|
||||
{profile.contacts && (
|
||||
{socialContacts && (
|
||||
<ToggleWrapper isContacts={true} >
|
||||
<ToggleContent label={t('SocialProfiles')} isOpen={true} >
|
||||
<Text.Body as="span">{socialContacts}</Text.Body>
|
||||
@ -493,4 +156,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionBodyContent));
|
||||
export default connect(mapStateToProps, { updateProfileCulture })(withRouter(SectionBodyContent));
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
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,19 +8,25 @@ import { useTranslation } from 'react-i18next';
|
||||
import { resendUserInvites } from "../../../../../store/services/api";
|
||||
import { EmployeeStatus } from "../../../../../helpers/constants";
|
||||
import { updateUserStatus } from "../../../../../store/people/actions";
|
||||
import { fetchProfile } from '../../../../../store/profile/actions';
|
||||
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;
|
||||
const { profile, history, settings, isAdmin, viewer, updateUserStatus, fetchProfile } = props;
|
||||
|
||||
const selectedUserIds = new Array(profile.id);
|
||||
|
||||
@ -41,8 +47,9 @@ const SectionHeaderContent = props => {
|
||||
};
|
||||
|
||||
const onDisableClick = () => {
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds)
|
||||
.then(() => toastr.success(t("SuccessChangeUserStatus")))
|
||||
.then(() => fetchProfile(profile.id));
|
||||
};
|
||||
|
||||
const onEditPhoto = () => {
|
||||
@ -50,8 +57,9 @@ const SectionHeaderContent = props => {
|
||||
};
|
||||
|
||||
const onEnableClick = () => {
|
||||
updateUserStatus(EmployeeStatus.Active, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
updateUserStatus(EmployeeStatus.Active, selectedUserIds)
|
||||
.then(() => toastr.success(t("SuccessChangeUserStatus")))
|
||||
.then(() => fetchProfile(profile.id));
|
||||
};
|
||||
|
||||
const onReassignDataClick = user => {
|
||||
@ -70,7 +78,7 @@ const SectionHeaderContent = props => {
|
||||
const onInviteAgainClick = () => {
|
||||
resendUserInvites(selectedUserIds)
|
||||
.then(() => toastr.success("The invitation was successfully sent"))
|
||||
.catch(e => toastr.error("ERROR"));
|
||||
.catch(error => toastr.error(error));
|
||||
};
|
||||
const getUserContextOptions = (user, viewer, t) => {
|
||||
|
||||
@ -174,6 +182,10 @@ const SectionHeaderContent = props => {
|
||||
const { t } = useTranslation();
|
||||
const contextOptions = () => getUserContextOptions(profile, viewer, t);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<div style={{ width: "16px" }}>
|
||||
@ -181,13 +193,13 @@ const SectionHeaderContent = props => {
|
||||
iconName={"ArrowPathIcon"}
|
||||
color="#A3A9AE"
|
||||
size="16"
|
||||
onClick={() => history.push(settings.homepage)}
|
||||
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"
|
||||
@ -211,4 +223,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { updateUserStatus })(withRouter(SectionHeaderContent));
|
||||
export default connect(mapStateToProps, { updateUserStatus, fetchProfile })(withRouter(SectionHeaderContent));
|
||||
|
@ -5,35 +5,8 @@ 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
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${config.homepage}/locales/Profile/{{lng}}/{{ns}}.json`
|
||||
}
|
||||
});
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
newInstance.init({
|
||||
resources: resources,
|
||||
lng: 'en',
|
||||
newInstance.use(Backend).init({
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
@ -43,6 +16,33 @@ if (process.env.NODE_ENV === "production") {
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${config.homepage}/locales/Profile/{{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
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -43,36 +43,34 @@ class PureProfile extends React.Component {
|
||||
render() {
|
||||
console.log("Profile render")
|
||||
|
||||
const { profile } = this.props;
|
||||
return (
|
||||
profile
|
||||
?
|
||||
<PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionHeaderContent={
|
||||
<SectionHeaderContent profile={profile} />
|
||||
}
|
||||
sectionBodyContent={
|
||||
<SectionBodyContent profile={profile} />
|
||||
}
|
||||
/>
|
||||
: <PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionBodyContent={
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
const { profile, isVisitor } = this.props;
|
||||
|
||||
const articleProps = isVisitor ? {} : {
|
||||
articleHeaderContent: <ArticleHeaderContent />,
|
||||
articleMainButtonContent: <ArticleMainButtonContent />,
|
||||
articleBodyContent: <ArticleBodyContent />
|
||||
};
|
||||
|
||||
const sectionProps = profile ? {
|
||||
sectionHeaderContent: <SectionHeaderContent profile={profile} />,
|
||||
sectionBodyContent: <SectionBodyContent profile={profile} />
|
||||
} : {
|
||||
sectionBodyContent: <Loader className="pageLoader" type="rombs" size={40} />
|
||||
};
|
||||
|
||||
return <PageLayout {...articleProps} {...sectionProps} />;
|
||||
};
|
||||
};
|
||||
|
||||
const ProfileContainer = withTranslation()(PureProfile);
|
||||
|
||||
const Profile = (props) => <I18nextProvider i18n={i18n}><ProfileContainer {...props} /></I18nextProvider>;
|
||||
const Profile = (props) => {
|
||||
const { language } = props;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
return <I18nextProvider i18n={i18n}><ProfileContainer {...props} /></I18nextProvider>
|
||||
};
|
||||
|
||||
Profile.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
@ -84,7 +82,9 @@ Profile.propTypes = {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
profile: state.profile.targetUser
|
||||
profile: state.profile.targetUser,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
isVisitor: state.auth.user.isVisitor,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
"EditButton": "Edit",
|
||||
"Actions": "Actions",
|
||||
"ChangeEmailSuccess": "Mail has been successfully changed",
|
||||
"NotFoundLanguage": "In case you cannot find your language in the list of the available ones, feel free to write to us at <1>{{supportEmail}}</1> to take part in the translation and get up to 1 year free of charge.",
|
||||
|
||||
"PhoneChange": "Change phone",
|
||||
"PhoneLbl": "Phone",
|
||||
@ -31,5 +32,9 @@
|
||||
|
||||
"CustomEmployedSinceDate": "{{employedSinceDate}}",
|
||||
"CustomPosition": "{{position}}",
|
||||
"CustomDepartment": "{{department}}"
|
||||
"CustomDepartment": "{{department}}",
|
||||
"Culture_en": "English (United Kingdom)",
|
||||
"Culture_en-US": "English (United States)",
|
||||
"Culture_ru-RU": "Russian (Russia)",
|
||||
"LearnMore": "Learn more..."
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
{
|
||||
"UserType": "Тип",
|
||||
"Email": "Email",
|
||||
"Sex": "Пол",
|
||||
"Birthdate": "Дата рождения",
|
||||
"Location": "Местоположение",
|
||||
"Language": "Язык",
|
||||
"EditUserDialogTitle": "Редактировать",
|
||||
"Subscriptions": "Подписки",
|
||||
"Comments": "Комментарии",
|
||||
"ContactInformation": "Контактные данные",
|
||||
"PendingTitle": "Pending",
|
||||
"EmailChangeButton": "Изменить email",
|
||||
"SendInviteAgain": "Отправить приглашение ещё раз",
|
||||
"EditPhoto": "Изменить фотографию",
|
||||
"PasswordChangeButton": "Измененить пароль",
|
||||
"DisableUserButton": "Disable",
|
||||
"EnableUserButton": "Enable",
|
||||
"ReassignData": "Reassign data",
|
||||
"RemoveData": "Delete personal data",
|
||||
"DeleteSelfProfile": "Delete profile",
|
||||
"EditButton": "Редактировать",
|
||||
"Actions": "Actions",
|
||||
"ChangeEmailSuccess": "Mail has been successfully changed",
|
||||
"NotFoundLanguage": "Если Вы не можете найти свой язык в списке доступных, Вы всегда можете написать нам по адресу <1>{{supportEmail}}</1>, чтобы принять участие в переводе и получить до 1 года бесплатного использования.",
|
||||
|
||||
"PhoneChange": "Измененить номер телефона",
|
||||
"PhoneLbl": "Основной телефон",
|
||||
"EditSubscriptionsBtn": "Edit subscriptions",
|
||||
"InviteAgainLbl": "Invite again",
|
||||
"LDAPLbl": "LDAP",
|
||||
|
||||
"CustomEmployedSinceDate": "{{employedSinceDate}}",
|
||||
"CustomPosition": "{{position}}",
|
||||
"CustomDepartment": "{{department}}",
|
||||
"Culture_en": "Английский (Великобритания)",
|
||||
"Culture_en-US": "Английский (США)",
|
||||
"Culture_ru-RU": "Русский (Россия)",
|
||||
"LearnMore": "Подробнее..."
|
||||
}
|
@ -14,6 +14,7 @@ class DateField extends React.Component {
|
||||
isRequired,
|
||||
hasError,
|
||||
labelText,
|
||||
calendarHeaderContent,
|
||||
|
||||
inputName,
|
||||
inputValue,
|
||||
@ -35,6 +36,8 @@ class DateField extends React.Component {
|
||||
onChange={inputOnChange}
|
||||
hasError={hasError}
|
||||
tabIndex={inputTabIndex}
|
||||
displayType="auto"
|
||||
calendarHeaderContent={calendarHeaderContent}
|
||||
/>
|
||||
</FieldContainer>
|
||||
);
|
||||
|
@ -51,7 +51,7 @@ class DepartmentField extends React.Component {
|
||||
className="department-add-btn"
|
||||
/>
|
||||
<AdvancedSelector
|
||||
isDropDown={true}
|
||||
displayType="dropdown"
|
||||
isOpen={selectorIsVisible}
|
||||
maxHeight={336}
|
||||
width={379}
|
||||
|
@ -19,7 +19,9 @@ class RadioField extends React.Component {
|
||||
radioValue,
|
||||
radioOptions,
|
||||
radioIsDisabled,
|
||||
radioOnChange
|
||||
radioOnChange,
|
||||
|
||||
tooltipContent
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -27,6 +29,7 @@ class RadioField extends React.Component {
|
||||
isRequired={isRequired}
|
||||
hasError={hasError}
|
||||
labelText={labelText}
|
||||
tooltipContent={tooltipContent}
|
||||
>
|
||||
<RadioButtonGroup
|
||||
name={radioName}
|
||||
|
@ -30,7 +30,9 @@ class TextChangeField extends React.Component {
|
||||
buttonText,
|
||||
buttonIsDisabled,
|
||||
buttonOnClick,
|
||||
buttonTabIndex
|
||||
buttonTabIndex,
|
||||
|
||||
tooltipContent
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -38,6 +40,7 @@ class TextChangeField extends React.Component {
|
||||
isRequired={isRequired}
|
||||
hasError={hasError}
|
||||
labelText={labelText}
|
||||
tooltipContent={tooltipContent}
|
||||
>
|
||||
<InputContainer>
|
||||
<TextInput
|
||||
|
@ -20,7 +20,8 @@ class TextField extends React.Component {
|
||||
inputIsDisabled,
|
||||
inputOnChange,
|
||||
inputAutoFocussed,
|
||||
inputTabIndex
|
||||
inputTabIndex,
|
||||
tooltipContent
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -28,6 +29,7 @@ class TextField extends React.Component {
|
||||
isRequired={isRequired}
|
||||
hasError={hasError}
|
||||
labelText={labelText}
|
||||
tooltipContent={tooltipContent}
|
||||
>
|
||||
<TextInput
|
||||
name={inputName}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { withRouter } from 'react-router'
|
||||
import { connect } from 'react-redux'
|
||||
import { Avatar, Button, Textarea, toastr, AvatarEditor } from 'asc-web-components'
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Avatar, Button, Textarea, toastr, AvatarEditor, Text } from 'asc-web-components'
|
||||
import { withTranslation, Trans } from 'react-i18next';
|
||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts, mapGroupsToGroupSelectorOptions, mapGroupSelectorOptionsToGroups, filterGroupSelectorOptions } from "../../../../../store/people/selectors";
|
||||
import { createProfile, loadAvatar } from '../../../../../store/profile/actions';
|
||||
import { createProfile } from '../../../../../store/profile/actions';
|
||||
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
|
||||
import TextField from './FormFields/TextField'
|
||||
import PasswordField from './FormFields/PasswordField'
|
||||
@ -14,6 +14,7 @@ import DepartmentField from './FormFields/DepartmentField'
|
||||
import ContactsField from './FormFields/ContactsField'
|
||||
import InfoFieldContainer from './FormFields/InfoFieldContainer'
|
||||
import { departments, department, position, employedSinceDate } from '../../../../../helpers/customNames';
|
||||
import { createThumbnailsAvatar, loadAvatar } from "../../../../../store/services/api";
|
||||
|
||||
class CreateUserForm extends React.Component {
|
||||
|
||||
@ -43,38 +44,86 @@ class CreateUserForm extends React.Component {
|
||||
this.onSaveAvatar = this.onSaveAvatar.bind(this);
|
||||
this.onCloseAvatarEditor = this.onCloseAvatarEditor.bind(this);
|
||||
this.createAvatar = this.createAvatar.bind(this);
|
||||
this.onLoadFileAvatar = this.onLoadFileAvatar.bind(this);
|
||||
|
||||
}
|
||||
|
||||
createAvatar(userId,userName){
|
||||
this.props.updateAvatar(
|
||||
userId,
|
||||
{
|
||||
croppedImage: this.state.croppedAvatarImage,
|
||||
defaultImage: this.state.defaultAvatarImage
|
||||
})
|
||||
.then((result) => {
|
||||
createThumbnailsAvatar(userId, {
|
||||
x: this.state.avatar.x,
|
||||
y: this.state.avatar.y,
|
||||
width: this.state.avatar.width,
|
||||
height: this.state.avatar.height,
|
||||
tmpFile: this.state.avatar.tmpFile
|
||||
})
|
||||
.then(() => {
|
||||
toastr.success("Success");
|
||||
this.props.history.push(`${this.props.settings.homepage}/view/${userName}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
this.props.history.push(`${this.props.settings.homepage}/view/${userName}`);
|
||||
});
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
openAvatarEditor(){
|
||||
let avatarDefault = this.state.profile.avatarDefault ? "data:image/png;base64," + this.state.profile.avatarDefault : null;
|
||||
let _this = this;
|
||||
if(avatarDefault !== null){
|
||||
let img = new Image();
|
||||
img.onload = function () {
|
||||
_this.setState({
|
||||
avatar:{
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
})
|
||||
};
|
||||
img.src = avatarDefault;
|
||||
}
|
||||
this.setState({
|
||||
visibleAvatarEditor: true,
|
||||
});
|
||||
}
|
||||
onSaveAvatar(result){
|
||||
this.setState({
|
||||
croppedAvatarImage: result.croppedImage,
|
||||
defaultAvatarImage: result.defaultImage,
|
||||
})
|
||||
onLoadFileAvatar(file) {
|
||||
let data = new FormData();
|
||||
let _this = this;
|
||||
data.append("file", file);
|
||||
data.append("Autosave", false);
|
||||
|
||||
loadAvatar(0, data)
|
||||
.then((response) => {
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var stateCopy = Object.assign({}, _this.state);
|
||||
stateCopy.avatar = {
|
||||
tmpFile: response.data,
|
||||
image: response.data,
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
_this.setState(stateCopy);
|
||||
};
|
||||
img.src = response.data;
|
||||
})
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
onSaveAvatar(isUpdate, result, file){
|
||||
var stateCopy = Object.assign({}, this.state);
|
||||
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.croppedAvatarImage = file;
|
||||
if(isUpdate){
|
||||
stateCopy.avatar.x = Math.round(result.x*this.state.avatar.defaultWidth - result.width/2);
|
||||
stateCopy.avatar.y = Math.round(result.y*this.state.avatar.defaultHeight - result.height/2);
|
||||
stateCopy.avatar.width = result.width;
|
||||
stateCopy.avatar.height = result.height;
|
||||
}
|
||||
this.setState(stateCopy);
|
||||
}
|
||||
onCloseAvatarEditor(){
|
||||
this.setState({
|
||||
visibleAvatarEditor: false,
|
||||
croppedAvatarImage: "",
|
||||
avatar:{
|
||||
tmpFile: ""
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -95,7 +144,6 @@ class CreateUserForm extends React.Component {
|
||||
return {
|
||||
visibleAvatarEditor: false,
|
||||
croppedAvatarImage: "",
|
||||
defaultAvatarImage: "",
|
||||
isLoading: false,
|
||||
errors: {
|
||||
firstName: false,
|
||||
@ -109,6 +157,16 @@ class CreateUserForm extends React.Component {
|
||||
allOptions: allOptions,
|
||||
options: [...allOptions],
|
||||
selected: selected
|
||||
},
|
||||
avatar: {
|
||||
tmpFile:"",
|
||||
image: profile.avatarDefault ? "data:image/png;base64," + profile.avatarDefault : null,
|
||||
defaultWidth: 0,
|
||||
defaultHeight: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -153,12 +211,15 @@ class CreateUserForm extends React.Component {
|
||||
|
||||
this.props.createProfile(this.state.profile)
|
||||
.then((profile) => {
|
||||
toastr.success("Success");
|
||||
this.props.history.push(`${this.props.settings.homepage}/view/${profile.userName}`);
|
||||
//if(this.state.defaultImage !== '') this.createAvatar(profile.id,profile.userName);
|
||||
if(this.state.avatar.tmpFile !== ""){
|
||||
this.createAvatar(profile.id,profile.userName);
|
||||
}else{
|
||||
toastr.success("Success");
|
||||
this.props.history.push(`${this.props.settings.homepage}/view/${profile.userName}`);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message)
|
||||
toastr.error(error);
|
||||
this.setState({ isLoading: false })
|
||||
});
|
||||
}
|
||||
@ -232,7 +293,7 @@ class CreateUserForm extends React.Component {
|
||||
|
||||
render() {
|
||||
const { isLoading, errors, profile, selector } = this.state;
|
||||
const { t, settings } = this.props;
|
||||
const { t, settings, i18n } = this.props;
|
||||
|
||||
const pattern = getUserContactsPattern();
|
||||
const contacts = getUserContacts(profile.contacts);
|
||||
@ -249,11 +310,19 @@ class CreateUserForm extends React.Component {
|
||||
editLabel={t("AddPhoto")}
|
||||
editAction={this.openAvatarEditor}
|
||||
/>
|
||||
<AvatarEditor
|
||||
image={profile.avatarDefault ? "data:image/png;base64,"+profile.avatarDefault : null}
|
||||
visible={this.state.visibleAvatarEditor}
|
||||
onClose={this.onCloseAvatarEditor}
|
||||
onSave={this.onSaveAvatar} />
|
||||
<AvatarEditor
|
||||
image={this.state.avatar.image}
|
||||
visible={this.state.visibleAvatarEditor}
|
||||
onClose={this.onCloseAvatarEditor}
|
||||
onSave={this.onSaveAvatar}
|
||||
onLoadFile={this.onLoadFileAvatar}
|
||||
headerLabel={t("editAvatar")}
|
||||
chooseFileLabel ={t("chooseFileLabel")}
|
||||
unknownTypeError={t("unknownTypeError")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
unknownError ={t("unknownError")}
|
||||
/>
|
||||
|
||||
</AvatarContainer>
|
||||
<MainFieldsContainer>
|
||||
<TextField
|
||||
@ -286,6 +355,12 @@ class CreateUserForm extends React.Component {
|
||||
inputIsDisabled={isLoading}
|
||||
inputOnChange={this.onInputChange}
|
||||
inputTabIndex={3}
|
||||
|
||||
tooltipContent={
|
||||
<Trans i18nKey="EmailPopupHelper" i18n={i18n}>
|
||||
The main e-mail is needed to restore access to the portal in case of loss of the password and send notifications. <p className="tooltip_email" style={{marginTop: "1rem", marginBottom: "1rem"}} >You can create a new mail on the domain as the primary. In this case, you must set a one-time password so that the user can log in to the portal for the first time.</p> The main e-mail can be used as a login when logging in to the portal.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<PasswordField
|
||||
isRequired={true}
|
||||
@ -309,6 +384,7 @@ class CreateUserForm extends React.Component {
|
||||
passwordSettings={settings.passwordSettings}
|
||||
/>
|
||||
<DateField
|
||||
calendarHeaderContent={t("CalendarSelectDate")}
|
||||
labelText={`${t("Birthdate")}:`}
|
||||
inputName="birthday"
|
||||
inputValue={profile.birthday ? new Date(profile.birthday) : undefined}
|
||||
@ -328,6 +404,7 @@ class CreateUserForm extends React.Component {
|
||||
radioOnChange={this.onInputChange}
|
||||
/>
|
||||
<DateField
|
||||
calendarHeaderContent={t("CalendarSelectDate")}
|
||||
labelText={`${t("CustomEmployedSinceDate", { employedSinceDate })}:`}
|
||||
inputName="workFrom"
|
||||
inputValue={profile.workFrom ? new Date(profile.workFrom) : undefined}
|
||||
@ -402,7 +479,6 @@ class CreateUserForm extends React.Component {
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
settings: state.auth.settings,
|
||||
@ -413,7 +489,6 @@ const mapStateToProps = (state) => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
createProfile,
|
||||
loadAvatar
|
||||
createProfile
|
||||
}
|
||||
)(withRouter(withTranslation()(CreateUserForm)));
|
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { withRouter } from 'react-router'
|
||||
import { connect } from 'react-redux'
|
||||
import { Avatar, Button, Textarea, Text, toastr, ModalDialog, TextInput, AvatarEditor } from 'asc-web-components'
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Avatar, Button, Textarea, Text, toastr, ModalDialog, TextInput, AvatarEditor, Link } from 'asc-web-components'
|
||||
import { withTranslation, Trans } from 'react-i18next';
|
||||
import { toEmployeeWrapper, getUserRole, getUserContactsPattern, getUserContacts, mapGroupsToGroupSelectorOptions, mapGroupSelectorOptionsToGroups, filterGroupSelectorOptions } from "../../../../../store/people/selectors";
|
||||
import { updateProfile, loadAvatar, createThumbnailsAvatar, deleteAvatar } from '../../../../../store/profile/actions';
|
||||
import { updateProfile } from '../../../../../store/profile/actions';
|
||||
import { sendInstructionsToChangePassword, sendInstructionsToChangeEmail } from "../../../../../store/services/api";
|
||||
import { MainContainer, AvatarContainer, MainFieldsContainer } from './FormFields/Form'
|
||||
import TextField from './FormFields/TextField'
|
||||
@ -15,6 +15,20 @@ import DepartmentField from './FormFields/DepartmentField'
|
||||
import ContactsField from './FormFields/ContactsField'
|
||||
import InfoFieldContainer from './FormFields/InfoFieldContainer'
|
||||
import { departments, department, position, employedSinceDate, typeGuest, typeUser } from '../../../../../helpers/customNames';
|
||||
import { createThumbnailsAvatar, loadAvatar, deleteAvatar } from "../../../../../store/services/api";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Table = styled.table`
|
||||
width: 100%;
|
||||
margin-bottom: 23px;
|
||||
`;
|
||||
|
||||
const Th = styled.th`
|
||||
padding: 11px 0 10px 0px;
|
||||
border-top: 1px solid #ECEEF1;
|
||||
`;
|
||||
|
||||
const Td = styled.td``;
|
||||
|
||||
class UpdateUserForm extends React.Component {
|
||||
|
||||
@ -153,8 +167,8 @@ class UpdateUserForm extends React.Component {
|
||||
this.props.history.push(`${this.props.settings.homepage}/view/${profile.userName}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message)
|
||||
this.setState({isLoading: false})
|
||||
toastr.error(error);
|
||||
this.setState({isLoading: false});
|
||||
});
|
||||
}
|
||||
|
||||
@ -201,9 +215,9 @@ class UpdateUserForm extends React.Component {
|
||||
onSendEmailChangeInstructions() {
|
||||
sendInstructionsToChangeEmail(this.state.profile.id, this.state.dialog.newEmail)
|
||||
.then((res) => {
|
||||
res.data.error ? toastr.error(res.data.error.message) : toastr.success(res.data.response)
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => toastr.error(error.message))
|
||||
.catch((error) => toastr.error(error))
|
||||
.finally(this.onDialogClose);
|
||||
}
|
||||
|
||||
@ -232,9 +246,9 @@ class UpdateUserForm extends React.Component {
|
||||
onSendPasswordChangeInstructions() {
|
||||
sendInstructionsToChangePassword(this.state.profile.email)
|
||||
.then((res) => {
|
||||
res.data.error ? toastr.error(res.data.error.message) : toastr.success(res.data.response)
|
||||
toastr.success(res);
|
||||
})
|
||||
.catch((error) => toastr.error(error.message))
|
||||
.catch((error) => toastr.error(error))
|
||||
.finally(this.onDialogClose);
|
||||
}
|
||||
|
||||
@ -324,61 +338,51 @@ class UpdateUserForm extends React.Component {
|
||||
let _this = this;
|
||||
data.append("file", file);
|
||||
data.append("Autosave", false);
|
||||
this.props.loadAvatar(this.state.profile.id, data)
|
||||
.then((result) => {
|
||||
loadAvatar(this.state.profile.id, data)
|
||||
.then((response) => {
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var stateCopy = Object.assign({}, _this.state);
|
||||
stateCopy.avatar = {
|
||||
tmpFile: result.data.response.data,
|
||||
image: result.data.response.data,
|
||||
tmpFile: response.data,
|
||||
image: response.data,
|
||||
defaultWidth: img.width,
|
||||
defaultHeight: img.height
|
||||
}
|
||||
_this.setState(stateCopy);
|
||||
};
|
||||
img.src = result.data.response.data;
|
||||
img.src = response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
onSaveAvatar(isUpdate, result) {
|
||||
if(isUpdate){
|
||||
this.props.createThumbnailsAvatar(this.state.profile.id, {
|
||||
createThumbnailsAvatar(this.state.profile.id, {
|
||||
x: Math.round(result.x*this.state.avatar.defaultWidth - result.width/2),
|
||||
y: Math.round(result.y*this.state.avatar.defaultHeight - result.height/2),
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
tmpFile: this.state.avatar.tmpFile
|
||||
})
|
||||
.then((result) => {
|
||||
if(result.status === 200){
|
||||
.then((response) => {
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.avatar.tmpFile = '';
|
||||
stateCopy.profile.avatarMax = result.data.response.max + '?_='+Math.floor(Math.random() * Math.floor(10000));
|
||||
stateCopy.profile.avatarMax = response.max + '?_='+Math.floor(Math.random() * Math.floor(10000));
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
.catch((error) => toastr.error(error));
|
||||
}else{
|
||||
this.props.deleteAvatar(this.state.profile.id)
|
||||
.then((result) => {
|
||||
if(result.status === 200){
|
||||
deleteAvatar(this.state.profile.id)
|
||||
.then((response) => {
|
||||
let stateCopy = Object.assign({}, this.state);
|
||||
stateCopy.visibleAvatarEditor = false;
|
||||
stateCopy.profile.avatarMax = result.data.response.big;
|
||||
stateCopy.profile.avatarMax = response.big;
|
||||
toastr.success("Success");
|
||||
this.setState(stateCopy);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastr.error(error.message);
|
||||
});
|
||||
.catch((error) => toastr.error(error));
|
||||
}
|
||||
}
|
||||
onCloseAvatarEditor() {
|
||||
@ -422,10 +426,49 @@ class UpdateUserForm extends React.Component {
|
||||
|
||||
render() {
|
||||
const { isLoading, errors, profile, dialog, selector } = this.state;
|
||||
const { t } = this.props;
|
||||
const { t, i18n } = this.props;
|
||||
|
||||
const pattern = getUserContactsPattern();
|
||||
const contacts = getUserContacts(profile.contacts);
|
||||
const tooltipTypeContent =
|
||||
<>
|
||||
<Text.Body style={{paddingBottom: 17}} fontSize={13}>{t("ProfileTypePopupHelper")}</Text.Body>
|
||||
<Table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Th>{t("ProductsAndInstruments_Products")}</Th><Th>{t("Employee")}</Th><Th>{t("GuestCaption")}</Th>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("Mail")}</Td><Td>review</Td><Td>-</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("DocumentsProduct")}</Td><Td>full access</Td><Td>view</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("ProjectsProduct")}</Td><Td>review</Td><Td>-</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("CommunityProduct")}</Td><Td>full access</Td><Td>view</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("People")}</Td><Td>review</Td><Td>-</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("Message")}</Td><Td>review</Td><Td>review</Td>
|
||||
</tr>
|
||||
<tr>
|
||||
<Td>{t("Calendar")}</Td><Td>review</Td><Td>review</Td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Link
|
||||
color="#316DAA"
|
||||
isHovered={true}
|
||||
href="https://helpcenter.onlyoffice.com/ru/gettingstarted/people.aspx#ManagingAccessRights_block"
|
||||
style={{marginTop: 23}}>
|
||||
{t("TermsOfUsePopupHelperLink")}
|
||||
</Link>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -446,6 +489,7 @@ class UpdateUserForm extends React.Component {
|
||||
onClose={this.onCloseAvatarEditor}
|
||||
onSave={this.onSaveAvatar}
|
||||
onLoadFile={this.onLoadFileAvatar}
|
||||
headerLabel={t("editAvatar")}
|
||||
chooseFileLabel ={t("chooseFileLabel")}
|
||||
unknownTypeError={t("unknownTypeError")}
|
||||
maxSizeFileError={t("maxSizeFileError")}
|
||||
@ -461,6 +505,12 @@ class UpdateUserForm extends React.Component {
|
||||
buttonIsDisabled={isLoading}
|
||||
buttonOnClick={this.onEmailChange}
|
||||
buttonTabIndex={1}
|
||||
|
||||
tooltipContent={
|
||||
<Trans i18nKey="EmailPopupHelper" i18n={i18n}>
|
||||
The main e-mail is needed to restore access to the portal in case of loss of the password and send notifications. <p style={{height: "0", visibility: "hidden"}}>You can create a new mail on the domain as the primary. In this case, you must set a one-time password so that the user can log in to the portal for the first time.</p> The main e-mail can be used as a login when logging in to the portal.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<TextChangeField
|
||||
labelText={`${t("Password")}:`}
|
||||
@ -502,6 +552,7 @@ class UpdateUserForm extends React.Component {
|
||||
inputTabIndex={5}
|
||||
/>
|
||||
<DateField
|
||||
calendarHeaderContent={t("CalendarSelectDate")}
|
||||
labelText={`${t("Birthdate")}:`}
|
||||
inputName="birthday"
|
||||
inputValue={profile.birthday ? new Date(profile.birthday) : undefined}
|
||||
@ -530,8 +581,11 @@ class UpdateUserForm extends React.Component {
|
||||
]}
|
||||
radioIsDisabled={isLoading}
|
||||
radioOnChange={this.onUserTypeChange}
|
||||
|
||||
tooltipContent={tooltipTypeContent}
|
||||
/>
|
||||
<DateField
|
||||
calendarHeaderContent={t("CalendarSelectDate")}
|
||||
labelText={`${t("CustomEmployedSinceDate", { employedSinceDate })}:`}
|
||||
inputName="workFrom"
|
||||
inputValue={profile.workFrom ? new Date(profile.workFrom) : undefined}
|
||||
@ -625,9 +679,6 @@ const mapStateToProps = (state) => {
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
updateProfile,
|
||||
loadAvatar,
|
||||
deleteAvatar,
|
||||
createThumbnailsAvatar
|
||||
updateProfile
|
||||
}
|
||||
)(withRouter(withTranslation()(UpdateUserForm)));
|
@ -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) => {
|
||||
@ -29,13 +33,13 @@ const SectionHeaderContent = (props) => {
|
||||
: "";
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
history.push(settings.homepage)
|
||||
}, [history, settings]);
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={onClick}/>
|
||||
<Header>{headerText}</Header>
|
||||
<Header truncate={true}>{headerText}</Header>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
@ -28,6 +28,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -42,7 +45,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,14 +2,21 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
import { PageLayout, Loader } from "asc-web-components";
|
||||
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
||||
import { SectionHeaderContent, CreateUserForm, UpdateUserForm } from './Section';
|
||||
import { fetchProfile } from '../../../store/profile/actions';
|
||||
import {
|
||||
ArticleHeaderContent,
|
||||
ArticleMainButtonContent,
|
||||
ArticleBodyContent
|
||||
} from "../../Article";
|
||||
import {
|
||||
SectionHeaderContent,
|
||||
CreateUserForm,
|
||||
UpdateUserForm
|
||||
} from "./Section";
|
||||
import { fetchProfile } from "../../../store/profile/actions";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
class ProfileAction extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { match, fetchProfile } = this.props;
|
||||
const { userId } = match.params;
|
||||
@ -30,34 +37,42 @@ class ProfileAction extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log("ProfileAction render")
|
||||
console.log("ProfileAction render");
|
||||
|
||||
let loaded = false;
|
||||
const { profile, match } = this.props;
|
||||
const { profile, isVisitor, match, language } = this.props;
|
||||
const { userId, type } = match.params;
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
if (type) {
|
||||
loaded = true;
|
||||
} else if (profile) {
|
||||
loaded = profile.userName === userId || profile.id === userId;
|
||||
}
|
||||
|
||||
const articleProps = isVisitor
|
||||
? {}
|
||||
: {
|
||||
articleHeaderContent: <ArticleHeaderContent />,
|
||||
articleMainButtonContent: <ArticleMainButtonContent />,
|
||||
articleBodyContent: <ArticleBodyContent />
|
||||
};
|
||||
|
||||
const sectionProps = loaded
|
||||
? {
|
||||
sectionHeaderContent: <SectionHeaderContent />,
|
||||
sectionBodyContent: type ? <CreateUserForm /> : <UpdateUserForm />
|
||||
}
|
||||
: {
|
||||
sectionBodyContent: (
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
{loaded
|
||||
? <PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionHeaderContent={<SectionHeaderContent />}
|
||||
sectionBodyContent={type ? <CreateUserForm /> : <UpdateUserForm />}
|
||||
/>
|
||||
: <PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionBodyContent={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
/>}
|
||||
<PageLayout {...articleProps} {...sectionProps} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
}
|
||||
@ -71,10 +86,15 @@ ProfileAction.propTypes = {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
profile: state.profile.targetUser
|
||||
profile: state.profile.targetUser,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
isVisitor: state.auth.user.isVisitor,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
fetchProfile
|
||||
})(ProfileAction);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchProfile
|
||||
}
|
||||
)(ProfileAction);
|
||||
|
@ -18,6 +18,17 @@
|
||||
"SocialProfiles": "Social Profiles",
|
||||
"Search": "Search",
|
||||
"SelectAll": "Select all",
|
||||
"EmailPopupHelper": "The main e-mail is needed to restore access to the portal in case of loss of the password and send notifications. <1> You can create a new mail on the domain as the primary. In this case, you must set a one-time password so that the user can log in to the portal for the first time.</1> The main e-mail can be used as a login when logging in to the portal.",
|
||||
"ProfileTypePopupHelper": "Guests have limited access to some portal features and modules",
|
||||
"ProductsAndInstruments_Products": "Modules",
|
||||
"GuestCaption": "Guest",
|
||||
"TermsOfUsePopupHelperLink": "Read more about terms of use",
|
||||
"Mail": "Mail",
|
||||
"DocumentsProduct": "Documents",
|
||||
"ProjectsProduct": "Projects",
|
||||
"CommunityProduct": "Community",
|
||||
"People": "People",
|
||||
"Message": "Talk",
|
||||
|
||||
"ActivationLink": "Activation link",
|
||||
"AddPhoto": "Add photo",
|
||||
@ -37,8 +48,12 @@
|
||||
"CustomNewGuest": "New {{typeGuest, lowercase}}",
|
||||
"CustomAddDepartments": "Add {{departments, lowercase}}",
|
||||
|
||||
"chooseFileLabel": "Drop files here, or click to select files",
|
||||
"chooseFileLabel": "Drop file here, or click to select file",
|
||||
"unknownTypeError": "Unknown image file type",
|
||||
"maxSizeFileError": "Maximum file size exceeded",
|
||||
"unknownError": "Error"
|
||||
"unknownError": "Error",
|
||||
"editAvatar": "Edit photo",
|
||||
"CalendarSelectDate": "Select Date:",
|
||||
"Employee": "Employee",
|
||||
"Calendar": "Calendar"
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
{
|
||||
"EditPhoto": "Изменить фотографию",
|
||||
"FirstName": "Имя",
|
||||
"LastName": "Фамилия",
|
||||
"Email": "Email",
|
||||
"Password": "Пароль",
|
||||
"Birthdate": "Дата рождения",
|
||||
"Sex": "Пол",
|
||||
"Location": "Местоположение",
|
||||
"Comments": "Комментарии",
|
||||
"SaveButton": "Сохранить",
|
||||
"CancelButton": "Отмена",
|
||||
"CopyEmailAndPassword": "Копировать email и пароль",
|
||||
"UserType": "Тип",
|
||||
"AddButton": "Добавить",
|
||||
"ContactInformation": "Контактные данные",
|
||||
"AddContact": "Добавить новый контакт",
|
||||
"SocialProfiles": "Социальные профили",
|
||||
"Search": "Поиск",
|
||||
"SelectAll": "Выбрать все",
|
||||
"EmailPopupHelper": "Основной email нужен для восстановления доступа к порталу в случае потери пароля, а также для отправки оповещений. <1>Вы можете создать новый email на домене в качестве основного. В этом случае потребуется задать одноразовый пароль, чтобы пользователь смог войти на портал в первый раз.</1> Основной email можно использовать как логин при входе на портал.",
|
||||
"ProfileTypePopupHelper": "Гости имеют ограниченный доступ к некоторым функциям и модулям портала",
|
||||
"ProductsAndInstruments_Products": "Модули",
|
||||
"GuestCaption": "Гость",
|
||||
"TermsOfUsePopupHelperLink": "Подробнее об условиях использования",
|
||||
"Mail": "Почта",
|
||||
"DocumentsProduct": "Документы",
|
||||
"ProjectsProduct": "Проекты",
|
||||
"CommunityProduct": "Сообщество",
|
||||
"People": "Люди",
|
||||
"Message": "Чат",
|
||||
|
||||
"ActivationLink": "Activation link",
|
||||
"AddPhoto": "Add photo",
|
||||
"TemporaryPassword": "Temporary password",
|
||||
"SexMale": "Мужской",
|
||||
"SexFemale": "Женский",
|
||||
"RequiredField": "Обязательное поле",
|
||||
"ChangeButton": "Изменить",
|
||||
"Phone": "Телефон",
|
||||
|
||||
"CustomEmployedSinceDate": "{{employedSinceDate}}",
|
||||
"CustomPosition": "{{position}}",
|
||||
"CustomDepartment": "{{department}}",
|
||||
"CustomTypeGuest": "{{typeGuest}}",
|
||||
"CustomTypeUser": "{{typeUser}}",
|
||||
"CustomNewEmployee": "Новый {{typeUser, lowercase}}",
|
||||
"CustomNewGuest": "Новый {{typeGuest, lowercase}}",
|
||||
"CustomAddDepartments": "Добавить {{departments, lowercase}}",
|
||||
|
||||
"chooseFileLabel": "Перетащите файл сюда или нажмите, чтобы выбрать файл",
|
||||
"unknownTypeError": "Неизвестный тип файла изображения",
|
||||
"maxSizeFileError": "Превышен максимальный размер файла",
|
||||
"unknownError": "Ошибка",
|
||||
"editAvatar": "Изменить фотографию",
|
||||
"CalendarSelectDate": "Выберите дату:",
|
||||
"Employee": "Гость",
|
||||
"Calendar": "Календарь"
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import Cookies from "universal-cookie";
|
||||
//import Cookies from "universal-cookie";
|
||||
import setAuthorizationToken from "./store/services/setAuthorizationToken";
|
||||
import { AUTH_KEY } from "./helpers/constants";
|
||||
import store from "./store/store";
|
||||
@ -11,7 +11,8 @@ import App from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { setIsLoaded, getUserInfo } from "./store/auth/actions";
|
||||
|
||||
var token = new Cookies().get(AUTH_KEY);
|
||||
//var token = new Cookies().get(AUTH_KEY);
|
||||
const token = localStorage.getItem(AUTH_KEY);
|
||||
|
||||
if (token) {
|
||||
setAuthorizationToken(token);
|
||||
|
@ -51,7 +51,8 @@
|
||||
"DeleteSelfProfile",
|
||||
"EditButton",
|
||||
"ChangeEmailSuccess",
|
||||
"Actions"
|
||||
"Actions",
|
||||
"NotFoundLanguage"
|
||||
]
|
||||
},
|
||||
"ProfileAction": {
|
||||
@ -68,7 +69,21 @@
|
||||
"SaveButton",
|
||||
"CopyEmailAndPassword",
|
||||
"UserType",
|
||||
"CancelButton"
|
||||
"CancelButton",
|
||||
|
||||
"EmailPopupHelper",
|
||||
"ProfileTypePopupHelper",
|
||||
"ProductsAndInstruments_Products",
|
||||
"GuestCaption",
|
||||
"TermsOfUsePopupHelperLink",
|
||||
"Mail",
|
||||
"People"
|
||||
],
|
||||
"FeedResource": [
|
||||
"DocumentsProduct",
|
||||
"ProjectsProduct",
|
||||
"CommunityProduct",
|
||||
"Message"
|
||||
]
|
||||
},
|
||||
"Home": {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import * as api from "../services/api";
|
||||
import { setGroups, fetchPeopleAsync } from "../people/actions";
|
||||
import setAuthorizationToken from "../../store/services/setAuthorizationToken";
|
||||
import { fetchGroups, fetchPeople } from "../people/actions";
|
||||
import { setAuthorizationToken } from "../../store/services/client";
|
||||
import { getFilterByLocation } from "../../helpers/converters";
|
||||
import config from "../../../package.json";
|
||||
|
||||
export const LOGIN_POST = "LOGIN_POST";
|
||||
export const SET_CURRENT_USER = "SET_CURRENT_USER";
|
||||
@ -48,37 +49,28 @@ export async function getUserInfo(dispatch) {
|
||||
const { user, modules, settings } = await api.getInitInfo();
|
||||
let newSettings = settings;
|
||||
if (user.isAdmin) {
|
||||
const inviteLinkResp = await api.getInvitationLinks();
|
||||
newSettings = Object.assign(newSettings, inviteLinkResp);
|
||||
const inviteLinks = await api.getInvitationLinks();
|
||||
newSettings = Object.assign(newSettings, inviteLinks);
|
||||
}
|
||||
|
||||
dispatch(setCurrentUser(user));
|
||||
dispatch(setModules(modules));
|
||||
dispatch(setSettings(newSettings));
|
||||
|
||||
const groupResp = await api.getGroupList();
|
||||
await fetchGroups(dispatch);
|
||||
|
||||
dispatch(setGroups(groupResp.data.response));
|
||||
var re = new RegExp(`${config.homepage}((/?)$|/filter)`, "gm");
|
||||
const match = window.location.pathname.match(re);
|
||||
|
||||
const newFilter = getFilterByLocation(window.location);
|
||||
|
||||
await fetchPeopleAsync(dispatch, newFilter);
|
||||
if (match && match.length > 0)
|
||||
{
|
||||
const newFilter = getFilterByLocation(window.location);
|
||||
await fetchPeople(newFilter, dispatch);
|
||||
}
|
||||
|
||||
return dispatch(setIsLoaded(true));
|
||||
}
|
||||
|
||||
export function login(data) {
|
||||
return dispatch => {
|
||||
return api
|
||||
.login(data)
|
||||
.then(res => {
|
||||
const token = res.data.response.token;
|
||||
setAuthorizationToken(token);
|
||||
})
|
||||
.then(() => getUserInfo(dispatch));
|
||||
};
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return dispatch => {
|
||||
setAuthorizationToken();
|
||||
|
@ -4,4 +4,8 @@ export function isAdmin(user) {
|
||||
|
||||
export function isMe(user, userName) {
|
||||
return userName === "@self" || userName === user.userName;
|
||||
};
|
||||
};
|
||||
|
||||
export function getCurrentModule(modules, currentModuleId) {
|
||||
return modules.find(module => module.id === currentModuleId);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import * as api from "../../store/services/api";
|
||||
import { setGroups, fetchPeopleByFilter } from "../people/actions";
|
||||
import { setGroups, fetchPeople } from "../people/actions";
|
||||
import history from "../../history";
|
||||
|
||||
export const SET_GROUP = "SET_GROUP";
|
||||
export const CLEAN_GROUP = "CLEAN_GROUP";
|
||||
@ -17,42 +18,24 @@ export function resetGroup() {
|
||||
};
|
||||
}
|
||||
|
||||
export function checkResponseError(res) {
|
||||
if (res && res.data && res.data.error) {
|
||||
console.error(res.data.error);
|
||||
throw new Error(res.data.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchGroup(groupId) {
|
||||
return dispatch => {
|
||||
api.getGroup(groupId).then(res => {
|
||||
checkResponseError(res);
|
||||
dispatch(setGroup(res.data.response || null));
|
||||
});
|
||||
api.getGroup(groupId)
|
||||
.then(group => dispatch(setGroup(group || null)));
|
||||
};
|
||||
}
|
||||
|
||||
export function createGroup(groupName, groupManager, members) {
|
||||
return (dispatch, getState) => {
|
||||
const { people } = getState();
|
||||
const { groups, filter } = people;
|
||||
|
||||
let newGroup;
|
||||
const { groups } = people;
|
||||
|
||||
return api
|
||||
.createGroup(groupName, groupManager, members)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
newGroup = res.data.response;
|
||||
|
||||
//dispatch(setGroup(newGroup));
|
||||
return dispatch(setGroups([...groups, newGroup]));
|
||||
})
|
||||
.then(() => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
})
|
||||
.then(() => {
|
||||
.then(newGroup => {
|
||||
history.goBack();
|
||||
dispatch(resetGroup());
|
||||
dispatch(setGroups([...groups, newGroup]));
|
||||
return Promise.resolve(newGroup);
|
||||
});
|
||||
};
|
||||
@ -61,28 +44,17 @@ export function createGroup(groupName, groupManager, members) {
|
||||
export function updateGroup(id, groupName, groupManager, members) {
|
||||
return (dispatch, getState) => {
|
||||
const { people } = getState();
|
||||
const { groups, filter } = people;
|
||||
|
||||
let newGroup;
|
||||
const { groups } = people;
|
||||
|
||||
return api
|
||||
.updateGroup(id, groupName, groupManager, members)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
newGroup = res.data.response;
|
||||
|
||||
//dispatch(setGroup(newGroup));
|
||||
|
||||
.then(newGroup => {
|
||||
history.goBack();
|
||||
dispatch(resetGroup());
|
||||
const newGroups = groups.map(g =>
|
||||
g.id === newGroup.id ? newGroup : g
|
||||
);
|
||||
|
||||
return dispatch(setGroups(newGroups));
|
||||
})
|
||||
.then(() => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(setGroups(newGroups));
|
||||
return Promise.resolve(newGroup);
|
||||
});
|
||||
};
|
||||
@ -96,12 +68,11 @@ export function deleteGroup(id) {
|
||||
return api
|
||||
.deleteGroup(id)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return dispatch(setGroups(groups.filter(g => g.id !== id)));
|
||||
})
|
||||
.then(() => {
|
||||
const newFilter = filter.clone(true);
|
||||
return fetchPeopleByFilter(dispatch, newFilter);
|
||||
return fetchPeople(newFilter, dispatch);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
SORT_BY,
|
||||
SORT_ORDER,
|
||||
PAGE,
|
||||
PAGE_COUNT
|
||||
PAGE_COUNT,
|
||||
EmployeeStatus
|
||||
} from "../../helpers/constants";
|
||||
|
||||
export const SET_GROUPS = "SET_GROUPS";
|
||||
@ -68,7 +69,7 @@ export function selectGroup(groupId) {
|
||||
let newFilter = filter.clone();
|
||||
newFilter.group = groupId;
|
||||
|
||||
return fetchPeopleByFilter(dispatch, newFilter);
|
||||
return fetchPeople(newFilter, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ export function deselectUser(user) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setFilter(filter) {
|
||||
export function setFilterUrl(filter) {
|
||||
const defaultFilter = Filter.getDefault();
|
||||
const params = [];
|
||||
|
||||
@ -114,13 +115,17 @@ export function setFilter(filter) {
|
||||
params.push(`${PAGE_COUNT}=${filter.pageCount}`);
|
||||
}
|
||||
|
||||
params.push(`${PAGE}=${filter.page+1}`);
|
||||
params.push(`${PAGE}=${filter.page + 1}`);
|
||||
params.push(`${SORT_BY}=${filter.sortBy}`);
|
||||
params.push(`${SORT_ORDER}=${filter.sortOrder}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
history.push(`${config.homepage}/filter?${params.join("&")}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function setFilter(filter) {
|
||||
setFilterUrl(filter);
|
||||
return {
|
||||
type: SET_FILTER,
|
||||
filter
|
||||
@ -136,55 +141,57 @@ export function setSelectorUsers(users) {
|
||||
|
||||
export function fetchSelectorUsers() {
|
||||
return dispatch => {
|
||||
api
|
||||
.getSelectorUserList()
|
||||
.then(res => dispatch(setSelectorUsers(res.data.response)));
|
||||
api.getSelectorUserList().then(data => {
|
||||
const users = data.items;
|
||||
return dispatch(setSelectorUsers(users));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPeople(filter) {
|
||||
return dispatch => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
};
|
||||
export function fetchGroups(dispatchFunc = null) {
|
||||
return api.getGroupList().then(groups => {
|
||||
return dispatchFunc
|
||||
? dispatchFunc(setGroups(groups))
|
||||
: Promise.resolve(dispatch => dispatch(setGroups(groups)));
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchPeopleByFilter(dispatch, filter) {
|
||||
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
||||
export function fetchPeople(filter, dispatchFunc = null) {
|
||||
return dispatchFunc
|
||||
? fetchPeopleByFilter(dispatchFunc, filter)
|
||||
: (dispatch, getState) => {
|
||||
if (filter) {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
} else {
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return api.getUserList(filterData).then(res => {
|
||||
filterData.total = res.data.total;
|
||||
function fetchPeopleByFilter(dispatch, filter) {
|
||||
let filterData = filter && filter.clone();
|
||||
|
||||
if (!filterData) {
|
||||
filterData = Filter.getDefault();
|
||||
filterData.employeeStatus = EmployeeStatus.Active;
|
||||
}
|
||||
|
||||
return api.getUserList(filterData).then(data => {
|
||||
filterData.total = data.total;
|
||||
dispatch(setFilter(filterData));
|
||||
dispatch({
|
||||
type: SELECT_GROUP,
|
||||
groupId: filterData.group
|
||||
});
|
||||
return dispatch(setUsers(res.data.response));
|
||||
return dispatch(setUsers(data.items));
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchPeopleAsync(dispatch, filter = null) {
|
||||
let filterData = (filter && filter.clone()) || Filter.getDefault();
|
||||
|
||||
const usersResp = await api.getUserList(filterData);
|
||||
|
||||
filterData.total = usersResp.data.total;
|
||||
|
||||
dispatch(setFilter(filterData));
|
||||
dispatch({
|
||||
type: SELECT_GROUP,
|
||||
groupId: filterData.group
|
||||
});
|
||||
dispatch(setUsers(usersResp.data.response));
|
||||
}
|
||||
|
||||
export function updateUserStatus(status, userIds) {
|
||||
return dispatch => {
|
||||
return api.updateUserStatus(status, userIds).then(res => {
|
||||
if (res && res.data && res.data.error && res.data.error.message)
|
||||
throw res.data.error.message;
|
||||
|
||||
const users = res.data.response;
|
||||
|
||||
return api.updateUserStatus(status, userIds).then(users => {
|
||||
users.forEach(user => {
|
||||
dispatch(setUser(user));
|
||||
});
|
||||
@ -194,12 +201,7 @@ export function updateUserStatus(status, userIds) {
|
||||
|
||||
export function updateUserType(type, userIds) {
|
||||
return dispatch => {
|
||||
return api.updateUserType(type, userIds).then(res => {
|
||||
if (res && res.data && res.data.error && res.data.error.message)
|
||||
throw res.data.error.message;
|
||||
|
||||
const users = res.data.response;
|
||||
|
||||
return api.updateUserType(type, userIds).then(users => {
|
||||
users.forEach(user => {
|
||||
dispatch(setUser(user));
|
||||
});
|
||||
@ -214,6 +216,6 @@ export function resetFilter() {
|
||||
|
||||
const newFilter = filter.clone(true);
|
||||
|
||||
return fetchPeopleByFilter(dispatch, newFilter);
|
||||
return fetchPeople(newFilter, dispatch);
|
||||
};
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { toUrlParams } from "../services/converter";
|
||||
import { EmployeeStatus } from "../../helpers/constants";
|
||||
|
||||
const DEFAULT_PAGE = 0;
|
||||
const DEFAULT_PAGE_COUNT = 25;
|
||||
const DEFAULT_TOTAL = 0;
|
||||
const DEFAULT_SORT_BY = "firstname";
|
||||
const DEFAULT_SORT_ORDER = "ascending";
|
||||
const DEFAULT_EMPLOYEE_STATUS = EmployeeStatus.Active;
|
||||
const DEFAULT_EMPLOYEE_STATUS = null;
|
||||
const DEFAULT_ACTIVATION_STATUS = null;
|
||||
const DEFAULT_ROLE = null;
|
||||
const DEFAULT_SEARCH = null;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import * as api from "../../store/services/api";
|
||||
import { isMe } from '../auth/selectors';
|
||||
import { getUserByUserName } from '../people/selectors';
|
||||
import { fetchPeopleByFilter } from "../people/actions";
|
||||
import { fetchPeople } from "../people/actions";
|
||||
import { setCurrentUser } from "../auth/actions";
|
||||
|
||||
export const SET_PROFILE = 'SET_PROFILE';
|
||||
export const CLEAN_PROFILE = 'CLEAN_PROFILE';
|
||||
@ -19,20 +20,13 @@ export function resetProfile() {
|
||||
};
|
||||
};
|
||||
|
||||
export function checkResponseError(res) {
|
||||
if (res && res.data && res.data.error) {
|
||||
console.error(res.data.error);
|
||||
throw new Error(res.data.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function employeeWrapperToMemberModel(profile) {
|
||||
const comment = profile.notes;
|
||||
const department = profile.groups ? profile.groups.map(group => group.id) : [];
|
||||
const worksFrom = profile.workFrom;
|
||||
|
||||
return { ...profile, comment, department, worksFrom };
|
||||
}
|
||||
};
|
||||
|
||||
export function fetchProfile(userName) {
|
||||
return (dispatch, getState) => {
|
||||
@ -43,16 +37,15 @@ export function fetchProfile(userName) {
|
||||
} else {
|
||||
const user = getUserByUserName(people.users, userName);
|
||||
if (!user) {
|
||||
api.getUser(userName).then(res => {
|
||||
checkResponseError(res);
|
||||
dispatch(setProfile(res.data.response));
|
||||
api.getUser(userName).then(user => {
|
||||
dispatch(setProfile(user));
|
||||
});
|
||||
} else {
|
||||
dispatch(setProfile(user));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function createProfile(profile) {
|
||||
return (dispatch, getState) => {
|
||||
@ -61,12 +54,11 @@ export function createProfile(profile) {
|
||||
const member = employeeWrapperToMemberModel(profile);
|
||||
let result;
|
||||
|
||||
return api.createUser(member).then(res => {
|
||||
checkResponseError(res);
|
||||
result = res.data.response;
|
||||
return dispatch(setProfile(result));
|
||||
return api.createUser(member).then(user => {
|
||||
result = user;
|
||||
return dispatch(setProfile(user));
|
||||
}).then(() => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
return fetchPeople(filter, dispatch);
|
||||
}).then(() => {
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
@ -80,66 +72,28 @@ export function updateProfile(profile) {
|
||||
const member = employeeWrapperToMemberModel(profile);
|
||||
let result;
|
||||
|
||||
return api.updateUser(member).then(res => {
|
||||
checkResponseError(res);
|
||||
result = res.data.response;
|
||||
return Promise.resolve(dispatch(setProfile(result)));
|
||||
return api.updateUser(member).then(user => {
|
||||
result = user;
|
||||
return Promise.resolve(dispatch(setProfile(user)));
|
||||
}).then(() => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
return fetchPeople(filter, dispatch);
|
||||
}).then(() => {
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
};
|
||||
};
|
||||
export function loadAvatar(profileId, data) {
|
||||
return (dispatch, getState) => {
|
||||
return api.loadAvatar(
|
||||
profileId,
|
||||
data
|
||||
).then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
};
|
||||
};
|
||||
export function createThumbnailsAvatar(profileId, data) {
|
||||
return (dispatch, getState) => {
|
||||
return api.createThumbnailsAvatar(
|
||||
profileId,
|
||||
data
|
||||
).then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
};
|
||||
};
|
||||
export function deleteAvatar(profileId) {
|
||||
return (dispatch, getState) => {
|
||||
return api.deleteAvatar(profileId)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
|
||||
export function updateProfileCulture(id, culture) {
|
||||
return (dispatch) => {
|
||||
return api.updateUserCulture(id, culture).then(user => {
|
||||
dispatch(setCurrentUser(user));
|
||||
return dispatch(setProfile(user));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function getInvitationLink(isGuest = false) {
|
||||
return dispatch => {
|
||||
return api.getInvitationLink(isGuest)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
return api.getInvitationLink(isGuest);
|
||||
}
|
||||
}
|
||||
|
||||
export function getShortenedLink(link) {
|
||||
return dispatch => {
|
||||
return api.getShortenedLink(link)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,55 +1,56 @@
|
||||
import { request } from "./client";
|
||||
import axios from "axios";
|
||||
import * as fakeApi from "./fakeApi";
|
||||
import Filter from "../people/filter";
|
||||
|
||||
const PREFIX = "api";
|
||||
const VERSION = "2.0";
|
||||
const API_URL = `${window.location.origin}/${PREFIX}/${VERSION}`;
|
||||
|
||||
const IS_FAKE = false;
|
||||
const linkTtl = 6 * 3600 * 1000;
|
||||
|
||||
export function login(data) {
|
||||
return axios.post(`${API_URL}/authentication`, data);
|
||||
}
|
||||
|
||||
export function getModulesList() {
|
||||
return IS_FAKE
|
||||
? fakeApi.getModulesList()
|
||||
: axios
|
||||
.get(`${API_URL}/modules`)
|
||||
.then(res => {
|
||||
const modules = res.data.response;
|
||||
return axios.all(
|
||||
modules.map(m => axios.get(`${window.location.origin}/${m}`))
|
||||
);
|
||||
})
|
||||
.then(res => {
|
||||
const response = res.map(d => d.data.response);
|
||||
return Promise.resolve({ data: { response } });
|
||||
});
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/modules"
|
||||
}).then(modules => {
|
||||
return axios.all(
|
||||
modules.map(m =>
|
||||
request({
|
||||
method: "get",
|
||||
url: `${window.location.origin}/${m}`
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getSettings() {
|
||||
return IS_FAKE
|
||||
? fakeApi.getSettings()
|
||||
: axios.get(`${API_URL}/settings.json`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/settings.json"
|
||||
});
|
||||
}
|
||||
|
||||
export function getPortalCultures() {
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/settings/cultures.json"
|
||||
});
|
||||
}
|
||||
|
||||
export function getPortalPasswordSettings() {
|
||||
return IS_FAKE
|
||||
? fakeApi.getPortalPasswordSettings()
|
||||
: axios.get(`${API_URL}/settings/security/password`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/settings/security/password"
|
||||
});
|
||||
}
|
||||
|
||||
export function getUser(userId) {
|
||||
return IS_FAKE
|
||||
? fakeApi.getUser()
|
||||
: axios.get(`${API_URL}/people/${userId || "@self"}.json`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/people/@self.json"
|
||||
});
|
||||
}
|
||||
|
||||
export function getSelectorUserList() {
|
||||
return axios.get(`${API_URL}/people/filter.json?fields=id,displayName,groups`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/people/filter.json?fields=id,displayName,groups"
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserList(filter = Filter.getDefault()) {
|
||||
@ -57,191 +58,238 @@ export function getUserList(filter = Filter.getDefault()) {
|
||||
filter && filter instanceof Filter
|
||||
? `/filter.json?${filter.toUrlParams()}`
|
||||
: "";
|
||||
return IS_FAKE ? fakeApi.getUsers() : axios.get(`${API_URL}/people${params}`);
|
||||
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/people${params}`
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupList() {
|
||||
return IS_FAKE ? fakeApi.getGroups() : axios.get(`${API_URL}/group`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: "/group"
|
||||
});
|
||||
}
|
||||
|
||||
export function createUser(data) {
|
||||
return IS_FAKE ? fakeApi.createUser() : axios.post(`${API_URL}/people`, data);
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/people",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUser(data) {
|
||||
return IS_FAKE
|
||||
? fakeApi.updateUser()
|
||||
: axios.put(`${API_URL}/people/${data.id}`, data);
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/people/${data.id}`,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUserCulture(id, cultureName) {
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/people/${id}/culture`,
|
||||
data: { cultureName }
|
||||
});
|
||||
}
|
||||
export function loadAvatar(profileId, data) {
|
||||
return IS_FAKE
|
||||
? fakeApi.loadAvatar()
|
||||
: axios.post(`${API_URL}/people/${profileId}/photo`, data);
|
||||
return request({
|
||||
method: "post",
|
||||
url: `/people/${profileId}/photo`,
|
||||
data
|
||||
});
|
||||
}
|
||||
export function createThumbnailsAvatar(profileId, data) {
|
||||
return IS_FAKE
|
||||
? fakeApi.createThumbnailsAvatar()
|
||||
: axios.post(`${API_URL}/people/${profileId}/photo/thumbnails.json`, data);
|
||||
return request({
|
||||
method: "post",
|
||||
url: `/people/${profileId}/photo/thumbnails.json`,
|
||||
data
|
||||
});
|
||||
}
|
||||
export function deleteAvatar(profileId) {
|
||||
|
||||
return IS_FAKE
|
||||
? fakeApi.deleteAvatar()
|
||||
: axios.delete(`${API_URL}/people/${profileId}/photo`, profileId);
|
||||
return request({
|
||||
method: "delete",
|
||||
url: `/people/${profileId}/photo`
|
||||
});
|
||||
}
|
||||
|
||||
export function getInitInfo() {
|
||||
return axios.all([getUser(), getModulesList(), getSettings(), getPortalPasswordSettings()]).then(
|
||||
axios.spread(function (userResp, modulesResp, settingsResp, passwordSettingsResp) {
|
||||
let info = {
|
||||
user: userResp.data.response,
|
||||
modules: modulesResp.data.response,
|
||||
settings: settingsResp.data.response
|
||||
};
|
||||
return axios
|
||||
.all([
|
||||
getUser(),
|
||||
getModulesList(),
|
||||
getSettings(),
|
||||
getPortalPasswordSettings(),
|
||||
getPortalCultures()
|
||||
])
|
||||
.then(
|
||||
axios.spread((user, modules, settings, passwordSettings, cultures) => {
|
||||
const info = {
|
||||
user,
|
||||
modules,
|
||||
settings
|
||||
};
|
||||
|
||||
info.settings.passwordSettings = passwordSettingsResp.data.response;
|
||||
info.settings.passwordSettings = passwordSettings;
|
||||
info.settings.cultures = cultures || [];
|
||||
|
||||
return Promise.resolve(info);
|
||||
})
|
||||
);
|
||||
return Promise.resolve(info);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getInvitationLinks() {
|
||||
const isGuest = true;
|
||||
return axios.all([getInvitationLink(), getInvitationLink(isGuest)]).then(
|
||||
axios.spread(function (userInvitationLinkResp, guestInvitationLinkResp) {
|
||||
let links = {
|
||||
inviteLinks: {}
|
||||
axios.spread((userInvitationLinkResp, guestInvitationLinkResp) => {
|
||||
const links = {
|
||||
inviteLinks: {
|
||||
userLink: userInvitationLinkResp,
|
||||
guestLink: guestInvitationLinkResp
|
||||
}
|
||||
};
|
||||
|
||||
links.inviteLinks = {
|
||||
userLink: userInvitationLinkResp,
|
||||
guestLink: guestInvitationLinkResp
|
||||
}
|
||||
|
||||
return Promise.resolve(links);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function updateUserStatus(status, userIds) {
|
||||
return IS_FAKE
|
||||
? fakeApi.updateUserStatus(status, userIds)
|
||||
: axios.put(`${API_URL}/people/status/${status}`, { userIds });
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/people/status/${status}`,
|
||||
data: { userIds }
|
||||
});
|
||||
}
|
||||
|
||||
export function updateUserType(type, userIds) {
|
||||
return IS_FAKE
|
||||
? fakeApi.updateUserType(type, userIds)
|
||||
: axios.put(`${API_URL}/people/type/${type}`, { userIds });
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/people/type/${type}`,
|
||||
data: { userIds }
|
||||
});
|
||||
}
|
||||
|
||||
export function resendUserInvites(userIds) {
|
||||
return IS_FAKE
|
||||
? fakeApi.resendUserInvites(userIds)
|
||||
: axios.put(`${API_URL}/people/invite`, { userIds });
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/people/invite",
|
||||
data: { userIds }
|
||||
});
|
||||
}
|
||||
|
||||
export function sendInstructionsToDelete() {
|
||||
return IS_FAKE
|
||||
? fakeApi.sendInstructionsToDelete()
|
||||
: axios.put(`${API_URL}/people/self/delete.json`);
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/people/self/delete.json"
|
||||
});
|
||||
}
|
||||
|
||||
export function sendInstructionsToChangePassword(email) {
|
||||
return IS_FAKE
|
||||
? fakeApi.sendInstructionsToChangePassword()
|
||||
: axios.post(`${API_URL}/people/password.json`, { email });
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/people/password.json",
|
||||
data: { email }
|
||||
});
|
||||
}
|
||||
|
||||
export function sendInstructionsToChangeEmail(userId, email) {
|
||||
return IS_FAKE
|
||||
? fakeApi.sendInstructionsToChangeEmail()
|
||||
: axios.post(`${API_URL}/people/email.json`, { userId, email });
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/people/email.json",
|
||||
data: { userId, email }
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteUser(userId) {
|
||||
return IS_FAKE
|
||||
? fakeApi.deleteUser(userId)
|
||||
: axios.delete(`${API_URL}/people/${userId}.json`);
|
||||
return request({
|
||||
method: "delete",
|
||||
url: `/people/${userId}.json`
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteUsers(userIds) {
|
||||
return IS_FAKE
|
||||
? fakeApi.deleteUsers(userIds)
|
||||
: axios
|
||||
.put(`${API_URL}/people/delete.json`, { userIds })
|
||||
.then(CheckError);
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/people/delete.json",
|
||||
data: { userIds }
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroup(groupId) {
|
||||
return IS_FAKE
|
||||
? fakeApi.getGroup(groupId)
|
||||
: axios.get(`${API_URL}/group/${groupId}.json`);
|
||||
return request({
|
||||
method: "get",
|
||||
url: `/group/${groupId}.json`
|
||||
});
|
||||
}
|
||||
|
||||
const GUEST_INVITE_LINK = "guestInvitationLink";
|
||||
const USER_INVITE_LINK = "userInvitationLink";
|
||||
const INVITE_LINK_TTL = "localStorageLinkTtl";
|
||||
const LINKS_TTL = 6 * 3600 * 1000;
|
||||
|
||||
export function getInvitationLink(isGuest) {
|
||||
let localStorageLinkTtl = localStorage.getItem('localStorageLinkTtl');
|
||||
const curLinksTtl = localStorage.getItem(INVITE_LINK_TTL);
|
||||
const now = +new Date();
|
||||
|
||||
if (localStorageLinkTtl === null) {
|
||||
localStorage.setItem('localStorageLinkTtl', +new Date());
|
||||
}
|
||||
else if (+new Date() - localStorageLinkTtl > linkTtl) {
|
||||
localStorage.clear();
|
||||
localStorage.setItem('localStorageLinkTtl', +new Date());
|
||||
if (!curLinksTtl) {
|
||||
localStorage.setItem(INVITE_LINK_TTL, now);
|
||||
} else if (now - curLinksTtl > LINKS_TTL) {
|
||||
localStorage.removeItem(GUEST_INVITE_LINK);
|
||||
localStorage.removeItem(USER_INVITE_LINK);
|
||||
localStorage.setItem(INVITE_LINK_TTL, now);
|
||||
}
|
||||
|
||||
if (IS_FAKE) {
|
||||
return fakeApi.getInvitationLink(isGuest);
|
||||
}
|
||||
else
|
||||
if (isGuest) {
|
||||
const guestInvitationLink = localStorage.getItem('guestInvitationLink');
|
||||
return guestInvitationLink
|
||||
? guestInvitationLink
|
||||
: axios.get(`${API_URL}/portal/users/invite/2.json`)
|
||||
.then(res => {
|
||||
localStorage.setItem('guestInvitationLink', res.data.response);
|
||||
return Promise.resolve(res.data.response);
|
||||
})
|
||||
}
|
||||
else {
|
||||
const userInvitationLink = localStorage.getItem('userInvitationLink');
|
||||
return userInvitationLink
|
||||
? userInvitationLink
|
||||
: axios.get(`${API_URL}/portal/users/invite/1.json`)
|
||||
.then(res => {
|
||||
localStorage.setItem('userInvitationLink', res.data.response);
|
||||
return Promise.resolve(res.data.response);
|
||||
})
|
||||
}
|
||||
const link = localStorage.getItem(
|
||||
isGuest ? GUEST_INVITE_LINK : USER_INVITE_LINK
|
||||
);
|
||||
|
||||
return link
|
||||
? Promise.resolve(link)
|
||||
: request({
|
||||
method: "get",
|
||||
url: `/portal/users/invite/${isGuest ? 2 : 1}.json`
|
||||
}).then(link => {
|
||||
localStorage.setItem(
|
||||
isGuest ? GUEST_INVITE_LINK : USER_INVITE_LINK,
|
||||
link
|
||||
);
|
||||
return Promise.resolve(link);
|
||||
});
|
||||
}
|
||||
|
||||
export function getShortenedLink(link) {
|
||||
return IS_FAKE
|
||||
? fakeApi.getShortenedLink(link)
|
||||
: axios.put(`${API_URL}/portal/getshortenlink.json`, link);
|
||||
}
|
||||
|
||||
function CheckError(res) {
|
||||
if (res.data && res.data.error) {
|
||||
const error = res.data.error.message || "Unknown error has happened";
|
||||
console.trace(error);
|
||||
throw error;
|
||||
}
|
||||
return Promise.resolve(res);
|
||||
return request({
|
||||
method: "put",
|
||||
url: "/portal/getshortenlink.json",
|
||||
data: link
|
||||
});
|
||||
}
|
||||
|
||||
export function createGroup(groupName, groupManager, members) {
|
||||
const group = { groupName, groupManager, members };
|
||||
return axios.post(`${API_URL}/group.json`, group);
|
||||
const data = { groupName, groupManager, members };
|
||||
return request({
|
||||
method: "post",
|
||||
url: "/group.json",
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function updateGroup(id, groupName, groupManager, members) {
|
||||
const group = { groupId: id, groupName, groupManager, members };
|
||||
return axios.put(`${API_URL}/group/${id}.json`, group);
|
||||
const data = { groupId: id, groupName, groupManager, members };
|
||||
return request({
|
||||
method: "put",
|
||||
url: `/group/${id}.json`,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteGroup(id) {
|
||||
return axios.delete(`${API_URL}/group/${id}.json`);
|
||||
return request({
|
||||
method: "delete",
|
||||
url: `/group/${id}.json`
|
||||
});
|
||||
}
|
||||
|
99
products/ASC.People/Client/src/store/services/client.js
Normal file
99
products/ASC.People/Client/src/store/services/client.js
Normal file
@ -0,0 +1,99 @@
|
||||
import axios from "axios";
|
||||
import Cookies from "universal-cookie";
|
||||
import history from "../../history";
|
||||
import { AUTH_KEY } from "../../helpers/constants.js";
|
||||
|
||||
const PREFIX = "api";
|
||||
const VERSION = "2.0";
|
||||
const baseURL = `${window.location.origin}/${PREFIX}/${VERSION}`;
|
||||
|
||||
/**
|
||||
* @description axios instance for ajax requests
|
||||
*/
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: baseURL,
|
||||
responseType: "json",
|
||||
timeout: 30000 // default is `0` (no timeout)
|
||||
});
|
||||
|
||||
setAuthorizationToken(localStorage.getItem(AUTH_KEY));
|
||||
|
||||
client.interceptors.response.use(
|
||||
response => {
|
||||
return response;
|
||||
},
|
||||
error => {
|
||||
if (error.response.status === 401) {
|
||||
//place your reentry code
|
||||
history.push("/login/error=unauthorized");
|
||||
}
|
||||
|
||||
if (error.response.status === 502) {
|
||||
//toastr.error(error.response);
|
||||
history.push(`/error/${error.response.status}`);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
);
|
||||
|
||||
export function setAuthorizationToken(token) {
|
||||
const cookies = new Cookies();
|
||||
|
||||
if (token) {
|
||||
client.defaults.headers.common["Authorization"] = token;
|
||||
localStorage.setItem(AUTH_KEY, token);
|
||||
|
||||
const current = new Date();
|
||||
const nextYear = new Date();
|
||||
|
||||
nextYear.setFullYear(current.getFullYear() + 1);
|
||||
|
||||
cookies.set(AUTH_KEY, token, {
|
||||
path: "/",
|
||||
expires: nextYear
|
||||
});
|
||||
} else {
|
||||
localStorage.clear();
|
||||
delete client.defaults.headers.common["Authorization"];
|
||||
cookies.remove(AUTH_KEY, {
|
||||
path: "/"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const checkResponseError = res => {
|
||||
if (res && res.data && res.data.error) {
|
||||
console.error(res.data.error);
|
||||
throw new Error(res.data.error.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description wrapper for making ajax requests
|
||||
* @param {object} object with method,url,data etc.
|
||||
*/
|
||||
export const request = function(options) {
|
||||
const onSuccess = function(response) {
|
||||
checkResponseError(response);
|
||||
return response.data && response.data.hasOwnProperty("total")
|
||||
? { total: +response.data.total, items: response.data.response }
|
||||
: response.data.response;
|
||||
};
|
||||
const onError = function(error) {
|
||||
console.error("Request Failed:", error.config);
|
||||
if (error.response) {
|
||||
console.error("Status:", error.response.status);
|
||||
console.error("Data:", error.response.data);
|
||||
console.error("Headers:", error.response.headers);
|
||||
} else {
|
||||
console.error("Error Message:", error.message);
|
||||
}
|
||||
return Promise.reject(error.response || error.message);
|
||||
};
|
||||
|
||||
return client(options)
|
||||
.then(onSuccess)
|
||||
.catch(onError);
|
||||
};
|
@ -1,14 +1,33 @@
|
||||
import axios from 'axios';
|
||||
import Cookies from 'universal-cookie';
|
||||
//import Cookies from 'universal-cookie';
|
||||
import { AUTH_KEY } from '../../helpers/constants';
|
||||
import history from '../../history';
|
||||
import { toastr } from 'asc-web-components';
|
||||
|
||||
export default function setAuthorizationToken(token) {
|
||||
const cookies = new Cookies();
|
||||
axios.interceptors.response.use(response => {
|
||||
return response;
|
||||
}, error => {
|
||||
if (error.response.status === 401) {
|
||||
//place your reentry code
|
||||
history.push("/login/error=unauthorized");
|
||||
}
|
||||
|
||||
if (error.response.status === 502) {
|
||||
//toastr.error(error.response);
|
||||
history.push(`/error/${error.response.status}`);
|
||||
}
|
||||
|
||||
return error;
|
||||
});
|
||||
|
||||
//const cookies = new Cookies();
|
||||
|
||||
if (token) {
|
||||
axios.defaults.headers.common["Authorization"] = token;
|
||||
localStorage.setItem(AUTH_KEY, token);
|
||||
|
||||
const current = new Date();
|
||||
/*const current = new Date();
|
||||
const nextYear = new Date();
|
||||
|
||||
nextYear.setFullYear(current.getFullYear() + 1);
|
||||
@ -16,13 +35,13 @@ export default function setAuthorizationToken(token) {
|
||||
cookies.set(AUTH_KEY, token, {
|
||||
path: '/',
|
||||
expires: nextYear,
|
||||
});
|
||||
});*/
|
||||
}
|
||||
else {
|
||||
localStorage.clear();
|
||||
delete axios.defaults.headers.common["Authorization"];
|
||||
cookies.remove(AUTH_KEY, {
|
||||
/*cookies.remove(AUTH_KEY, {
|
||||
path: '/'
|
||||
});
|
||||
});*/
|
||||
}
|
||||
}
|
@ -1802,14 +1802,15 @@ asap@~2.0.6:
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
"asc-web-components@file:../../../packages/asc-web-components":
|
||||
version "1.0.87"
|
||||
version "1.0.102"
|
||||
dependencies:
|
||||
moment "^2.24.0"
|
||||
prop-types "^15.7.2"
|
||||
rc-tree "^2.1.2"
|
||||
react-autosize-textarea "^7.0.0"
|
||||
react-avatar-edit "^0.8.3"
|
||||
react-avatar-editor "^11.0.7"
|
||||
react-custom-scrollbars "^4.2.1"
|
||||
react-dropzone "^10.1.8"
|
||||
react-text-mask "^5.4.3"
|
||||
react-toastify "^5.3.2"
|
||||
react-virtualized-auto-sizer "^1.0.2"
|
||||
@ -1897,6 +1898,13 @@ atob@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
attr-accept@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52"
|
||||
integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==
|
||||
dependencies:
|
||||
core-js "^2.5.0"
|
||||
|
||||
autoprefixer@^9.6.1:
|
||||
version "9.6.1"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
|
||||
@ -2968,7 +2976,7 @@ core-js@3.1.4:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
|
||||
integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
|
||||
|
||||
core-js@^2.4.0, core-js@^2.6.4:
|
||||
core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.4:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||
@ -4320,6 +4328,13 @@ file-loader@3.0.1:
|
||||
loader-utils "^1.0.2"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
file-selector@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"
|
||||
integrity sha512-Kx7RTzxyQipHuiqyZGf+Nz4vY9R1XGxuQl/hLoJwq+J4avk/9wxxgZyHKtbyIPJmbD4A66DWGYfyykWNpcYutQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
filesize@3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
|
||||
@ -6298,11 +6313,6 @@ kleur@^3.0.3:
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
konva@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/konva/-/konva-2.5.1.tgz#cca611a9522e831e54cf57c508a1aed3f0ceac25"
|
||||
integrity sha512-YdHEWqmbWPieqIZuLx7JFGm9Ui08hSUaSJ2k2Ml8o5giFgJ0WmxAS0DPXIM+Ty2ADRagOHZfXSJ/skwYqqlwgQ==
|
||||
|
||||
last-call-webpack-plugin@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555"
|
||||
@ -8736,12 +8746,12 @@ react-autosize-textarea@^7.0.0:
|
||||
line-height "^0.3.1"
|
||||
prop-types "^15.5.6"
|
||||
|
||||
react-avatar-edit@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/react-avatar-edit/-/react-avatar-edit-0.8.3.tgz#0ebf21391328fc255429bdfbc782f795827109bf"
|
||||
integrity sha512-QEedh6DjDCSI7AUsUHHtfhxApCWC5hJAoywxUA5PtUdw03iIjEurgVqPOIt1UBHhU/Zk/9amElRF3oepN9JZSg==
|
||||
react-avatar-editor@^11.0.7:
|
||||
version "11.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-avatar-editor/-/react-avatar-editor-11.0.7.tgz#021053cfeaa138407b79279ee5a0384f273f0c54"
|
||||
integrity sha512-GbNYBd1/L1QyuU9VRvOW0hSkW1R0XSneOWZFgqI5phQf6dX+dF/G3/AjiJ0hv3JWh2irMQ7DL0oYDKzwtTnNBQ==
|
||||
dependencies:
|
||||
konva "2.5.1"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-custom-scrollbars@^4.2.1:
|
||||
version "4.2.1"
|
||||
@ -8798,6 +8808,15 @@ react-dom@^16.9.0:
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.15.0"
|
||||
|
||||
react-dropzone@^10.1.8:
|
||||
version "10.1.9"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.9.tgz#8093ecd7d2dc4002280eb2dac1d5fa4216c800ee"
|
||||
integrity sha512-7iqALZ0mzk+4g/AsYxEy3QyWPMTVQYKQVkYUe9zIbH18u+pi7EBDg010KEwfIX6jeTDH2qP0E6/eUnXvBYrovA==
|
||||
dependencies:
|
||||
attr-accept "^1.1.3"
|
||||
file-selector "^0.1.11"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-error-overlay@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.1.tgz#b8d3cf9bb991c02883225c48044cb3ee20413e0f"
|
||||
|
@ -486,6 +486,42 @@ namespace ASC.Employee.Core.Controllers
|
||||
return new EmployeeWraperFull(user, ApiContext, UserManager, UserPhotoManager, WebItemSecurity, TenantManager, CommonLinkUtility, DisplayUserSettings);
|
||||
}
|
||||
|
||||
[Update("{userid}/culture")]
|
||||
public EmployeeWraperFull UpdateMemberCulture(string userid, UpdateMemberModel memberModel)
|
||||
{
|
||||
var user = GetUserInfo(userid);
|
||||
|
||||
if (UserManager.IsSystemUser(user.ID))
|
||||
throw new SecurityException();
|
||||
|
||||
PermissionContext.DemandPermissions(new UserSecurityProvider(user.ID), Constants.Action_EditUser);
|
||||
|
||||
var curLng = user.CultureName;
|
||||
|
||||
if (SetupInfo.EnabledCultures.Find(c => string.Equals(c.Name, memberModel.CultureName, StringComparison.InvariantCultureIgnoreCase)) != null)
|
||||
{
|
||||
if (curLng != memberModel.CultureName)
|
||||
{
|
||||
user.CultureName = memberModel.CultureName;
|
||||
|
||||
try
|
||||
{
|
||||
UserManager.SaveUserInfo(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
user.CultureName = curLng;
|
||||
throw ex;
|
||||
}
|
||||
|
||||
MessageService.Send(MessageAction.UserUpdatedLanguage, MessageTarget.Create(user.ID), user.DisplayUserName(false, DisplayUserSettings));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return new EmployeeWraperFull(user, ApiContext, UserManager, UserPhotoManager, WebItemSecurity, TenantManager, CommonLinkUtility, DisplayUserSettings); ;
|
||||
}
|
||||
|
||||
[Update("{userid}")]
|
||||
public EmployeeWraperFull UpdateMember(string userid, UpdateMemberModel memberModel)
|
||||
{
|
||||
@ -621,6 +657,50 @@ namespace ASC.Employee.Core.Controllers
|
||||
return new EmployeeWraperFull(user, ApiContext, UserManager, UserPhotoManager, WebItemSecurity, TenantManager, CommonLinkUtility, DisplayUserSettings);
|
||||
}
|
||||
|
||||
[Delete("@self")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "ProfileRemove")]
|
||||
public EmployeeWraperFull DeleteProfile()
|
||||
{
|
||||
ApiContext.AuthByClaim();
|
||||
|
||||
if (UserManager.IsSystemUser(SecurityContext.CurrentAccount.ID))
|
||||
throw new SecurityException();
|
||||
|
||||
var user = GetUserInfo(SecurityContext.CurrentAccount.ID.ToString());
|
||||
|
||||
if (!UserManager.UserExists(user))
|
||||
throw new Exception(Resource.ErrorUserNotFound);
|
||||
|
||||
if (user.IsLDAP())
|
||||
throw new SecurityException();
|
||||
|
||||
_ = SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem);
|
||||
|
||||
user.Status = EmployeeStatus.Terminated;
|
||||
|
||||
UserManager.SaveUserInfo(user);
|
||||
|
||||
var userName = user.DisplayUserName(false, DisplayUserSettings);
|
||||
MessageService.Send(MessageAction.UsersUpdatedStatus, MessageTarget.Create(user.ID), userName);
|
||||
|
||||
CookiesManager.ResetUserCookie(user.ID);
|
||||
MessageService.Send(MessageAction.CookieSettingsUpdated);
|
||||
|
||||
if (CoreBaseSettings.Personal)
|
||||
{
|
||||
UserPhotoManager.RemovePhoto(user.ID);
|
||||
UserManager.DeleteUser(user.ID);
|
||||
MessageService.Send(MessageAction.UserDeleted, MessageTarget.Create(user.ID), userName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//StudioNotifyService.Instance.SendMsgProfileHasDeletedItself(user);
|
||||
//StudioNotifyService.SendMsgProfileDeletion(Tenant.TenantId, user);
|
||||
}
|
||||
|
||||
return new EmployeeWraperFull(user, ApiContext, UserManager, UserPhotoManager, WebItemSecurity, TenantManager, CommonLinkUtility, DisplayUserSettings);
|
||||
}
|
||||
|
||||
[Update("{userid}/contacts")]
|
||||
public EmployeeWraperFull UpdateMemberContacts(string userid, UpdateMemberModel memberModel)
|
||||
{
|
||||
|
@ -28,5 +28,6 @@ namespace ASC.People.Models
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public bool? Disable { get; set; }
|
||||
public string CultureName { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ namespace ASC.Web.Api.Controllers
|
||||
public string GeInviteLink(EmployeeType employeeType)
|
||||
{
|
||||
PermissionContext.DemandPermissions(Constants.Action_AddRemoveUser);
|
||||
return CommonLinkUtility.GetConfirmationUrl(string.Empty, ConfirmType.LinkInvite, (int)employeeType, AuthContext.CurrentAccount.ID)
|
||||
return CommonLinkUtility.GetConfirmationUrl(string.Empty, ConfirmType.LinkInvite, (int)employeeType)
|
||||
+ $"&emplType={employeeType:d}";
|
||||
}
|
||||
|
||||
|
@ -256,7 +256,7 @@ namespace ASC.Api.Settings
|
||||
settings.TrustedDomains = Tenant.TrustedDomains;
|
||||
settings.TrustedDomainsType = Tenant.TrustedDomainsType;
|
||||
var timeZone = Tenant.TimeZone;
|
||||
settings.Timezone = timeZone.ToSerializedString();
|
||||
settings.Timezone = timeZone.Id;
|
||||
settings.UtcOffset = timeZone.GetUtcOffset(DateTime.UtcNow);
|
||||
settings.UtcHoursOffset = settings.UtcOffset.TotalHours;
|
||||
}
|
||||
@ -277,6 +277,19 @@ namespace ASC.Api.Settings
|
||||
return SetupInfo.EnabledCultures;
|
||||
}
|
||||
|
||||
[Read("timezones")]
|
||||
public IEnumerable<TimeZoneInfo> GetTimeZones()
|
||||
{
|
||||
var timeZones = TimeZoneInfo.GetSystemTimeZones().ToList();
|
||||
|
||||
if (timeZones.All(tz => tz.Id != "UTC"))
|
||||
{
|
||||
timeZones.Add(TimeZoneInfo.Utc);
|
||||
}
|
||||
|
||||
return timeZones;
|
||||
}
|
||||
|
||||
//[Read("recalculatequota")]
|
||||
//public void RecalculateQuota()
|
||||
//{
|
||||
@ -380,7 +393,7 @@ namespace ASC.Api.Settings
|
||||
}
|
||||
|
||||
[Read("security/password")]
|
||||
[Authorize(AuthenticationSchemes = "confirm")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "Everyone")]
|
||||
public object GetPasswordSettings()
|
||||
{
|
||||
var UserPasswordSettings = PasswordSettings.Load();
|
||||
|
@ -1,19 +1,21 @@
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { BrowserRouter, Route, Switch } from "react-router-dom";
|
||||
import { Router, Route, Switch } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import StudioLayout from "./components/Layout/index";
|
||||
import Login from "./components/pages/Login";
|
||||
import { PrivateRoute } from "./helpers/privateRoute";
|
||||
import PrivateRoute from "./helpers/privateRoute";
|
||||
import PublicRoute from "./helpers/publicRoute";
|
||||
import { Error404 } from "./components/pages/Error";
|
||||
import history from './history';
|
||||
|
||||
const Home = lazy(() => import("./components/pages/Home"));
|
||||
const About = lazy(() => import("./components/pages/About"));
|
||||
const Confirm = lazy(() => import("./components/pages/Confirm"));
|
||||
const Settings = lazy(() => import("./components/pages/Settings"));
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Router history={history}>
|
||||
<StudioLayout>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
@ -21,13 +23,14 @@ const App = () => {
|
||||
<Switch>
|
||||
<PublicRoute exact path={["/login","/login/error=:error", "/login/confirmed-email=:confirmedEmail"]} component={Login} />
|
||||
<Route path="/confirm" component={Confirm} />
|
||||
<PrivateRoute exact path="/" component={Home} />
|
||||
<PrivateRoute exact path={["/","/error=:error"]} component={Home} />
|
||||
<PrivateRoute exact path="/about" component={About} />
|
||||
<PrivateRoute restricted path="/settings" component={Settings} />
|
||||
<PrivateRoute component={Error404} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</StudioLayout>
|
||||
</BrowserRouter>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -6,15 +6,13 @@ import { Layout, Toast } from "asc-web-components";
|
||||
import { logout } from "../../store/auth/actions";
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from "./i18n";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { isAdmin } from "../../store/auth/selectors";
|
||||
|
||||
class PureStudioLayout extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if(this.props.hasChanges !== nextProps.hasChanges) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
onProfileClick = () => {
|
||||
window.location.href = "/products/people/view/@self";
|
||||
@ -65,8 +63,23 @@ class PureStudioLayout extends React.Component {
|
||||
};
|
||||
|
||||
|
||||
const getAvailableModules = modules => {
|
||||
const getAvailableModules = (modules, currentUser) => {
|
||||
const isUserAdmin = isAdmin(currentUser);
|
||||
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 {
|
||||
@ -80,21 +93,27 @@ const getAvailableModules = modules => {
|
||||
};
|
||||
}) || [];
|
||||
|
||||
return products.length ? [separator, ...products] : products;
|
||||
return products.length ? [separator, ...products, ...customModules] : products;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
let availableModules = getAvailableModules(state.auth.modules);
|
||||
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.currentModuleId
|
||||
currentModuleId: state.auth.settings.currentProductId,
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
};
|
||||
const StudioLayoutContainer = withTranslation()(PureStudioLayout);
|
||||
|
||||
const StudioLayout = (props) => <I18nextProvider i18n={i18n}><StudioLayoutContainer {...props} /></I18nextProvider>;
|
||||
const StudioLayout = (props) => {
|
||||
const { language } = props;
|
||||
i18n.changeLanguage(language);
|
||||
return (<I18nextProvider i18n={i18n}><StudioLayoutContainer {...props} /></I18nextProvider>);
|
||||
};
|
||||
|
||||
StudioLayout.propTypes = {
|
||||
logout: PropTypes.func.isRequired
|
||||
};
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"Profile": "Профиль",
|
||||
"AboutCompanyTitle": "О программе",
|
||||
"LogoutButton": "Выйти"
|
||||
}
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,7 +52,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { PageLayout, Text, Link } from "asc-web-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
@ -29,7 +30,7 @@ const BodyStyle = styled.div`
|
||||
content: "";
|
||||
height: 2px;
|
||||
margin-top: 9px;
|
||||
width: 36%;
|
||||
width: 26%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
@ -38,7 +39,7 @@ const BodyStyle = styled.div`
|
||||
content: "";
|
||||
height: 2px;
|
||||
margin-top: 9px;
|
||||
width: 36%;
|
||||
width: 26%;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
@ -53,9 +54,13 @@ const VersionStyle = styled.div`
|
||||
padding: 8px 0px 20px 0px;
|
||||
`;
|
||||
|
||||
const Body = () => {
|
||||
const Body = ({language}) => {
|
||||
const { t } = useTranslation("translation", { i18n });
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<BodyStyle>
|
||||
<p style={{ textAlign: "center", margin: "0px" }}>
|
||||
@ -127,7 +132,7 @@ const Body = () => {
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Text.Body className="text_p" fontSize={12}>
|
||||
{t("LicensedUnder")}:{" "}
|
||||
{t("LicensedUnder", {license: "GNU GPL v.3"} )}:{" "}
|
||||
<Link
|
||||
href="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
isHovered={true}
|
||||
@ -153,8 +158,12 @@ const Body = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const About = () => {
|
||||
return <PageLayout sectionBodyContent={<Body />} />;
|
||||
};
|
||||
const About = ({language}) => <PageLayout sectionBodyContent={<Body language={language} />} />;
|
||||
|
||||
export default About;
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(About);
|
||||
|
@ -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 {{license}}",
|
||||
|
||||
|
||||
|
||||
|
||||
"SourceCode": "Source code is available on", "_comment": "{0}GNU GPL v.3{1}","_comment":"SYNTAX ERROR"
|
||||
"SourceCode": "Source code is available on"
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"AboutCompanyTitle": "О программе",
|
||||
"AboutCompanyVersion": "Версия",
|
||||
"AboutCompanyLicensor": "АВТОРСКИЕ ПРАВА",
|
||||
"AboutCompanyAddressTitle": "адрес",
|
||||
"AboutCompanyEmailTitle": "email",
|
||||
"AboutCompanyTelTitle": "тел.",
|
||||
"LicensedUnder": "Это программное обеспечение лицензируется под {{license}}",
|
||||
|
||||
|
||||
|
||||
"SourceCode": "Исходный код программы доступен по cсылке"
|
||||
}
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,7 +52,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,18 +1,23 @@
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { Redirect, Route, Switch } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import PublicRoute from "../../../helpers/publicRoute";
|
||||
import ConfirmRoute from "../../../helpers/confirmRoute";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
// import ChangeEmailForm from "./sub-components/changeEmail";
|
||||
|
||||
const ActivateUserForm = lazy(() => import("./sub-components/activateUser"));
|
||||
const CreateUserForm = lazy(() => import("./sub-components/createUser"));
|
||||
const ChangePasswordForm = lazy(() => import("./sub-components/changePassword"));
|
||||
const ActivateEmailForm = lazy(() => import("./sub-components/activateEmail"));
|
||||
const ChangeEmailForm = lazy(() => import("./sub-components/changeEmail"));
|
||||
const ChangePhoneForm = lazy(() => import("./sub-components/changePhone"));
|
||||
const ProfileRemoveForm = lazy(() => import("./sub-components/profileRemove"));
|
||||
const Error404 = lazy(() => import("../Error"));
|
||||
|
||||
const Confirm = ({ match }) => {
|
||||
const Confirm = ({ match, language }) => {
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
|
||||
//console.log("Confirm render");
|
||||
return (
|
||||
@ -21,35 +26,52 @@ const Confirm = ({ match }) => {
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<Switch>
|
||||
<PublicRoute
|
||||
path={[`${match.path}/LinkInvite`, `${match.path}/Activation`]}
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${match.path}/LinkInvite`}
|
||||
component={CreateUserForm}
|
||||
/>
|
||||
<Route
|
||||
<ConfirmRoute
|
||||
forUnauthorized
|
||||
path={`${match.path}/Activation`}
|
||||
component={ActivateUserForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${match.path}/EmailActivation`}
|
||||
component={ActivateEmailForm}
|
||||
/>
|
||||
<Route
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${match.path}/EmailChange`}
|
||||
component={ChangeEmailForm}
|
||||
/>
|
||||
<Route
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${match.path}/PasswordChange`}
|
||||
component={ChangePasswordForm}
|
||||
/>
|
||||
<ConfirmRoute
|
||||
exact
|
||||
path={`${match.path}/ProfileRemove`}
|
||||
component={ProfileRemoveForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/PhoneActivation`}
|
||||
component={ChangePhoneForm}
|
||||
/>
|
||||
<Redirect to={{ pathname: "/" }} />
|
||||
<Route component={Error404} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</I18nextProvider >
|
||||
);
|
||||
};
|
||||
|
||||
export default Confirm;
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Confirm);
|
||||
|
@ -13,10 +13,16 @@
|
||||
"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...",
|
||||
"ChangePasswordSuccess": "Password has been successfully changed",
|
||||
"DeleteProfileBtn": "Delete my account",
|
||||
"DeleteProfileConfirmation": "Attention! You are about to delete your account.",
|
||||
"DeleteProfileConfirmationInfo": "By clicking the \"Delete my account\" button you agree with our Privacy policy.",
|
||||
"DeleteProfileSuccessMessage": "Your account has been successfully deleted.",
|
||||
"DeleteProfileSuccessMessageInfo": "See our Privacy policy to learn more about deleting your account and data accociated with it.",
|
||||
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"InviteTitle": "Вы приглашены присоединиться к этому порталу!",
|
||||
"LoginRegistryButton": "Присоединиться",
|
||||
"LoginWithAccount": "или войдите с:",
|
||||
"Email": "Email",
|
||||
"InvitePassword": "Пароль",
|
||||
"FirstName": "Имя",
|
||||
"LastName": "Фамилия",
|
||||
"CopyEmailAndPassword": "Скопировать email и пароль",
|
||||
"ErrorPasswordMessage": "Пароль должен содержать",
|
||||
"ErrorPasswordLength": "от {{fromNumber}} до {{toNumber}} символов",
|
||||
"ErrorPasswordNoDigits": "цифры",
|
||||
"ErrorPasswordNoUpperCase": "заглавные буквы",
|
||||
"ErrorPasswordNoSpecialSymbols": "специальные символы",
|
||||
"EmailAndPasswordCopiedToClipboard": "Email и пароль скопированы",
|
||||
"PassworResetTitle": "Теперь вы можете создать новый пароль.",
|
||||
"PasswordCustomMode": "Пароль",
|
||||
"ImportContactsOkButton": "OK",
|
||||
"LoadingProcessing": "Загрузка...",
|
||||
"ChangePasswordSuccess": "Пароль был успешно изменен",
|
||||
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
}
|
@ -3,30 +3,23 @@ import { withRouter } from "react-router";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { PageLayout, Loader } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { logout, validateActivatingEmail } from '../../../../store/auth/actions';
|
||||
import { logout, changeEmail } from '../../../../store/auth/actions';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
class ActivateEmail extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
queryString: `type=EmailActivation&${props.location.search.slice(1)}`
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { history, logout, validateActivatingEmail } = this.props;
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
const { history, logout, changeEmail, linkData } = this.props;
|
||||
const [email, uid, key] = [linkData.email, linkData.uid, linkData.confirmHeader];
|
||||
logout();
|
||||
validateActivatingEmail(linkParams)
|
||||
changeEmail(uid, email, key)
|
||||
.then((res) => {
|
||||
const email = decodeURIComponent(res.data.response.email);
|
||||
history.push(`/login/confirmed-email=${email}`);
|
||||
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('activate email error', e);
|
||||
history.push(`/login/error=${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,4 +40,4 @@ ActivateEmail.propTypes = {
|
||||
const ActivateEmailForm = (props) => (<PageLayout sectionBodyContent={<ActivateEmail {...props} />} />);
|
||||
|
||||
|
||||
export default connect(null, { logout, validateActivatingEmail })(withRouter(withTranslation()(ActivateEmailForm)));
|
||||
export default connect(null, { logout, changeEmail })(withRouter(withTranslation()(ActivateEmailForm)));
|
@ -0,0 +1,328 @@
|
||||
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 { getConfirmationInfo, 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;
|
||||
}
|
||||
|
||||
.display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const emailInputName = 'email';
|
||||
|
||||
class Confirm extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
email: props.linkData.email,
|
||||
firstName: props.linkData.firstname,
|
||||
firstNameValid: true,
|
||||
lastName: props.linkData.lastname,
|
||||
lastNameValid: true,
|
||||
password: '',
|
||||
passwordValid: true,
|
||||
errorText: '',
|
||||
isLoading: false,
|
||||
passwordEmpty: false,
|
||||
key: props.linkData.confirmHeader,
|
||||
linkType: props.linkData.type,
|
||||
userId: props.linkData.uid
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
this.setState({ isLoading: true }, function () {
|
||||
const { activateConfirmUser, history } = 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 });
|
||||
}
|
||||
|
||||
if (!this.state.password.trim()) {
|
||||
this.setState({ passwordEmpty: true });
|
||||
hasError = 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
|
||||
};
|
||||
activateConfirmUser(personalData, loginData, this.state.key, this.state.userId, EmployeeActivationStatus.Activated)
|
||||
.then(() => history.push('/'))
|
||||
.catch(error => {
|
||||
console.error("activate error", error);
|
||||
this.setState({
|
||||
errorText: error,
|
||||
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 { getConfirmationInfo, history } = this.props;
|
||||
|
||||
getConfirmationInfo(this.state.key, this.state.linkType)
|
||||
.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}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
className='confirm-row display-none'
|
||||
id='email'
|
||||
name={emailInputName}
|
||||
value={this.state.email}
|
||||
size='huge'
|
||||
scale={true}
|
||||
isReadOnly={true}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<PasswordInput
|
||||
className='confirm-row'
|
||||
id='password'
|
||||
inputName='password'
|
||||
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 = {
|
||||
getConfirmationInfo: 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.settings.passwordSettings
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { getConfirmationInfo, activateConfirmUser })(withRouter(withTranslation()(ActivateUserForm)));
|
@ -3,35 +3,41 @@ import { withRouter } from "react-router";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { PageLayout, Loader } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { logout, changeEmail } from '../../../../store/auth/actions';
|
||||
import { changeEmail } from '../../../../store/auth/actions';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
||||
class ChangeEmail extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
queryString: `type=EmailChange&${props.location.search.slice(1)}`
|
||||
};
|
||||
componentDidMount() {
|
||||
const { changeEmail, userId, isLoaded, linkData } = this.props;
|
||||
if (isLoaded) {
|
||||
const [email, key] = [linkData.email, linkData.confirmHeader];
|
||||
changeEmail(userId, email , key)
|
||||
.then((res) => {
|
||||
console.log('change client email success', res)
|
||||
window.location.href = `${window.location.origin}/products/people/view/@self?email_change=success`;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('change client email error', e)
|
||||
window.location.href = `${window.location.origin}/error=${e}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(){
|
||||
const { logout, changeEmail, userId, isLoaded } = this.props;
|
||||
if (isLoaded){
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
// logout();
|
||||
const email = decodeURIComponent(linkParams.email);
|
||||
changeEmail(userId, {email}, this.state.queryString)
|
||||
.then((res) => {
|
||||
console.log('change client email success', res)
|
||||
window.location.href = `${window.location.origin}/products/people/view/@self?email_change=success`;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('change client email error', e)
|
||||
});
|
||||
componentDidUpdate() {
|
||||
const { changeEmail, userId, isLoaded, linkData } = this.props;
|
||||
if (isLoaded) {
|
||||
const [email, key] = [linkData.email, linkData.confirmHeader];
|
||||
changeEmail(userId, email, key)
|
||||
.then((res) => {
|
||||
console.log('change client email success', res)
|
||||
window.location.href = `${window.location.origin}/products/people/view/@self?email_change=success`;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('change client email error', e)
|
||||
});
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,11 +49,9 @@ class ChangeEmail extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChangeEmail.propTypes = {
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
changeEmail: PropTypes.func.isRequired
|
||||
};
|
||||
const ChangeEmailForm = (props) => (<PageLayout sectionBodyContent={<ChangeEmail {...props} />} />);
|
||||
|
||||
@ -58,4 +62,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { logout, changeEmail })(withRouter(withTranslation()(ChangeEmailForm)));
|
||||
export default connect(mapStateToProps, { changeEmail })(withRouter(withTranslation()(ChangeEmailForm)));
|
@ -1,29 +1,38 @@
|
||||
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, 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 } from "../../../../../src/store/auth/actions";
|
||||
import {
|
||||
changePassword,
|
||||
getConfirmationInfo,
|
||||
logout
|
||||
} from "../../../../../src/store/auth/actions";
|
||||
|
||||
const BodyStyle = styled.div`
|
||||
const BodyStyle = styled(Container)`
|
||||
margin-top: 70px;
|
||||
|
||||
p {
|
||||
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;
|
||||
@ -39,160 +48,180 @@ 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 } = props;
|
||||
const { params } = match;
|
||||
const { t } = useTranslation("translation", { i18n });
|
||||
class Form extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { linkData } = props;
|
||||
|
||||
const onSubmit = useCallback(
|
||||
e => {
|
||||
errorText && setErrorText("");
|
||||
|
||||
let hasError = false;
|
||||
|
||||
if (!password.trim()) {
|
||||
hasError = true;
|
||||
setPasswordValid(!hasError);
|
||||
}
|
||||
|
||||
if (hasError) return false;
|
||||
|
||||
setIsLoading(true);
|
||||
console.log("changePassword onSubmit", match, location, history);
|
||||
|
||||
const str = location.search.split("&");
|
||||
const userId = str[1].slice(4);
|
||||
const key = `type=PasswordChange&${location.search.slice(1)}`;
|
||||
|
||||
changePassword(userId, {password}, key)
|
||||
.then(() => {
|
||||
console.log("UPDATE PASSWORD");
|
||||
history.push("/");
|
||||
})
|
||||
.catch(e => {
|
||||
history.push("/");
|
||||
console.log("ERROR UPDATE PASSWORD", e);
|
||||
});
|
||||
},
|
||||
[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);
|
||||
this.state = {
|
||||
password: "",
|
||||
passwordValid: true,
|
||||
isValidConfirmLink: false,
|
||||
isLoading: false,
|
||||
passwordEmpty: false,
|
||||
key: linkData.confirmHeader,
|
||||
userId: linkData.uid
|
||||
};
|
||||
}, [onKeyPress, params.error]);
|
||||
}
|
||||
|
||||
const settings = {
|
||||
minLength: 6,
|
||||
upperCase: false,
|
||||
digits: false,
|
||||
specSymbols: false
|
||||
onKeyPress = target => {
|
||||
if (target.key === "Enter") {
|
||||
this.onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const tooltipPasswordLength =
|
||||
"from " + settings.minLength + " to 30 characters";
|
||||
onChange = event => {
|
||||
this.setState({ password: event.target.value });
|
||||
!this.state.passwordValid && this.setState({ passwordValid: true });
|
||||
event.target.value.trim() && this.setState({ passwordEmpty: false });
|
||||
this.onKeyPress(event);
|
||||
};
|
||||
|
||||
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
|
||||
onSubmit = e => {
|
||||
this.setState({ isLoading: true }, function() {
|
||||
const { userId, password, key } = this.state;
|
||||
const { history, changePassword } = this.props;
|
||||
let hasError = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
changePassword(userId, password, key)
|
||||
.then(() => {
|
||||
history.push("/");
|
||||
toastr.success(this.props.t("ChangePasswordSuccess"));
|
||||
})
|
||||
.catch(error => {
|
||||
toastr.error(this.props.t(`${error}`));
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { getConfirmationInfo, history } = this.props;
|
||||
getConfirmationInfo(this.state.key)
|
||||
.catch(error => {
|
||||
toastr.error(this.props.t(`${error}`));
|
||||
history.push("/");
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", this.onKeyPress);
|
||||
window.addEventListener("keyup", this.onKeyPress);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("keydown", this.onKeyPress);
|
||||
window.removeEventListener("keyup", this.onKeyPress);
|
||||
}
|
||||
|
||||
validatePassword = value => this.setState({ passwordValid: value });
|
||||
|
||||
render() {
|
||||
const { settings, isConfirmLoaded, t } = 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">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
</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"
|
||||
/>
|
||||
<CardTitle className="card-title">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
</CardTitle>
|
||||
</Card>
|
||||
<Button
|
||||
className="button-style"
|
||||
primary
|
||||
size="big"
|
||||
tabIndex={2}
|
||||
label={
|
||||
isLoading ? t("LoadingProcessing") : t("ImportContactsOkButton")
|
||||
}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={this.onSubmit}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</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 = {
|
||||
match: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
Form.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
changePassword: PropTypes.func.isRequired
|
||||
changePassword: PropTypes.func.isRequired,
|
||||
logout: PropTypes.func.isRequired,
|
||||
linkData: PropTypes.object.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.settings.passwordSettings,
|
||||
isAuthenticated: state.auth.isAuthenticated
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{ changePassword }
|
||||
mapStateToProps,
|
||||
{ changePassword, getConfirmationInfo, logout }
|
||||
)(withRouter(withTranslation()(ChangePasswordForm)));
|
||||
|
@ -42,6 +42,7 @@ const PhoneForm = props => {
|
||||
const { t, currentPhone } = props;
|
||||
|
||||
const [phone, setPhone] = useState(currentPhone);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const subTitleTranslation = `Enter mobile phone number`;
|
||||
|
@ -6,7 +6,7 @@ import styled from 'styled-components';
|
||||
import { Collapse } from 'reactstrap';
|
||||
import { connect } from 'react-redux';
|
||||
import { welcomePageTitle } from './../../../../helpers/customNames';
|
||||
import { getPasswordSettings, createConfirmUser } from '../../../../store/auth/actions';
|
||||
import { getConfirmationInfo, createConfirmUser, logout, login } from '../../../../store/auth/actions';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const inputWidth = '400px';
|
||||
@ -66,17 +66,22 @@ class Confirm extends React.PureComponent {
|
||||
errorText: '',
|
||||
isLoading: false,
|
||||
passwordEmpty: false,
|
||||
queryString: `type=LinkInvite&${props.location.search.slice(1)}`
|
||||
key: props.linkData.confirmHeader,
|
||||
linkType: props.linkData.type
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
this.setState({ isLoading: true }, function () {
|
||||
const { history, createConfirmUser } = this.props;
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
const isVisitor = parseInt(linkParams.emplType) === 2;
|
||||
/*componentWillMount() {
|
||||
const { isAuthenticated, logout } = this.props;
|
||||
|
||||
if(isAuthenticated)
|
||||
logout();
|
||||
}*/
|
||||
|
||||
onSubmit = () => {
|
||||
this.setState({ isLoading: true }, () => {
|
||||
const { history, createConfirmUser, linkData } = this.props;
|
||||
const isVisitor = parseInt(linkData.emplType) === 2;
|
||||
|
||||
this.setState({ errorText: "" });
|
||||
|
||||
@ -112,19 +117,26 @@ class Confirm extends React.PureComponent {
|
||||
const loginData = {
|
||||
userName: this.state.email,
|
||||
password: this.state.password
|
||||
}
|
||||
const registerData = {
|
||||
};
|
||||
|
||||
const personalData = {
|
||||
firstname: this.state.firstName,
|
||||
lastname: this.state.lastName,
|
||||
email: this.state.email,
|
||||
isVisitor: isVisitor
|
||||
email: this.state.email
|
||||
};
|
||||
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 });
|
||||
const registerData = Object.assign(personalData, { isVisitor: isVisitor })
|
||||
|
||||
createConfirmUser(registerData, loginData, this.state.key)
|
||||
.then(() => {
|
||||
toastr.success("User has been created successfully");
|
||||
return history.push('/');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("confirm error", error);
|
||||
this.setState({
|
||||
errorText: error,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -139,9 +151,9 @@ class Confirm extends React.PureComponent {
|
||||
validatePassword = (value) => this.setState({ passwordValid: value });
|
||||
|
||||
componentDidMount() {
|
||||
const { getPasswordSettings, history } = this.props;
|
||||
const { getConfirmationInfo, history } = this.props;
|
||||
|
||||
getPasswordSettings(this.state.queryString)
|
||||
getConfirmationInfo(this.state.key, this.state.linkType)
|
||||
.then(
|
||||
function () {
|
||||
console.log("get settings success");
|
||||
@ -188,7 +200,7 @@ class Confirm extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('Confirm render');
|
||||
console.log('createUser render');
|
||||
const { settings, isConfirmLoaded, t } = this.props;
|
||||
return (
|
||||
!isConfirmLoaded
|
||||
@ -260,6 +272,7 @@ class Confirm extends React.PureComponent {
|
||||
onChange={this.onChangeEmail}
|
||||
onKeyDown={this.onKeyPress}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<PasswordInput
|
||||
@ -292,7 +305,6 @@ class Confirm extends React.PureComponent {
|
||||
onKeyDown={this.onKeyPress}
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
className='confirm-row'
|
||||
primary
|
||||
@ -324,7 +336,7 @@ class Confirm extends React.PureComponent {
|
||||
|
||||
|
||||
Confirm.propTypes = {
|
||||
getPasswordSettings: PropTypes.func.isRequired,
|
||||
getConfirmationInfo: PropTypes.func.isRequired,
|
||||
createConfirmUser: PropTypes.func.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
@ -335,8 +347,9 @@ const CreateUserForm = (props) => (<PageLayout sectionBodyContent={<Confirm {...
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isConfirmLoaded: state.auth.isConfirmLoaded,
|
||||
settings: state.auth.password
|
||||
isAuthenticated: state.auth.isAuthenticated,
|
||||
settings: state.auth.settings.passwordSettings
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser })(withRouter(withTranslation()(CreateUserForm)));
|
||||
export default connect(mapStateToProps, { getConfirmationInfo, createConfirmUser, login, logout })(withRouter(withTranslation()(CreateUserForm)));
|
@ -0,0 +1,111 @@
|
||||
import React from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { Button, PageLayout, Text } from 'asc-web-components';
|
||||
import styled from 'styled-components';
|
||||
import { welcomePageTitle } from './../../../../helpers/customNames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { deleteSelf } from './../../../../store/services/api';
|
||||
import { logout } from '../../../../store/auth/actions';
|
||||
|
||||
const ProfileRemoveContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.start-basis {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.confirm-row {
|
||||
margin: 23px 0 0;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
class ProfileRemove extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
isProfileDeleted: false
|
||||
};
|
||||
}
|
||||
|
||||
onDeleteProfile = () => {
|
||||
this.setState({ isLoading: true }, function () {
|
||||
const { linkData, logout } = this.props;
|
||||
deleteSelf(linkData.confirmHeader)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isProfileDeleted: true
|
||||
});
|
||||
//setAuthorizationToken();
|
||||
console.log('success delete', res)
|
||||
return logout()
|
||||
})
|
||||
.catch((e) => {
|
||||
this.setState({ isLoading: false });
|
||||
console.log('error delete', e)
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
console.log('profileRemove render');
|
||||
const { t } = this.props;
|
||||
const { isProfileDeleted } = this.state;
|
||||
return (
|
||||
<ProfileRemoveContainer>
|
||||
<div className='start-basis'>
|
||||
|
||||
<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>
|
||||
|
||||
{!isProfileDeleted
|
||||
? <>
|
||||
<Text.Body className='confirm-row' as='p' fontSize={18} >{t('DeleteProfileConfirmation')}</Text.Body>
|
||||
<Text.Body className='confirm-row' as='p' fontSize={16} >{t('DeleteProfileConfirmationInfo')}</Text.Body>
|
||||
|
||||
<Button
|
||||
className='confirm-row'
|
||||
primary
|
||||
size='big'
|
||||
label={t('DeleteProfileBtn')}
|
||||
tabIndex={1}
|
||||
isLoading={this.state.isLoading}
|
||||
onClick={this.onDeleteProfile}
|
||||
/>
|
||||
</>
|
||||
: <>
|
||||
<Text.Body className='confirm-row' as='p' fontSize={18} >{t('DeleteProfileSuccessMessage')}</Text.Body>
|
||||
<Text.Body className='confirm-row' as='p' fontSize={16} >{t('DeleteProfileSuccessMessageInfo')}</Text.Body>
|
||||
</>
|
||||
}
|
||||
|
||||
</div>
|
||||
</ProfileRemoveContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ProfileRemove.propTypes = {
|
||||
location: PropTypes.object.isRequired,
|
||||
};
|
||||
const ProfileRemoveForm = (props) => (<PageLayout sectionBodyContent={<ProfileRemove {...props} />} />);
|
||||
|
||||
|
||||
export default connect(null, { logout })(withRouter(withTranslation()(ProfileRemoveForm)));
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,24 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import { ErrorContainer } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
|
||||
export const Error404 = () => {
|
||||
const Error404Container = ({language}) => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
return <ErrorContainer>{t("Error404Text")}</ErrorContainer>;
|
||||
};
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture,
|
||||
};
|
||||
}
|
||||
|
||||
export const Error404 = connect(mapStateToProps)(Error404Container);
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Error404Text": "Извините, страница не найдена."
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from "react-router";
|
||||
import { Container, Col, Row, Collapse } from 'reactstrap';
|
||||
import { ModuleTile, Loader, PageLayout } from 'asc-web-components';
|
||||
import { ModuleTile, Loader, PageLayout, toastr } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
|
||||
@ -30,8 +30,14 @@ Tiles.propTypes = {
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const Body = ({ modules, history, isLoaded }) => {
|
||||
const Body = ({ modules, match, history, isLoaded }) => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
const { params } = match;
|
||||
|
||||
useEffect(() => {
|
||||
params.error && toastr.error(params.error);
|
||||
}, [params.error]);
|
||||
|
||||
return (
|
||||
!isLoaded
|
||||
? (
|
||||
|
@ -31,6 +31,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
const resources = {
|
||||
en: {
|
||||
translation: require("./locales/en/translation.json")
|
||||
},
|
||||
ru: {
|
||||
translation: require("./locales/ru/translation.json")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,210 +1,356 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState, useEffect, useCallback } 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, PageLayout, Text } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { login } from '../../../store/auth/actions';
|
||||
import styled from 'styled-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
import { welcomePageTitle } from './../../../helpers/customNames';
|
||||
import {
|
||||
Collapse,
|
||||
Container,
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
CardTitle,
|
||||
CardImg
|
||||
} from "reactstrap";
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
PageLayout,
|
||||
Text,
|
||||
Link,
|
||||
toastr,
|
||||
Checkbox,
|
||||
HelpButton
|
||||
} from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import { login } from "../../../store/auth/actions";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import { welcomePageTitle } from "./../../../helpers/customNames";
|
||||
import { sendInstructionsToChangePassword } from "../../../store/services/api";
|
||||
import SubModalDialog from "./sub-components/modal-dialog";
|
||||
|
||||
const FormContainer = styled(Container)`
|
||||
margin-top: 70px;
|
||||
margin-top: 70px;
|
||||
|
||||
.login-row {
|
||||
margin: 23px 0 0;
|
||||
.link-style {
|
||||
float: right;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
border: none;
|
||||
.text-body {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.btn-style {
|
||||
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;
|
||||
`;
|
||||
|
||||
const mdOptions = { size: 6, offset: 3 };
|
||||
|
||||
const Form = props => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
const { login, match, location, history } = 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 { t } = useTranslation("translation", { i18n });
|
||||
const { login, match, history, language } = 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 onSubmit = useCallback((e) => {
|
||||
//e.preventDefault();
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
const [isChecked, setIsisChecked] = useState(false);
|
||||
|
||||
errorText && setErrorText("");
|
||||
const onClick = () => {
|
||||
setOpenDialog(true);
|
||||
setIsDisabled(true);
|
||||
setEmail(identifier);
|
||||
};
|
||||
|
||||
let hasError = false;
|
||||
const onDialogClose = () => {
|
||||
setOpenDialog(false);
|
||||
setIsDisabled(false);
|
||||
setIsLoading(false);
|
||||
setEmail("");
|
||||
};
|
||||
|
||||
if (!identifier.trim()) {
|
||||
hasError = true;
|
||||
setIdentifierValid(!hasError);
|
||||
}
|
||||
const onSendPasswordInstructions = useCallback(() => {
|
||||
setIsLoading(true);
|
||||
sendInstructionsToChangePassword(email)
|
||||
.then(res => toastr.success(res), message => toastr.error(message))
|
||||
.finally(onDialogClose());
|
||||
}, [email]);
|
||||
|
||||
if (!password.trim()) {
|
||||
hasError = true;
|
||||
setPasswordValid(!hasError);
|
||||
}
|
||||
const onSubmit = useCallback(() => {
|
||||
errorText && setErrorText("");
|
||||
let hasError = false;
|
||||
|
||||
if (hasError)
|
||||
return false;
|
||||
const userName = identifier.trim();
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
let payload = {
|
||||
userName: identifier,
|
||||
password: password
|
||||
};
|
||||
|
||||
login(payload)
|
||||
.then(function () {
|
||||
console.log("auth success", match, location, history);
|
||||
setIsLoading(false)
|
||||
history.push('/');
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("auth error", e);
|
||||
setErrorText(e.message);
|
||||
setIsLoading(false)
|
||||
});
|
||||
}, [errorText, history, identifier, location, login, match, password]);
|
||||
|
||||
const onKeyPress = useCallback((event) => {
|
||||
if (event.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]);
|
||||
|
||||
const onChangePassword = event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
errorText && setErrorText("");
|
||||
if (!userName) {
|
||||
hasError = true;
|
||||
setIdentifierValid(!hasError);
|
||||
}
|
||||
|
||||
const onChangeLogin = event => {
|
||||
setIdentifier(event.target.value);
|
||||
!identifierValid && setIdentifierValid(true);
|
||||
errorText && setErrorText("");
|
||||
const pass = password.trim();
|
||||
|
||||
if (!pass) {
|
||||
hasError = true;
|
||||
setPasswordValid(!hasError);
|
||||
}
|
||||
|
||||
// console.log('Login render');
|
||||
if (hasError) return false;
|
||||
|
||||
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 />
|
||||
<CardTitle className="card-title">{t('CustomWelcomePageTitle', { welcomePageTitle })}</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}>
|
||||
<Button
|
||||
primary
|
||||
size='big'
|
||||
label={isLoading ? t('LoadingProcessing') : t('LoginButton')}
|
||||
tabIndex={3}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit} />
|
||||
</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>
|
||||
setIsLoading(true);
|
||||
|
||||
login(userName, pass).then(
|
||||
() => {
|
||||
//console.log("auth success", match, location, history);
|
||||
setIsLoading(false);
|
||||
history.push("/");
|
||||
},
|
||||
error => {
|
||||
//console.error("auth error", error);
|
||||
setErrorText(error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [errorText, history, identifier, login, password]);
|
||||
|
||||
const LoginForm = (props) => (<PageLayout sectionBodyContent={<Form {...props} />} />);
|
||||
const onKeyPress = useCallback(
|
||||
event => {
|
||||
if (event.key === "Enter") {
|
||||
!isDisabled ? onSubmit() : onSendPasswordInstructions();
|
||||
}
|
||||
},
|
||||
[onSendPasswordInstructions, onSubmit, isDisabled]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
const onChangePassword = event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
errorText && setErrorText("");
|
||||
};
|
||||
|
||||
const onChangeLogin = event => {
|
||||
setIdentifier(event.target.value);
|
||||
!identifierValid && setIdentifierValid(true);
|
||||
errorText && setErrorText("");
|
||||
};
|
||||
|
||||
const onChangeEmail = event => {
|
||||
setEmail(event.target.value);
|
||||
};
|
||||
|
||||
// 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
|
||||
/>
|
||||
<CardTitle className="card-title">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
</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
|
||||
tooltipContent={
|
||||
<Text.Body fontSize={12}>{t("RememberHelper")}</Text.Body>
|
||||
}
|
||||
/>
|
||||
</TooltipStyle>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{openDialog ? (
|
||||
<SubModalDialog
|
||||
openDialog={openDialog}
|
||||
isLoading={isLoading}
|
||||
email={email}
|
||||
onChangeEmail={onChangeEmail}
|
||||
onSendPasswordInstructions={onSendPasswordInstructions}
|
||||
onDialogClose={onDialogClose}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Row className="button-row">
|
||||
<Col sm="12" md={mdOptions}>
|
||||
<Button
|
||||
primary
|
||||
size="big"
|
||||
label={isLoading ? t("LoadingProcessing") : t("LoginButton")}
|
||||
tabIndex={3}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
const LoginForm = props => (
|
||||
<PageLayout sectionBodyContent={<Form {...props} />} />
|
||||
);
|
||||
|
||||
LoginForm.propTypes = {
|
||||
login: PropTypes.func.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
}
|
||||
login: PropTypes.func.isRequired,
|
||||
match: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
LoginForm.defaultProps = {
|
||||
identifier: "",
|
||||
password: ""
|
||||
identifier: "",
|
||||
password: "",
|
||||
email: ""
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
language: state.auth.user.cultureName || state.auth.settings.culture
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, { login })(withRouter(LoginForm));
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ login }
|
||||
)(withRouter(LoginForm));
|
||||
|
@ -5,6 +5,13 @@
|
||||
"RegistrationEmailWatermark": "Your registration email",
|
||||
"MessageEmailConfirmed": "Your email was activated successfully.",
|
||||
"MessageAuthorize": "Please authorize yourself.",
|
||||
|
||||
"ForgotPassword": "Forgot your password?",
|
||||
"PasswordRecoveryTitle": "Password recovery",
|
||||
"MessageSendPasswordRecoveryInstructionsOnEmail": "Please enter the email you used while registering on the portal. The password recovery instructions will be send to that email address.",
|
||||
"SendButton": "Send",
|
||||
"CancelButton": "Cancel",
|
||||
"Remember": "Remember",
|
||||
"RememberHelper": "The default session lifetime is 20 minutes. Check this option to set it to 1 year. To set your own value, go to the settings.",
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"LoadingProcessing": "Загрузка...",
|
||||
"LoginButton": "Войти",
|
||||
"Password": "Пароль",
|
||||
"RegistrationEmailWatermark": "Регистрационный email",
|
||||
"MessageEmailConfirmed": "Ваш email успешно активирован.",
|
||||
"MessageAuthorize": "Пожалуйста авторизуйтесь.",
|
||||
"ForgotPassword": "Забыли пароль?",
|
||||
"PasswordRecoveryTitle": "Восстановление пароля",
|
||||
"MessageSendPasswordRecoveryInstructionsOnEmail": "Пожалуйста, введите адрес электронной почты, указанный при регистрации на портале. Инструкции для восстановления пароля будут отправлены на этот адрес электронной почты.",
|
||||
"SendButton": "Отправить",
|
||||
"CancelButton": "Отмена",
|
||||
"Remember": "Запомнить",
|
||||
"RememberHelper": "Время существования сессии по умолчанию составляет 20 минут. Отметьте эту опцию, чтобы установить значение 1 год. Чтобы задать собственное значение, перейдите в настройки.",
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Button, TextInput, Text, ModalDialog } from "asc-web-components";
|
||||
|
||||
class SubModalDialog extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
openDialog,
|
||||
isLoading,
|
||||
email,
|
||||
onChangeEmail,
|
||||
onSendPasswordInstructions,
|
||||
onDialogClose,
|
||||
t
|
||||
} = this.props;
|
||||
return (
|
||||
<ModalDialog
|
||||
visible={openDialog}
|
||||
headerContent={
|
||||
<Text.Body isBold={false} fontSize={21}>
|
||||
{t("PasswordRecoveryTitle")}
|
||||
</Text.Body>
|
||||
}
|
||||
bodyContent={[
|
||||
<Text.Body
|
||||
key="text-body"
|
||||
className="text-body"
|
||||
isBold={false}
|
||||
fontSize={13}
|
||||
>
|
||||
{t("MessageSendPasswordRecoveryInstructionsOnEmail")}
|
||||
</Text.Body>,
|
||||
<TextInput
|
||||
key="e-mail"
|
||||
id="e-mail"
|
||||
name="e-mail"
|
||||
type="text"
|
||||
size="base"
|
||||
scale={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
value={email}
|
||||
onChange={onChangeEmail}
|
||||
/>
|
||||
]}
|
||||
footerContent={[
|
||||
<Button
|
||||
className="btn-style"
|
||||
key="SendBtn"
|
||||
label={isLoading ? t("LoadingProcessing") : t("SendButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={true}
|
||||
onClick={onSendPasswordInstructions}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
tabIndex={2}
|
||||
/>,
|
||||
<Button
|
||||
key="CancelBtn"
|
||||
label={t("CancelButton")}
|
||||
size="big"
|
||||
scale={false}
|
||||
primary={false}
|
||||
onClick={onDialogClose}
|
||||
isDisabled={isLoading}
|
||||
tabIndex={3}
|
||||
/>
|
||||
]}
|
||||
onClose={onDialogClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SubModalDialog.propTypes = {
|
||||
openDialog: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
onChangeEmail: PropTypes.func.isRequired,
|
||||
onSendPasswordInstructions: PropTypes.func.isRequired,
|
||||
onDialogClose: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SubModalDialog;
|
@ -0,0 +1,210 @@
|
||||
import React from 'react';
|
||||
import { utils } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
Icons,
|
||||
Link
|
||||
} from "asc-web-components";
|
||||
import { setNewSelectedNode } from '../../../../../store/auth/actions';
|
||||
import { withRouter } from "react-router";
|
||||
import { settingsTree } from '../../../../../helpers/constants';
|
||||
import styled from 'styled-components';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
const StyledTreeMenu = styled(TreeMenu)`
|
||||
.inherit-title-link {
|
||||
& > span {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const getItems = (data, path, t) => {
|
||||
return data.map(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const link = path + getSelectedLinkByKey(item.key);
|
||||
return (
|
||||
<TreeNode
|
||||
title={<Link className='inherit-title-link' href={link}>{t(`Settings_${item.link}`)}</Link>}
|
||||
key={item.key}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
>
|
||||
{getItems(item.children, path, t)}
|
||||
</TreeNode>
|
||||
);
|
||||
};
|
||||
const link = path + getSelectedLinkByKey(item.key);
|
||||
return (
|
||||
<TreeNode
|
||||
key={item.key}
|
||||
title={<Link className='inherit-title-link' href={link}>{t(`Settings_${item.link}`)}</Link>}
|
||||
icon={item.icon && React.createElement(Icons[item.icon], {
|
||||
size: 'scale',
|
||||
isfill: true,
|
||||
color: 'dimgray',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getKeyByLink = (data, linkArr) => {
|
||||
const length = linkArr.length;
|
||||
if (length === 1 || !linkArr[1].length) {
|
||||
const arrLength = data.length;
|
||||
for (let i = 0; i < arrLength; i++) {
|
||||
if (data[i].link === linkArr[0]) {
|
||||
return data[i].children ? data[i].children[0].key : data[i].key;
|
||||
}
|
||||
}
|
||||
} else if (length === 2) {
|
||||
const arrLength = data.length;
|
||||
let key;
|
||||
|
||||
for (let i = 0; i < arrLength; i++) {
|
||||
if (data[i].link === linkArr[0]) {
|
||||
key = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedArr = data[key].children;
|
||||
const childrenLength = selectedArr.length;
|
||||
for (let i = 0; i < childrenLength; i++) {
|
||||
if (selectedArr[i].link === linkArr[1]) {
|
||||
return selectedArr[i].key;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return '0-0';
|
||||
}
|
||||
|
||||
const getSelectedLinkByKey = key => {
|
||||
const length = key.length;
|
||||
if (length === 1) {
|
||||
return '/' + settingsTree[key].link;
|
||||
}
|
||||
else if (length === 3) {
|
||||
return '/' + settingsTree[key[0]].link + '/' + settingsTree[key[0]].children[key[2]].link;
|
||||
}
|
||||
}
|
||||
|
||||
class ArticleBodyContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { selectedKeys, match, history, setNewSelectedNode, i18n, language } = props;
|
||||
const fullSettingsUrl = props.match.url;
|
||||
const locationPathname = props.location.pathname;
|
||||
|
||||
if (locationPathname === fullSettingsUrl) {
|
||||
const newPath = match.path + getSelectedLinkByKey(selectedKeys[0]);
|
||||
history.push(newPath);
|
||||
return;
|
||||
}
|
||||
|
||||
const fullSettingsUrlLength = fullSettingsUrl.length;
|
||||
|
||||
const resultPath = locationPathname.slice(fullSettingsUrlLength + 1);
|
||||
const arrayOfParams = resultPath.split('/');
|
||||
|
||||
const key = getKeyByLink(settingsTree, arrayOfParams);
|
||||
const link = getSelectedLinkByKey(key);
|
||||
|
||||
setNewSelectedNode([key]);
|
||||
const path = match.path + link;
|
||||
history.push(path);
|
||||
|
||||
i18n.changeLanguage(language);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { selectedKeys, match, history } = this.props;
|
||||
const settingsPath = getSelectedLinkByKey(selectedKeys[0]);
|
||||
const newPath = match.path + settingsPath;
|
||||
history.push(newPath);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (!utils.array.isArrayEqual(nextProps.selectedKeys, this.props.selectedKeys)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSelect = value => {
|
||||
const { selectedKeys, setNewSelectedNode } = this.props;
|
||||
|
||||
if (value) {
|
||||
if (utils.array.isArrayEqual(value, selectedKeys)) {
|
||||
|
||||
return;
|
||||
}
|
||||
const selectedKey = value[0];
|
||||
if (selectedKey.length === 3) {
|
||||
setNewSelectedNode(value);
|
||||
}
|
||||
else if (selectedKey.length === 1 && (selectedKey.toString() !== selectedKeys.toString()[0] || selectedKeys.toString()[2] !== '0')) {
|
||||
const selectedKeys = settingsTree[value].children ? [`${value.toString()}-0`] : value;
|
||||
setNewSelectedNode(selectedKeys);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switcherIcon = obj => {
|
||||
if (obj.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
if (obj.expanded) {
|
||||
return (
|
||||
<Icons.ExpanderDownIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Icons.ExpanderRightIcon size="scale" isfill={true} color="dimgray" />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedKeys, match, t } = this.props;
|
||||
|
||||
console.log("SettingsTreeMenu", this.props);
|
||||
|
||||
return (
|
||||
<StyledTreeMenu
|
||||
className="people-tree-menu"
|
||||
checkable={false}
|
||||
draggable={false}
|
||||
disabled={false}
|
||||
multiple={false}
|
||||
showIcon={true}
|
||||
defaultExpandAll={true}
|
||||
switcherIcon={this.switcherIcon}
|
||||
onSelect={this.onSelect}
|
||||
selectedKeys={selectedKeys}
|
||||
>
|
||||
{getItems(settingsTree, match.path, t)}
|
||||
</StyledTreeMenu>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
selectedKeys: state.auth.settings.settingsTree.selectedKey,
|
||||
language: state.auth.user.cultureName
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { setNewSelectedNode })(withRouter(withTranslation()(ArticleBodyContent)));
|
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Text } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ArticleHeaderContent = () => {
|
||||
const { t } = useTranslation();
|
||||
return <Text.MenuHeader>{t('Settings')}</Text.MenuHeader>;
|
||||
}
|
||||
|
||||
export default ArticleHeaderContent;
|
@ -0,0 +1,2 @@
|
||||
export { default as ArticleHeaderContent } from './Header';
|
||||
export { default as ArticleBodyContent } from './Body';
|
@ -0,0 +1,33 @@
|
||||
import React, { lazy } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
import { Scrollbar } from 'asc-web-components'
|
||||
|
||||
const CustomizationSettings = lazy(() => import("../../sub-components/common/customization"));
|
||||
const NotImplementedSettings = lazy(() => import("../../sub-components/notImplementedSettings"));
|
||||
const AccessRight = lazy(() => import("../../sub-components/security/accessRights"));
|
||||
class SectionBodyContent extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Scrollbar stype="mediumBlack">
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={[`${this.props.match.path}/common/customization`,`${this.props.match.path}/common`, this.props.match.path]}
|
||||
component={CustomizationSettings}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${this.props.match.path}/security/access-rights`}
|
||||
component={AccessRight}
|
||||
/>
|
||||
|
||||
<Route component={NotImplementedSettings} />
|
||||
</Switch>
|
||||
</Scrollbar>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(SectionBodyContent);
|
@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import { Text, utils } from 'asc-web-components';
|
||||
import styled from 'styled-components';
|
||||
import { settingsTree } from '../../../../../helpers/constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 getSelectedLinkByKey = key => {
|
||||
const length = key.length;
|
||||
if (length === 1) {
|
||||
return settingsTree[key].link;
|
||||
}
|
||||
else if (length === 3) {
|
||||
return settingsTree[key[0]].children[key[2]].link;
|
||||
}
|
||||
};
|
||||
|
||||
const SectionHeaderContent = props => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const header = getSelectedLinkByKey(props.selectedKey)
|
||||
return (
|
||||
<Header truncate={true}>
|
||||
{t(`Settings_${header}`)}
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
selectedKey: state.auth.settings.settingsTree.selectedKey[0]
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user