Merge branch 'master' of https://github.com/ONLYOFFICE/CommunityServer-AspNetCore
This commit is contained in:
commit
7bcf2a6bf8
@ -2,15 +2,15 @@
|
||||
"folders": [
|
||||
{
|
||||
"name": "ASC.Web.Components"
|
||||
"path": ".\\web\\ASC.Web.Components"
|
||||
"path": "./web/ASC.Web.Components"
|
||||
},
|
||||
{
|
||||
"name": "ASC.Web.Client"
|
||||
"path": ".\\web\\ASC.Web.Client"
|
||||
"path": "./web/ASC.Web.Client"
|
||||
},
|
||||
{
|
||||
"name": "ASC.People.Client"
|
||||
"path": ".\\products\\ASC.People\\Client"
|
||||
"path": "./products/ASC.People/Client"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview8.19405.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
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.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@ -22,36 +26,62 @@ namespace ASC.Api.Core.Auth
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (SecurityContext.IsAuthenticated)
|
||||
var Request = QueryHelpers.ParseQuery(Context.Request.Headers["confirm"]);
|
||||
_ = Request.TryGetValue("type", out var type);
|
||||
var _type = typeof(ConfirmType).TryParseEnum(type, ConfirmType.EmpInvite);
|
||||
|
||||
if (SecurityContext.IsAuthenticated && _type != ConfirmType.EmailChange)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(Context.User, new AuthenticationProperties(), Scheme.Name)));
|
||||
}
|
||||
|
||||
var Request = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(Context.Request.Headers["confirm"]);
|
||||
_ = Request.TryGetValue("type", out var type);
|
||||
_ = Request.TryGetValue("key", out var key);
|
||||
_ = Request.TryGetValue("emplType", out var emplType);
|
||||
_ = Request.TryGetValue("email", out var _email);
|
||||
var validInterval = SetupInfo.ValidEmailKeyInterval;
|
||||
|
||||
var _type = typeof(ConfirmType).TryParseEnum(type, ConfirmType.EmpInvite);
|
||||
EmailValidationKeyProvider.ValidationResult checkKeyResult;
|
||||
switch (_type)
|
||||
{
|
||||
case ConfirmType.EmpInvite:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + emplType, key, validInterval);
|
||||
break;
|
||||
|
||||
case ConfirmType.LinkInvite:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_type + emplType, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.EmailChange:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + SecurityContext.CurrentAccount.ID, key, validInterval);
|
||||
break;
|
||||
case ConfirmType.PasswordChange:
|
||||
var userHash = Request.TryGetValue("p", out var p) && p == "1";
|
||||
var hash = string.Empty;
|
||||
|
||||
if (userHash)
|
||||
{
|
||||
var tenantId = CoreContext.TenantManager.GetCurrentTenant().TenantId;
|
||||
hash = CoreContext.Authentication.GetUserPasswordHash(tenantId, CoreContext.UserManager.GetUserByEmail(tenantId, _email).ID);
|
||||
}
|
||||
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type + (string.IsNullOrEmpty(hash) ? string.Empty : Hasher.Base64Hash(hash)), key, validInterval);
|
||||
break;
|
||||
default:
|
||||
checkKeyResult = EmailValidationKeyProvider.ValidateEmailKey(_email + _type, key, validInterval);
|
||||
break;
|
||||
}
|
||||
|
||||
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem);
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new Claim(ClaimTypes.Role, _type.ToString())
|
||||
};
|
||||
|
||||
if (!SecurityContext.IsAuthenticated)
|
||||
{
|
||||
SecurityContext.AuthenticateMe(ASC.Core.Configuration.Constants.CoreSystem, claims);
|
||||
}
|
||||
else
|
||||
{
|
||||
SecurityContext.AuthenticateMe(SecurityContext.CurrentAccount, claims);
|
||||
}
|
||||
|
||||
var result = checkKeyResult switch
|
||||
{
|
||||
|
@ -30,27 +30,27 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ARSoft.Tools.NetStandard.DXSdata" Version="1.0.0" />
|
||||
<PackageReference Include="Autofac" Version="4.9.3" />
|
||||
<PackageReference Include="Autofac" Version="4.9.4" />
|
||||
<PackageReference Include="Autofac.Configuration" Version="4.1.0" />
|
||||
<PackageReference Include="Confluent.Kafka" Version="1.1.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.9.1" />
|
||||
<PackageReference Include="Grpc" Version="2.23.0-pre1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="Confluent.Kafka" Version="1.2.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.10.0-rc1" />
|
||||
<PackageReference Include="Grpc" Version="2.24.0-pre1" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="log4net" Version="2.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.0.0-preview8.19405.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="3.0.0-preview8.19405.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="3.0.0" />
|
||||
<PackageReference Include="MySql.Data" Version="8.0.17" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="NLog" Version="4.6.6" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3-beta1" />
|
||||
<PackageReference Include="NLog" Version="4.6.7" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.5" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NVelocity" Version="1.2.0" />
|
||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||
|
@ -44,15 +44,15 @@
|
||||
<None Remove="protos\UserPhotoCacheItem.proto" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.CloudFront" Version="3.3.101.30" />
|
||||
<PackageReference Include="AWSSDK.Core" Version="3.3.103.27" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.3.104.15" />
|
||||
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.101.33" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="AWSSDK.CloudFront" Version="3.3.101.46" />
|
||||
<PackageReference Include="AWSSDK.Core" Version="3.3.103.43" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.3.104.31" />
|
||||
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.3.101.50" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MailKit" Version="2.2.0" />
|
||||
<PackageReference Include="MailKit" Version="2.3.1.6" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Protobuf Include="protos\NotifyMessage.proto" />
|
||||
|
@ -157,7 +157,7 @@ namespace ASC.Core
|
||||
|
||||
public bool UserExists(int tenantId, Guid id)
|
||||
{
|
||||
return !UserExists(GetUsers(tenantId, id));
|
||||
return UserExists(GetUsers(tenantId, id));
|
||||
}
|
||||
|
||||
public bool UserExists(UserInfo user)
|
||||
|
@ -169,7 +169,7 @@ namespace ASC.Core
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string AuthenticateMe(IAccount account)
|
||||
public static string AuthenticateMe(IAccount account, List<Claim> additionalClaims = null)
|
||||
{
|
||||
if (account == null || account.Equals(Configuration.Constants.Guest)) throw new InvalidCredentialException("account");
|
||||
|
||||
@ -222,6 +222,11 @@ namespace ASC.Core
|
||||
};
|
||||
claims.AddRange(roles.Select(r => new Claim(ClaimTypes.Role, r)));
|
||||
|
||||
if(additionalClaims != null)
|
||||
{
|
||||
claims.AddRange(additionalClaims);
|
||||
}
|
||||
|
||||
Principal = new CustomClaimsPrincipal(new ClaimsIdentity(account, claims), account);
|
||||
|
||||
return cookie;
|
||||
|
@ -102,7 +102,8 @@ namespace ASC.Core.Data
|
||||
var groupQuery = new SqlQuery("core_usergroup cug")
|
||||
.Select("cug.userid")
|
||||
.Where(Exp.EqColumns("cug.tenant", "u.tenant"))
|
||||
.Where(Exp.EqColumns("u.id", "cug.userid"));
|
||||
.Where(Exp.EqColumns("u.id", "cug.userid"))
|
||||
.Where("cug.removed", false);
|
||||
|
||||
foreach (var groups in includeGroups)
|
||||
{
|
||||
|
@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="UAParser" Version="3.1.41" />
|
||||
<PackageReference Include="UAParser" Version="3.1.42" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -20,14 +20,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Api.Gax" Version="2.10.0-beta02" />
|
||||
<PackageReference Include="Google.Api.Gax.Rest" Version="2.10.0-beta02" />
|
||||
<PackageReference Include="Google.Apis" Version="1.40.3" />
|
||||
<PackageReference Include="Google.Apis.Auth" Version="1.40.3" />
|
||||
<PackageReference Include="Google.Apis.Core" Version="1.40.3" />
|
||||
<PackageReference Include="Google.Apis.Storage.v1" Version="1.40.3.1635" />
|
||||
<PackageReference Include="Google.Api.Gax" Version="2.10.0-beta04" />
|
||||
<PackageReference Include="Google.Api.Gax.Rest" Version="2.10.0-beta04" />
|
||||
<PackageReference Include="Google.Apis" Version="1.41.1" />
|
||||
<PackageReference Include="Google.Apis.Auth" Version="1.41.1" />
|
||||
<PackageReference Include="Google.Apis.Core" Version="1.41.1" />
|
||||
<PackageReference Include="Google.Apis.Storage.v1" Version="1.41.1.1716" />
|
||||
<PackageReference Include="Google.Cloud.Storage.V1" Version="2.4.0-beta03" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetOpenAuth.Ultimate" Version="4.3.4.13329" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="UAParser" Version="3.1.41" />
|
||||
<PackageReference Include="UAParser" Version="3.1.42" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -13,9 +13,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.6.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.23.0-pre1">
|
||||
<PackageReference Include="Grpc.Tools" Version="2.24.0-pre1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3-beta1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -53,7 +53,8 @@
|
||||
"hub": {
|
||||
"url": "",
|
||||
"internal": ""
|
||||
}
|
||||
},
|
||||
"cultures": "en-US,ru-RU"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"default": {
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ConnectionStrings": {
|
||||
"default": {
|
||||
"name": "default",
|
||||
"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",
|
||||
"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",
|
||||
"providerName": "MySql.Data.MySqlClient"
|
||||
}
|
||||
},
|
||||
|
@ -8,6 +8,7 @@
|
||||
"axios": "^0.19.0",
|
||||
"bootstrap": "4.3.1",
|
||||
"connected-react-router": "6.5.2",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"history": "4.9.0",
|
||||
"i18next": "17.0.12",
|
||||
"i18next-browser-languagedetector": "3.0.3",
|
||||
|
@ -9,6 +9,7 @@ import Profile from './components/pages/Profile';
|
||||
import ProfileAction from './components/pages/ProfileAction';
|
||||
import GroupAction from './components/pages/GroupAction';
|
||||
import { Error404 } from "./components/pages/Error";
|
||||
import Reassign from './components/pages/Reassign';
|
||||
|
||||
/*const Profile = lazy(() => import("./components/pages/Profile"));
|
||||
const ProfileAction = lazy(() => import("./components/pages/ProfileAction"));
|
||||
@ -49,6 +50,11 @@ const App = ({ settings }) => {
|
||||
component={GroupAction}
|
||||
restricted
|
||||
/>
|
||||
<PrivateRoute
|
||||
path={`${homepage}/reassign/:userId`}
|
||||
component={Reassign}
|
||||
restricted
|
||||
/>
|
||||
<PrivateRoute component={Error404} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
@ -7,12 +7,20 @@ import {
|
||||
DropDownItem,
|
||||
toastr
|
||||
} from "asc-web-components";
|
||||
import InviteDialog from './../../dialogs/Invite';
|
||||
import { isAdmin } from '../../../store/auth/selectors';
|
||||
import { withTranslation, I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { typeUser, typeGuest, department } from './../../../helpers/customNames';
|
||||
|
||||
class PureArticleMainButtonContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dialogVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
onDropDownItemClick = (link) => {
|
||||
this.props.history.push(link);
|
||||
};
|
||||
@ -21,48 +29,57 @@ class PureArticleMainButtonContent extends React.Component {
|
||||
toastr.success(text);
|
||||
};
|
||||
|
||||
onInvitationDialogClick = () => this.setState({ dialogVisible: !this.state.dialogVisible });
|
||||
|
||||
render() {
|
||||
console.log("People ArticleMainButtonContent render");
|
||||
const { isAdmin, settings, t } = this.props;
|
||||
return (
|
||||
isAdmin ?
|
||||
<MainButton
|
||||
isDisabled={false}
|
||||
isDropdown={true}
|
||||
text={t('Actions')}
|
||||
>
|
||||
<DropDownItem
|
||||
icon="CatalogEmployeeIcon"
|
||||
label={t('CustomNewEmployee', { typeUser })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/create/user`)}
|
||||
<>
|
||||
<MainButton
|
||||
isDisabled={false}
|
||||
isDropdown={true}
|
||||
text={t('Actions')}
|
||||
>
|
||||
<DropDownItem
|
||||
icon="CatalogEmployeeIcon"
|
||||
label={t('CustomNewEmployee', { typeUser })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/create/user`)}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="CatalogGuestIcon"
|
||||
label={t('CustomNewGuest', { typeGuest })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/create/guest`)}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="CatalogDepartmentsIcon"
|
||||
label={t('CustomNewDepartment', { department })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/group/create`)}
|
||||
/>
|
||||
<DropDownItem isSeparator />
|
||||
<DropDownItem
|
||||
icon="InvitationLinkIcon"
|
||||
label={t('InviteLinkTitle')}
|
||||
onClick={this.onInvitationDialogClick}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="PlaneIcon"
|
||||
label={t('LblInviteAgain')}
|
||||
onClick={this.onNotImplementedClick.bind(this, "Invite again action")}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="ImportIcon"
|
||||
label={t('ImportPeople')}
|
||||
onClick={this.onNotImplementedClick.bind(this, "Import people action")}
|
||||
/>
|
||||
</MainButton>
|
||||
<InviteDialog
|
||||
visible={this.state.dialogVisible}
|
||||
onClose={this.onInvitationDialogClick}
|
||||
onCloseButton={this.onInvitationDialogClick}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="CatalogGuestIcon"
|
||||
label={t('CustomNewGuest', { typeGuest })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/create/guest`)}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="CatalogDepartmentsIcon"
|
||||
label={t('CustomNewDepartment', { department })}
|
||||
onClick={this.onDropDownItemClick.bind(this, `${settings.homepage}/group/create`)}
|
||||
/>
|
||||
<DropDownItem isSeparator />
|
||||
<DropDownItem
|
||||
icon="InvitationLinkIcon"
|
||||
label={t('InviteLinkTitle')}
|
||||
onClick={this.onNotImplementedClick.bind(this, "Invitation link action")}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="PlaneIcon"
|
||||
label={t('LblInviteAgain')}
|
||||
onClick={this.onNotImplementedClick.bind(this, "Invite again action")}
|
||||
/>
|
||||
<DropDownItem
|
||||
icon="ImportIcon"
|
||||
label={t('ImportPeople')}
|
||||
onClick={this.onNotImplementedClick.bind(this, "Import people action")}
|
||||
/>
|
||||
</MainButton>
|
||||
</>
|
||||
:
|
||||
<></>
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ class PurePeopleLayout extends React.Component {
|
||||
}
|
||||
|
||||
onAboutClick = () => {
|
||||
console.log('About clicked');
|
||||
window.location.href = "/about";
|
||||
}
|
||||
|
||||
onLogoutClick = () => {
|
||||
|
@ -0,0 +1,57 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
import config from "../../../../package.json";
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: 'en',
|
||||
fallbackLng: "en",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === 'lowercase') return value.toLowerCase();
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${config.homepage}/locales/Invite/{{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',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === 'lowercase') return value.toLowerCase();
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default newInstance;
|
@ -0,0 +1,201 @@
|
||||
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
|
||||
} 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';
|
||||
|
||||
const ModalDialogContainer = styled.div`
|
||||
.margin-text {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.margin-link {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.margin-textarea {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.flex{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
|
||||
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'));
|
||||
};
|
||||
|
||||
onCheckedGuest = () => this.setState({ isGuest: !this.state.isGuest });
|
||||
|
||||
onGetShortenedLink = () => {
|
||||
this.setState({ isLoading: true });
|
||||
const { getShortenedLink, userInvitationLink, guestInvitationLink } = this.props;
|
||||
|
||||
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 });
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</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>;
|
||||
|
||||
InviteDialog.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onCloseButton: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { getInvitationLink, getShortenedLink })(withRouter(InviteDialog));
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"InviteLinkTitle": "Invitation link",
|
||||
"HelpAnswerLinkInviteSettings": "Share the link to invite your colleagues to your portal.",
|
||||
"InviteLinkValidInterval": "This link is valid for {{ count }} day only.",
|
||||
"CopyToClipboard": "Copy the link",
|
||||
"CloseButton": "Close",
|
||||
"LinkCopySuccess": "Link has been copied to the clipboard",
|
||||
"GetShortenLink": "Get shortened link",
|
||||
"InviteUsersAsCollaborators": "Add users as {{typeGuests, lowercase}}",
|
||||
"LoadingProcessing": "Loading...",
|
||||
|
||||
"InviteLinkValidInterval_plural": "This link is valid for {{ count }} days only."
|
||||
}
|
@ -1,116 +1,522 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import React from "react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { withRouter } from "react-router";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Button,
|
||||
TextInput,
|
||||
Text,
|
||||
InputBlock,
|
||||
Icons,
|
||||
SelectedItem
|
||||
SelectedItem,
|
||||
AdvancedSelector,
|
||||
FieldContainer,
|
||||
ComboBox,
|
||||
ComboButton,
|
||||
ModalDialog,
|
||||
SearchInput,
|
||||
toastr,
|
||||
utils
|
||||
} from "asc-web-components";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { department, headOfDepartment, typeUser } from '../../../../../helpers/customNames';
|
||||
import { connect } from 'react-redux';
|
||||
import { resetGroup } from '../../../../../store/group/actions';
|
||||
import {
|
||||
department,
|
||||
headOfDepartment,
|
||||
typeUser
|
||||
} from "../../../../../helpers/customNames";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
resetGroup,
|
||||
createGroup,
|
||||
updateGroup
|
||||
} from "../../../../../store/group/actions";
|
||||
import styled from "styled-components";
|
||||
import { fetchSelectorUsers } from "../../../../../store/people/actions";
|
||||
import { GUID_EMPTY } from "../../../../../helpers/constants";
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
const SectionBodyContent = (props) => {
|
||||
const { history, group, resetGroup } = props;
|
||||
const [value, setValue] = useState(group ? group.name : "");
|
||||
const [error, setError] = useState(null);
|
||||
const [inLoading, setInLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
useEffect(() => {
|
||||
setValue(group ? group.name : "");
|
||||
setError(null);
|
||||
setInLoading(false);
|
||||
}, [group]);
|
||||
.group-name_container {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
const groupMembers = group && group.members ? group.members : [];
|
||||
.head_container {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.members_container {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.search_container {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.selected-members_container {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
|
||||
.selected-item {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons_container {
|
||||
margin-top: 60px;
|
||||
|
||||
.cancel-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media ${utils.device.tablet} {
|
||||
.search_container {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class SectionBodyContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.mapPropsToState();
|
||||
}
|
||||
|
||||
mapPropsToState = () => {
|
||||
const { group, users, groups, t } = this.props;
|
||||
|
||||
const newState = {
|
||||
id: group ? group.id : "",
|
||||
groupName: group ? group.name : "",
|
||||
searchValue: "",
|
||||
error: null,
|
||||
inLoading: false,
|
||||
isHeaderSelectorOpen: false,
|
||||
isUsersSelectorOpen: false,
|
||||
users: users,
|
||||
groups: groups,
|
||||
modalVisible: false,
|
||||
header: group
|
||||
? {
|
||||
key: 0,
|
||||
label: "{SELECTED HEADER NAME}" //group.head
|
||||
}
|
||||
: {
|
||||
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 })
|
||||
}
|
||||
};
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { users, fetchSelectorUsers } = this.props;
|
||||
if(!users || !users.length) {
|
||||
fetchSelectorUsers();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
//const { users, group } = this.props;
|
||||
if(!isEqual(this.props, prevProps)) {
|
||||
this.setState(this.mapPropsToState());
|
||||
}
|
||||
}
|
||||
|
||||
onGroupChange = e => {
|
||||
this.setState({
|
||||
groupName: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
onSearchChange = e => {
|
||||
this.setState({
|
||||
searchValue: e.target.value
|
||||
});
|
||||
};
|
||||
|
||||
onHeadSelectorSearch = value => {
|
||||
/*setOptions(
|
||||
options.filter(option => {
|
||||
return option.label.indexOf(value) > -1;
|
||||
})
|
||||
);*/
|
||||
};
|
||||
|
||||
onHeadSelectorSelect = option => {
|
||||
this.setState({
|
||||
groupManager: {
|
||||
key: option.key,
|
||||
label: option.label
|
||||
},
|
||||
isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
onHeadSelectorClick = () => {
|
||||
this.setState({
|
||||
isHeaderSelectorOpen: !this.state.isHeaderSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
onUsersSelectorSearch = (value) => {
|
||||
/*setOptions(
|
||||
options.filter(option => {
|
||||
return option.label.indexOf(value) > -1;
|
||||
})
|
||||
);*/
|
||||
};
|
||||
onUsersSelectorSelect = (selectedOptions) => {
|
||||
//console.log("onSelect", selectedOptions);
|
||||
//this.onUsersSelectorClick();
|
||||
this.setState({
|
||||
groupMembers: selectedOptions.map(option => {
|
||||
return {
|
||||
key: option.key,
|
||||
label: option.label
|
||||
};
|
||||
}),
|
||||
isUsersSelectorOpen: !this.state.isUsersSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
onUsersSelectorClick = () => {
|
||||
this.setState({
|
||||
isUsersSelectorOpen: !this.state.isUsersSelectorOpen
|
||||
});
|
||||
};
|
||||
|
||||
toggleModalVisible = () => {
|
||||
this.setState({
|
||||
modalVisible: !this.state.modalVisible
|
||||
});
|
||||
};
|
||||
|
||||
onSave = () => {
|
||||
const { history, group, createGroup, updateGroup, resetGroup } = 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 });
|
||||
resetGroup();
|
||||
history.goBack();
|
||||
})
|
||||
.catch(error => {
|
||||
toastr.error(error.message);
|
||||
this.setState({ inLoading: false });
|
||||
});
|
||||
};
|
||||
|
||||
onCancel = () => {
|
||||
const { history, resetGroup } = this.props;
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
resetGroup();
|
||||
history.goBack();
|
||||
}, [history, resetGroup]);
|
||||
};
|
||||
|
||||
const onChange = useCallback((e) => setValue(e.target.value), [setValue]);
|
||||
onSelectedItemClose = (member) => {
|
||||
this.setState({
|
||||
groupMembers: this.state.groupMembers.filter(g => g.key !== member.key)
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Group render", props);
|
||||
renderModal = () => {
|
||||
const { groups, modalVisible } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<label htmlFor="group-name">
|
||||
<Text.Body as="span" isBold={true}>{t('CustomDepartmentName', { department })}:</Text.Body>
|
||||
</label>
|
||||
<div style={{width: "320px"}}>
|
||||
<TextInput id="group-name" name="group-name" scale={true} isAutoFocussed={true} tabIndex={1} value={value} onChange={onChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
<label htmlFor="head-selector">
|
||||
<Text.Body as="span" isBold={true}>{t('CustomHeadOfDepartment', { headOfDepartment })}:</Text.Body>
|
||||
</label>
|
||||
<InputBlock
|
||||
id="head-selector"
|
||||
value={t('CustomAddEmployee', { typeUser })}
|
||||
iconName="ExpanderDownIcon"
|
||||
iconSize={8}
|
||||
isIconFill={true}
|
||||
iconColor="#A3A9AE"
|
||||
scale={false}
|
||||
isReadOnly={true}
|
||||
tabIndex={2}
|
||||
>
|
||||
<Icons.CatalogEmployeeIcon size="medium" />
|
||||
</InputBlock>
|
||||
</div>
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
<label htmlFor="employee-selector">
|
||||
<Text.Body as="span" isBold={true}>Members:</Text.Body>
|
||||
</label>
|
||||
<InputBlock
|
||||
id="employee-selector"
|
||||
value={t('CustomAddEmployee', { typeUser })}
|
||||
iconName="ExpanderDownIcon"
|
||||
iconSize={8}
|
||||
isIconFill={true}
|
||||
iconColor="#A3A9AE"
|
||||
scale={false}
|
||||
isReadOnly={true}
|
||||
tabIndex={3}
|
||||
>
|
||||
<Icons.CatalogGuestIcon size="medium" />
|
||||
</InputBlock>
|
||||
</div>
|
||||
<div style={{ marginTop: "16px", display: "flex", flexWrap: "wrap", flexDirection: "row" }}>
|
||||
{groupMembers.map(member =>
|
||||
<SelectedItem
|
||||
key={member.id}
|
||||
text={member.displayName}
|
||||
onClick={(e) => console.log("onClick", e.target)}
|
||||
onClose={(e) => console.log("onClose", e.target)}
|
||||
isInline={true}
|
||||
style={{ marginRight: "8px", marginBottom: "8px" }}
|
||||
return (
|
||||
<ModalDialog
|
||||
zIndex={1001}
|
||||
visible={modalVisible}
|
||||
headerContent="New User"
|
||||
bodyContent={
|
||||
<div className="create_new_user_modal">
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
labelText={"First name:"}
|
||||
>
|
||||
<TextInput
|
||||
value={""}
|
||||
hasError={false}
|
||||
className="firstName-input"
|
||||
scale={true}
|
||||
autoComplete="off"
|
||||
onChange={e => {
|
||||
//set(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
labelText={"Last name:"}
|
||||
>
|
||||
<TextInput
|
||||
value={""}
|
||||
hasError={false}
|
||||
className="lastName-input"
|
||||
scale={true}
|
||||
autoComplete="off"
|
||||
onChange={e => {
|
||||
//set(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
labelText={"E-mail:"}
|
||||
>
|
||||
<TextInput
|
||||
value={""}
|
||||
hasError={false}
|
||||
className="email-input"
|
||||
scale={true}
|
||||
autoComplete="off"
|
||||
onChange={e => {
|
||||
//set(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
isVertical={true}
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
labelText={"Group:"}
|
||||
>
|
||||
<ComboBox
|
||||
options={groups}
|
||||
className="group-input"
|
||||
onSelect={option => console.log("Selected option", option)}
|
||||
selectedOption={{
|
||||
key: 0,
|
||||
label: "Select"
|
||||
}}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
/>
|
||||
</FieldContainer>
|
||||
</div>
|
||||
}
|
||||
footerContent={[
|
||||
<Button
|
||||
key="CreateBtn"
|
||||
label="Create"
|
||||
primary={true}
|
||||
size="big"
|
||||
onClick={e => {
|
||||
console.log("CreateBtn click", e);
|
||||
this.toggleModalVisible();
|
||||
}}
|
||||
/>
|
||||
]}
|
||||
onClose={this.toggleModalVisible}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const {
|
||||
groupName,
|
||||
users,
|
||||
groups,
|
||||
groupMembers,
|
||||
isHeaderSelectorOpen: isHeadSelectorOpen,
|
||||
isUsersSelectorOpen,
|
||||
inLoading,
|
||||
error,
|
||||
searchValue,
|
||||
modalVisible,
|
||||
groupManager
|
||||
} = this.state;
|
||||
return (
|
||||
<MainContainer>
|
||||
<div style={{ visibility: "hidden", width: 1, height: 1 }}>
|
||||
<Icons.SearchIcon size="small" />
|
||||
</div>
|
||||
<FieldContainer
|
||||
className="group-name_container"
|
||||
isRequired={true}
|
||||
hasError={false}
|
||||
isVertical={true}
|
||||
labelText={t("CustomDepartmentName", { department })}
|
||||
>
|
||||
<TextInput
|
||||
id="group-name"
|
||||
name="group-name"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
value={groupName}
|
||||
onChange={this.onGroupChange}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
className="head_container"
|
||||
isRequired={false}
|
||||
hasError={false}
|
||||
isVertical={true}
|
||||
labelText={t("CustomHeadOfDepartment", { headOfDepartment })}
|
||||
>
|
||||
<ComboButton
|
||||
id="head-selector"
|
||||
tabIndex={2}
|
||||
options={[]}
|
||||
isOpen={isHeadSelectorOpen}
|
||||
selectedOption={groupManager}
|
||||
scaled={true}
|
||||
size="content"
|
||||
opened={isHeadSelectorOpen}
|
||||
onClick={this.onHeadSelectorClick}
|
||||
>
|
||||
<Icons.CatalogGuestIcon size="medium" />
|
||||
</ComboButton>
|
||||
<AdvancedSelector
|
||||
isDropDown={true}
|
||||
isOpen={isHeadSelectorOpen}
|
||||
size="full"
|
||||
placeholder={"Search"}
|
||||
onSearchChanged={this.onHeadSelectorSearch}
|
||||
options={users}
|
||||
groups={groups}
|
||||
isMultiSelect={false}
|
||||
buttonLabel={t("CustomAddEmployee", { typeUser })}
|
||||
selectAllLabel={"Select all"}
|
||||
onSelect={this.onHeadSelectorSelect}
|
||||
onCancel={this.onHeadSelectorClick}
|
||||
allowCreation={false}
|
||||
//onAddNewClick={toggleModalVisible}
|
||||
allowAnyClickClose={true}
|
||||
/>
|
||||
</FieldContainer>
|
||||
<FieldContainer
|
||||
className="members_container"
|
||||
isRequired={false}
|
||||
hasError={false}
|
||||
isVertical={true}
|
||||
labelText="Members"
|
||||
>
|
||||
<ComboButton
|
||||
id="users-selector"
|
||||
tabIndex={3}
|
||||
options={[]}
|
||||
isOpen={isUsersSelectorOpen}
|
||||
selectedOption={{
|
||||
key: 0,
|
||||
label: t("CustomAddEmployee", { typeUser })
|
||||
}}
|
||||
scaled={true}
|
||||
size="content"
|
||||
opened={isUsersSelectorOpen}
|
||||
onClick={this.onUsersSelectorClick}
|
||||
>
|
||||
<Icons.CatalogGuestIcon size="medium" />
|
||||
</ComboButton>
|
||||
<AdvancedSelector
|
||||
isDropDown={true}
|
||||
isOpen={isUsersSelectorOpen}
|
||||
size="full"
|
||||
placeholder={"Search"}
|
||||
onSearchChanged={this.onUsersSelectorSearch}
|
||||
options={users}
|
||||
groups={groups}
|
||||
isMultiSelect={true}
|
||||
buttonLabel={t("CustomAddEmployee", { typeUser })}
|
||||
selectAllLabel={"Select all"}
|
||||
onSelect={this.onUsersSelectorSelect}
|
||||
onCancel={this.onUsersSelectorClick}
|
||||
allowCreation={true}
|
||||
onAddNewClick={this.toggleModalVisible}
|
||||
allowAnyClickClose={!modalVisible}
|
||||
/>
|
||||
</FieldContainer>
|
||||
{groupMembers && groupMembers.length > 0 && (
|
||||
<div className="search_container">
|
||||
<SearchInput
|
||||
id="member-search"
|
||||
isDisabled={inLoading}
|
||||
scale={true}
|
||||
placeholder="Search"
|
||||
value={searchValue}
|
||||
onChange={this.onSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>{error && <strong>{error}</strong>}</div>
|
||||
<div style={{ marginTop: "60px" }}>
|
||||
<Button label={t('SaveButton')} primary type="submit" isDisabled={inLoading} size="big" tabIndex={4} />
|
||||
<Button
|
||||
label={t('CancelButton')}
|
||||
style={{ marginLeft: "8px" }}
|
||||
size="big"
|
||||
isDisabled={inLoading}
|
||||
onClick={onCancel}
|
||||
tabIndex={5}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
<div className="selected-members_container">
|
||||
{groupMembers.map(member => (
|
||||
<SelectedItem
|
||||
key={member.key}
|
||||
text={member.label}
|
||||
onClose={this.onSelectedItemClose.bind(this, member)}
|
||||
isInline={true}
|
||||
className="selected-item"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div>{error && <strong>{error}</strong>}</div>
|
||||
<div className="buttons_container">
|
||||
<Button
|
||||
label={t("SaveButton")}
|
||||
primary
|
||||
type="submit"
|
||||
isLoading={inLoading}
|
||||
size="big"
|
||||
tabIndex={4}
|
||||
onClick={this.onSave}
|
||||
/>
|
||||
<Button
|
||||
label={t("CancelButton")}
|
||||
className="cancel-button"
|
||||
size="big"
|
||||
isDisabled={inLoading}
|
||||
onClick={this.onCancel}
|
||||
tabIndex={5}
|
||||
/>
|
||||
</div>
|
||||
{this.renderModal()}
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SectionBodyContent.propTypes = {
|
||||
group: PropTypes.object
|
||||
@ -118,13 +524,42 @@ SectionBodyContent.propTypes = {
|
||||
|
||||
SectionBodyContent.defaultProps = {
|
||||
group: null
|
||||
}
|
||||
};
|
||||
|
||||
const convertUsers = users => {
|
||||
return users
|
||||
? users.map(u => {
|
||||
return {
|
||||
key: u.id,
|
||||
groups: u.groups || [],
|
||||
label: u.displayName
|
||||
};
|
||||
})
|
||||
: [];
|
||||
};
|
||||
|
||||
const convertGroups = groups => {
|
||||
return groups
|
||||
? groups.map(g => {
|
||||
return {
|
||||
key: g.id,
|
||||
label: g.name,
|
||||
total: 0
|
||||
};
|
||||
})
|
||||
: [];
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
settings: state.auth.settings,
|
||||
group: state.group.targetGroup
|
||||
group: state.group.targetGroup,
|
||||
groups: convertGroups(state.people.groups),
|
||||
users: convertUsers(state.people.selector.users) //TODO: replace to api requests with search
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { resetGroup })(withRouter(SectionBodyContent));
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ resetGroup, createGroup, updateGroup, fetchSelectorUsers }
|
||||
)(withRouter(withTranslation()(SectionBodyContent)));
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter } from "react-router";
|
||||
import PropTypes from "prop-types";
|
||||
import { IconButton, Text } from 'asc-web-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { department } from './../../../../../helpers/customNames';
|
||||
|
||||
import { IconButton, Text } from "asc-web-components";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { department } from "./../../../../../helpers/customNames";
|
||||
import { resetGroup } from "../../../../../store/group/actions";
|
||||
|
||||
const wrapperStyle = {
|
||||
display: "flex",
|
||||
@ -16,19 +16,31 @@ const textStyle = {
|
||||
marginLeft: "16px"
|
||||
};
|
||||
|
||||
const SectionHeaderContent = (props) => {
|
||||
const {group, history, settings} = props;
|
||||
const { t } = useTranslation();
|
||||
class SectionHeaderContent extends React.Component {
|
||||
onBackClick = () => {
|
||||
const { history, settings, resetGroup } = this.props;
|
||||
|
||||
const headerText = group ? t('CustomEditDepartment', { department }) : t('CustomNewDepartment', { department });
|
||||
resetGroup();
|
||||
history.push(settings.homepage);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<IconButton iconName={'ArrowPathIcon'} size="16" onClick={() => history.push(settings.homepage)}/>
|
||||
<Text.ContentHeader style={textStyle}>{headerText}</Text.ContentHeader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
render() {
|
||||
const { group, t } = this.props;
|
||||
const headerText = group
|
||||
? t("CustomEditDepartment", { department })
|
||||
: t("CustomNewDepartment", { department });
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<IconButton
|
||||
iconName={"ArrowPathIcon"}
|
||||
size="16"
|
||||
onClick={this.onBackClick}
|
||||
/>
|
||||
<Text.ContentHeader style={textStyle}>{headerText}</Text.ContentHeader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SectionHeaderContent.propTypes = {
|
||||
group: PropTypes.object,
|
||||
@ -44,6 +56,9 @@ function mapStateToProps(state) {
|
||||
settings: state.auth.settings,
|
||||
group: state.group.targetGroup
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ resetGroup }
|
||||
)(withTranslation()(withRouter(SectionHeaderContent)));
|
||||
|
@ -37,6 +37,7 @@ class GroupAction extends React.Component {
|
||||
<I18nextProvider i18n={i18n}>
|
||||
{group || !match.params.groupId
|
||||
? <PageLayout
|
||||
withBodyScroll={false}
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
|
@ -186,8 +186,9 @@ class SectionBodyContent extends React.PureComponent {
|
||||
.finally(() => onLoading(false));
|
||||
};
|
||||
|
||||
onReassignDataClick = () => {
|
||||
toastr.success("Context action: Reassign data");
|
||||
onReassignDataClick = user => {
|
||||
const { history, settings } = this.props;
|
||||
history.push(`${settings.homepage}/reassign/${user.userName}`);
|
||||
};
|
||||
|
||||
onDeletePersonalDataClick = user => {
|
||||
@ -390,7 +391,7 @@ class SectionBodyContent extends React.PureComponent {
|
||||
{
|
||||
key: "reassign-data",
|
||||
label: t("ReassignData"),
|
||||
onClick: this.onReassignDataClick
|
||||
onClick: this.onReassignDataClick.bind(this, user)
|
||||
},
|
||||
{
|
||||
key: "delete-personal-data",
|
||||
|
@ -16,11 +16,7 @@ import {
|
||||
} from "asc-web-components";
|
||||
import { connect } from "react-redux";
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
toEmployeeWrapper,
|
||||
getUserRole,
|
||||
getUserContacts
|
||||
} from "../../../../../store/people/selectors";
|
||||
import { getUserRole, getUserContacts } from "../../../../../store/people/selectors";
|
||||
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
|
||||
|
||||
const ProfileWrapper = styled.div`
|
||||
@ -448,6 +444,7 @@ const SectionBodyContent = props => {
|
||||
<EditButtonWrapper>
|
||||
<Button
|
||||
size="big"
|
||||
scale={true}
|
||||
label={t("EditUserDialogTitle")}
|
||||
onClick={onEditProfileClick}
|
||||
/>
|
||||
|
@ -1,15 +1,13 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Text,
|
||||
IconButton,
|
||||
ContextMenuButton,
|
||||
toastr
|
||||
} from "asc-web-components";
|
||||
import { Text, IconButton, ContextMenuButton, toastr } from "asc-web-components";
|
||||
import { withRouter } from "react-router";
|
||||
import { isAdmin, isMe } from "../../../../../store/auth/selectors";
|
||||
import { getUserStatus } from "../../../../../store/people/selectors";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { resendUserInvites } from "../../../../../store/services/api";
|
||||
import { EmployeeStatus } from "../../../../../helpers/constants";
|
||||
import { updateUserStatus } from "../../../../../store/people/actions";
|
||||
|
||||
const wrapperStyle = {
|
||||
display: "flex",
|
||||
@ -22,10 +20,12 @@ const textStyle = {
|
||||
};
|
||||
|
||||
const SectionHeaderContent = props => {
|
||||
const { profile, history, settings, isAdmin, viewer } = props;
|
||||
const { profile, history, settings, isAdmin, viewer, updateUserStatus } = props;
|
||||
|
||||
const onEditClick = user => {
|
||||
history.push(`${settings.homepage}/edit/${user.userName}`);
|
||||
const selectedUserIds = new Array(profile.id);
|
||||
|
||||
const onEditClick = () => {
|
||||
history.push(`${settings.homepage}/edit/${profile.userName}`);
|
||||
};
|
||||
|
||||
const onChangePasswordClick = () => {
|
||||
@ -41,7 +41,8 @@ const SectionHeaderContent = props => {
|
||||
};
|
||||
|
||||
const onDisableClick = () => {
|
||||
toastr.success("Context action: Disable");
|
||||
updateUserStatus(EmployeeStatus.Disabled, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
};
|
||||
|
||||
const onEditPhoto = () => {
|
||||
@ -49,31 +50,34 @@ const SectionHeaderContent = props => {
|
||||
};
|
||||
|
||||
const onEnableClick = () => {
|
||||
toastr.success("Context action: Enable");
|
||||
updateUserStatus(EmployeeStatus.Active, selectedUserIds);
|
||||
toastr.success(t("SuccessChangeUserStatus"));
|
||||
};
|
||||
|
||||
const onReassignDataClick = () => {
|
||||
toastr.success("Context action: Reassign data");
|
||||
|
||||
const onReassignDataClick = user => {
|
||||
const { history, settings } = props;
|
||||
history.push(`${settings.homepage}/reassign/${user.userName}`);
|
||||
};
|
||||
|
||||
const onDeletePersonalDataClick = user => {
|
||||
|
||||
const onDeletePersonalDataClick = () => {
|
||||
toastr.success("Context action: Delete personal data");
|
||||
};
|
||||
|
||||
|
||||
const onDeleteProfileClick = () => {
|
||||
toastr.success("Context action: Delete profile");
|
||||
};
|
||||
|
||||
const onInviteAgainClick = () => {
|
||||
toastr.success("Context action: Invite again");
|
||||
};
|
||||
|
||||
const onInviteAgainClick = () => {
|
||||
resendUserInvites(selectedUserIds)
|
||||
.then(() => toastr.success("The invitation was successfully sent"))
|
||||
.catch(e => toastr.error("ERROR"));
|
||||
};
|
||||
const getUserContextOptions = (user, viewer, t) => {
|
||||
|
||||
let status = "";
|
||||
|
||||
if(isAdmin|| (!isAdmin && isMe(user, viewer.userName))) {
|
||||
status = getUserStatus(user);
|
||||
if (isAdmin || (!isAdmin && isMe(user, viewer.userName))) {
|
||||
status = getUserStatus(user);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
@ -83,7 +87,7 @@ const SectionHeaderContent = props => {
|
||||
{
|
||||
key: "edit",
|
||||
label: t('EditUserDialogTitle'),
|
||||
onClick: onEditClick.bind(this, user)
|
||||
onClick: onEditClick
|
||||
},
|
||||
{
|
||||
key: "edit-photo",
|
||||
@ -126,12 +130,12 @@ const SectionHeaderContent = props => {
|
||||
{
|
||||
key: "reassign-data",
|
||||
label: t('ReassignData'),
|
||||
onClick: onReassignDataClick
|
||||
onClick: onReassignDataClick.bind(this, user)
|
||||
},
|
||||
{
|
||||
key: "delete-personal-data",
|
||||
label: t('RemoveData'),
|
||||
onClick: onDeletePersonalDataClick.bind(this, user)
|
||||
onClick: onDeletePersonalDataClick
|
||||
},
|
||||
{
|
||||
key: "delete-profile",
|
||||
@ -144,7 +148,7 @@ const SectionHeaderContent = props => {
|
||||
{
|
||||
key: "edit",
|
||||
label: t('EditButton'),
|
||||
onClick: onEditClick.bind(this, user)
|
||||
onClick: onEditClick
|
||||
},
|
||||
{
|
||||
key: "edit-photo",
|
||||
@ -157,7 +161,7 @@ const SectionHeaderContent = props => {
|
||||
onClick: onInviteAgainClick
|
||||
},
|
||||
{
|
||||
key: "key5",
|
||||
key: "disable",
|
||||
label: t('DisableUserButton'),
|
||||
onClick: onDisableClick
|
||||
}
|
||||
@ -207,4 +211,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));
|
||||
export default connect(mapStateToProps, { updateUserStatus })(withRouter(SectionHeaderContent));
|
||||
|
@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { withRouter } from "react-router";
|
||||
// import { useTranslation } from 'react-i18next';
|
||||
import { connect } from "react-redux";
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
||||
const InfoContainer = styled.div`
|
||||
margin-bottom: 24px;
|
||||
`;
|
||||
|
||||
const SectionBodyContent = props => {
|
||||
// const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<InfoContainer>
|
||||
See this feature in next version!
|
||||
</InfoContainer>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionBodyContent));
|
@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Text, IconButton } from "asc-web-components";
|
||||
import { withRouter } from "react-router";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const wrapperStyle = {
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
};
|
||||
|
||||
const textStyle = {
|
||||
marginLeft: "16px",
|
||||
marginRight: "16px"
|
||||
};
|
||||
|
||||
const SectionHeaderContent = props => {
|
||||
const { history, settings } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div style={wrapperStyle}>
|
||||
<div style={{ width: "16px" }}>
|
||||
<IconButton
|
||||
iconName={"ArrowPathIcon"}
|
||||
color="#A3A9AE"
|
||||
size="16"
|
||||
onClick={() => history.push(settings.homepage)}
|
||||
/>
|
||||
</div>
|
||||
<Text.ContentHeader truncate={true} style={textStyle}>
|
||||
{/* {profile.displayName}
|
||||
{profile.isLDAP && ` (${t('LDAPLbl')})`}
|
||||
- */}
|
||||
{t('ReassignmentData')}
|
||||
</Text.ContentHeader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
profile: state.profile.targetUser,
|
||||
settings: state.auth.settings
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withRouter(SectionHeaderContent));
|
@ -0,0 +1,2 @@
|
||||
export { default as SectionHeaderContent } from './Header';
|
||||
export { default as SectionBodyContent } from './Body';
|
@ -0,0 +1,50 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
import config from "../../../../package.json";
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `${config.homepage}/locales/Reassign/{{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',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default newInstance;
|
@ -0,0 +1,75 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
// import PropTypes from "prop-types";
|
||||
import { PageLayout } from "asc-web-components";
|
||||
import { ArticleHeaderContent, ArticleMainButtonContent, ArticleBodyContent } from '../../Article';
|
||||
// import { SectionHeaderContent } from './Section';
|
||||
// import { fetchProfile } from '../../../store/profile/actions';
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { SectionHeaderContent, SectionBodyContent } from './Section';
|
||||
|
||||
class Reassign extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
// const { match, fetchProfile } = this.props;
|
||||
// const { userId } = match.params;
|
||||
|
||||
// if (userId) {
|
||||
// fetchProfile(userId);
|
||||
// }
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// const { match, fetchProfile } = this.props;
|
||||
// const { userId } = match.params;
|
||||
// const prevUserId = prevProps.match.params.userId;
|
||||
|
||||
// if (userId !== undefined && userId !== prevUserId) {
|
||||
// fetchProfile(userId);
|
||||
// }
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log("Reassign render")
|
||||
|
||||
// let loaded = false;
|
||||
// const { profile, match } = this.props;
|
||||
// const { userId, type } = match.params;
|
||||
|
||||
// if (type) {
|
||||
// loaded = true;
|
||||
// } else if (profile) {
|
||||
// loaded = profile.userName === userId || profile.id === userId;
|
||||
// }
|
||||
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<PageLayout
|
||||
articleHeaderContent={<ArticleHeaderContent />}
|
||||
articleMainButtonContent={<ArticleMainButtonContent />}
|
||||
articleBodyContent={<ArticleBodyContent />}
|
||||
sectionHeaderContent={<SectionHeaderContent />}
|
||||
sectionBodyContent={<SectionBodyContent />}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reassign.propTypes = {
|
||||
// match: PropTypes.object.isRequired,
|
||||
// profile: PropTypes.object,
|
||||
// fetchProfile: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
// profile: state.profile.targetUser
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
})(Reassign);
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"ReassignmentData": "Reassignment of data",
|
||||
"ReassignsToUser": "Employee to whom the data will be transferred —",
|
||||
"ChooseUser": "Choose user",
|
||||
"ReassignsTransferedListHdr": "Will be transferred:",
|
||||
"ReassignsTransferedListItem1": "General documents and personal documents that are available to other portal users;",
|
||||
"ReassignsTransferedListItem2": "Open projects, milestones and tasks;",
|
||||
"ReassignsTransferedListItem3": "Contacts, open tasks, unclosed opportunities and CRM cases;",
|
||||
"NotBeUndone": "Note: this action cannot be undone.",
|
||||
"ReassignsReadMore": "More about data transfer",
|
||||
"DeleteProfileAfterReassignment": "Delete profile when reassignment will be finished",
|
||||
"CancelButton": "Cancel",
|
||||
"ReassignButton": "Reassign",
|
||||
|
||||
"LDAPLbl": "LDAP"
|
||||
}
|
@ -4,7 +4,6 @@ $font-family-base: 'Open Sans', sans-serif;
|
||||
// Import Bootstrap and its default variables
|
||||
@import '~bootstrap/scss/bootstrap.scss';
|
||||
@import '~react-toastify/dist/ReactToastify.min.css';
|
||||
@import "~react-datepicker/dist/react-datepicker.css";
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const AUTH_KEY = "asc_auth_key";
|
||||
export const GUID_EMPTY = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
/**
|
||||
* Enum for employee activation status.
|
||||
|
@ -3,5 +3,6 @@ export const department = 'Department';
|
||||
export const position = 'Position';
|
||||
export const employedSinceDate = 'Employed since';
|
||||
export const typeGuest = 'Guest';
|
||||
export const typeGuests = 'Guests';
|
||||
export const typeUser = 'Employee';
|
||||
export const headOfDepartment = 'Head of Department';
|
||||
|
@ -9,6 +9,22 @@
|
||||
"ImportPeople"
|
||||
]
|
||||
},
|
||||
"dialogs": {
|
||||
"Invite": {
|
||||
"Resource": [
|
||||
"HelpAnswerLinkInviteSettings",
|
||||
"CopyToClipboard",
|
||||
"CloseButton",
|
||||
"GetShortenLink",
|
||||
"LoadingProcessing",
|
||||
"InviteUsersAsCollaborators",
|
||||
"InviteLinkTitle"
|
||||
],
|
||||
"ResourceJS": [
|
||||
"LinkCopySuccess"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"Profile": {
|
||||
"Resource": [
|
||||
@ -112,5 +128,25 @@
|
||||
"SaveButton",
|
||||
"CancelButton"
|
||||
]
|
||||
},
|
||||
"Reassign":{
|
||||
"PeopleResource":[
|
||||
"ReassignmentData",
|
||||
"ReassignsTransferedListHdr",
|
||||
"ReassignsTransferedListItem1",
|
||||
"ReassignsTransferedListItem2",
|
||||
"ReassignsTransferedListItem3",
|
||||
"ReassignsReadMore",
|
||||
"DeleteProfileAfterReassignment",
|
||||
"CancelButton",
|
||||
"ReassignButton",
|
||||
"ReassignsToUser"
|
||||
],
|
||||
"UserControlsCommonResource.resx":[
|
||||
"NotBeUndone"
|
||||
],
|
||||
"PeopleJSResource":[
|
||||
"ChooseUser"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ const initialState = {
|
||||
datePattern: "mm/dd/yy",
|
||||
dateTimePattern: "DD, mm dd, yy h:mm:ss tt",
|
||||
timePattern: "h:mm tt"
|
||||
}
|
||||
},
|
||||
hasShortenService: false
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import * as api from "../../store/services/api";
|
||||
import { setGroups } from '../people/actions';
|
||||
|
||||
export const SET_GROUP = "SET_PROFILE";
|
||||
export const CLEAN_GROUP = "CLEAN_PROFILE";
|
||||
export const SET_GROUP = "SET_GROUP";
|
||||
export const CLEAN_GROUP = "CLEAN_GROUP";
|
||||
|
||||
export function setGroup(targetGroup) {
|
||||
return {
|
||||
@ -32,3 +33,49 @@ export function fetchGroup(groupId) {
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function createGroup(groupName, groupManager, members) {
|
||||
return (dispatch, getState) => {
|
||||
const { people } = getState();
|
||||
const { groups } = people;
|
||||
|
||||
return api.createGroup(groupName, groupManager, members)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
const newGroup = res.data.response;
|
||||
|
||||
dispatch(setGroup(newGroup));
|
||||
dispatch(setGroups([...groups, newGroup]));
|
||||
|
||||
return Promise.resolve(newGroup);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function updateGroup(id, groupName, groupManager, members) {
|
||||
return (dispatch, getState) => {
|
||||
const { people } = getState();
|
||||
const { groups } = people;
|
||||
|
||||
return api.updateGroup(id, groupName, groupManager, members)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
const newGroup = res.data.response;
|
||||
|
||||
dispatch(setGroup(newGroup));
|
||||
|
||||
const newGroups = groups.map(g => {
|
||||
|
||||
if(g.id === id) {
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
return g;
|
||||
})
|
||||
|
||||
dispatch(setGroups(newGroups));
|
||||
|
||||
return Promise.resolve(newGroup);
|
||||
});;
|
||||
};
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ export const DESELECT_USER = "DESELECT_USER";
|
||||
export const SET_SELECTED = "SET_SELECTED";
|
||||
export const SET_FILTER = "SET_FILTER";
|
||||
export const SELECT_GROUP = "SELECT_GROUP";
|
||||
export const SET_SELECTOR_USERS = "SET_SELECTOR_USERS";
|
||||
|
||||
export function setUser(user) {
|
||||
return {
|
||||
@ -79,6 +80,20 @@ export function setFilter(filter) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setSelectorUsers(users) {
|
||||
return {
|
||||
type: SET_SELECTOR_USERS,
|
||||
users
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchSelectorUsers() {
|
||||
return dispatch => {
|
||||
api.getSelectorUserList()
|
||||
.then(res => dispatch(setSelectorUsers(res.data.response)));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchPeople(filter) {
|
||||
return dispatch => {
|
||||
return fetchPeopleByFilter(dispatch, filter);
|
||||
|
@ -7,7 +7,8 @@ import {
|
||||
SET_SELECTED,
|
||||
SET_FILTER,
|
||||
SELECT_GROUP,
|
||||
SET_USER
|
||||
SET_USER,
|
||||
SET_SELECTOR_USERS
|
||||
} from "./actions";
|
||||
import { isUserSelected, skipUser, getUsersBySelected } from "./selectors";
|
||||
import Filter from "./filter";
|
||||
@ -18,7 +19,10 @@ const initialState = {
|
||||
selection: [],
|
||||
selected: "none",
|
||||
selectedGroup: null,
|
||||
filter: Filter.getDefault()
|
||||
filter: Filter.getDefault(),
|
||||
selector: {
|
||||
users: []
|
||||
}
|
||||
};
|
||||
|
||||
const peopleReducer = (state = initialState, action) => {
|
||||
@ -66,6 +70,12 @@ const peopleReducer = (state = initialState, action) => {
|
||||
return Object.assign({}, state, {
|
||||
selectedGroup: action.groupId
|
||||
});
|
||||
case SET_SELECTOR_USERS:
|
||||
return Object.assign({}, state, {
|
||||
selector: Object.assign({}, state.selector, {
|
||||
users: action.users
|
||||
})
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export function employeeWrapperToMemberModel(profile) {
|
||||
const department = profile.groups ? profile.groups.map(group => group.id) : [];
|
||||
const worksFrom = profile.workFrom;
|
||||
|
||||
return {...profile, comment, department, worksFrom};
|
||||
return { ...profile, comment, department, worksFrom };
|
||||
}
|
||||
|
||||
export function fetchProfile(userName) {
|
||||
@ -56,8 +56,8 @@ export function fetchProfile(userName) {
|
||||
|
||||
export function createProfile(profile) {
|
||||
return (dispatch, getState) => {
|
||||
const {people} = getState();
|
||||
const {filter} = people;
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
const member = employeeWrapperToMemberModel(profile);
|
||||
let result;
|
||||
|
||||
@ -75,8 +75,8 @@ export function createProfile(profile) {
|
||||
|
||||
export function updateProfile(profile) {
|
||||
return (dispatch, getState) => {
|
||||
const {people} = getState();
|
||||
const {filter} = people;
|
||||
const { people } = getState();
|
||||
const { filter } = people;
|
||||
const member = employeeWrapperToMemberModel(profile);
|
||||
let result;
|
||||
|
||||
@ -120,5 +120,26 @@ export function deleteAvatar(profileId) {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
export function getInvitationLink(isGuest = false) {
|
||||
return dispatch => {
|
||||
return api.getInvitationLink(isGuest)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getShortenedLink(link) {
|
||||
return dispatch => {
|
||||
return api.getShortenedLink(link)
|
||||
.then(res => {
|
||||
checkResponseError(res);
|
||||
return Promise.resolve(res);
|
||||
});
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ 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);
|
||||
@ -16,17 +17,17 @@ 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 } });
|
||||
});
|
||||
.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 } });
|
||||
});
|
||||
}
|
||||
|
||||
export function getSettings() {
|
||||
@ -47,6 +48,10 @@ export function getUser(userId) {
|
||||
: axios.get(`${API_URL}/people/${userId || "@self"}.json`);
|
||||
}
|
||||
|
||||
export function getSelectorUserList() {
|
||||
return axios.get(`${API_URL}/people.json?fields=id,displayName,groups`);
|
||||
}
|
||||
|
||||
export function getUserList(filter = Filter.getDefault()) {
|
||||
const params =
|
||||
filter && filter instanceof Filter
|
||||
@ -86,8 +91,8 @@ export function deleteAvatar(profileId) {
|
||||
}
|
||||
|
||||
export function getInitInfo() {
|
||||
return axios.all([getUser(), getModulesList(), getSettings(), getPortalPasswordSettings()]).then(
|
||||
axios.spread(function(userResp, modulesResp, settingsResp, passwordSettingsResp) {
|
||||
return axios.all([getUser(), getModulesList(), getSettings(), getPortalPasswordSettings(), getInvitationLink(), getInvitationLink(true)]).then(
|
||||
axios.spread(function (userResp, modulesResp, settingsResp, passwordSettingsResp, userInvitationLinkResp, guestInvitationLinkResp) {
|
||||
let info = {
|
||||
user: userResp.data.response,
|
||||
modules: modulesResp.data.response,
|
||||
@ -95,6 +100,10 @@ export function getInitInfo() {
|
||||
};
|
||||
|
||||
info.settings.passwordSettings = passwordSettingsResp.data.response;
|
||||
info.settings.inviteLinks = {
|
||||
userLink: userInvitationLinkResp,
|
||||
guestLink: guestInvitationLinkResp
|
||||
}
|
||||
|
||||
return Promise.resolve(info);
|
||||
})
|
||||
@ -157,6 +166,49 @@ export function getGroup(groupId) {
|
||||
: axios.get(`${API_URL}/group/${groupId}.json`);
|
||||
}
|
||||
|
||||
export function getInvitationLink(isGuest) {
|
||||
let localStorageLinkTtl = localStorage.getItem('localStorageLinkTtl');
|
||||
|
||||
if (localStorageLinkTtl === null) {
|
||||
localStorage.setItem('localStorageLinkTtl', +new Date());
|
||||
}
|
||||
else if (+new Date() - localStorageLinkTtl > linkTtl) {
|
||||
localStorage.clear();
|
||||
localStorage.setItem('localStorageLinkTtl', +new Date());
|
||||
}
|
||||
|
||||
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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
@ -165,3 +217,13 @@ function CheckError(res) {
|
||||
}
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
|
||||
export function createGroup(groupName, groupManager, members) {
|
||||
const group = {groupName, groupManager, members};
|
||||
return axios.post(`${API_URL}/group.json`, group);
|
||||
}
|
||||
|
||||
export function updateGroup(id, groupName, groupManager, members) {
|
||||
const group = {groupId: id, groupName, groupManager, members};
|
||||
return axios.put(`${API_URL}/group/${id}.json`, group);
|
||||
}
|
||||
|
@ -561,3 +561,13 @@ export function getGroup(groupId) {
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export function getInvitationLink(isGuest) {
|
||||
return fakeResponse(isGuest
|
||||
? "guest invitation link"
|
||||
: "user invitation link");
|
||||
}
|
||||
|
||||
export function getShortenedLink(link) {
|
||||
return fakeResponse("SHORT LINK: " + link);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export default function setAuthorizationToken(token) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
localStorage.clear();
|
||||
delete axios.defaults.headers.common["Authorization"];
|
||||
cookies.remove(AUTH_KEY, {
|
||||
path: '/'
|
||||
|
@ -1802,7 +1802,7 @@ asap@~2.0.6:
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
"asc-web-components@file:../../../packages/asc-web-components":
|
||||
version "1.0.62"
|
||||
version "1.0.87"
|
||||
dependencies:
|
||||
moment "^2.24.0"
|
||||
prop-types "^15.7.2"
|
||||
@ -1810,7 +1810,6 @@ asap@~2.0.6:
|
||||
react-autosize-textarea "^7.0.0"
|
||||
react-avatar-edit "^0.8.3"
|
||||
react-custom-scrollbars "^4.2.1"
|
||||
react-datepicker "^2.8.0"
|
||||
react-text-mask "^5.4.3"
|
||||
react-toastify "^5.3.2"
|
||||
react-virtualized-auto-sizer "^1.0.2"
|
||||
@ -2931,6 +2930,13 @@ copy-descriptor@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-to-clipboard@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467"
|
||||
integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==
|
||||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
copy-webpack-plugin@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
|
||||
@ -3347,11 +3353,6 @@ data-urls@^1.0.0, data-urls@^1.1.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.1.0.tgz#0d7e806c3cefe14a943532dbf968995ccfd46bd9"
|
||||
integrity sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
@ -8751,17 +8752,6 @@ react-custom-scrollbars@^4.2.1:
|
||||
prop-types "^15.5.10"
|
||||
raf "^3.1.0"
|
||||
|
||||
react-datepicker@^2.8.0:
|
||||
version "2.9.6"
|
||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.9.6.tgz#26190c9f71692149d0d163398aa19e08626444b1"
|
||||
integrity sha512-PLiVhyAr567gWuLMZwIH9WpTIZOZVLhEFyuUzSx3kmQdiikjrYpdNlxsfbbgaxRnee5y08KJZequaqRsNySXmw==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
date-fns "^2.0.1"
|
||||
prop-types "^15.7.2"
|
||||
react-onclickoutside "^6.9.0"
|
||||
react-popper "^1.3.4"
|
||||
|
||||
react-dev-utils@^9.0.3:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.0.3.tgz#7607455587abb84599451460eb37cef0b684131a"
|
||||
@ -8831,12 +8821,7 @@ react-lifecycles-compat@^3.0.4:
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-onclickoutside@^6.9.0:
|
||||
version "6.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"
|
||||
integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==
|
||||
|
||||
react-popper@^1.3.3, react-popper@^1.3.4:
|
||||
react-popper@^1.3.3:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.4.tgz#f0cd3b0d30378e1f663b0d79bcc8614221652ced"
|
||||
integrity sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA==
|
||||
@ -10438,6 +10423,11 @@ to-space-case@^1.0.0:
|
||||
dependencies:
|
||||
to-no-case "^1.0.0"
|
||||
|
||||
toggle-selection@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
|
@ -8,6 +8,7 @@ using ASC.Core;
|
||||
using ASC.Core.Tenants;
|
||||
using ASC.Core.Users;
|
||||
using ASC.MessagingSystem;
|
||||
using ASC.People.Models;
|
||||
using ASC.Web.Api.Models;
|
||||
using ASC.Web.Api.Routing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -50,16 +51,16 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Create]
|
||||
public GroupWrapperFull AddGroup(Guid groupManager, string groupName, IEnumerable<Guid> members)
|
||||
public GroupWrapperFull AddGroup(GroupModel groupModel)
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_EditGroups, Constants.Action_AddRemoveUser);
|
||||
|
||||
var group = CoreContext.UserManager.SaveGroupInfo(Tenant, new GroupInfo { Name = groupName });
|
||||
var group = CoreContext.UserManager.SaveGroupInfo(Tenant, new GroupInfo { Name = groupModel.GroupName });
|
||||
|
||||
TransferUserToDepartment(groupManager, @group, true);
|
||||
if (members != null)
|
||||
TransferUserToDepartment(groupModel.GroupManager, @group, true);
|
||||
if (groupModel.Members != null)
|
||||
{
|
||||
foreach (var member in members)
|
||||
foreach (var member in groupModel.Members)
|
||||
{
|
||||
TransferUserToDepartment(member, group, false);
|
||||
}
|
||||
@ -71,32 +72,32 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Update("{groupid}")]
|
||||
public GroupWrapperFull UpdateGroup(Guid groupid, Guid groupManager, string groupName, IEnumerable<Guid> members)
|
||||
public GroupWrapperFull UpdateGroup(Guid groupid, GroupModel groupModel)
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_EditGroups, Constants.Action_AddRemoveUser);
|
||||
var group = CoreContext.UserManager.GetGroups(Tenant.TenantId).SingleOrDefault(x => x.ID == groupid).NotFoundIfNull("group not found");
|
||||
if (group.ID == Constants.LostGroupInfo.ID)
|
||||
if (groupid == Constants.LostGroupInfo.ID)
|
||||
{
|
||||
throw new ItemNotFoundException("group not found");
|
||||
}
|
||||
|
||||
group.Name = groupName ?? group.Name;
|
||||
group.Name = groupModel.GroupName ?? group.Name;
|
||||
CoreContext.UserManager.SaveGroupInfo(Tenant, group);
|
||||
|
||||
RemoveMembersFrom(groupid, CoreContext.UserManager.GetUsersByGroup(Tenant, groupid, EmployeeStatus.All).Select(u => u.ID).Where(id => !members.Contains(id)));
|
||||
RemoveMembersFrom(new GroupModel { Groupid = groupid, Members = CoreContext.UserManager.GetUsersByGroup(Tenant, groupid, EmployeeStatus.All).Select(u => u.ID).Where(id => !groupModel.Members.Contains(id)) });
|
||||
|
||||
TransferUserToDepartment(groupManager, @group, true);
|
||||
if (members != null)
|
||||
TransferUserToDepartment(groupModel.GroupManager, @group, true);
|
||||
if (groupModel.Members != null)
|
||||
{
|
||||
foreach (var member in members)
|
||||
foreach (var member in groupModel.Members)
|
||||
{
|
||||
TransferUserToDepartment(member, group, false);
|
||||
}
|
||||
}
|
||||
|
||||
MessageService.Send(MessageAction.GroupUpdated, MessageTarget.Create(group.ID), group.Name);
|
||||
MessageService.Send(MessageAction.GroupUpdated, MessageTarget.Create(groupid), group.Name);
|
||||
|
||||
return GetById(groupid);
|
||||
return GetById(groupModel.Groupid);
|
||||
}
|
||||
|
||||
[Delete("{groupid}")]
|
||||
@ -122,36 +123,36 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Update("{groupid}/members/{newgroupid}")]
|
||||
public GroupWrapperFull TransferMembersTo(Guid groupid, Guid newgroupid)
|
||||
public GroupWrapperFull TransferMembersTo(TransferGroupMembersModel transferGroupMembersModel)
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_EditGroups, Constants.Action_AddRemoveUser);
|
||||
var oldgroup = GetGroupInfo(groupid);
|
||||
var oldgroup = GetGroupInfo(transferGroupMembersModel.GroupId);
|
||||
|
||||
var newgroup = GetGroupInfo(newgroupid);
|
||||
var newgroup = GetGroupInfo(transferGroupMembersModel.NewGroupId);
|
||||
|
||||
var users = CoreContext.UserManager.GetUsersByGroup(Tenant, oldgroup.ID);
|
||||
foreach (var userInfo in users)
|
||||
{
|
||||
TransferUserToDepartment(userInfo.ID, newgroup, false);
|
||||
}
|
||||
return GetById(newgroupid);
|
||||
return GetById(transferGroupMembersModel.NewGroupId);
|
||||
}
|
||||
|
||||
[Create("{groupid}/members")]
|
||||
public GroupWrapperFull SetMembersTo(Guid groupid, IEnumerable<Guid> members)
|
||||
public GroupWrapperFull SetMembersTo(GroupModel groupModel)
|
||||
{
|
||||
RemoveMembersFrom(groupid, CoreContext.UserManager.GetUsersByGroup(Tenant, groupid).Select(x => x.ID));
|
||||
AddMembersTo(groupid, members);
|
||||
return GetById(groupid);
|
||||
RemoveMembersFrom(new GroupModel { Groupid = groupModel.Groupid, Members = CoreContext.UserManager.GetUsersByGroup(Tenant, groupModel.Groupid).Select(x => x.ID) });
|
||||
AddMembersTo(groupModel);
|
||||
return GetById(groupModel.Groupid);
|
||||
}
|
||||
|
||||
[Update("{groupid}/members")]
|
||||
public GroupWrapperFull AddMembersTo(Guid groupid, IEnumerable<Guid> members)
|
||||
public GroupWrapperFull AddMembersTo(GroupModel groupModel)
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_EditGroups, Constants.Action_AddRemoveUser);
|
||||
var group = GetGroupInfo(groupid);
|
||||
var group = GetGroupInfo(groupModel.Groupid);
|
||||
|
||||
foreach (var userId in members)
|
||||
foreach (var userId in groupModel.Members)
|
||||
{
|
||||
TransferUserToDepartment(userId, group, false);
|
||||
}
|
||||
@ -159,27 +160,27 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Update("{groupid}/manager")]
|
||||
public GroupWrapperFull SetManager(Guid groupid, Guid userid)
|
||||
public GroupWrapperFull SetManager(SetManagerModel setManagerModel)
|
||||
{
|
||||
var group = GetGroupInfo(groupid);
|
||||
if (CoreContext.UserManager.UserExists(Tenant.TenantId, userid))
|
||||
var group = GetGroupInfo(setManagerModel.GroupId);
|
||||
if (CoreContext.UserManager.UserExists(Tenant.TenantId, setManagerModel.UserId))
|
||||
{
|
||||
CoreContext.UserManager.SetDepartmentManager(Tenant.TenantId, group.ID, userid);
|
||||
CoreContext.UserManager.SetDepartmentManager(Tenant.TenantId, group.ID, setManagerModel.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ItemNotFoundException("user not found");
|
||||
}
|
||||
return GetById(groupid);
|
||||
return GetById(setManagerModel.GroupId);
|
||||
}
|
||||
|
||||
[Delete("{groupid}/members")]
|
||||
public GroupWrapperFull RemoveMembersFrom(Guid groupid, IEnumerable<Guid> members)
|
||||
public GroupWrapperFull RemoveMembersFrom(GroupModel groupModel)
|
||||
{
|
||||
SecurityContext.DemandPermissions(Tenant, Constants.Action_EditGroups, Constants.Action_AddRemoveUser);
|
||||
var group = GetGroupInfo(groupid);
|
||||
var group = GetGroupInfo(groupModel.Groupid);
|
||||
|
||||
foreach (var userId in members)
|
||||
foreach (var userId in groupModel.Members)
|
||||
{
|
||||
RemoveUserFromDepartment(userId, group);
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Create]
|
||||
[Authorize(AuthenticationSchemes = "confirm")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "LinkInvite,Administrators")]
|
||||
public EmployeeWraperFull AddMember(MemberModel memberModel)
|
||||
{
|
||||
ApiContext.AuthByClaim();
|
||||
@ -342,8 +342,8 @@ namespace ASC.Employee.Core.Controllers
|
||||
? true
|
||||
: ("female".Equals(memberModel.Sex, StringComparison.OrdinalIgnoreCase) ? (bool?)false : null);
|
||||
|
||||
user.BirthDate = memberModel.Birthday != null ? TenantUtil.DateTimeFromUtc(Convert.ToDateTime(memberModel.Birthday)) : (DateTime?)null;
|
||||
user.WorkFromDate = memberModel.Worksfrom != null ? TenantUtil.DateTimeFromUtc(Convert.ToDateTime(memberModel.Worksfrom)) : DateTime.UtcNow.Date;
|
||||
user.BirthDate = memberModel.Birthday != null && memberModel.Birthday != DateTime.MinValue ? TenantUtil.DateTimeFromUtc(Convert.ToDateTime(memberModel.Birthday)) : (DateTime?)null;
|
||||
user.WorkFromDate = memberModel.Worksfrom != null && memberModel.Worksfrom != DateTime.MinValue ? TenantUtil.DateTimeFromUtc(Convert.ToDateTime(memberModel.Worksfrom)) : DateTime.UtcNow.Date;
|
||||
|
||||
UpdateContacts(memberModel.Contacts, user);
|
||||
|
||||
@ -588,7 +588,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
|
||||
return new ThumbnailsDataWrapper(Tenant, user.ID);
|
||||
}
|
||||
|
||||
|
||||
public FormFile Base64ToImage(string base64String, string fileName)
|
||||
{
|
||||
byte[] imageBytes = Convert.FromBase64String(base64String);
|
||||
@ -617,7 +617,7 @@ namespace ASC.Employee.Core.Controllers
|
||||
|
||||
SecurityContext.DemandPermissions(Tenant, new UserSecurityProvider(userId), Constants.Action_EditUser);
|
||||
|
||||
var userPhoto = Base64ToImage(model.base64CroppedImage, "userPhoto_"+ userId.ToString());
|
||||
var userPhoto = Base64ToImage(model.base64CroppedImage, "userPhoto_" + userId.ToString());
|
||||
var defaultUserPhoto = Base64ToImage(model.base64DefaultImage, "defaultPhoto" + userId.ToString());
|
||||
|
||||
if (userPhoto.Length > SetupInfo.MaxImageUploadSize)
|
||||
@ -869,8 +869,11 @@ namespace ASC.Employee.Core.Controllers
|
||||
}
|
||||
|
||||
[Update("{userid}/password")]
|
||||
[Authorize(AuthenticationSchemes = "confirm", Roles = "PasswordChange,EmailChange,Administrators")]
|
||||
public EmployeeWraperFull ChangeUserPassword(Guid userid, MemberModel memberModel)
|
||||
{
|
||||
ApiContext.AuthByClaim();
|
||||
|
||||
SecurityContext.DemandPermissions(Tenant, new UserSecurityProvider(userid), Constants.Action_EditUser);
|
||||
|
||||
var user = CoreContext.UserManager.GetUsers(Tenant.TenantId, userid);
|
||||
|
13
products/ASC.People/Server/Models/GroupModel.cs
Normal file
13
products/ASC.People/Server/Models/GroupModel.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ASC.People.Models
|
||||
{
|
||||
public class GroupModel
|
||||
{
|
||||
public Guid Groupid { get; set; }
|
||||
public Guid GroupManager { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
public IEnumerable<Guid> Members { get; set; }
|
||||
}
|
||||
}
|
10
products/ASC.People/Server/Models/SetManagerModel.cs
Normal file
10
products/ASC.People/Server/Models/SetManagerModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace ASC.People.Models
|
||||
{
|
||||
public class SetManagerModel
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace ASC.People.Models
|
||||
{
|
||||
public class TransferGroupMembersModel
|
||||
{
|
||||
public Guid GroupId { get; set; }
|
||||
public Guid NewGroupId { get; set; }
|
||||
}
|
||||
}
|
@ -24,8 +24,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0-preview8.19405.4" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0-preview8-19413-06" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -137,6 +137,13 @@ namespace ASC.Api.Settings
|
||||
return QuotaWrapper.GetCurrent(Tenant);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Read("cultures")]
|
||||
public List<CultureInfo> GetSupportedCultures()
|
||||
{
|
||||
return SetupInfo.EnabledCultures;
|
||||
}
|
||||
|
||||
[Read("recalculatequota")]
|
||||
public void RecalculateQuota()
|
||||
{
|
||||
|
@ -4,7 +4,7 @@ import { Loader } from "asc-web-components";
|
||||
import StudioLayout from "./components/Layout/index";
|
||||
import Login from "./components/pages/Login";
|
||||
import { PrivateRoute } from "./helpers/privateRoute";
|
||||
import { PublicRoute } from "./helpers/publicRoute";
|
||||
import PublicRoute from "./helpers/publicRoute";
|
||||
import { Error404 } from "./components/pages/Error";
|
||||
|
||||
const Home = lazy(() => import("./components/pages/Home"));
|
||||
@ -19,8 +19,8 @@ const App = () => {
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<Switch>
|
||||
<PublicRoute exact path={["/login","/login/:error"]} component={Login} />
|
||||
<PublicRoute path="/confirm" component={Confirm} />
|
||||
<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="/about" component={About} />
|
||||
<PrivateRoute component={Error404} />
|
||||
|
57
web/ASC.Web.Client/src/components/pages/About/i18n.js
Normal file
57
web/ASC.Web.Client/src/components/pages/About/i18n.js
Normal file
@ -0,0 +1,57 @@
|
||||
import i18n from "i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.init({
|
||||
lng: 'en',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === 'lowercase') return value.toLowerCase();
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
},
|
||||
backend: {
|
||||
loadPath: `/locales/About/{{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',
|
||||
fallbackLng: "en",
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === 'lowercase') return value.toLowerCase();
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default newInstance;
|
@ -1,22 +1,160 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import { PageLayout, Text, Link } from "asc-web-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "./i18n";
|
||||
import version from "../../../../package.json";
|
||||
import styled from "styled-components";
|
||||
|
||||
const About = () => (
|
||||
<div>
|
||||
<h1>Hello, world!</h1>
|
||||
<p>Welcome to your new single-page application, built with:</p>
|
||||
<ul>
|
||||
<li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>
|
||||
<li><a href='https://facebook.github.io/react/'>React</a> and <a href='https://redux.js.org/'>Redux</a> for client-side code</li>
|
||||
<li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>
|
||||
</ul>
|
||||
<p>To help you get started, we've also set up:</p>
|
||||
<ul>
|
||||
<li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>
|
||||
<li><strong>Development server integration</strong>. In development mode, the development server from <code>create-react-app</code> runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>
|
||||
<li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration produces minified, efficiently bundled JavaScript files.</li>
|
||||
</ul>
|
||||
<p>The <code>ClientApp</code> subdirectory is a standard React application based on the <code>create-react-app</code> template. If you open a command prompt in that directory, you can run <code>npm</code> commands such as <code>npm test</code> or <code>npm install</code>.</p>
|
||||
</div>
|
||||
);
|
||||
const BodyStyle = styled.div`
|
||||
margin-top: 24px;
|
||||
.text_p {
|
||||
text-align: center;
|
||||
}
|
||||
.text_span {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
text-align: center;
|
||||
max-width: 216px;
|
||||
max-height: 35px;
|
||||
}
|
||||
|
||||
.copyright-line {
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
|
||||
:before {
|
||||
background-color: #e1e1e1;
|
||||
content: "";
|
||||
height: 2px;
|
||||
margin-top: 9px;
|
||||
width: 36%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
:after {
|
||||
background-color: #e1e1e1;
|
||||
content: "";
|
||||
height: 2px;
|
||||
margin-top: 9px;
|
||||
width: 36%;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Style = styled.div`
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const VersionStyle = styled.div`
|
||||
padding: 8px 0px 20px 0px;
|
||||
`;
|
||||
|
||||
const Body = () => {
|
||||
const { t } = useTranslation("translation", { i18n });
|
||||
|
||||
return (
|
||||
<BodyStyle>
|
||||
<p style={{ textAlign: "center", margin: "0px" }}>
|
||||
<img
|
||||
className="logo-img"
|
||||
src="images/dark_general.png"
|
||||
width="320"
|
||||
height="181"
|
||||
alt="Logo"
|
||||
></img>
|
||||
</p>
|
||||
|
||||
<VersionStyle>
|
||||
<Text.Body className="text_p" fontSize={14} color="#A3A9AE">
|
||||
{`${t("AboutCompanyVersion")}: ${version.version}`}
|
||||
</Text.Body>
|
||||
</VersionStyle>
|
||||
|
||||
<Text.Body className="copyright-line" fontSize={14}>
|
||||
{t("AboutCompanyLicensor")}
|
||||
</Text.Body>
|
||||
|
||||
<Text.Body className="text_p" fontSize={16} isBold={true}>
|
||||
Ascensio System SIA
|
||||
</Text.Body>
|
||||
|
||||
<Style>
|
||||
<Text.Body className="text_p" fontSize={12}>
|
||||
<Text.Body
|
||||
className="text_span"
|
||||
fontSize={12}
|
||||
as="span"
|
||||
color="#A3A9AE"
|
||||
>
|
||||
{t("AboutCompanyAddressTitle")}:{" "}
|
||||
</Text.Body>
|
||||
20A-12 Ernesta Birznieka-Upisha street, Riga, Latvia, EU, LV-1050
|
||||
</Text.Body>
|
||||
|
||||
<Text.Body
|
||||
fontSize={12}
|
||||
className="text_span"
|
||||
as="span"
|
||||
color="#A3A9AE"
|
||||
>
|
||||
{t("AboutCompanyEmailTitle")}:{" "}
|
||||
<Link href="mailto:support@onlyoffice.com" fontSize={12}>
|
||||
support@onlyoffice.com
|
||||
</Link>
|
||||
</Text.Body>
|
||||
|
||||
<div style={{ marginTop: "4px" }}>
|
||||
<Text.Body className="text_p" fontSize={12}>
|
||||
<Text.Body
|
||||
fontSize={12}
|
||||
className="text_span"
|
||||
as="span"
|
||||
color="#A3A9AE"
|
||||
>
|
||||
{t("AboutCompanyTelTitle")}:{" "}
|
||||
</Text.Body>
|
||||
+371 660-16425
|
||||
</Text.Body>
|
||||
</div>
|
||||
|
||||
<Link href="http://www.onlyoffice.com" fontSize={12}>
|
||||
www.onlyoffice.com
|
||||
</Link>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Text.Body className="text_p" fontSize={12}>
|
||||
{t("LicensedUnder")}:{" "}
|
||||
<Link
|
||||
href="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
isHovered={true}
|
||||
fontSize={12}
|
||||
>
|
||||
GNU GPL v.3
|
||||
</Link>{" "}
|
||||
</Text.Body>
|
||||
|
||||
<Text.Body className="text_p" fontSize={12}>
|
||||
{t("SourceCode")}:{" "}
|
||||
<Link
|
||||
href="https://github.com/ONLYOFFICE/CommunityServer"
|
||||
isHovered={true}
|
||||
fontSize={12}
|
||||
>
|
||||
GitHub
|
||||
</Link>
|
||||
</Text.Body>
|
||||
</div>
|
||||
</Style>
|
||||
</BodyStyle>
|
||||
);
|
||||
};
|
||||
|
||||
const About = () => {
|
||||
return <PageLayout sectionBodyContent={<Body />} />;
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"AboutCompanyTitle": "About this program",
|
||||
"AboutCompanyVersion": "Version",
|
||||
"AboutCompanyLicensor": "Copyright",
|
||||
"AboutCompanyAddressTitle": "address",
|
||||
"AboutCompanyEmailTitle": "email",
|
||||
"AboutCompanyTelTitle": "tel.",
|
||||
"LicensedUnder": "This software is licensed under", "_comment": "{0}GNU GPL v.3{1}",
|
||||
|
||||
|
||||
|
||||
|
||||
"SourceCode": "Source code is available on", "_comment": "{0}GNU GPL v.3{1}","_comment":"SYNTAX ERROR"
|
||||
|
||||
}
|
@ -1,330 +1,48 @@
|
||||
import React from 'react';
|
||||
import { withRouter } from "react-router";
|
||||
import { I18nextProvider, withTranslation } from 'react-i18next';
|
||||
import i18n from './i18n';
|
||||
import { Button, TextInput, PageLayout, Text, PasswordInput, FieldContainer, toastr, Loader } from 'asc-web-components';
|
||||
import styled from 'styled-components';
|
||||
import { welcomePageTitle } from './../../../helpers/customNames';
|
||||
import { Collapse } from 'reactstrap';
|
||||
import { connect } from 'react-redux';
|
||||
import { getPasswordSettings, createConfirmUser } from '../../../store/auth/actions';
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { Switch, Redirect, Route } from "react-router-dom";
|
||||
import { Loader } from "asc-web-components";
|
||||
import PublicRoute from "../../../helpers/publicRoute";
|
||||
import i18n from "./i18n";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
const inputWidth = '400px';
|
||||
const CreateUserForm = lazy(() => import("./sub-components/createUser"));
|
||||
const ChangePasswordForm = lazy(() => import("./sub-components/changePassword"));
|
||||
const ActivateEmailForm = lazy(() => import("./sub-components/activateEmail"));
|
||||
const ChangePhoneForm = lazy(() => import("./sub-components/changePhone"));
|
||||
|
||||
const ConfirmContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: 200px;
|
||||
const Confirm = ({ match }) => {
|
||||
//console.log("Confirm render");
|
||||
|
||||
@media (max-width: 830px) {
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.start-basis {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: ${inputWidth}
|
||||
}
|
||||
|
||||
.confirm-row {
|
||||
margin: 23px 0 0;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
|
||||
const emailInputName = 'email';
|
||||
const passwordInputName = 'password';
|
||||
|
||||
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
|
||||
const validationEmail = new RegExp(emailRegex);
|
||||
|
||||
class Confirm extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
email: '',
|
||||
emailValid: true,
|
||||
firstName: '',
|
||||
firstNameValid: true,
|
||||
lastName: '',
|
||||
lastNameValid: true,
|
||||
password: '',
|
||||
passwordValid: true,
|
||||
errorText: '',
|
||||
isLoading: false,
|
||||
};
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
|
||||
const { location, history, createConfirmUser } = this.props;
|
||||
const queryString = location.search.slice(1);
|
||||
const queryParams = queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
const isVisitor = parseInt(linkParams.emplType) === 2;
|
||||
|
||||
this.state.errorText && 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 (!validationEmail.test(this.state.email.trim())) {
|
||||
hasError = true;
|
||||
this.setState({ emailValid: !hasError });
|
||||
}
|
||||
|
||||
if (!this.state.passwordValid) {
|
||||
hasError = true;
|
||||
this.setState({ passwordValid: !hasError });
|
||||
}
|
||||
|
||||
if (hasError)
|
||||
return false;
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
const loginData = {
|
||||
userName: this.state.email,
|
||||
password: this.state.password
|
||||
}
|
||||
const registerData = {
|
||||
firstname: this.state.firstName,
|
||||
lastname: this.state.lastName,
|
||||
email: this.state.email,
|
||||
isVisitor: isVisitor
|
||||
};
|
||||
createConfirmUser(registerData, loginData, queryString)
|
||||
.then(() => history.push('/'))
|
||||
.catch(e => {
|
||||
console.error("confirm error", e);
|
||||
this.setState({ errorText: e.message });
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
};
|
||||
|
||||
onKeyPress = (target) => {
|
||||
if (target.code === "Enter") {
|
||||
this.onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
onCopyToClipboard = () => toastr.success(this.props.t('EmailAndPasswordCopiedToClipboard'));
|
||||
validatePassword = (value) => this.setState({ passwordValid: value });
|
||||
|
||||
componentDidMount() {
|
||||
const { getPasswordSettings, history, location } = this.props;
|
||||
const queryString = location.search.slice(1);
|
||||
|
||||
getPasswordSettings(queryString)
|
||||
.then(
|
||||
function () {
|
||||
console.log("get settings success");
|
||||
}
|
||||
)
|
||||
.catch(e => {
|
||||
console.error("get settings error", e);
|
||||
history.push(`/login/${e}`);
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', this.onKeyPress);
|
||||
window.addEventListener('keyup', this.onKeyPress);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.onKeyPress);
|
||||
window.removeEventListener('keyup', this.onKeyPress);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { settings, isLoaded, t } = this.props;
|
||||
return (
|
||||
!isLoaded
|
||||
? (
|
||||
<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 className='confirm-row'>
|
||||
<div className='full-width'>
|
||||
<FieldContainer isVertical={true} className=''>
|
||||
<TextInput
|
||||
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={event => {
|
||||
this.setState({ firstName: event.target.value });
|
||||
!this.state.firstNameValid && this.setState({ firstNameValid: event.target.value });
|
||||
this.state.errorText && this.setState({ errorText: "" });
|
||||
}}
|
||||
onKeyDown={event => this.onKeyPress(event.target)}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer isVertical={true} className=''>
|
||||
<TextInput
|
||||
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={event => {
|
||||
this.setState({ lastName: event.target.value });
|
||||
!this.state.lastNameValid && this.setState({ lastNameValid: true });
|
||||
this.state.errorText && this.setState({ errorText: "" });;
|
||||
}}
|
||||
onKeyDown={event => this.onKeyPress(event.target)}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<FieldContainer isVertical={true} className=''>
|
||||
<TextInput
|
||||
id='email'
|
||||
name={emailInputName}
|
||||
value={this.state.email}
|
||||
placeholder={t('Email')}
|
||||
size='huge'
|
||||
scale={true}
|
||||
tabIndex={3}
|
||||
autoComplete='email'
|
||||
isDisabled={this.state.isLoading}
|
||||
hasError={!this.state.emailValid}
|
||||
onChange={event => {
|
||||
this.setState({ email: event.target.value });
|
||||
!this.state.emailValid && this.setState({ emailValid: true });
|
||||
this.state.errorText && this.setState({ errorText: "" });;
|
||||
}}
|
||||
onKeyDown={event => this.onKeyPress(event.target)}
|
||||
/>
|
||||
|
||||
</FieldContainer>
|
||||
</div>
|
||||
|
||||
<FieldContainer isVertical={true} className=''>
|
||||
<PasswordInput
|
||||
inputName={passwordInputName}
|
||||
emailInputName={emailInputName}
|
||||
inputValue={this.state.password}
|
||||
placeholder={t('InvitePassword')}
|
||||
size='huge'
|
||||
scale={true}
|
||||
tabIndex={4}
|
||||
maxLength={30}
|
||||
inputWidth={inputWidth}
|
||||
hasError={!this.state.passwordValid && !this.state.password.trim()}
|
||||
onChange={event => {
|
||||
this.setState({ password: event.target.value });
|
||||
!this.state.passwordValid && this.setState({ passwordValid: true });
|
||||
this.state.errorText && this.setState({ errorText: "" });
|
||||
this.onKeyPress(event.target);
|
||||
}}
|
||||
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}
|
||||
/>
|
||||
</FieldContainer>
|
||||
|
||||
<Button
|
||||
primary
|
||||
size='big'
|
||||
label={t('LoginRegistryButton')}
|
||||
tabIndex={5}
|
||||
isDisabled={this.state.isLoading}
|
||||
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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ConfirmWrapper = withTranslation()(Confirm);
|
||||
|
||||
const ConfirmWithTrans = (props) => <I18nextProvider i18n={i18n}><ConfirmWrapper {...props} /></I18nextProvider>;
|
||||
|
||||
ConfirmWithTrans.propTypes = {
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<Suspense
|
||||
fallback={<Loader className="pageLoader" type="rombs" size={40} />}
|
||||
>
|
||||
<Switch>
|
||||
<PublicRoute
|
||||
path={`${match.path}/type=LinkInvite`}
|
||||
component={CreateUserForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=EmailActivation`}
|
||||
component={ActivateEmailForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=PasswordChange`}
|
||||
component={ChangePasswordForm}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/type=PhoneActivation`}
|
||||
component={ChangePhoneForm}
|
||||
/>
|
||||
<Redirect to={{ pathname: "/" }} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</I18nextProvider>
|
||||
);
|
||||
};
|
||||
const ConfirmForm = (props) => (<PageLayout sectionBodyContent={<ConfirmWithTrans {...props} />} />);
|
||||
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isLoaded: state.auth.isLoaded,
|
||||
settings: state.auth.password
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser })(withRouter(ConfirmForm));
|
||||
export default Confirm;
|
||||
|
@ -13,6 +13,9 @@
|
||||
"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",
|
||||
"PasswordCustomMode": "Password",
|
||||
"ImportContactsOkButton": "OK",
|
||||
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
|
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { withRouter } from "react-router";
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { PageLayout, Loader } from 'asc-web-components';
|
||||
import { connect } from 'react-redux';
|
||||
import { logout, validateChangingEmail } 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, validateChangingEmail } = this.props;
|
||||
const queryParams = this.state.queryString.split('&');
|
||||
const arrayOfQueryParams = queryParams.map(queryParam => queryParam.split('='));
|
||||
const linkParams = Object.fromEntries(arrayOfQueryParams);
|
||||
logout();
|
||||
validateChangingEmail(linkParams)
|
||||
.then((res) => {
|
||||
const email = decodeURIComponent(res.data.response.email);
|
||||
history.push(`/login/confirmed-email=${email}`);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('Activate email render');
|
||||
return (
|
||||
<Loader className="pageLoader" type="rombs" size={40} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ActivateEmail.propTypes = {
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
const ActivateEmailForm = (props) => (<PageLayout sectionBodyContent={<ActivateEmail {...props} />} />);
|
||||
|
||||
|
||||
export default connect(null, { logout, validateChangingEmail })(withRouter(withTranslation()(ActivateEmailForm)));
|
@ -0,0 +1,179 @@
|
||||
import React, { useState, useEffect, useCallback } 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, TextInput, PageLayout, Text } from "asc-web-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "../i18n";
|
||||
import { welcomePageTitle } from "../../../../helpers/customNames";
|
||||
import { setNewPassword } from "../../../../../src/store/auth/actions";
|
||||
|
||||
const BodyStyle = styled.div`
|
||||
margin-top: 70px;
|
||||
|
||||
p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.password-row {
|
||||
margin: 23px 0 0;
|
||||
|
||||
.password-card {
|
||||
border: none;
|
||||
|
||||
.card-img {
|
||||
max-width: 216px;
|
||||
max-height: 35px;
|
||||
}
|
||||
.card-title {
|
||||
word-wrap: break-word;
|
||||
margin: 8px 0;
|
||||
text-align: left;
|
||||
font-size: 24px;
|
||||
color: #116d9d;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
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, setNewPassword } = props;
|
||||
const { params } = match;
|
||||
|
||||
const { t } = useTranslation("translation", { i18n });
|
||||
|
||||
const onSubmit = useCallback(
|
||||
e => {
|
||||
errorText && setErrorText("");
|
||||
|
||||
let hasError = false;
|
||||
|
||||
if (!password.trim()) {
|
||||
hasError = true;
|
||||
setPasswordValid(!hasError);
|
||||
}
|
||||
|
||||
if (hasError) return false;
|
||||
|
||||
let newPassword = {
|
||||
password: password
|
||||
};
|
||||
|
||||
setIsLoading(true);
|
||||
console.log("changePassword onSubmit", match, location, history);
|
||||
|
||||
setNewPassword(newPassword)
|
||||
.then(function() {
|
||||
console.log("UPDATE PASSWORD");
|
||||
history.push('/');
|
||||
})
|
||||
.catch(e => {
|
||||
history.push('/');
|
||||
console.log("ERROR UPDATE PASSWORD", e);
|
||||
});
|
||||
},
|
||||
[errorText, history, location, setNewPassword, match, password]
|
||||
);
|
||||
|
||||
const onKeyPress = useCallback(
|
||||
target => {
|
||||
if (target.code === "Enter" || target.code === "NumpadEnter") {
|
||||
onSubmit();
|
||||
}
|
||||
},
|
||||
[onSubmit]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
params.error && setErrorText(params.error);
|
||||
window.addEventListener("keydown", onKeyPress);
|
||||
window.addEventListener("keyup", onKeyPress);
|
||||
// Remove event listeners on cleanup
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyPress);
|
||||
window.removeEventListener("keyup", onKeyPress);
|
||||
};
|
||||
}, [onKeyPress, params.error]);
|
||||
|
||||
const 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
|
||||
/>
|
||||
<CardTitle className="card-title">
|
||||
{t("CustomWelcomePageTitle", { welcomePageTitle })}
|
||||
</CardTitle>
|
||||
</Card>
|
||||
|
||||
<Text.Body fontSize={14}>{t("PassworResetTitle")}</Text.Body>
|
||||
<TextInput
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
size="huge"
|
||||
scale={true}
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
autocomple="current-password"
|
||||
placeholder={t("PasswordCustomMode")}
|
||||
onChange={event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
errorText && setErrorText("");
|
||||
onKeyPress(event.target);
|
||||
}}
|
||||
value={password}
|
||||
hasError={!passwordValid}
|
||||
isDisabled={isLoading}
|
||||
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,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
ChangePasswordForm.defaultProps = {
|
||||
password: ""
|
||||
};
|
||||
|
||||
export default connect(null, { setNewPassword })(withRouter(withTranslation()(ChangePasswordForm)));
|
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const changePhoneForm = (props) => {
|
||||
return (<span>{props.location.pathname}</span>);
|
||||
}
|
||||
|
||||
export default changePhoneForm;
|
@ -0,0 +1,342 @@
|
||||
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 { getPasswordSettings, createConfirmUser } from '../../../../store/auth/actions';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const inputWidth = '400px';
|
||||
|
||||
const ConfirmContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: 200px;
|
||||
|
||||
@media (max-width: 830px) {
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.start-basis {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: ${inputWidth}
|
||||
}
|
||||
|
||||
.confirm-row {
|
||||
margin: 23px 0 0;
|
||||
}
|
||||
|
||||
.break-word {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const emailInputName = 'email';
|
||||
const passwordInputName = 'password';
|
||||
|
||||
const emailRegex = '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$';
|
||||
const validationEmail = new RegExp(emailRegex);
|
||||
|
||||
class Confirm extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
email: '',
|
||||
emailValid: true,
|
||||
firstName: '',
|
||||
firstNameValid: true,
|
||||
lastName: '',
|
||||
lastNameValid: true,
|
||||
password: '',
|
||||
passwordValid: true,
|
||||
errorText: '',
|
||||
isLoading: false,
|
||||
passwordEmpty: false,
|
||||
queryString: `type=LinkInvite&${props.location.search.slice(1)}`
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 (!validationEmail.test(this.state.email.trim())) {
|
||||
hasError = true;
|
||||
this.setState({ emailValid: !hasError });
|
||||
}
|
||||
|
||||
if (!this.state.passwordValid) {
|
||||
hasError = true;
|
||||
this.setState({ passwordValid: !hasError });
|
||||
}
|
||||
|
||||
!this.state.password.trim() && this.setState({ passwordEmpty: true });
|
||||
|
||||
if (hasError) {
|
||||
this.setState({ isLoading: false });
|
||||
return false;
|
||||
}
|
||||
|
||||
const loginData = {
|
||||
userName: this.state.email,
|
||||
password: this.state.password
|
||||
}
|
||||
const registerData = {
|
||||
firstname: this.state.firstName,
|
||||
lastname: this.state.lastName,
|
||||
email: this.state.email,
|
||||
isVisitor: isVisitor
|
||||
};
|
||||
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 });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onKeyPress = (event) => {
|
||||
if (event.key === "Enter") {
|
||||
this.onSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
onCopyToClipboard = () => toastr.success(this.props.t('EmailAndPasswordCopiedToClipboard'));
|
||||
validatePassword = (value) => this.setState({ passwordValid: value });
|
||||
|
||||
componentDidMount() {
|
||||
const { getPasswordSettings, history } = this.props;
|
||||
|
||||
getPasswordSettings(this.state.queryString)
|
||||
.then(
|
||||
function () {
|
||||
console.log("get settings success");
|
||||
}
|
||||
)
|
||||
.catch(e => {
|
||||
console.error("get settings error", e);
|
||||
history.push(`/login/error=${e}`);
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', this.onKeyPress);
|
||||
window.addEventListener('keyup', this.onKeyPress);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.onKeyPress);
|
||||
window.removeEventListener('keyup', this.onKeyPress);
|
||||
}
|
||||
|
||||
onChangeName = event => {
|
||||
this.setState({ firstName: event.target.value });
|
||||
!this.state.firstNameValid && this.setState({ firstNameValid: event.target.value });
|
||||
this.state.errorText && this.setState({ errorText: "" });
|
||||
}
|
||||
|
||||
onChangeSurname = event => {
|
||||
this.setState({ lastName: event.target.value });
|
||||
!this.state.lastNameValid && this.setState({ lastNameValid: true });
|
||||
this.state.errorText && this.setState({ errorText: "" });;
|
||||
}
|
||||
|
||||
onChangeEmail = event => {
|
||||
this.setState({ email: event.target.value });
|
||||
!this.state.emailValid && this.setState({ emailValid: 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('Confirm 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'
|
||||
id='email'
|
||||
name={emailInputName}
|
||||
value={this.state.email}
|
||||
placeholder={t('Email')}
|
||||
size='huge'
|
||||
scale={true}
|
||||
tabIndex={3}
|
||||
autoComplete='email'
|
||||
isDisabled={this.state.isLoading}
|
||||
hasError={!this.state.emailValid}
|
||||
onChange={this.onChangeEmail}
|
||||
onKeyDown={this.onKeyPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PasswordInput
|
||||
className='confirm-row'
|
||||
id='password'
|
||||
inputName={passwordInputName}
|
||||
emailInputName={emailInputName}
|
||||
inputValue={this.state.password}
|
||||
placeholder={t('InvitePassword')}
|
||||
size='huge'
|
||||
scale={true}
|
||||
tabIndex={4}
|
||||
maxLength={30}
|
||||
inputWidth={inputWidth}
|
||||
hasError={this.state.passwordEmpty}
|
||||
onChange={this.onChangePassword}
|
||||
onCopyToClipboard={this.onCopyToClipboard}
|
||||
onValidateInput={this.validatePassword}
|
||||
clipActionResource={t('CopyEmailAndPassword')}
|
||||
clipEmailResource={`${t('Email')}: `}
|
||||
clipPasswordResource={`${t('InvitePassword')}: `}
|
||||
tooltipPasswordTitle={`${t('ErrorPasswordMessage')}:`}
|
||||
tooltipPasswordLength={`${t('ErrorPasswordLength', { fromNumber: 6, toNumber: 30 })}:`}
|
||||
tooltipPasswordDigits={t('ErrorPasswordNoDigits')}
|
||||
tooltipPasswordCapital={t('ErrorPasswordNoUpperCase')}
|
||||
tooltipPasswordSpecial={`${t('ErrorPasswordNoSpecialSymbols')} (!@#$%^&*)`}
|
||||
generatorSpecial="!@#$%^&*"
|
||||
passwordSettings={settings}
|
||||
isDisabled={this.state.isLoading}
|
||||
onKeyDown={this.onKeyPress}
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
className='confirm-row'
|
||||
primary
|
||||
size='big'
|
||||
label={t('LoginRegistryButton')}
|
||||
tabIndex={5}
|
||||
isLoading={this.state.isLoading}
|
||||
onClick={this.onSubmit}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{/* <Row className='confirm-row'>
|
||||
|
||||
<Text.Body as='p' fontSize={14}>{t('LoginWithAccount')}</Text.Body>
|
||||
|
||||
</Row>
|
||||
*/}
|
||||
<Collapse className='confirm-row'
|
||||
isOpen={!!this.state.errorText}>
|
||||
<div className="alert alert-danger">{this.state.errorText}</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
</ConfirmContainer>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Confirm.propTypes = {
|
||||
getPasswordSettings: PropTypes.func.isRequired,
|
||||
createConfirmUser: PropTypes.func.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
const CreateUserForm = (props) => (<PageLayout sectionBodyContent={<Confirm {...props} />} />);
|
||||
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isConfirmLoaded: state.auth.isConfirmLoaded,
|
||||
settings: state.auth.password
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { getPasswordSettings, createConfirmUser })(withRouter(withTranslation()(CreateUserForm)));
|
@ -2,7 +2,7 @@ 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 } from 'asc-web-components';
|
||||
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';
|
||||
@ -38,14 +38,14 @@ const mdOptions = { size: 6, offset: 3 };
|
||||
|
||||
const Form = props => {
|
||||
const { t } = useTranslation('translation', { i18n });
|
||||
const [identifier, setIdentifier] = useState('');
|
||||
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 { login, match, location, history } = props;
|
||||
const { params } = match;
|
||||
|
||||
const onSubmit = useCallback((e) => {
|
||||
//e.preventDefault();
|
||||
@ -87,8 +87,8 @@ const Form = props => {
|
||||
});
|
||||
}, [errorText, history, identifier, location, login, match, password]);
|
||||
|
||||
const onKeyPress = useCallback((target) => {
|
||||
if (target.code === "Enter") {
|
||||
const onKeyPress = useCallback((event) => {
|
||||
if (event.key === "Enter") {
|
||||
onSubmit();
|
||||
}
|
||||
}, [onSubmit]);
|
||||
@ -102,7 +102,21 @@ const Form = props => {
|
||||
window.removeEventListener('keydown', onKeyPress);
|
||||
window.removeEventListener('keyup', onKeyPress);
|
||||
};
|
||||
}, [onKeyPress, params.error]);
|
||||
}, [onKeyPress, params]);
|
||||
|
||||
const onChangePassword = event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
errorText && setErrorText("");
|
||||
}
|
||||
|
||||
const onChangeLogin = event => {
|
||||
setIdentifier(event.target.value);
|
||||
!identifierValid && setIdentifierValid(true);
|
||||
errorText && setErrorText("");
|
||||
}
|
||||
|
||||
// console.log('Login render');
|
||||
|
||||
return (
|
||||
<FormContainer>
|
||||
@ -127,13 +141,9 @@ const Form = props => {
|
||||
isAutoFocussed={true}
|
||||
tabIndex={1}
|
||||
isDisabled={isLoading}
|
||||
autocomple="username"
|
||||
onChange={event => {
|
||||
setIdentifier(event.target.value);
|
||||
!identifierValid && setIdentifierValid(true);
|
||||
errorText && setErrorText("");
|
||||
}}
|
||||
onKeyDown={event => onKeyPress(event.target)} />
|
||||
autoComplete="username"
|
||||
onChange={onChangeLogin}
|
||||
onKeyDown={onKeyPress} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="login-row">
|
||||
@ -149,14 +159,9 @@ const Form = props => {
|
||||
scale={true}
|
||||
tabIndex={2}
|
||||
isDisabled={isLoading}
|
||||
autocomple="current-password"
|
||||
onChange={event => {
|
||||
setPassword(event.target.value);
|
||||
!passwordValid && setPasswordValid(true);
|
||||
errorText && setErrorText("");
|
||||
onKeyPress(event.target);
|
||||
}}
|
||||
onKeyDown={event => onKeyPress(event.target)} />
|
||||
autoComplete="current-password"
|
||||
onChange={onChangePassword}
|
||||
onKeyDown={onKeyPress} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="login-row">
|
||||
@ -171,6 +176,12 @@ const Form = props => {
|
||||
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}>
|
||||
|
@ -3,6 +3,8 @@
|
||||
"LoginButton": "Sign In",
|
||||
"Password": "Password",
|
||||
"RegistrationEmailWatermark": "Your registration email",
|
||||
"MessageEmailConfirmed": "Your email was activated successfully.",
|
||||
"MessageAuthorize": "Please authorize yourself.",
|
||||
|
||||
"CustomWelcomePageTitle": "{{welcomePageTitle}}"
|
||||
}
|
@ -21,4 +21,5 @@ export const PublicRoute = ({ component: Component, ...rest }) => {
|
||||
}
|
||||
/>
|
||||
)
|
||||
};
|
||||
};
|
||||
export default PublicRoute;
|
@ -13,8 +13,13 @@ import { getUserInfo } from './store/auth/actions';
|
||||
var token = (new Cookies()).get(AUTH_KEY);
|
||||
|
||||
if (token) {
|
||||
setAuthorizationToken(token);
|
||||
store.dispatch(getUserInfo);
|
||||
if (!window.location.pathname.includes("confirm/type=EmailActivation")) {
|
||||
setAuthorizationToken(token);
|
||||
store.dispatch(getUserInfo);
|
||||
}
|
||||
else {
|
||||
setAuthorizationToken();
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
|
@ -1,6 +1,17 @@
|
||||
{
|
||||
"pages": {
|
||||
"About": {
|
||||
"Resource": [
|
||||
"AboutCompanyTitle",
|
||||
"AboutCompanyVersion",
|
||||
"AboutCompanyLicensor",
|
||||
"AboutCompanyAddressTitle",
|
||||
"AboutCompanyEmailTitle",
|
||||
"AboutCompanyTelTitle"
|
||||
],
|
||||
"UserControlsCommonResource": [
|
||||
"LicensedUnder"
|
||||
]
|
||||
},
|
||||
"Home": {
|
||||
},
|
||||
@ -9,6 +20,8 @@
|
||||
"LoadingProcessing",
|
||||
"Password",
|
||||
"RegistrationEmailWatermark",
|
||||
"MessageEmailConfirmed",
|
||||
"MessageAuthorize",
|
||||
"LoginButton"
|
||||
]
|
||||
},
|
||||
|
@ -8,6 +8,8 @@ export const SET_SETTINGS = 'SET_SETTINGS';
|
||||
export const SET_IS_LOADED = 'SET_IS_LOADED';
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
export const SET_PASSWORD_SETTINGS = 'SET_PASSWORD_SETTINGS';
|
||||
export const SET_IS_CONFIRM_LOADED = 'SET_IS_CONFIRM_LOADED';
|
||||
export const SET_NEW_PASSWORD = 'SET_NEW_PASSWORD';
|
||||
|
||||
export function setCurrentUser(user) {
|
||||
return {
|
||||
@ -37,6 +39,12 @@ export function setIsLoaded(isLoaded) {
|
||||
};
|
||||
};
|
||||
|
||||
export function setIsConfirmLoaded(isConfirmLoaded) {
|
||||
return {
|
||||
type: SET_IS_CONFIRM_LOADED,
|
||||
isConfirmLoaded
|
||||
};
|
||||
};
|
||||
|
||||
export function setLogout() {
|
||||
return {
|
||||
@ -51,6 +59,13 @@ export function setPasswordSettings(password) {
|
||||
};
|
||||
};
|
||||
|
||||
export function setNewPasswordSettings(password) {
|
||||
return {
|
||||
type: SET_NEW_PASSWORD,
|
||||
password
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export function getUserInfo(dispatch) {
|
||||
return api.getUser()
|
||||
@ -85,7 +100,7 @@ export function getPasswordSettings(token) {
|
||||
return dispatch => {
|
||||
return api.getPasswordSettings(token)
|
||||
.then((res) => dispatch(setPasswordSettings(res.data.response)))
|
||||
.then(() => dispatch(setIsLoaded(true)));
|
||||
.then(() => dispatch(setIsConfirmLoaded(true)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -109,9 +124,25 @@ export function createConfirmUser(registerData, loginData, key) {
|
||||
};
|
||||
};
|
||||
|
||||
export function validateChangingEmail(data, key) {
|
||||
return dispatch => {
|
||||
return api.validateChangingEmail(data, key);
|
||||
}
|
||||
};
|
||||
|
||||
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 setNewPassword(res) {
|
||||
return dispatch => {
|
||||
return api.setNewPasswordSettings(res)
|
||||
.then(res => {
|
||||
//checkResponseError(res);
|
||||
dispatch(setNewPasswordSettings(res.data.response));
|
||||
})
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS } from './actions';
|
||||
import { SET_CURRENT_USER, SET_MODULES, SET_SETTINGS, SET_IS_LOADED, LOGOUT, SET_PASSWORD_SETTINGS, SET_IS_CONFIRM_LOADED, SET_NEW_PASSWORD } from './actions';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import config from "../../../package.json";
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
isLoaded: false,
|
||||
isConfirmLoaded: false,
|
||||
user: {},
|
||||
modules: [],
|
||||
settings: {
|
||||
@ -51,6 +52,14 @@ const authReducer = (state = initialState, action) => {
|
||||
return Object.assign({}, state, {
|
||||
isLoaded: action.isLoaded
|
||||
});
|
||||
case SET_IS_CONFIRM_LOADED:
|
||||
return Object.assign({}, state, {
|
||||
isConfirmLoaded: action.isConfirmLoaded
|
||||
});
|
||||
case SET_NEW_PASSWORD:
|
||||
return Object.assign({}, state, {
|
||||
password: action.password
|
||||
});
|
||||
case LOGOUT:
|
||||
return initialState;
|
||||
default:
|
||||
|
@ -47,3 +47,14 @@ export function createUser(data, key) {
|
||||
? fakeApi.createUser()
|
||||
: axios.post(`${API_URL}/people`, data, { headers: { 'confirm' : key } });
|
||||
}
|
||||
|
||||
export function validateChangingEmail(data, key) {
|
||||
return fakeApi.validateChangingEmail(data, key); ;
|
||||
}
|
||||
|
||||
export function setNewPasswordSettings(data, key) {
|
||||
const IS_FAKE = true;
|
||||
return IS_FAKE
|
||||
? fakeApi.setNewPasswordSettings()
|
||||
: axios.post(`${API_URL}/people`, data, { headers: { 'confirm' : key } });
|
||||
}
|
||||
|
@ -119,4 +119,23 @@ export function createUser() {
|
||||
"id": "00000000-0000-0000-0000-000000000000"
|
||||
};
|
||||
return fakeResponse(data);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateChangingEmail(payload, key) {
|
||||
const data = {
|
||||
"email": payload.email
|
||||
};
|
||||
return fakeResponse(data);
|
||||
}
|
||||
|
||||
|
||||
export function setNewPasswordSettings() {
|
||||
const data = {
|
||||
//minLength: 12,
|
||||
//upperCase: true,
|
||||
//digits: true,
|
||||
//specSymbols: true
|
||||
};
|
||||
|
||||
return fakeResponse(data);
|
||||
}
|
@ -19,6 +19,7 @@ export default function setAuthorizationToken(token) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
localStorage.clear();
|
||||
delete axios.defaults.headers.common["Authorization"];
|
||||
cookies.remove(AUTH_KEY);
|
||||
}
|
||||
|
@ -1802,7 +1802,7 @@ asap@~2.0.6:
|
||||
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
|
||||
|
||||
"asc-web-components@file:../../packages/asc-web-components":
|
||||
version "1.0.82"
|
||||
version "1.0.86"
|
||||
dependencies:
|
||||
moment "^2.24.0"
|
||||
prop-types "^15.7.2"
|
||||
@ -1811,7 +1811,6 @@ asap@~2.0.6:
|
||||
react-avatar-edit "^0.8.3"
|
||||
react-avatar-editor "^11.0.7"
|
||||
react-custom-scrollbars "^4.2.1"
|
||||
react-datepicker "^2.8.0"
|
||||
react-dropzone "^10.1.8"
|
||||
react-text-mask "^5.4.3"
|
||||
react-toastify "^5.3.2"
|
||||
@ -3356,11 +3355,6 @@ data-urls@^1.0.0, data-urls@^1.1.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.1.0.tgz#0d7e806c3cefe14a943532dbf968995ccfd46bd9"
|
||||
integrity sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
@ -8774,17 +8768,6 @@ react-custom-scrollbars@^4.2.1:
|
||||
prop-types "^15.5.10"
|
||||
raf "^3.1.0"
|
||||
|
||||
react-datepicker@^2.8.0:
|
||||
version "2.9.6"
|
||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.9.6.tgz#26190c9f71692149d0d163398aa19e08626444b1"
|
||||
integrity sha512-PLiVhyAr567gWuLMZwIH9WpTIZOZVLhEFyuUzSx3kmQdiikjrYpdNlxsfbbgaxRnee5y08KJZequaqRsNySXmw==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
date-fns "^2.0.1"
|
||||
prop-types "^15.7.2"
|
||||
react-onclickoutside "^6.9.0"
|
||||
react-popper "^1.3.4"
|
||||
|
||||
react-dev-utils@^9.0.3:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.0.3.tgz#7607455587abb84599451460eb37cef0b684131a"
|
||||
@ -8858,12 +8841,7 @@ react-lifecycles-compat@^3.0.4:
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-onclickoutside@^6.9.0:
|
||||
version "6.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"
|
||||
integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==
|
||||
|
||||
react-popper@^1.3.3, react-popper@^1.3.4:
|
||||
react-popper@^1.3.3:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.4.tgz#f0cd3b0d30378e1f663b0d79bcc8614221652ced"
|
||||
integrity sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA==
|
||||
|
@ -4,7 +4,5 @@ It can also be an scss file, however,
|
||||
you have to go to `webpack.config.js` file
|
||||
and enable the options in there
|
||||
*/
|
||||
|
||||
@import '../node_modules/bootstrap/dist/css/bootstrap.css';
|
||||
@import '../node_modules/react-toastify/dist/ReactToastify.min.css';
|
||||
@import "../node_modules/react-datepicker/dist/react-datepicker.css";
|
||||
@import '../node_modules/react-toastify/dist/ReactToastify.min.css';
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "asc-web-components",
|
||||
"version": "1.0.82",
|
||||
"version": "1.0.94",
|
||||
"description": "Ascensio System SIA component library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/asc-web-components.js",
|
||||
@ -105,7 +105,6 @@
|
||||
"react-avatar-edit": "^0.8.3",
|
||||
"react-avatar-editor": "^11.0.7",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-datepicker": "^2.8.0",
|
||||
"react-dropzone": "^10.1.8",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"react-text-mask": "^5.4.3",
|
||||
|
@ -77,6 +77,8 @@ const groups = [
|
||||
}
|
||||
];
|
||||
|
||||
const sizes = ["compact", "full"];
|
||||
|
||||
storiesOf("Components|AdvancedSelector", module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
@ -101,7 +103,7 @@ storiesOf("Components|AdvancedSelector", module)
|
||||
>
|
||||
{({ value, set }) => (
|
||||
<AdvancedSelector
|
||||
size={select("size", ["compact", "full"], "compact")}
|
||||
size={select("size", sizes, "full")}
|
||||
placeholder={text("placeholder", "Search users")}
|
||||
onSearchChanged={value => {
|
||||
action("onSearchChanged")(value);
|
||||
@ -131,6 +133,8 @@ storiesOf("Components|AdvancedSelector", module)
|
||||
})
|
||||
);
|
||||
}}
|
||||
allowCreation={boolean("allowCreation", false)}
|
||||
onAddNewClick={() => action("onSelect") }
|
||||
/>
|
||||
)}
|
||||
</ArrayValue>
|
||||
@ -165,7 +169,7 @@ storiesOf("Components|AdvancedSelector", module)
|
||||
>
|
||||
{({ value, set }) => (
|
||||
<AdvancedSelector
|
||||
size={select("size", ["compact", "full"], "compact")}
|
||||
size={select("size", sizes, "full")}
|
||||
isDropDown={true}
|
||||
isOpen={isOpen}
|
||||
placeholder={text("placeholder", "Search users")}
|
||||
@ -199,6 +203,8 @@ storiesOf("Components|AdvancedSelector", module)
|
||||
})
|
||||
);
|
||||
}}
|
||||
allowCreation={boolean("allowCreation", false)}
|
||||
onAddNewClick={() => action("onSelect") }
|
||||
/>
|
||||
)}
|
||||
</ArrayValue>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import styled, { css } from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
import SearchInput from "../search-input";
|
||||
import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list";
|
||||
@ -7,7 +7,9 @@ import { FixedSizeList } from "react-window";
|
||||
import Link from "../link";
|
||||
import Checkbox from "../checkbox";
|
||||
import Button from "../button";
|
||||
import { Icons } from "../icons";
|
||||
import ComboBox from "../combobox";
|
||||
import { Text } from "../text";
|
||||
import findIndex from "lodash/findIndex";
|
||||
import filter from "lodash/filter";
|
||||
import isEqual from "lodash/isEqual";
|
||||
@ -38,6 +40,9 @@ const Container = ({
|
||||
isDropDown,
|
||||
containerWidth,
|
||||
containerHeight,
|
||||
allowCreation,
|
||||
onAddNewClick,
|
||||
allowAnyClickClose,
|
||||
...props
|
||||
}) => <div {...props} />;
|
||||
/* eslint-enable react/prop-types */
|
||||
@ -47,44 +52,126 @@ const StyledContainer = styled(Container)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${props => (props.containerWidth ? `width: ${props.containerWidth}px;` : "")}
|
||||
${props => (props.containerWidth ? `width: ${props.containerWidth};` : "")}
|
||||
${props =>
|
||||
props.containerHeight
|
||||
? `height: ${props.containerHeight}px;`
|
||||
? `height: ${props.containerHeight};`
|
||||
: ""}
|
||||
|
||||
.data_container {
|
||||
margin: 16px 16px 0 16px;
|
||||
margin: 16px 16px -5px 16px;
|
||||
|
||||
.head_container {
|
||||
display: flex;
|
||||
margin-bottom: ${props => props.isDropDown ? 8 : 16}px;
|
||||
|
||||
.options_searcher {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
${props => props.isDropDown && props.size === "full" && css`
|
||||
margin-right: ${props =>
|
||||
props.allowCreation ?
|
||||
8 : 16
|
||||
}px;
|
||||
`}
|
||||
/*${props =>
|
||||
props.allowCreation ?
|
||||
css`
|
||||
width: 272px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`width: ${props => props.isDropDown ? '313px' : '100%'};`
|
||||
}*/
|
||||
}
|
||||
|
||||
.add_new_btn {
|
||||
${props =>
|
||||
props.allowCreation &&
|
||||
css`
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 32px;
|
||||
width: 36px;
|
||||
margin-right: 16px;
|
||||
line-height: 18px;
|
||||
`}
|
||||
}
|
||||
|
||||
.options_searcher {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.options_group_selector {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.option_select_all_checkbox {
|
||||
margin-bottom: 12px;
|
||||
/*margin-left: 8px;*/
|
||||
.data_column_one {
|
||||
${props =>
|
||||
props.isDropDown && props.groups && props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.options_list {
|
||||
margin-top: 4px;
|
||||
margin-left: -8px;
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: ${props => props.isMultiSelect ? 8 : 0}px;
|
||||
cursor: pointer;
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.options_list {
|
||||
.option {
|
||||
line-height: 32px;
|
||||
cursor: pointer;
|
||||
.data_column_two {
|
||||
${props =>
|
||||
props.isDropDown && props.groups && props.groups.length > 0
|
||||
? css`
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
border-left: 1px solid #eceef1;
|
||||
`
|
||||
: ""}
|
||||
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
.group_header {
|
||||
font-weight: 600;
|
||||
padding-left: 16px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.option_link {
|
||||
.group_list {
|
||||
margin-left: 8px;
|
||||
|
||||
.option {
|
||||
line-height: 32px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
cursor: pointer;
|
||||
|
||||
/*&:hover {
|
||||
background-color: #eceef1;
|
||||
}*/
|
||||
.option_checkbox {
|
||||
/*margin-left: 8px;*/
|
||||
}
|
||||
|
||||
.option_link {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eceef1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -121,7 +208,12 @@ class AdvancedSelector extends React.Component {
|
||||
handleClick = e => {
|
||||
if (
|
||||
this.props.isOpen &&
|
||||
this.props.allowAnyClickClose &&
|
||||
this.ref &&
|
||||
this.ref.current &&
|
||||
!this.ref.current.contains(e.target) &&
|
||||
e && e.target && e.target.className &&
|
||||
typeof e.target.className.indexOf === "function" &&
|
||||
e.target.className.indexOf("option_checkbox") === -1
|
||||
) {
|
||||
this.props.onCancel && this.props.onCancel();
|
||||
@ -137,6 +229,14 @@ class AdvancedSelector extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.isOpen !== prevProps.isOpen) {
|
||||
handleAnyClick(this.props.isOpen, this.handleClick);
|
||||
}
|
||||
|
||||
if(this.props.allowAnyClickClose !== prevProps.allowAnyClickClose) {
|
||||
handleAnyClick(this.props.allowAnyClickClose, this.handleClick);
|
||||
}
|
||||
|
||||
let newState = {};
|
||||
|
||||
if (!isEqual(this.props.selectedOptions, prevProps.selectedOptions)) {
|
||||
@ -167,10 +267,6 @@ class AdvancedSelector extends React.Component {
|
||||
if (!isEmpty(newState)) {
|
||||
this.setState({ ...this.state, ...newState });
|
||||
}
|
||||
|
||||
if (this.props.isOpen !== prevProps.isOpen) {
|
||||
handleAnyClick(this.props.isOpen, this.handleClick);
|
||||
}
|
||||
}
|
||||
|
||||
convertGroups = groups => {
|
||||
@ -193,7 +289,7 @@ class AdvancedSelector extends React.Component {
|
||||
const currentGroup = groups.length > 0 ? groups[0] : "No groups";
|
||||
return currentGroup;
|
||||
};
|
||||
|
||||
|
||||
onButtonClick = () => {
|
||||
this.props.onSelect &&
|
||||
this.props.onSelect(
|
||||
@ -274,15 +370,23 @@ class AdvancedSelector extends React.Component {
|
||||
isMultiSelect,
|
||||
buttonLabel,
|
||||
selectAllLabel,
|
||||
size
|
||||
size,
|
||||
isDropDown,
|
||||
onAddNewClick,
|
||||
allowCreation
|
||||
} = this.props;
|
||||
|
||||
const { selectedOptions, selectedAll, currentGroup, groups } = this.state;
|
||||
const {
|
||||
selectedOptions,
|
||||
selectedAll,
|
||||
currentGroup,
|
||||
groups,
|
||||
} = this.state;
|
||||
|
||||
const containerHeight =
|
||||
size === "compact" ? (!groups || !groups.length ? 336 : 326) : 545;
|
||||
/*const containerHeight =
|
||||
size === "compact" ? (!groups || !groups.length ? 336 : 326) : 614;
|
||||
const containerWidth =
|
||||
size === "compact" ? (!groups || !groups.length ? 325 : 326) : 690;
|
||||
size === "compact" ? (!groups || !groups.length ? 325 : 326) : isDropDown ? 690 : 326;
|
||||
const listHeight =
|
||||
size === "compact"
|
||||
? !groups || !groups.length
|
||||
@ -290,8 +394,31 @@ class AdvancedSelector extends React.Component {
|
||||
? 176
|
||||
: 226
|
||||
: 120
|
||||
: 345;
|
||||
: 488;
|
||||
const listWidth = isDropDown ? 320 : "100%";*/
|
||||
|
||||
let containerHeight;
|
||||
let containerWidth;
|
||||
let listHeight;
|
||||
let listWidth;
|
||||
const itemHeight = 32;
|
||||
const hasGroups = groups && groups.length > 0;
|
||||
|
||||
switch (size) {
|
||||
case "compact":
|
||||
containerHeight = hasGroups ? "326px" : "100%";
|
||||
containerWidth = "379px";
|
||||
listWidth = isDropDown ? 356 : 356;
|
||||
listHeight = hasGroups ? 488 : isMultiSelect ? 176 : 226;
|
||||
break;
|
||||
case "full":
|
||||
default:
|
||||
containerHeight = "100%";
|
||||
containerWidth = isDropDown ? "690px" : "326px";
|
||||
listWidth = isDropDown ? 320 : 302;
|
||||
listHeight = 488;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
@ -301,50 +428,96 @@ class AdvancedSelector extends React.Component {
|
||||
>
|
||||
<div ref={this.ref}>
|
||||
<div className="data_container">
|
||||
<SearchInput
|
||||
className="options_searcher"
|
||||
isDisabled={isDisabled}
|
||||
size="base"
|
||||
scale={true}
|
||||
isNeedFilter={false}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onSearchChanged}
|
||||
onClearSearch={onSearchChanged.bind(this, "")}
|
||||
/>
|
||||
{groups && groups.length > 0 && (
|
||||
<ComboBox
|
||||
className="options_group_selector"
|
||||
<div className="data_column_one">
|
||||
<div className="head_container">
|
||||
<SearchInput
|
||||
className="options_searcher"
|
||||
isDisabled={isDisabled}
|
||||
options={groups}
|
||||
selectedOption={currentGroup}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
size="content"
|
||||
onSelect={this.onCurrentGroupChange}
|
||||
size="base"
|
||||
scale={true}
|
||||
isNeedFilter={false}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onSearchChanged}
|
||||
onClearSearch={onSearchChanged.bind(this, "")}
|
||||
/>
|
||||
{allowCreation && (
|
||||
<Button
|
||||
className="add_new_btn"
|
||||
primary={false}
|
||||
size="base"
|
||||
label=""
|
||||
icon={
|
||||
<Icons.PlusIcon
|
||||
size="medium"
|
||||
isfill={true}
|
||||
color="#D8D8D8"
|
||||
/>
|
||||
}
|
||||
onClick={onAddNewClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isDropDown &&
|
||||
groups &&
|
||||
groups.length > 0 && (
|
||||
<ComboBox
|
||||
className="options_group_selector"
|
||||
isDisabled={isDisabled}
|
||||
options={groups}
|
||||
selectedOption={currentGroup}
|
||||
dropDownMaxHeight={200}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
size="content"
|
||||
onSelect={this.onCurrentGroupChange}
|
||||
/>
|
||||
)}
|
||||
{isMultiSelect && !groups && !groups.length && (
|
||||
<Checkbox
|
||||
label={selectAllLabel}
|
||||
isChecked={
|
||||
selectedAll || selectedOptions.length === options.length
|
||||
}
|
||||
isIndeterminate={!selectedAll && selectedOptions.length > 0}
|
||||
className="option_select_all_checkbox"
|
||||
onChange={this.onSelectedAllChange}
|
||||
/>
|
||||
)}
|
||||
<FixedSizeList
|
||||
className="options_list"
|
||||
height={listHeight}
|
||||
width={listWidth}
|
||||
itemSize={itemHeight}
|
||||
itemCount={this.props.options.length}
|
||||
itemData={this.props.options}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{this.renderRow.bind(this)}
|
||||
</FixedSizeList>
|
||||
</div>
|
||||
{isDropDown && size === "full" && groups && groups.length > 0 && (
|
||||
<div className="data_column_two">
|
||||
<Text.Body
|
||||
as="p"
|
||||
className="group_header"
|
||||
fontSize={15}
|
||||
isBold={true}
|
||||
>
|
||||
Groups
|
||||
</Text.Body>
|
||||
<FixedSizeList
|
||||
className="group_list"
|
||||
height={listHeight}
|
||||
itemSize={itemHeight}
|
||||
itemCount={this.props.groups.length}
|
||||
itemData={this.props.groups}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{this.renderRow.bind(this)}
|
||||
</FixedSizeList>
|
||||
</div>
|
||||
)}
|
||||
{isMultiSelect && (
|
||||
<Checkbox
|
||||
label={selectAllLabel}
|
||||
isChecked={
|
||||
selectedAll || selectedOptions.length === options.length
|
||||
}
|
||||
isIndeterminate={!selectedAll && selectedOptions.length > 0}
|
||||
className="option_select_all_checkbox"
|
||||
onChange={this.onSelectedAllChange}
|
||||
/>
|
||||
)}
|
||||
<FixedSizeList
|
||||
className="options_list"
|
||||
height={listHeight}
|
||||
itemSize={itemHeight}
|
||||
itemCount={this.props.options.length}
|
||||
itemData={this.props.options}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{this.renderRow.bind(this)}
|
||||
</FixedSizeList>
|
||||
</div>
|
||||
{isMultiSelect && (
|
||||
<div className="button_container">
|
||||
@ -398,14 +571,18 @@ AdvancedSelector.propTypes = {
|
||||
onChangeGroup: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
isDropDown: PropTypes.bool,
|
||||
isOpen: PropTypes.bool
|
||||
isOpen: PropTypes.bool,
|
||||
allowCreation: PropTypes.bool,
|
||||
onAddNewClick: PropTypes.func,
|
||||
allowAnyClickClose: PropTypes.bool
|
||||
};
|
||||
|
||||
AdvancedSelector.defaultProps = {
|
||||
isMultiSelect: false,
|
||||
size: "compact",
|
||||
buttonLabel: "Add members",
|
||||
selectAllLabel: "Select all"
|
||||
selectAllLabel: "Select all",
|
||||
allowAnyClickClose: true
|
||||
};
|
||||
|
||||
export default AdvancedSelector;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import Button from '.';
|
||||
|
||||
describe('<Button />', () => {
|
||||
@ -10,4 +10,27 @@ describe('<Button />', () => {
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('not re-render test', () => {
|
||||
const onClick= () => alert('Button clicked');
|
||||
|
||||
const wrapper = shallow(<Button size='base' isDisabled={false} onClick={onClick} label="OK" />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('re-render test by value', () => {
|
||||
const onClick= () => alert('Button clicked');
|
||||
|
||||
const wrapper = shallow(<Button size='base' isDisabled={false} onClick={onClick} label="OK" />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
...wrapper.props,
|
||||
label: "Cancel"
|
||||
});
|
||||
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import Loader from '../loader';
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
const activeCss = css`
|
||||
background-color: ${props => (props.primary ? '#1F97CA' : '#ECEEF1')};
|
||||
@ -198,20 +199,27 @@ Icon.defaultProps = {
|
||||
icon: null
|
||||
};
|
||||
|
||||
const Button = props => {
|
||||
//console.log("Button render");
|
||||
const { isLoading, label, primary, size, icon } = props;
|
||||
return (
|
||||
<StyledButton {...props}>
|
||||
{(isLoading || icon) &&
|
||||
isLoading
|
||||
? <Loader type="oval" size={size === "big" ? 16 : 14} color={primary ? "#FFFFFF" : '#333333'} className="loader" />
|
||||
: <Icon {...props} />
|
||||
}
|
||||
{label}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
class Button extends React.Component {
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !isEqual(this.props, nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
// console.log("Button render");
|
||||
const { isLoading, label, primary, size, icon } = this.props;
|
||||
return (
|
||||
<StyledButton {...this.props}>
|
||||
{(isLoading || icon) &&
|
||||
isLoading
|
||||
? <Loader type="oval" size={size === "big" ? 16 : 14} color={primary ? "#FFFFFF" : '#333333'} className="loader" />
|
||||
: <Icon {...this.props} />
|
||||
}
|
||||
{label}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
label: PropTypes.string,
|
||||
|
@ -1,198 +0,0 @@
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import { Weekdays, Days, Day } from "./sub-components/";
|
||||
import NewCalendar from "./";
|
||||
|
||||
const baseCalendarProps = {
|
||||
isDisabled: false,
|
||||
themeColor: "#ED7309",
|
||||
selectedDate: new Date(),
|
||||
openToDate: new Date(),
|
||||
minDate: new Date("1970/01/01"),
|
||||
maxDate: new Date("3000/01/01"),
|
||||
locale: "en",
|
||||
onChange: () => jest.fn()
|
||||
};
|
||||
|
||||
const baseWeekdaysProps = {
|
||||
optionsWeekdays: [
|
||||
{ key: "en_0", value: "Mo", color: "" },
|
||||
{ key: "en_1", value: "Tu", color: "" },
|
||||
{ key: "en_2", value: "We", color: "" },
|
||||
{ key: "en_3", value: "Th", color: "" },
|
||||
{ key: "en_4", value: "Fr", color: "" },
|
||||
{ key: "en_5", value: "Sa", color: "#A3A9AE" },
|
||||
{ key: "en_6", value: "Su", color: "#A3A9AE" }
|
||||
],
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const baseDaysProps = {
|
||||
optionsDays: [
|
||||
{
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 25
|
||||
},
|
||||
{
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 26
|
||||
}
|
||||
],
|
||||
onDayClick: jest.fn,
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const baseDayProps = {
|
||||
day: {
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 26
|
||||
},
|
||||
|
||||
onDayClick: jest.fn,
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const selectedDate = new Date("09/12/2019");
|
||||
const openToDate = new Date("09/12/2019");
|
||||
const minDate = new Date("01/01/1970");
|
||||
const maxDate = new Date("01/01/2020");
|
||||
|
||||
describe("Weekdays tests:", () => {
|
||||
it("Weekdays renders without error", () => {
|
||||
const wrapper = mount(<Weekdays {...baseWeekdaysProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Weekdays not re-render test", () => {
|
||||
const wrapper = shallow(<Weekdays {...baseWeekdaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Weekdays property size passed", () => {
|
||||
const wrapper = mount(<Weekdays {...baseWeekdaysProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Days tests:", () => {
|
||||
it("Days renders without error", () => {
|
||||
const wrapper = mount(<Days {...baseDaysProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Days not re-render test", () => {
|
||||
const wrapper = shallow(<Days {...baseDaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Days property size passed", () => {
|
||||
const wrapper = mount(<Days {...baseDaysProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
/*
|
||||
it("Days click event", () => {
|
||||
const mockCallBack = jest.fn();
|
||||
|
||||
const button = shallow(<Days {...baseDaysProps} />);
|
||||
button.find("DayContent").simulate("click");
|
||||
expect(mockCallBack.mock.calls.length).toEqual(1);
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
describe("Day tests:", () => {
|
||||
it("Day renders without error", () => {
|
||||
const wrapper = mount(<Day {...baseDayProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Day not re-render test", () => {
|
||||
const wrapper = shallow(<Day {...baseDayProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Day property size passed", () => {
|
||||
const wrapper = mount(<Day {...baseDayProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Calendar tests:", () => {
|
||||
it("Calendar renders without error", () => {
|
||||
const wrapper = mount(<NewCalendar {...baseCalendarProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Calendar has rendered content.", () => {
|
||||
const wrapper = mount(<NewCalendar {...baseCalendarProps} />);
|
||||
expect(wrapper.find("div")).toExist();
|
||||
expect(wrapper.find("ul")).not.toExist();
|
||||
});
|
||||
|
||||
it("Calendar not re-render test", () => {
|
||||
const wrapper = shallow(<NewCalendar {...baseCalendarProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(
|
||||
wrapper.props,
|
||||
wrapper.state
|
||||
);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Calendar selectedDate test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} selectedDate={selectedDate} />
|
||||
);
|
||||
expect(wrapper.props().selectedDate).toEqual(selectedDate);
|
||||
});
|
||||
|
||||
it("Calendar openToDate test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} openToDate={openToDate} />
|
||||
);
|
||||
expect(wrapper.props().openToDate).toEqual(openToDate);
|
||||
});
|
||||
|
||||
it("Calendar minDate test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} minDate={minDate} />
|
||||
);
|
||||
expect(wrapper.props().minDate).toEqual(minDate);
|
||||
});
|
||||
|
||||
it("Calendar maxDate test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} maxDate={maxDate} />
|
||||
);
|
||||
expect(wrapper.props().maxDate).toEqual(maxDate);
|
||||
});
|
||||
|
||||
it("Calendar themeColor test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} themeColor={"#fff"} />
|
||||
);
|
||||
expect(wrapper.props().themeColor).toEqual("#fff");
|
||||
});
|
||||
|
||||
it("Calendar locale test", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} locale={"en-GB"} />
|
||||
);
|
||||
expect(wrapper.prop("locale")).toEqual("en-GB");
|
||||
});
|
||||
|
||||
it("Calendar disabled when isDisabled is passed", () => {
|
||||
const wrapper = mount(
|
||||
<NewCalendar {...baseCalendarProps} isDisabled={true} />
|
||||
);
|
||||
expect(wrapper.prop("isDisabled")).toEqual(true);
|
||||
});
|
||||
});
|
@ -7,9 +7,9 @@ Custom calendar
|
||||
#### Usage
|
||||
|
||||
```js
|
||||
import { NewCalendar } from "asc-web-components";
|
||||
import { Calendar } from "asc-web-components";
|
||||
|
||||
<NewCalendar
|
||||
<Calendar
|
||||
onChange={date => {
|
||||
console.log("Selected date:", date);
|
||||
}}
|
@ -10,7 +10,7 @@ import {
|
||||
} from "@storybook/addon-knobs/react";
|
||||
import withReadme from "storybook-readme/with-readme";
|
||||
import Readme from "./README.md";
|
||||
import NewCalendar from ".";
|
||||
import Calendar from ".";
|
||||
|
||||
function myDateKnob(name, defaultValue) {
|
||||
const stringTimestamp = date(name, defaultValue);
|
||||
@ -51,7 +51,7 @@ storiesOf("Components|Calendar", module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
.add("base", () => (
|
||||
<NewCalendar
|
||||
<Calendar
|
||||
onChange={date => {
|
||||
action("Selected date")(date);
|
||||
}}
|
319
web/ASC.Web.Components/src/components/calendar/calendar.test.js
Normal file
319
web/ASC.Web.Components/src/components/calendar/calendar.test.js
Normal file
@ -0,0 +1,319 @@
|
||||
import React from "react";
|
||||
import { mount, shallow, render } from "enzyme";
|
||||
import { Weekdays, Days, Day } from "./sub-components/";
|
||||
import Calendar from "./";
|
||||
import ComboBox from "../combobox";
|
||||
import moment from "moment";
|
||||
|
||||
const baseCalendarProps = {
|
||||
isDisabled: false,
|
||||
themeColor: "#ED7309",
|
||||
selectedDate: new Date(),
|
||||
openToDate: new Date(),
|
||||
minDate: new Date("1970/01/01"),
|
||||
maxDate: new Date("3000/01/01"),
|
||||
locale: "en",
|
||||
onChange: () => jest.fn()
|
||||
};
|
||||
|
||||
const baseWeekdaysProps = {
|
||||
optionsWeekdays: [
|
||||
{ key: "en_0", value: "Mo", color: "" },
|
||||
{ key: "en_1", value: "Tu", color: "" },
|
||||
{ key: "en_2", value: "We", color: "" },
|
||||
{ key: "en_3", value: "Th", color: "" },
|
||||
{ key: "en_4", value: "Fr", color: "" },
|
||||
{ key: "en_5", value: "Sa", color: "#A3A9AE" },
|
||||
{ key: "en_6", value: "Su", color: "#A3A9AE" }
|
||||
],
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const baseDaysProps = {
|
||||
optionsDays: [
|
||||
{
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 25
|
||||
},
|
||||
{
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 26
|
||||
}
|
||||
],
|
||||
onDayClick: jest.fn,
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const baseDayProps = {
|
||||
day: {
|
||||
className: "calendar-month_neighboringMonth",
|
||||
dayState: "prev",
|
||||
disableClass: null,
|
||||
value: 26
|
||||
},
|
||||
onDayClick: jest.fn(),
|
||||
size: "base"
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ key: 0, value: "one" },
|
||||
{ key: 1, value: "two" },
|
||||
{ key: 2, value: "three" }
|
||||
];
|
||||
const baseComboBoxProps = {
|
||||
options: options,
|
||||
selectedOption: { key: 0, value: "one" }
|
||||
};
|
||||
|
||||
const selectedDate = new Date("09/12/2019");
|
||||
const openToDate = new Date("09/12/2019");
|
||||
const minDate = new Date("01/01/1970");
|
||||
const maxDate = new Date("01/01/2020");
|
||||
const months = moment.months();
|
||||
|
||||
describe("Weekdays tests:", () => {
|
||||
it("Weekdays renders without error", () => {
|
||||
const wrapper = mount(<Weekdays {...baseWeekdaysProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Weekdays not re-render test", () => {
|
||||
const wrapper = shallow(<Weekdays {...baseWeekdaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Weekdays render test", () => {
|
||||
const wrapper = shallow(<Weekdays {...baseWeekdaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
...wrapper.props,
|
||||
size: "big"
|
||||
});
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it("Weekdays property size passed", () => {
|
||||
const wrapper = mount(<Weekdays {...baseWeekdaysProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Days tests:", () => {
|
||||
it("Days renders without error", () => {
|
||||
const wrapper = mount(<Days {...baseDaysProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Days not re-render test", () => {
|
||||
const wrapper = shallow(<Days {...baseDaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Days render test", () => {
|
||||
const wrapper = shallow(<Days {...baseDaysProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
...wrapper.props,
|
||||
size: "big"
|
||||
});
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it("Days property size passed", () => {
|
||||
const wrapper = mount(<Days {...baseDaysProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Day tests:", () => {
|
||||
it("Day renders without error", () => {
|
||||
const wrapper = mount(<Day {...baseDayProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Day not re-render test", () => {
|
||||
const wrapper = shallow(<Day {...baseDayProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it("Day render test", () => {
|
||||
const wrapper = shallow(<Day {...baseDayProps} />).instance();
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
...wrapper.props,
|
||||
size: "big"
|
||||
});
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it("Day property size passed", () => {
|
||||
const wrapper = mount(<Day {...baseDayProps} size={"big"} />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Calendar tests:", () => {
|
||||
it("Calendar renders without error", () => {
|
||||
const wrapper = mount(<Calendar {...baseCalendarProps} />);
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("Calendar selectedDate test", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} selectedDate={selectedDate} />
|
||||
);
|
||||
expect(wrapper.props().selectedDate).toEqual(selectedDate);
|
||||
});
|
||||
|
||||
it("Calendar openToDate test", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} openToDate={openToDate} />
|
||||
);
|
||||
expect(wrapper.props().openToDate).toEqual(openToDate);
|
||||
});
|
||||
|
||||
it("Calendar minDate test", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} minDate={minDate} />
|
||||
);
|
||||
expect(wrapper.props().minDate).toEqual(minDate);
|
||||
});
|
||||
|
||||
it("Calendar maxDate test", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} maxDate={maxDate} />
|
||||
);
|
||||
expect(wrapper.props().maxDate).toEqual(maxDate);
|
||||
});
|
||||
|
||||
it("Calendar themeColor test", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} themeColor={"#fff"} />
|
||||
);
|
||||
expect(wrapper.props().themeColor).toEqual("#fff");
|
||||
});
|
||||
|
||||
it("Calendar locale test", () => {
|
||||
const wrapper = mount(<Calendar {...baseCalendarProps} locale={"en-GB"} />);
|
||||
expect(wrapper.prop("locale")).toEqual("en-GB");
|
||||
});
|
||||
|
||||
it("Calendar size test", () => {
|
||||
const wrapper = shallow(<Calendar {...baseCalendarProps} size="big" />);
|
||||
expect(wrapper.prop("size")).toEqual("big");
|
||||
});
|
||||
|
||||
it("Calendar disabled when isDisabled is passed", () => {
|
||||
const wrapper = mount(
|
||||
<Calendar {...baseCalendarProps} isDisabled={true} />
|
||||
);
|
||||
expect(wrapper.prop("isDisabled")).toEqual(true);
|
||||
});
|
||||
it("Calendar has rendered content ComboBox", () => {
|
||||
const wrapper = mount(<Calendar {...baseCalendarProps} />);
|
||||
expect(wrapper).toExist(<ComboBox {...baseComboBoxProps} />);
|
||||
});
|
||||
|
||||
it("Calendar check the onChange callback", () => {
|
||||
const onChange = jest.fn();
|
||||
const props = {
|
||||
selectedDate: new Date("03/03/2000"),
|
||||
onChange
|
||||
};
|
||||
const wrapper = shallow(<Calendar {...props} />).instance();
|
||||
wrapper.onDayClick({
|
||||
value: 1,
|
||||
disableClass: "",
|
||||
className: "",
|
||||
dayState: "prev"
|
||||
});
|
||||
expect(onChange).toBeCalled();
|
||||
|
||||
const wrapper2 = shallow(<Calendar {...props} />).instance();
|
||||
wrapper2.onDayClick({
|
||||
value: 1,
|
||||
disableClass: "",
|
||||
className: "",
|
||||
dayState: "next"
|
||||
});
|
||||
expect(onChange).toBeCalled();
|
||||
|
||||
const wrapper3 = shallow(<Calendar {...props} />).instance();
|
||||
wrapper3.onDayClick({
|
||||
value: 1,
|
||||
disableClass: "",
|
||||
className: "",
|
||||
dayState: "now"
|
||||
});
|
||||
expect(onChange).toBeCalled();
|
||||
});
|
||||
|
||||
it("Calendar check onSelectYear function", () => {
|
||||
const props = {
|
||||
openToDate: new Date("05/01/2000"),
|
||||
selectedDate: new Date("01/01/2000"),
|
||||
minDate: new Date("01/01/1970"),
|
||||
maxDate: new Date("01/01/2020")
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Calendar {...props} />).instance();
|
||||
wrapper.onSelectYear({
|
||||
key: 2020,
|
||||
value: 2020
|
||||
});
|
||||
|
||||
expect(wrapper.state.openToDate).toEqual(new Date("01/01/2020"));
|
||||
});
|
||||
|
||||
it("Calendar check onSelectMonth function", () => {
|
||||
const props = {
|
||||
openToDate: new Date("01/01/2000"),
|
||||
selectedDate: new Date("01/01/2000")
|
||||
};
|
||||
const wrapper = shallow(<Calendar {...props} />).instance();
|
||||
wrapper.onSelectMonth({ key: "1", label: "February", disabled: false });
|
||||
|
||||
expect(wrapper.state.openToDate).toEqual(new Date("02/01/2000"));
|
||||
});
|
||||
|
||||
it("Calendar check Compare dates function", () => {
|
||||
const date = new Date();
|
||||
const wrapper = shallow(<Calendar {...baseCalendarProps} />).instance();
|
||||
expect(wrapper.compareDates(date, date) === 0).toEqual(true);
|
||||
expect(wrapper.compareDates(date, new Date("01/01/2000")) === 0).toEqual(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("Calendar error date test", () => {
|
||||
const wrapper = shallow(<Calendar {...baseCalendarProps} />);
|
||||
wrapper.setState({ hasError: true, isDisabled: true });
|
||||
expect(wrapper.instance().state.hasError).toEqual(true);
|
||||
expect(wrapper.instance().state.isDisabled).toEqual(true);
|
||||
});
|
||||
|
||||
it("Calendar not error date test", () => {
|
||||
const wrapper = shallow(<Calendar {...baseCalendarProps} />);
|
||||
wrapper.setState({ hasError: false, isDisabled: false });
|
||||
expect(wrapper.instance().state.hasError).toEqual(false);
|
||||
});
|
||||
|
||||
it("Calendar componentDidUpdate() test", () => {
|
||||
const wrapper = mount(<Calendar {...baseCalendarProps} />).instance();
|
||||
wrapper.componentDidUpdate(wrapper.props, wrapper.state);
|
||||
|
||||
const wrapper2 = mount(
|
||||
<Calendar {...baseCalendarProps} selectedDate={new Date("01/01/1910")} />
|
||||
).instance();
|
||||
|
||||
expect(wrapper.props).toBe(wrapper.props);
|
||||
expect(wrapper.state).toBe(wrapper.state);
|
||||
|
||||
expect(wrapper2.props).toBe(wrapper2.props);
|
||||
expect(wrapper2.state).toBe(wrapper2.state);
|
||||
});
|
||||
});
|
@ -594,35 +594,6 @@ class Calendar extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const {
|
||||
selectedDate,
|
||||
openToDate,
|
||||
minDate,
|
||||
maxDate,
|
||||
isDisabled,
|
||||
locale,
|
||||
themeColor
|
||||
} = this.props;
|
||||
|
||||
const { hasError, optionsDays } = this.state;
|
||||
|
||||
if (
|
||||
this.compareDates(selectedDate, nextProps.selectedDate) === 0 &&
|
||||
this.compareDates(openToDate, nextProps.openToDate) === 0 &&
|
||||
this.compareDates(minDate, nextProps.minDate) === 0 &&
|
||||
this.compareDates(maxDate, nextProps.maxDate) === 0 &&
|
||||
isDisabled === nextProps.isDisabled &&
|
||||
hasError === nextState.hasError &&
|
||||
optionsDays === nextState.optionsDays &&
|
||||
locale === nextProps.locale &&
|
||||
themeColor === nextProps.themeColor
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log("Calendar render");
|
||||
|
||||
@ -648,6 +619,7 @@ class Calendar extends Component {
|
||||
<ComboBoxMonthStyle size={size}>
|
||||
<ComboBox
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
dropDownMaxHeight={dropDownSizeMonth}
|
||||
onSelect={this.onSelectMonth}
|
||||
selectedOption={selectedOptionMonth}
|
||||
@ -658,6 +630,7 @@ class Calendar extends Component {
|
||||
<ComboBoxDateStyle>
|
||||
<ComboBox
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
dropDownMaxHeight={dropDownSizeYear}
|
||||
onSelect={this.onSelectYear}
|
||||
selectedOption={selectedOptionYear}
|
@ -83,6 +83,7 @@ const options = [
|
||||
dropDownMaxHeight={200}
|
||||
noBorder={false}
|
||||
scale={true}
|
||||
scaledOptions={true}
|
||||
size='content'
|
||||
onSelect={option => console.log('selected', option)}
|
||||
/>
|
||||
@ -90,14 +91,60 @@ const options = [
|
||||
|
||||
#### Properties
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------------- | --------- | :------: | ------------------------------------------ | ------- | --------------------------------------------------------- |
|
||||
| `options` | `array` | ✅ | - | - | Combo box options |
|
||||
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
|
||||
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
|
||||
| `selectedOption` | `object` | ✅ | - | - | Selected option |
|
||||
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
|
||||
| `dropDownMaxHeight` | `number` | - | - | - | Height of Dropdown |
|
||||
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
|
||||
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
|
||||
| `advancedOptions` | `element` | - | - | - | If you need display options not basic options |
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------------- | --------- | :------: | ------------------------------------------ | ------- | ----------------------------------------------------------- |
|
||||
| `options` | `array` | ✅ | - | - | Combo box options |
|
||||
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
|
||||
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
|
||||
| `selectedOption` | `object` | ✅ | - | - | Selected option |
|
||||
| `onSelect` | `func` | - | - | - | Will be triggered whenever an ComboBox is selected option |
|
||||
| `dropDownMaxHeight` | `number` | - | - | - | Height of Dropdown |
|
||||
| `scaled` | `bool` | - | - | `true` | Indicates that component is scaled by parent |
|
||||
| `scaledOptions` | `bool` | - | - | `false` | Indicates that component`s options is scaled by ComboButton |
|
||||
| `size` | `oneOf` | - | `base`, `middle`, `big`, `huge`, `content` | `base` | Select component width, one of default |
|
||||
| `advancedOptions` | `element` | - | - | - | If you need display options not basic options |
|
||||
|
||||
## ComboButton
|
||||
|
||||
#### Description
|
||||
|
||||
> This description is for reference only, the component described below is not exported.
|
||||
|
||||
To create designs using combobox logic, there is a child component ComboButton.
|
||||
This is an independent element that responds to changes in parameters and serves only to demonstrate set values.
|
||||
|
||||
```js
|
||||
<ComboButton
|
||||
noBorder={false}
|
||||
isDisabled={false}
|
||||
selectedOption={{
|
||||
key: 0,
|
||||
label: "Select"
|
||||
}}
|
||||
withOptions={false}
|
||||
optionsLength={0}
|
||||
withAdvancedOptions={true}
|
||||
innerContainer={<>Demo container</>}
|
||||
innerContainerClassName="optionalBlock"
|
||||
isOpen={false}
|
||||
size="content"
|
||||
scaled={false}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Properties
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------------------- | -------- | :------: | -------------------------------- | ---------------- | -------------------------------------------------------- |
|
||||
| `isDisabled` | `bool` | - | - | `false` | Indicates that component is disabled |
|
||||
| `noBorder` | `bool` | - | - | `false` | Indicates that component is displayed without borders |
|
||||
| `selectedOption` | `object` | - | - | - | Selected option |
|
||||
| `withOptions` | `bool` | - | - | `true` | Lets you style as ComboBox with options |
|
||||
| `optionsLength` | `number` | - | - | - | Lets you style as ComboBox with options |
|
||||
| `withAdvancedOptions` | `bool` | - | - | `false` | Lets you style as a ComboBox with advanced options |
|
||||
| `innerContainer` | `node` | - | - | - | Allows displaying third-party element inside ComboButton |
|
||||
| `innerContainerClassName` | `string` | - | - | `innerContainer` | Required to access third-party container |
|
||||
| `isOpen` | `bool` | - | - | `false` | Lets you style as ComboBox arrow |
|
||||
| `scaled` | `bool` | - | - | `false` | Indicates that component is scaled by parent |
|
||||
| `size` | `oneOf` | - | `base`, `...`, `huge`, `content` | `content` | Select component width, one of default |
|
||||
| `onClick` | `func` | - | - | - | Will be triggered whenever an ComboButton is clicked |
|
||||
|
@ -58,7 +58,7 @@ storiesOf('Components|Input', module)
|
||||
];
|
||||
|
||||
const needScrollDropDown = boolean('Need scroll dropdown', false);
|
||||
const dropDownMaxHeight = needScrollDropDown && number('dropDownMaxHeight', 200);
|
||||
const dropDownMaxHeight = needScrollDropDown ? number('dropDownMaxHeight', 200) : null;
|
||||
const optionsMultiSelect = options('children',
|
||||
{
|
||||
button: 'button',
|
||||
@ -125,6 +125,7 @@ storiesOf('Components|Input', module)
|
||||
noBorder={boolean('noBorder', false)}
|
||||
dropDownMaxHeight={dropDownMaxHeight}
|
||||
scaled={boolean('scaled', false)}
|
||||
scaledOptions={boolean('scaledOptions', false)}
|
||||
size={select('size', sizeOptions, 'content')}
|
||||
>
|
||||
{childrenItems}
|
||||
@ -146,7 +147,6 @@ storiesOf('Components|Input', module)
|
||||
>
|
||||
<Icons.NavLogoIcon size="medium" key='comboIcon' />
|
||||
</ComboBox>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -31,6 +31,7 @@ const baseProps = {
|
||||
isDisabled: false,
|
||||
selectedOption: {
|
||||
key: 0,
|
||||
icon: 'CatalogFolderIcon',
|
||||
label: "Select"
|
||||
},
|
||||
options: baseOptions,
|
||||
@ -41,7 +42,7 @@ const baseProps = {
|
||||
};
|
||||
|
||||
describe('<ComboBox />', () => {
|
||||
it('renders without error', () => {
|
||||
it('rendered without error', () => {
|
||||
const wrapper = mount(
|
||||
<ComboBox {...baseProps} />
|
||||
);
|
||||
@ -49,7 +50,7 @@ describe('<ComboBox />', () => {
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('render with advanced options', () => {
|
||||
it('with advanced options', () => {
|
||||
const wrapper = mount(
|
||||
<ComboBox {...baseProps} options={[]} advancedOptions={advancedOptions} />
|
||||
);
|
||||
@ -57,13 +58,79 @@ describe('<ComboBox />', () => {
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it('disabled when isDisabled is passed', () => {
|
||||
it('disabled', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} isDisabled={true} />);
|
||||
|
||||
expect(wrapper.prop('isDisabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('not re-render test', () => {
|
||||
it('without borders', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} noBorder={true} />);
|
||||
|
||||
expect(wrapper.prop('noBorder')).toEqual(true);
|
||||
});
|
||||
|
||||
it('opened', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} opened={true} />);
|
||||
|
||||
expect(wrapper.prop('opened')).toEqual(true);
|
||||
});
|
||||
|
||||
it('with DropDown max height', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} dropDownMaxHeight={200} />);
|
||||
|
||||
expect(wrapper.prop('dropDownMaxHeight')).toEqual(200);
|
||||
});
|
||||
|
||||
it('without scaled', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={false} />);
|
||||
|
||||
expect(wrapper.prop('scaled')).toEqual(false);
|
||||
});
|
||||
|
||||
it('scaled', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={true} />);
|
||||
|
||||
expect(wrapper.prop('scaled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('scaled options', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaledOptions={true} />);
|
||||
|
||||
expect(wrapper.prop('scaledOptions')).toEqual(true);
|
||||
});
|
||||
|
||||
it('middle size options', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={false} size='middle' />);
|
||||
|
||||
expect(wrapper.prop('size')).toEqual('middle');
|
||||
});
|
||||
|
||||
it('big size options', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={false} size='big' />);
|
||||
|
||||
expect(wrapper.prop('size')).toEqual('big');
|
||||
});
|
||||
|
||||
it('huge size options', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={false} size='huge' />);
|
||||
|
||||
expect(wrapper.prop('size')).toEqual('huge');
|
||||
});
|
||||
|
||||
it('by content size options', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} scaled={false} size='content' />);
|
||||
|
||||
expect(wrapper.prop('size')).toEqual('content');
|
||||
});
|
||||
|
||||
it('with children node', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} ><div>demo</div></ComboBox>);
|
||||
|
||||
expect(wrapper.contains(<div>demo</div>)).toBe(true)
|
||||
});
|
||||
|
||||
it('not re-render', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props, wrapper.state);
|
||||
@ -71,23 +138,121 @@ describe('<ComboBox />', () => {
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('re-render test', () => {
|
||||
it('re-render', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} />).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
noBorder: true,
|
||||
isDisabled: false,
|
||||
selectedOption: {
|
||||
key: 0,
|
||||
label: "Select"
|
||||
},
|
||||
options: baseOptions,
|
||||
opened: false,
|
||||
onSelect: () => jest.fn(),
|
||||
size: 'base',
|
||||
scaled: true
|
||||
}, wrapper.state);
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({ opened: true }, wrapper.state);
|
||||
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
it('causes function comboBoxClick() with disabled prop', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} isDisabled={true} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.comboBoxClick();
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function comboBoxClick()', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.comboBoxClick();
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(true);
|
||||
});
|
||||
|
||||
it('causes function optionClick()', () => {
|
||||
const onSelect = jest.fn();
|
||||
const selectedOption = {
|
||||
key: 1,
|
||||
label: "Select"
|
||||
};
|
||||
const wrapper = shallow(<ComboBox {...baseProps} opened={true} onSelect={onSelect} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.optionClick(selectedOption);
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
expect(onSelect).toHaveBeenCalledWith(selectedOption);
|
||||
});
|
||||
|
||||
it('causes function stopAction()', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.stopAction(new Event('click'));
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function handleClick() with opened prop', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} opened={true} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.handleClick(new Event('click'));
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function handleClick()', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.handleClick(new Event('click'));
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function handleClick() with simulate', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} opened={true} />);
|
||||
|
||||
wrapper.simulate('click');
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(false);
|
||||
});
|
||||
|
||||
it('causes function handleClick() with simulate and ComboBox not opened', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} />);
|
||||
|
||||
wrapper.simulate('click');
|
||||
|
||||
expect(wrapper.state('isOpen')).toBe(true);
|
||||
});
|
||||
|
||||
it('componentDidUpdate() state lifecycle test', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
wrapper.setState({ isOpen: false });
|
||||
|
||||
instance.componentDidUpdate(wrapper.props(), wrapper.state());
|
||||
|
||||
expect(wrapper.state()).toBe(wrapper.state());
|
||||
});
|
||||
|
||||
it('componentDidUpdate() props lifecycle test', () => {
|
||||
const wrapper = shallow(<ComboBox {...baseProps} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.componentDidUpdate({
|
||||
opened: true,
|
||||
selectedOption: {
|
||||
key: 1,
|
||||
label: "Select"
|
||||
}
|
||||
}, wrapper.state());
|
||||
|
||||
expect(wrapper.props()).toBe(wrapper.props());
|
||||
});
|
||||
|
||||
it('componentWillUnmount() lifecycle test', () => {
|
||||
const wrapper = mount(<ComboBox {...baseProps} opened={true} />);
|
||||
const componentWillUnmount = jest.spyOn(wrapper.instance(), 'componentWillUnmount');
|
||||
|
||||
wrapper.unmount();
|
||||
expect(componentWillUnmount).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -3,12 +3,11 @@ import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components';
|
||||
import DropDownItem from '../drop-down-item'
|
||||
import DropDown from '../drop-down'
|
||||
import { Icons } from '../icons'
|
||||
import { handleAnyClick } from '../../utils/event';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import ComboButton from './sub-components/combo-button'
|
||||
|
||||
const StyledComboBox = styled.div`
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#333333'};
|
||||
width: ${props =>
|
||||
(props.scaled && '100%') ||
|
||||
(props.size === 'base' && '173px') ||
|
||||
@ -19,92 +18,6 @@ const StyledComboBox = styled.div`
|
||||
};
|
||||
|
||||
position: relative;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background: #FFFFFF;
|
||||
|
||||
${props => !props.noBorder && `
|
||||
border: 1px solid #D0D5DA;
|
||||
border-radius: 3px;
|
||||
`}
|
||||
|
||||
${props => props.isDisabled && !props.noBorder && `
|
||||
border-color: #ECEEF1;
|
||||
background: #F8F9F9;
|
||||
`}
|
||||
|
||||
${props => !props.noBorder && `
|
||||
height: 32px;
|
||||
`}
|
||||
|
||||
:hover{
|
||||
border-color: ${state => state.isOpen ? '#2DA7DB' : '#A3A9AE' };
|
||||
cursor: ${props => (props.isDisabled || !props.options.length ) ? (props.advancedOptions) ? 'pointer' : 'default' : 'pointer'};
|
||||
|
||||
${props => props.isDisabled && `
|
||||
border-color: #ECEEF1;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledComboButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: ${props => props.noBorder ? `18px` : `30px`};
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
width: 16px;
|
||||
margin-right: 8px;
|
||||
margin-top: -2px;
|
||||
`;
|
||||
|
||||
const StyledOptionalItem = styled.div`
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
font-family: Open Sans;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
margin-right: 8px;
|
||||
|
||||
${props => props.noBorder && `
|
||||
line-height: 11px;
|
||||
border-bottom: 1px dashed transparent;
|
||||
|
||||
:hover{
|
||||
border-bottom: 1px dashed;
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
const StyledArrowIcon = styled.div`
|
||||
display: flex;
|
||||
align-self: start;
|
||||
width: ${props => props.needDisplay ? '8px' : '0px'};
|
||||
flex: 0 0 ${props => props.needDisplay ? '8px' : '0px'};
|
||||
margin-top: ${props => props.noBorder ? `5px` : `12px`};
|
||||
margin-right: ${props => props.needDisplay ? '8px' : '0px'};
|
||||
margin-left: ${props => props.needDisplay ? 'auto' : '0px'};
|
||||
|
||||
${props => props.isOpen && `
|
||||
transform: scale(1, -1);
|
||||
`}
|
||||
`;
|
||||
|
||||
class ComboBox extends React.Component {
|
||||
@ -122,26 +35,27 @@ class ComboBox extends React.Component {
|
||||
handleAnyClick(true, this.handleClick);
|
||||
}
|
||||
|
||||
handleClick = (e) =>
|
||||
this.state.isOpen
|
||||
&& !this.ref.current.contains(e.target)
|
||||
&& this.toggle(false);
|
||||
handleClick = (e) =>{
|
||||
if (this.state.isOpen && !this.ref.current.contains(e.target)) {
|
||||
this.toggle(false);
|
||||
}
|
||||
}
|
||||
|
||||
stopAction = (e) => e.preventDefault();
|
||||
|
||||
toggle = (isOpen) => this.setState({ isOpen: isOpen });
|
||||
|
||||
comboBoxClick = (e) => {
|
||||
if (this.props.isDisabled || e.target.closest('.optionalBlock')) return;
|
||||
if (this.props.isDisabled || e && e.target.closest('.optionalBlock')) return;
|
||||
this.toggle(!this.state.isOpen);
|
||||
};
|
||||
|
||||
optionClick = (option) => {
|
||||
this.toggle(!this.state.isOpen);
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen,
|
||||
selectedOption: option
|
||||
});
|
||||
|
||||
this.props.onSelect && this.props.onSelect(option);
|
||||
};
|
||||
|
||||
@ -149,7 +63,7 @@ class ComboBox extends React.Component {
|
||||
handleAnyClick(false, this.handleClick);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps,nextState) {
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !isEqual(this.props, nextProps) || !isEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
@ -170,62 +84,57 @@ class ComboBox extends React.Component {
|
||||
render() {
|
||||
//console.log("ComboBox render");
|
||||
const {
|
||||
dropDownMaxHeight,
|
||||
isDisabled,
|
||||
directionX,
|
||||
directionY,
|
||||
scaled,
|
||||
children,
|
||||
options,
|
||||
noBorder,
|
||||
advancedOptions
|
||||
} = this.props;
|
||||
dropDownMaxHeight,
|
||||
directionX,
|
||||
directionY,
|
||||
scaled,
|
||||
size,
|
||||
options,
|
||||
advancedOptions,
|
||||
isDisabled,
|
||||
children,
|
||||
noBorder,
|
||||
scaledOptions } = this.props;
|
||||
const { isOpen, selectedOption } = this.state;
|
||||
|
||||
const dropDownMaxHeightProp = dropDownMaxHeight ? { maxHeight: dropDownMaxHeight } : {};
|
||||
const dropDownManualWidthProp = scaled ? { manualWidth: '100%' } : {};
|
||||
const boxIconColor = isDisabled ? '#D0D5DA' : '#333333';
|
||||
const arrowIconColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
||||
|
||||
const dropDownMaxHeightProp = dropDownMaxHeight
|
||||
? { maxHeight: dropDownMaxHeight }
|
||||
: {};
|
||||
const dropDownManualWidthProp = scaledOptions
|
||||
? { manualWidth: '100%' }
|
||||
: {};
|
||||
|
||||
const optionsLength = options.length
|
||||
? options.length
|
||||
: 0;
|
||||
|
||||
const advancedOptionsLength = advancedOptions
|
||||
? advancedOptions.props.children.length
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<StyledComboBox ref={this.ref}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
<StyledComboBox
|
||||
ref={this.ref}
|
||||
isDisabled={isDisabled}
|
||||
scaled={scaled}
|
||||
size={size}
|
||||
data={selectedOption}
|
||||
onClick={this.comboBoxClick}
|
||||
onSelect={this.stopAction}
|
||||
{...this.props}
|
||||
>
|
||||
<StyledComboButton noBorder={noBorder}>
|
||||
{children &&
|
||||
<StyledOptionalItem className='optionalBlock'>
|
||||
{children}
|
||||
</StyledOptionalItem>
|
||||
}
|
||||
{selectedOption && selectedOption.icon &&
|
||||
<StyledIcon>
|
||||
{React.createElement(Icons[selectedOption.icon],
|
||||
{
|
||||
size: 'scale',
|
||||
color: boxIconColor,
|
||||
isfill: true
|
||||
})
|
||||
}
|
||||
</StyledIcon>
|
||||
}
|
||||
<StyledLabel noBorder={noBorder}>
|
||||
{selectedOption.label}
|
||||
</StyledLabel>
|
||||
<StyledArrowIcon needDisplay={options.length > 0 || advancedOptions !== undefined} noBorder={noBorder} isOpen={isOpen}>
|
||||
{(options.length > 0 || advancedOptions !== undefined) &&
|
||||
React.createElement(Icons['ExpanderDownIcon'],
|
||||
{
|
||||
size: 'scale',
|
||||
color: arrowIconColor,
|
||||
isfill: true
|
||||
})
|
||||
}
|
||||
</StyledArrowIcon>
|
||||
</StyledComboButton>
|
||||
<ComboButton
|
||||
noBorder={noBorder}
|
||||
isDisabled={isDisabled}
|
||||
selectedOption={selectedOption}
|
||||
withOptions={optionsLength > 0}
|
||||
optionsLength={optionsLength}
|
||||
withAdvancedOptions={advancedOptionsLength > 0}
|
||||
innerContainer={children}
|
||||
innerContainerClassName='optionalBlock'
|
||||
isOpen={isOpen}
|
||||
size={size}
|
||||
scaled={scaled}
|
||||
/>
|
||||
<DropDown
|
||||
directionX={directionX}
|
||||
directionY={directionY}
|
||||
@ -234,15 +143,17 @@ class ComboBox extends React.Component {
|
||||
{...dropDownMaxHeightProp}
|
||||
{...dropDownManualWidthProp}
|
||||
>
|
||||
{advancedOptions
|
||||
? advancedOptions
|
||||
: options.map((option) =>
|
||||
<DropDownItem {...option}
|
||||
key={option.key}
|
||||
disabled={option.disabled || (option.label === selectedOption.label)}
|
||||
onClick={this.optionClick.bind(this, option)}
|
||||
/>
|
||||
)}
|
||||
{advancedOptions
|
||||
? advancedOptions
|
||||
: options.map((option) =>
|
||||
<DropDownItem {...option}
|
||||
key={option.key}
|
||||
disabled={
|
||||
option.disabled
|
||||
|| (option.label === selectedOption.label)}
|
||||
onClick={this.optionClick.bind(this, option)}
|
||||
/>
|
||||
)}
|
||||
</DropDown>
|
||||
</StyledComboBox>
|
||||
);
|
||||
@ -264,14 +175,16 @@ ComboBox.propTypes = {
|
||||
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge', 'content']),
|
||||
directionX: PropTypes.oneOf(['left', 'right']),
|
||||
directionY: PropTypes.oneOf(['bottom', 'top']),
|
||||
scaled: PropTypes.bool
|
||||
scaled: PropTypes.bool,
|
||||
scaledOptions: PropTypes.bool
|
||||
}
|
||||
|
||||
ComboBox.defaultProps = {
|
||||
noBorder: false,
|
||||
isDisabled: false,
|
||||
size: 'base',
|
||||
scaled: true
|
||||
scaled: true,
|
||||
scaledOptions: false
|
||||
}
|
||||
|
||||
export default ComboBox;
|
@ -0,0 +1,205 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components';
|
||||
import { Icons } from '../../icons'
|
||||
|
||||
const StyledComboButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: ${props => props.noBorder ? `18px` : `30px`};
|
||||
width: ${props =>
|
||||
(props.scaled && '100%') ||
|
||||
(props.size === 'base' && '173px') ||
|
||||
(props.size === 'middle' && '300px') ||
|
||||
(props.size === 'big' && '350px') ||
|
||||
(props.size === 'huge' && '500px') ||
|
||||
(props.size === 'content' && 'fit-content')
|
||||
};
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
padding-left: 8px;
|
||||
|
||||
background: ${props => !props.noBorder ? '#FFFFFF' : 'none'};
|
||||
|
||||
color: ${props => props.isDisabled ? '#D0D5DA' : '#333333'};
|
||||
|
||||
${props => !props.noBorder && `
|
||||
border: 1px solid #D0D5DA;
|
||||
border-radius: 3px;
|
||||
`}
|
||||
|
||||
${props => props.isDisabled && !props.noBorder && `
|
||||
border-color: #ECEEF1;
|
||||
background: #F8F9F9;
|
||||
`}
|
||||
|
||||
${props => !props.noBorder && `
|
||||
height: 32px;
|
||||
`}
|
||||
|
||||
:hover{
|
||||
border-color: ${props => props.isOpen ? '#2DA7DB' : '#A3A9AE'};
|
||||
cursor: ${props => (props.isDisabled || (!props.containOptions && !props.withAdvancedOptions))
|
||||
? 'default'
|
||||
: 'pointer'};
|
||||
|
||||
${props => props.isDisabled && `
|
||||
border-color: #ECEEF1;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledOptionalItem = styled.div`
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
width: 16px;
|
||||
margin-right: 8px;
|
||||
margin-top: -2px;
|
||||
`;
|
||||
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
font-family: Open Sans;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
margin-right: 8px;
|
||||
|
||||
${props => props.noBorder && `
|
||||
line-height: 11px;
|
||||
border-bottom: 1px dashed transparent;
|
||||
|
||||
:hover{
|
||||
border-bottom: 1px dashed;
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
const StyledArrowIcon = styled.div`
|
||||
display: flex;
|
||||
align-self: start;
|
||||
width: ${props => props.needDisplay ? '8px' : '0px'};
|
||||
flex: 0 0 ${props => props.needDisplay ? '8px' : '0px'};
|
||||
margin-top: ${props => props.noBorder ? `5px` : `12px`};
|
||||
margin-right: ${props => props.needDisplay ? '8px' : '0px'};
|
||||
margin-left: ${props => props.needDisplay ? 'auto' : '0px'};
|
||||
|
||||
${props => props.isOpen && `
|
||||
transform: scale(1, -1);
|
||||
`}
|
||||
`;
|
||||
|
||||
class ComboButton extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
noBorder,
|
||||
onClick,
|
||||
isDisabled,
|
||||
innerContainer,
|
||||
innerContainerClassName,
|
||||
selectedOption,
|
||||
optionsLength,
|
||||
withOptions,
|
||||
withAdvancedOptions,
|
||||
isOpen,
|
||||
scaled,
|
||||
size } = this.props;
|
||||
|
||||
const boxIconColor = isDisabled ? '#D0D5DA' : '#333333';
|
||||
const arrowIconColor = isDisabled ? '#D0D5DA' : '#A3A9AE';
|
||||
|
||||
return (
|
||||
<StyledComboButton
|
||||
isOpen={isOpen}
|
||||
isDisabled={isDisabled}
|
||||
noBorder={noBorder}
|
||||
containOptions={optionsLength}
|
||||
withAdvancedOptions={withAdvancedOptions}
|
||||
onClick={onClick}
|
||||
scaled={scaled}
|
||||
size={size}
|
||||
>
|
||||
{innerContainer &&
|
||||
<StyledOptionalItem className={innerContainerClassName}>
|
||||
{innerContainer}
|
||||
</StyledOptionalItem>
|
||||
}
|
||||
{selectedOption && selectedOption.icon &&
|
||||
<StyledIcon>
|
||||
{React.createElement(Icons[selectedOption.icon],
|
||||
{
|
||||
size: 'scale',
|
||||
color: boxIconColor,
|
||||
isfill: true
|
||||
})
|
||||
}
|
||||
</StyledIcon>
|
||||
}
|
||||
<StyledLabel noBorder={noBorder}>
|
||||
{selectedOption.label}
|
||||
</StyledLabel>
|
||||
<StyledArrowIcon
|
||||
needDisplay={withOptions || withAdvancedOptions}
|
||||
noBorder={noBorder}
|
||||
isOpen={isOpen}>
|
||||
{(withOptions || withAdvancedOptions) &&
|
||||
React.createElement(Icons['ExpanderDownIcon'],
|
||||
{
|
||||
size: 'scale',
|
||||
color: arrowIconColor,
|
||||
isfill: true
|
||||
})
|
||||
}
|
||||
</StyledArrowIcon>
|
||||
</StyledComboButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ComboButton.propTypes = {
|
||||
noBorder: PropTypes.bool,
|
||||
isDisabled: PropTypes.bool,
|
||||
selectedOption: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.object),
|
||||
PropTypes.object
|
||||
]),
|
||||
withOptions: PropTypes.bool,
|
||||
optionsLength: PropTypes.number,
|
||||
withAdvancedOptions: PropTypes.bool,
|
||||
innerContainer: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
]),
|
||||
innerContainerClassName: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
size: PropTypes.oneOf(['base', 'middle', 'big', 'huge', 'content']),
|
||||
scaled: PropTypes.bool,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
ComboButton.defaultProps = {
|
||||
noBorder: false,
|
||||
isDisabled: false,
|
||||
withOptions: true,
|
||||
withAdvancedOptions: false,
|
||||
innerContainerClassName: 'innerContainer',
|
||||
isOpen: false,
|
||||
size: 'content',
|
||||
scaled: false
|
||||
}
|
||||
|
||||
export default ComboButton;
|
@ -1,29 +0,0 @@
|
||||
# DateInput
|
||||
|
||||
#### Description
|
||||
|
||||
Custom date input
|
||||
|
||||
#### Usage
|
||||
|
||||
```js
|
||||
import { DateInput } from 'asc-web-components';
|
||||
|
||||
<DateInput selected={new Date()} dateFormat="dd.MM.yyyy" onChange={date => {}}/>
|
||||
```
|
||||
|
||||
#### Properties
|
||||
|
||||
https://reactdatepicker.com/
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------ | -------- | :------: | ------ | ------- | --------------------------------------- |
|
||||
| `id` | `string` | - | - | - | Used as HTML `id` property |
|
||||
| `name` | `string` | - | - | - | Used as HTML `name` property |
|
||||
| `disabled` | `bool` | - | - | - | Used as HTML `disabled` property |
|
||||
| `readOnly` | `bool` | - | - | - | Used as HTML `readOnly` property |
|
||||
| `selected` | `date` | - | - | - | Selected date value |
|
||||
| `onChange` | `func` | - | - | - | OnChange event |
|
||||
| `dateFormat` | `string` | - | - | - | Date format string |
|
||||
| `hasError` | `bool` | - | - | - | Indicates the input field has an error |
|
||||
| `hasWarning` | `bool` | - | - | - | Indicates the input field has a warning |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user