Merge branch 'feature/workspaces' of github.com:ONLYOFFICE/AppServer into feature/workspaces

# Conflicts:
#	package.json
#	packages/asc-web-components/package.json
#	yarn.lock
This commit is contained in:
Artem Tarasov 2021-03-03 12:00:23 +03:00
commit 96da41faa1
118 changed files with 5655 additions and 4864 deletions

View File

@ -1,34 +1,40 @@
{
"folders": [
{
"name": "root"
"path": "./"
},
{
"name": "packages"
"path": "./packages"
},
{
"name": "ASC.Web.Login"
"path": "./web/ASC.Web.Login"
},
{
"name": "ASC.Web.Client"
"path": "./web/ASC.Web.Client"
},
{
"name": "ASC.People.Client"
"path": "./products/ASC.People/Client"
},
{
"name": "ASC.Files.Client"
"path": "./products/ASC.Files/Client"
}
],
"settings": {
"window.zoomLevel": 0,
"files.autoSave": "afterDelay",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
"folders": [
{
"name": "✨ appserver",
"path": "."
},
{
"name": "📦 @appserver/common",
"path": "packages\\asc-web-common"
},
{
"name": "📦 @appserver/components",
"path": "packages\\asc-web-components"
},
{
"name": "🚀 @appserver/files",
"path": "products\\ASC.Files\\Client"
},
{
"name": "🚀 @appserver/people",
"path": "products\\ASC.People\\Client"
},
{
"name": "🚀 @appserver/studio",
"path": "web\\ASC.Web.Client"
},
{
"name": "🚀 @appserver/login",
"path": "web\\ASC.Web.Login"
}
}
],
"settings": {
"window.zoomLevel": 0,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"extensions": {
"recommendations": ["folke.vscode-monorepo-workspace"]
}
}

View File

@ -12,8 +12,9 @@
"scripts": {
"wipe": "rimraf node_modules yarn.lock web/**/node_modules products/**/node_modules",
"build": "yarn workspaces run build",
"start": "concurrently \"wsrun --parallel start\"",
"sb-components": "yarn workspace @appserver/components storybook"
"start": "concurrently \"wsrun --parallel start\"",
"test": "yarn workspace @appserver/components test",
"sb-components": "yarn workspace @appserver/components storybook"
},
"devDependencies": {
"lerna": "^3.22.1",

View File

@ -1,6 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
import Error520Container from "../../pages/errors/520";
import Error520 from "studio/Error520";
class ErrorBoundary extends React.Component {
constructor(props) {
@ -22,7 +22,7 @@ class ErrorBoundary extends React.Component {
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <Error520Container />;
return <Error520 />;
}
return this.props.children;

View File

@ -13,7 +13,7 @@ const CloseButton = (props) => {
color={"#A3A9AE"}
clickColor={"#A3A9AE"}
size={10}
iconName={"/static/images/cross.react.svg"}
iconName="/static/images/cross.react.svg"
isFill={true}
isDisabled={isDisabled}
onClick={!isDisabled ? onClick : undefined}

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -3,8 +3,8 @@ import React from "react";
import { Redirect, Route } from "react-router-dom";
//import Loader from "@appserver/components/loader";
import PageLayout from "../PageLayout";
import Error401 from '../../pages/errors/401'
import Error404 from '../../pages/errors/404'
import Error401 from "studio/Error401";
import Error404 from "studio/Error404";
import RectangleLoader from "../Loaders/RectangleLoader/RectangleLoader";
import { inject, observer } from "mobx-react";
import { isMe } from "../../utils";
@ -76,11 +76,25 @@ const PrivateRoute = ({ component: Component, ...rest }) => {
if (restricted) {
console.log("PrivateRoute render Error401", rest);
return <Error401 />;
return (
<Redirect
to={{
pathname: "/error401",
state: { from: props.location },
}}
/>
);
}
console.log("PrivateRoute render Error404", rest);
return <Error404 />;
return (
<Redirect
to={{
pathname: "/error404",
state: { from: props.location },
}}
/>
);
};
//console.log("PrivateRoute render", rest);

View File

@ -1,79 +0,0 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import { withKnobs, text, select } from "@storybook/addon-knobs/react";
import { BooleanValue } from "react-values";
import ProfileMenu from ".";
import Section from "../../../.storybook/decorators/section";
import withReadme from "storybook-readme/with-readme";
import Readme from "./README.md";
import DropDownItem from "@appserver/components/drop-down-item";
import Avatar from "@appserver/components/avatar";
const roleOptions = ["owner", "admin", "guest", "user"];
const defaultAvatar =
"https://static-www.onlyoffice.com/images/team/developers_photos/personal_44_2x.jpg";
storiesOf("Components|ProfileMenu", module)
.addDecorator(withKnobs)
.addDecorator(withReadme(Readme))
.add("base", () => {
const userRole = select("avatarRole", roleOptions, "admin");
const userAvatar = text("avatarSource", "") || defaultAvatar;
const userEmail = text("email", "") || "janedoe@gmail.com";
const userDisplayName = text("displayName", "") || "Jane Doe";
return (
<Section>
<BooleanValue>
{({ value, toggle }) => (
<div
style={{
position: "relative",
float: "right",
height: "56px",
paddingRight: "4px",
}}
>
<Avatar
size="medium"
role={userRole}
source={userAvatar}
userName={userDisplayName}
onClick={() => toggle(!value)}
/>
<ProfileMenu
avatarRole={userRole}
avatarSource={userAvatar}
displayName={userDisplayName}
email={userEmail}
open={value}
>
<DropDownItem
key="1"
label="Profile"
onClick={() => console.log("Profile click")}
/>
<DropDownItem
key="2"
label="Subscriptions"
onClick={() => console.log("Subscriptions click")}
/>
<DropDownItem key="sep" isSeparator />
<DropDownItem
key="3"
label="About this program"
onClick={() => console.log("About click")}
/>
<DropDownItem
key="4"
label="Log out"
onClick={() => console.log("Log out click")}
/>
</ProfileMenu>
</div>
)}
</BooleanValue>
</Section>
);
});

View File

@ -1,18 +0,0 @@
import React from "react";
import { mount } from "enzyme";
import ProfileMenu from ".";
const baseProps = {
avatarRole: "admin",
avatarSource: "",
displayName: "Jane Doe",
email: "janedoe@gmail.com",
};
describe("<Layout />", () => {
it("renders without error", () => {
const wrapper = mount(<ProfileMenu {...baseProps} />);
expect(wrapper).toExist();
});
});

View File

@ -1,32 +0,0 @@
# ProfileMenu
### Usage
```js
import ProfileMenu from "@appserver/common/components/ProfileMenu";
```
```jsx
<ProfileMenu
avatarRole="admin"
avatarSource=""
displayName="Jane Doe"
email="janedoe@gmail.com"
/>
```
To add preview of user profile, you must use ProfileMenu component inherited from DropDownItem.
To add an avatar username and email, you need to add parameters of Avatar component: avatarSource - link to user's avatar, avatarRole - user's role, displayName - user name and email - users email address.
### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | :------------: | :------: | :----------------------------: | :-----: | ---------------------- |
| `avatarRole` | `oneOf` | - | `owner`,`admin`,`guest`,`user` | `user` | Adds a user role table |
| `avatarSource` | `string` | - | - | - | Avatar image source |
| `className` | `string` | - | - | - | Accepts class |
| `displayName` | `string` | - | - | - | User name for display |
| `email` | `string` | - | - | - | User email for display |
| `id` | `string` | - | - | - | Accepts id |
| `style` | `obj`, `array` | - | - | - | Accepts css style |

View File

@ -1,63 +0,0 @@
import styled, { css } from "styled-components";
import DropDownItem from "@appserver/components/drop-down-item";
const commonStyle = css`
font-family: "Open Sans", sans-serif, Arial;
font-style: normal;
color: #ffffff;
margin-left: 60px;
margin-top: -3px;
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
export const StyledProfileMenu = styled(DropDownItem)`
position: relative;
overflow: visible;
padding: 0px;
cursor: pointer;
display: inline-block;
margin-top: -6px;
`;
export const MenuContainer = styled.div`
position: relative;
height: 76px;
background: linear-gradient(200.71deg, #2274aa 0%, #0f4071 100%);
border-radius: 6px 6px 0px 0px;
padding: 16px;
cursor: default;
box-sizing: border-box;
`;
export const AvatarContainer = styled.div`
display: inline-block;
float: left;
`;
export const MainLabelContainer = styled.div`
font-size: 16px;
line-height: 28px;
${commonStyle}
`;
export const LabelContainer = styled.div`
font-weight: normal;
font-size: 11px;
line-height: 16px;
${commonStyle}
`;
export const TopArrow = styled.div`
position: absolute;
cursor: default;
top: -6px;
right: 16px;
width: 24px;
height: 6px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.27954 1.12012C10.8122 -0.295972 13.1759 -0.295971 14.7086 1.12012L18.8406 4.93793C19.5796 5.62078 20.5489 6 21.5551 6H24H0H2.43299C3.4392 6 4.40845 5.62077 5.1475 4.93793L9.27954 1.12012Z' fill='%23206FA4'/%3E%3C/svg%3E");
`;

View File

@ -1 +0,0 @@
export default from "./ProfileMenu";

View File

@ -9,7 +9,6 @@ export { default as Layout } from "./Layout";
export { default as ScrollToTop } from "./Layout/ScrollToTop";
export * from "./Layout/context";
export { default as PageLayout } from "./PageLayout";
export { default as ProfileMenu } from "./ProfileMenu";
export { default as ErrorContainer } from "./ErrorContainer";
export { default as ErrorBoundary } from "./ErrorBoundary";
export { default as FilterInput } from "./FilterInput";

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,19 +0,0 @@
import React, { useEffect } from "react";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const Error404Container = () => {
const { t } = useTranslation("translation", { i18n });
useEffect(() => {
changeLanguage(i18n);
}, []);
return <ErrorContainer headerText={t("Error401Text")} />;
};
const Error401 = Error404Container;
export default Error401;

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,19 +0,0 @@
import React, { useEffect } from "react";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const Error403Container = () => {
const { t } = useTranslation("translation", { i18n });
useEffect(() => {
changeLanguage(i18n);
}, []);
return <ErrorContainer headerText={t("Error403Text")} />;
};
const Error403 = Error403Container;
export default Error403;

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,19 +0,0 @@
import React, { useEffect } from "react";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const Error404Container = () => {
const { t } = useTranslation("translation", { i18n });
useEffect(() => {
changeLanguage(i18n);
}, []);
return <ErrorContainer headerText={t("Error404Text")} />;
};
const Error404 = Error404Container;
export default Error404;

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,25 +0,0 @@
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const Error520Container = ({ match }) => {
const { t } = useTranslation("translation", { i18n });
const { error } = (match && match.params) || {};
useEffect(() => {
changeLanguage(i18n);
}, []);
return <ErrorContainer headerText={t("Error520Text")} bodyText={error} />;
};
Error520Container.propTypes = {
match: PropTypes.object,
};
const Error520 = Error520Container;
export default Error520;

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,26 +0,0 @@
import React, { useEffect } from "react";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const ComingSoonContainer = () => {
const { t } = useTranslation("translation", { i18n });
useEffect(() => {
changeLanguage(i18n);
}, []);
return (
<ErrorContainer
headerText={t("ComingSoonHeader")}
bodyText={t("ComingSoonText")}
buttonText={t("ComingSoonButtonText")}
buttonUrl="https://www.onlyoffice.com/blog"
/>
);
};
const ComingSoon = ComingSoonContainer;
export default ComingSoon;

View File

@ -1,5 +0,0 @@
{
"ComingSoonHeader": "Coming soon.",
"ComingSoonText": "Please be patient",
"ComingSoonButtonText": "Read more"
}

View File

@ -1,5 +0,0 @@
{
"ComingSoonHeader": "Скоро появится.",
"ComingSoonText": "Будьте терпеливы",
"ComingSoonButtonText": "Узнать больше"
}

View File

@ -1,19 +0,0 @@
import i18n from "i18next";
import en from "./locales/en/translation.json";
import ru from "./locales/ru/translation.json";
import { i18nBaseSettings } from "../../../constants";
const newInstance = i18n.createInstance();
const resources = {
en: {
translation: en,
},
ru: {
translation: ru,
},
};
newInstance.init({ ...i18nBaseSettings, resources });
export default newInstance;

View File

@ -1,19 +0,0 @@
import React, { useEffect } from "react";
import ErrorContainer from "../../../components/ErrorContainer";
import { useTranslation } from "react-i18next";
import i18n from "./i18n";
import { changeLanguage } from "../../../utils";
const ErrorOfflineContainer = () => {
const { t } = useTranslation("translation", { i18n });
useEffect(() => {
changeLanguage(i18n);
}, []);
return <ErrorContainer headerText={t("ErrorOfflineText")} />;
};
const Offline = ErrorOfflineContainer;
export default Offline;

View File

@ -1,4 +1,4 @@
import { action, computed, makeObservable, observable } from "mobx";
import { makeAutoObservable } from "mobx";
import api from "../api";
import { setWithCredentialsStatus } from "../api/client";
import history from "../history";
@ -23,28 +23,7 @@ class AuthStore {
this.moduleStore = new ModuleStore();
this.settingsStore = new SettingsStore();
makeObservable(this, {
isLoading: observable,
isAuthenticated: observable,
isAdmin: computed,
isLoaded: computed,
language: computed,
product: computed,
availableModules: computed,
userStore: observable,
moduleStore: observable,
settingsStore: observable,
version: observable,
init: action,
login: action,
logout: action,
setIsAuthenticated: action,
replaceFileStream: action,
getEncryptionAccess: action,
setEncryptionAccess: action,
setProductVersion: action,
reset: action,
});
makeAutoObservable(this);
}
init = async () => {
@ -93,9 +72,11 @@ class AuthStore {
}
get product() {
return this.moduleStore.modules.find(
(item) => item.id === this.settingsStore.currentProductId
) || "";
return (
this.moduleStore.modules.find(
(item) => item.id === this.settingsStore.currentProductId
) || ""
);
}
get availableModules() {
@ -107,7 +88,9 @@ class AuthStore {
const isUserAdmin = user.isAdmin;
const customModules = this.getCustomModules(isUserAdmin);
const products = modules.map((m) => toModuleWrapper(m, false));
const newModules = JSON.parse(JSON.stringify(modules));
const products = newModules.map((m) => toModuleWrapper(m, false));
const primaryProducts = products.filter((m) => m.isPrimary === true);
const dummyProducts = products.filter((m) => m.isPrimary === false);

View File

@ -50,7 +50,12 @@ class ModuleStore {
this.setModules(extendedModules);
};
toModuleWrapper = (item, noAction = true, iconName = null, iconUrl = null) => {
toModuleWrapper = (
item,
noAction = true,
iconName = null,
iconUrl = null
) => {
switch (item.id) {
case "6743007c-6f95-4d20-8c88-a8601ce5e76d":
item.iconName = "CrmIcon";
@ -72,7 +77,7 @@ class ModuleStore {
break;
case "32D24CB5-7ECE-4606-9C94-19216BA42086":
item.iconName = "CalendarCheckedIcon";
item.iconUrl = "static/images/calendar.checked.react.svg";
item.iconUrl = "/static/images/calendar.checked.react.svg";
item.imageUrl = "/images/calendar.svg";
break;
case "BF88953E-3C43-4850-A3FB-B1E43AD53A3E":

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -0,0 +1 @@
dist/

View File

@ -1,6 +0,0 @@
{
"parser": "babel-eslint",
"env": {
"jest": true
}
}

View File

@ -0,0 +1,13 @@
module.exports = {
parser: "babel-eslint",
extends: ["eslint:recommended", "plugin:react/recommended"],
settings: {
react: {
version: "detect",
},
},
env: {
browser: true,
node: true,
},
};

View File

@ -0,0 +1,25 @@
const presets = [
[
"@babel/preset-env",
{
modules: false,
},
],
"@babel/preset-react",
];
const plugins = [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-namespace-from",
"babel-plugin-styled-components",
];
module.exports = {
presets,
plugins,
env: {
test: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
};

View File

@ -152,6 +152,7 @@ const StyledWeekday = styled.div`
text-align: center;
}
`;
StyledWeekday.defaultProps = { theme: Base };
StyledWeekday.defaultProps = { theme: Base };

View File

@ -71,9 +71,11 @@ const StyledLabel = styled.label`
}
rect:last-child {
fill: ${(props) =>
props.color === "#FFFF"
? props.theme.checkbox.indeterminateColor
: "white"};
props.color
? props.color === "#FFFF"
? props.theme.checkbox.indeterminateColor
: "white"
: props.theme.checkbox.indeterminateColor};
stroke: ${(props) =>
props.color === "#FFFF"
? props.theme.checkbox.fillColor
@ -118,7 +120,7 @@ const StyledLabel = styled.label`
? css`
cursor: not-allowed;
`
: !props.indeterminate
: !props.isIndeterminate
? css`
cursor: pointer;
@ -139,11 +141,13 @@ const StyledLabel = styled.label`
}
rect:last-child {
fill: ${(props) =>
props.indeterminate && props.color === "#FFFF"
? props.theme.checkbox.hoverIndeterminateColor
: props.indeterminate
? "white"
: props.color};
props.color
? props.isIndeterminate && props.color === "#FFFF"
? props.theme.checkbox.hoverIndeterminateColor
: props.isIndeterminate
? "white"
: props.color
: props.theme.checkbox.hoverIndeterminateColor};
`}
}

View File

@ -0,0 +1,4 @@
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });

View File

@ -2,7 +2,7 @@
import React from "react";
import { mount, shallow } from "enzyme";
import EmailInput from ".";
import { EmailSettings } from "../../utils/email/";
import { EmailSettings } from "../utils/email/";
const baseProps = {
id: "emailInputId",

View File

@ -15,10 +15,11 @@ import {
} from "./styled-group-button";
import ExpanderDownIcon from "../../../public/images/expander-down.react.svg";
import commonIconsStyles from "../utils/common-icons-style";
import Base from "../themes/base";
const textColor = "#333333",
disabledTextColor = "#A3A9AE";
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
${commonIconsStyles}
path {
@ -29,6 +30,7 @@ const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
fill: ${(props) => props.color};
}
`;
StyledExpanderDownIcon.defaultProps = { theme: Base };
class GroupButton extends React.Component {
constructor(props) {
super(props);

View File

@ -103,7 +103,8 @@ describe("<IconButton />", () => {
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
});
it("call onChange", () => {
//TODO: Fix tests
/* it("call onChange", () => {
const onChange = jest.fn();
const wrapper = mount(
<InputBlock
@ -152,5 +153,5 @@ describe("<IconButton />", () => {
const input = wrapper.find(".append div");
input.first().simulate("click");
expect(onIconClick).not.toHaveBeenCalled();
});
});*/
});

View File

@ -0,0 +1,16 @@
module.exports = {
setupFiles: ["<rootDir>/test/setup-tests.js"],
setupFilesAfterEnv: ["<rootDir>/scripts/setup-test-framework.js"],
transform: {
"^.+\\.js$": "<rootDir>/test/transform-babel-jest.js",
},
/* It solves css/less/scss import issues.
You might have similar issues with different file extensions (e.g. md).
Just search for "<file type> jest loader"
*/
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
"<rootDir>/test/transform-file.js",
},
coverageReporters: ["json", "lcov", "text", "clover", "cobertura"],
};

View File

@ -4,17 +4,96 @@
"private": true,
"scripts": {
"build": "echo 'skip it'",
"start": "echo 'skip it'",
"storybook": "start-storybook -p 6006",
"start": "echo 'skip it,
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"devDependencies": {
"@storybook/addon-actions": "^6.1.20",
"@storybook/addon-actions": "^6.1.20",
"@storybook/addon-controls": "^6.1.20",
"@storybook/addon-docs": "^6.1.20",
"@storybook/addon-essentials": "^6.1.20",
"@storybook/addon-links": "^6.1.20",
"@storybook/react": "^6.1.20",
"react-values": "^0.3.3"
"react-values": "^0.3.3",
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-proposal-export-namespace-from": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@emotion/babel-preset-css-prop": "^10.2.1",
"@emotion/styled": "^10.0.27",
"@svgr/rollup": "^5.5.0",
"@svgr/webpack": "^5.5.0",
"@testing-library/react": "^9.5.0",
"@types/jest": "^24.9.1",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-loader": "^8.2.2",
"babel-plugin-inline-react-svg": "^1.1.2",
"babel-plugin-transform-dynamic-import": "^2.1.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-plugin-transform-rename-import": "^2.3.0",
"babel-preset-react-app": "^9.1.2",
"cross-env": "^6.0.3",
"css-loader": "^3.6.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^6.8.0",
"eslint-plugin-react": "^7.17.0",
"jest": "^24.9.0",
"jest-enzyme": "^7.1.2",
"jest-junit": "^10.0.0",
"jest-styled-components": "^7.0.3",
"postcss": "^7.0.35",
"prettier": "2.1.2",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-values": "^0.3.3",
"rollup": "^1.32.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-generate-package-json": "^3.2.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-postcss": "^2.9.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-url": "^3.0.1",
"storybook-readme": "^5.0.9",
"styled-components": "^5.2.1",
"svg-inline-loader": "^0.8.2"
},
"dependencies": {
"email-addresses": "^3.1.0",
"fast-deep-equal": "^3.1.3",
"html-to-react": "^1.4.5",
"lodash": "4.17.19",
"lodash-es": "4.17.15",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"punycode": "^2.1.1",
"rc-tree": "^2.1.4",
"react-autosize-textarea": "^7.1.0",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.15.0",
"react-dropzone": "^11.2.4",
"react-lifecycles-compat": "^3.0.4",
"react-onclickoutside": "^6.9.0",
"react-text-mask": "^5.4.3",
"react-toastify": "^5.5.0",
"react-tooltip": "^3.11.6",
"react-virtualized-auto-sizer": "^1.0.3",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.5",
"resize-image": "^0.1.0"
}
}

View File

@ -0,0 +1,134 @@
/* eslint-disable global-require */
module.exports = function getBabelPreset() {
// This is similar to how `env` works in Babel:
// https://babeljs.io/docs/usage/babelrc/#env-option
// We are not using `env` because its ignored in versions > babel-core@6.10.4:
// https://github.com/babel/babel/issues/4539
// https://github.com/facebook/create-react-app/issues/720
// Its also nice that we can enforce `NODE_ENV` being specified.
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
const isEnvDevelopment = env === "development";
const isEnvProduction = env === "production";
const isEnvTest = env === "test";
if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) {
throw new Error(
"The babel preset of requires that you specify `NODE_ENV` or " +
'`BABEL_ENV` environment variables. Valid values are "development", ' +
`"test", and "production". Instead, received: ${JSON.stringify(env)}.`
);
}
return {
presets: [
isEnvTest && [
// ES features necessary for user's Node version
require("@babel/preset-env").default,
{
targets: {
browsers: ["last 1 versions"],
node: "8",
},
},
],
(isEnvProduction || isEnvDevelopment) && [
// Latest stable ECMAScript features
require("@babel/preset-env").default,
{
targets: {
browsers: ["last 1 versions"],
},
corejs: 2,
// `entry` transforms `@babel/polyfill` into individual requires for
// the targeted browsers. This is safer than `usage` which performs
// static code analysis to determine what's required.
// This is probably a fine default to help trim down bundles when
// end-users inevitably import '@babel/polyfill'.
useBuiltIns: "entry",
// Do not transform modules to CJS
modules: false,
include: ["transform-classes"],
},
],
[
require("@babel/preset-react").default,
{
// Adds component stack to warning messages
// Adds __self attribute to JSX which React will use for some warnings
development: isEnvDevelopment || isEnvTest,
// Will use the native built-in instead of trying to polyfill
// behavior for any plugins that require one.
useBuiltIns: true,
},
],
[
"@emotion/babel-preset-css-prop",
{
sourceMap: isEnvDevelopment,
autoLabel: !isEnvProduction,
},
],
].filter(Boolean),
plugins: [
require("babel-plugin-styled-components").default,
// Experimental macros support. Will be documented after it's had some time
// in the wild.
require("babel-plugin-macros").default,
// https://github.com/emotion-js/emotion/tree/master/packages/babel-plugin-emotion
// export { default } from './foo'
require("@babel/plugin-proposal-export-default-from").default,
// export * from './foo'
require("@babel/plugin-proposal-export-namespace-from").default,
// Necessary to include regardless of the environment because
// in practice some other transforms (such as object-rest-spread)
// don't work without it: https://github.com/babel/babel/issues/7215
require("@babel/plugin-transform-destructuring").default,
// class { handleClick = () => { } }
// Enable loose mode to use assignment instead of defineProperty
// See discussion in https://github.com/facebook/create-react-app/issues/4263
[
require("@babel/plugin-proposal-class-properties").default,
{
loose: true,
},
],
// The following two plugins use Object.assign directly, instead of Babel's
// extends helper. Note that this assumes `Object.assign` is available.
// { ...todo, completed: true }
[
require("@babel/plugin-proposal-object-rest-spread").default,
{
useBuiltIns: true,
},
],
// Polyfills the runtime needed for async/await and generators
[
require("@babel/plugin-transform-runtime").default,
{
helpers: false,
regenerator: true,
},
],
isEnvProduction && [
// Remove PropTypes from production build
require("babel-plugin-transform-react-remove-prop-types").default,
{
mode: "wrap",
},
],
// function* () { yield 42; yield 43; }
!isEnvTest && [
require("@babel/plugin-transform-regenerator").default,
{
// Async functions are converted to generators by @babel/preset-env
async: false,
},
],
// Adds syntax support for import()
require("@babel/plugin-syntax-dynamic-import").default,
isEnvTest &&
// Transform dynamic import to require
require("babel-plugin-transform-dynamic-import").default,
].filter(Boolean),
};
};

View File

@ -0,0 +1,6 @@
// enzyme setup
import "jest-enzyme";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter(), disableLifecycleMethods: true });

View File

@ -61,8 +61,8 @@ describe("<SearchInput />", () => {
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
});
it("call onClearSearch", () => {
// TODO: Fix icons tests
/*it("call onClearSearch", () => {
const onClearSearch = jest.fn();
const onChange = jest.fn();
const wrapper = mount(
@ -181,5 +181,5 @@ describe("<SearchInput />", () => {
const inputBlock = wrapper.find(InputBlock);
expect(inputBlock.prop("iconSize")).toEqual(22);
});
});*/
});

View File

@ -1,5 +1,6 @@
import styled, { css } from "styled-components";
import Base from "../themes/base";
import PropTypes from "prop-types";
const ButtonWrapper = ({ label, iconName, isDisabled, ...props }) => (
<button type="button" {...props}></button>

View File

@ -0,0 +1,17 @@
const path = require("path");
module.exports = function replaceImport(originalPath, callingFileName) {
// This replacement rewrites imports of ui-kit to an import using a relative
// path pointing at the root folder.
// This allows to import from the bundled ui-kit using
// import { PrimaryButton } from 'ui-kit'
// instead of
// import { PrimaryButton } from '../../..'
if (originalPath === "ui-kit" && callingFileName.endsWith(".bundlespec.js")) {
const fromPath = path.dirname(callingFileName);
const toPath = process.cwd();
const relativePath = path.relative(fromPath, toPath);
return relativePath;
}
return originalPath;
};

View File

@ -0,0 +1,76 @@
import fs from "fs";
import path from "path";
import colors from "colors/safe";
const shouldSilenceWarnings = (...messages) =>
[/Warning: componentWillReceiveProps has been renamed/].some((msgRegex) =>
messages.some((msg) => msgRegex.test(msg))
);
global.window.app = {
mcApiUrl: "http://localhost:8080",
};
// setup file
const logOrThrow = (log, method, messages) => {
const warning = `console.${method} calls not allowed in tests`;
if (process.env.CI) {
if (shouldSilenceWarnings(messages)) {
return;
}
log(warning, "\n", ...messages);
throw new Error(warning);
} else {
log(colors.bgYellow.black(" WARN "), warning, "\n", ...messages);
}
};
// eslint-disable-next-line no-console
const logMessage = console.log;
global.console.log = (...messages) => {
logOrThrow(logMessage, "log", messages);
};
// eslint-disable-next-line no-console
const logInfo = console.info;
global.console.info = (...messages) => {
logOrThrow(logInfo, "info", messages);
};
// eslint-disable-next-line no-console
const logWarning = console.warn;
global.console.warn = (...messages) => {
logOrThrow(logWarning, "warn", messages);
};
// eslint-disable-next-line no-console
const logError = console.error;
global.console.error = (...messages) => {
logOrThrow(logError, "error", messages);
};
// Avoid unhandled promise rejections from going unnoticed
// https://github.com/facebook/jest/issues/3251#issuecomment-299183885
// In Node v7 unhandled promise rejections will terminate the process
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
process.on("unhandledRejection", (reason) => {
logMessage("UNHANDLED REJECTION", reason);
// We create a file in case there is an unhandled rejection
// We later check for the existence of this file to fail CI
if (process.env.CI && !process.env.HAS_CREATED_UNHANDLED_REJECTION_FILE) {
const rootPath = process.cwd();
fs.writeFileSync(
path.join(
rootPath,
"./fail-tests-because-there-was-an-unhandled-rejection.lock"
),
""
);
process.env.HAS_CREATED_UNHANDLED_REJECTION_FILE = true;
}
});
// Avoid memory leak by adding too many listeners
process.env.LISTENING_TO_UNHANDLED_REJECTION = true;
}

View File

@ -0,0 +1,16 @@
const babelPresetJest = require("babel-preset-jest");
const getBabelPreset = require("../scripts/get-babel-preset");
const babelOptions = getBabelPreset();
const jestBabelConfig = {
...babelOptions,
plugins: [
...babelOptions.plugins,
...babelPresetJest()
.plugins /*,
['module-rewrite', { replaceFunc: './test/replace-module-paths.js' }],*/,
],
};
module.exports = require("babel-jest").createTransformer(jestBabelConfig);

View File

@ -0,0 +1 @@
module.exports = "test-file-stub";

View File

@ -357,7 +357,7 @@ const Base = {
disableIndeterminateColor: gray,
hoverBorderColor: gray,
hoverIndeterminateColor: gray,
hoverIndeterminateColor: black,
},
// slider: {
@ -1508,7 +1508,7 @@ const Base = {
cursor: "pointer",
color: white,
},
comboBox: {
color: black,
minWidth: "80px",

View File

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import { ToggleButtonContainer, HiddenInput } from "./styled-toggle-button";
import { ToggleButtonIcon, ToggleButtonCheckedIcon } from "./svg";
import Text from "../text";
import globalColors from "../utils/globalColors";
const ToggleIcon = ({ isChecked }) => {
return (
@ -32,6 +33,8 @@ class ToggleButton extends Component {
render() {
const { isDisabled, label, onChange, id, className, style } = this.props;
const { gray } = globalColors;
const colorProps = isDisabled ? { color: gray } : {};
//console.log("ToggleButton render");

View File

@ -18,25 +18,36 @@
"@babel/preset-react": "7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^3.6.0",
"html-webpack-plugin": "4.5.0",
"interpolate-html-plugin": "^4.0.0",
"json-loader": "^0.5.7",
"mini-css-extract-plugin": "^1.3.9",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"serve": "11.3.2",
"source-map-loader": "^1.1.2",
"style-loader": "1.2.1",
"webpack": "5.14.0",
"webpack-cli": "4.5.0",
"webpack-dev-server": "3.11.2",
"serve": "11.3.2"
"workbox-webpack-plugin": "^6.1.1"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@welldone-software/why-did-you-render": "^4.2.5",
"attr-accept": "^2.2.2",
"axios": "^0.21.0",
"email-addresses": "^3.1.0",
"moment": "^2.29.1",
"@welldone-software/why-did-you-render": "^4.2.5",
"copy-to-clipboard": "^3.2.0",
"email-addresses": "^3.1.0",
"fast-deep-equal": "^3.1.3",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"rc-tree": "^2.1.4",
"re-resizable": "^6.9.0",
@ -47,6 +58,7 @@
"react-device-detect": "^1.14.0",
"react-dom": "^16.14.0",
"react-dropzone": "^11.2.4",
"react-i18next": "^11.7.3",
"react-onclickoutside": "^6.9.0",
"react-resize-detector": "^5.2.0",
"react-router": "^5.2.0",
@ -60,14 +72,7 @@
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.5",
"resize-image": "^0.1.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"sjcl": "^1.0.8",
"styled-components": "^5.2.1",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"react-i18next": "^11.7.3",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0"
"styled-components": "^5.2.1"
}
}

View File

@ -254,7 +254,7 @@
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script>
console.log("FILES APP STARTED");
console.log("It's FILES INIT");
var pathname = window.location.pathname.toLowerCase();
if (
pathname.indexOf("doceditor") === -1 &&

View File

@ -24,7 +24,7 @@ import "./custom.scss";
import "./i18n";
//import { regDesktop } from "@appserver/common/src/desktop";
const Error404 = React.lazy(() => import("@appserver/common/pages/errors/404"));
const Error404 = React.lazy(() => import("studio/Error404"));
class FilesContent extends React.Component {
constructor(props) {
@ -106,7 +106,7 @@ const Files = inject(({ auth, initFilesStore }) => {
//isDesktop: auth.settingsStore.isDesktopClient,
user: auth.userStore.user,
isAuthenticated: auth.isAuthenticated,
homepage: auth.settingsStore.homepage || config.homepage,
homepage: config.homepage, // auth.settingsStore.homepage
encryptionKeys: auth.settingsStore.encryptionKeys,
isEncryption: auth.settingsStore.isEncryptionSupport,
isLoaded: auth.isLoaded && initFilesStore.isLoaded,

View File

@ -2,8 +2,8 @@ import React from "react";
import styled from "styled-components";
import Heading from "@appserver/components/heading";
import ToggleButton from "@appserver/components/toggle-button";
import Error403 from "@appserver/common/pages/errors/403";
import Error520 from "@appserver/common/pages/errors/520";
import Error403 from "studio/Error403";
import Error520 from "studio/Error520";
import ConnectClouds from "./ConnectedClouds";
import { inject, observer } from "mobx-react";

View File

@ -1,18 +1,35 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
.ModuleFederationPlugin;
const path = require("path");
const deps = require("./package.json").dependencies;
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage;
module.exports = {
entry: "./src/index",
var config = {
mode: "development",
devtool: "inline-source-map",
entry: "./src/index",
devServer: {
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "dist")],
contentBasePublicPath: "/products/files/",
publicPath: homepage,
contentBase: [path.join(__dirname, "public")],
contentBasePublicPath: homepage,
port: 5008,
historyApiFallback: true,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
index: homepage,
},
proxy: [
{
context: "/api",
target: "http://localhost:8092",
},
],
hot: false,
hotOnly: false,
headers: {
@ -21,8 +38,14 @@ module.exports = {
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
//openPage: "http://localhost:8092/products/files",
},
output: {
publicPath: "auto", //homepage
chunkFilename: "[id].[contenthash].js",
path: path.resolve(process.cwd(), "dist"),
},
resolve: {
extensions: [".jsx", ".js", ".json"],
fallback: {
@ -30,11 +53,6 @@ module.exports = {
},
},
output: {
publicPath: "auto",
chunkFilename: "[id].[contenthash].js",
},
module: {
rules: [
{
@ -95,11 +113,12 @@ module.exports = {
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "files",
filename: "remoteEntry.js",
remotes: {
studio: "studio@http://localhost:5001/remoteEntry.js",
studio: "studio@/remoteEntry.js",
},
exposes: {
"./app": "./src/Files.jsx",
@ -118,6 +137,30 @@ module.exports = {
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
//base: `${homepage}/`,
}),
new CopyPlugin({
patterns: [
{
from: "public",
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
},
},
],
}),
],
};
module.exports = (env, argv) => {
if (argv.mode === "production") {
config.mode = "production";
} else {
config.devtool = "source-map";
}
return config;
};

View File

@ -18,21 +18,27 @@
"@babel/preset-react": "7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^3.6.0",
"html-webpack-plugin": "4.5.0",
"json-loader": "^0.5.7",
"serve": "11.3.2",
"source-map-loader": "^1.1.2",
"style-loader": "1.2.1",
"webpack": "5.14.0",
"webpack-cli": "4.5.0",
"webpack-dev-server": "3.11.2",
"serve": "11.3.2"
"webpack-dev-server": "3.11.2"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"attr-accept": "^2.2.2",
"axios": "^0.21.0",
"email-addresses": "^3.1.0",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0",
"moment": "^2.29.1",
"copy-to-clipboard": "^3.2.0",
"fast-deep-equal": "^3.1.3",
@ -46,6 +52,7 @@
"react-device-detect": "^1.14.0",
"react-dom": "^16.14.0",
"react-dropzone": "^11.2.4",
"react-i18next": "^11.7.3",
"react-hammerjs": "^1.0.1",
"react-onclickoutside": "^6.9.0",
"react-player": "^1.15.3",
@ -66,11 +73,6 @@
"sass-loader": "^10.1.0",
"sjcl": "^1.0.8",
"screenfull": "^5.1.0",
"styled-components": "^5.2.1",
"i18next": "^19.8.4",
"i18next-http-backend": "^1.1.0",
"react-i18next": "^11.7.3",
"mobx": "^6.1.1",
"mobx-react": "^7.1.0"
"styled-components": "^5.2.1"
}
}

View File

@ -72,19 +72,19 @@
display: none;
}
}
.burger-loader-svg{
.burger-loader-svg {
width: 24px;
height: 24px;
padding-left: 16px;
}
.logo-loader-svg{
.logo-loader-svg {
width: 168px;
height: 24px;
position: relative;
cursor: pointer;
padding-left: 16px;
}
.avatar-loader-svg{
.avatar-loader-svg {
width: 36px;
height: 36px;
}
@ -113,8 +113,11 @@
></rect>
<defs>
<linearGradient id="fill0">
<stop offset="0.599964" stop-color="#fff" stop-opacity="0.25">
</stop>
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
@ -136,8 +139,11 @@
></rect>
<defs>
<linearGradient id="fill01">
<stop offset="0.599964" stop-color="#fff" stop-opacity="0.25">
</stop>
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
@ -161,8 +167,11 @@
></circle>
<defs>
<linearGradient id="fill02">
<stop offset="0.599964" stop-color="#fff" stop-opacity="0.25">
</stop>
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
@ -191,8 +200,11 @@
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill1">
<stop offset="0.599964" stop-color="#000000" stop-opacity="0.1">
</stop>
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
@ -218,8 +230,11 @@
<rect x="3" y="3" rx="5" ry="5" width="100%" />
</clipPath>
<linearGradient id="fill2">
<stop offset="0.599964" stop-color="#000000" stop-opacity="0.1">
</stop>
<stop
offset="0.599964"
stop-color="#000000"
stop-opacity="0.1"
></stop>
</linearGradient>
</defs>
</svg>
@ -238,5 +253,8 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script>
console.log("It's PEOPLE INIT");
</script>
</body>
</html>

View File

@ -1,3 +1,4 @@
//import "./wdyr";
import React from "react";
import Shell from "studio/shell";

View File

@ -17,7 +17,7 @@ import config from "../package.json";
import "./custom.scss";
import "./i18n";
const Error404 = React.lazy(() => import("@appserver/common/pages/errors/404"));
const Error404 = React.lazy(() => import("studio/Error404"));
const PeopleContent = (props) => {
const { homepage, isLoaded, loadBaseInfo } = props;

View File

@ -3,7 +3,7 @@ import React from "react";
import { Router, Switch, Redirect } from "react-router-dom";
import history from "@appserver/common/history";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import Error404 from "@appserver/common/pages/errors/404";
import Error404 from "studio/Error404";
import Home from "./components/pages/Home";
//import Profile from "./components/pages/Profile";

View File

@ -62,11 +62,12 @@ const getItems = (data) => {
key={item.key}
icon={
item.root ? (
<DepartmentsGroupIcon
size="scale"
isfill={true}
color="#657077"
/>
<StyledDepartmentsGroupIcon size="scale" color="#657077" /* isfill={true} */ /> // TODO: Add isFill prop if need
// <DepartmentsGroupIcon
// size="scale"
// isfill={true}
// color="#657077"
// />
) : (
""
)

View File

@ -5,12 +5,14 @@ import PropTypes from "prop-types";
import Button from "@appserver/components/button";
import ModalDialog from "@appserver/components/modal-dialog";
import Text from "@appserver/components/text";
import history from "@appserver/common/history";
import { withTranslation, Trans } from "react-i18next";
import api from "@appserver/common/api";
import toastr from "@appserver/common/components/Toast";
import ModalDialogContainer from "../ModalDialogContainer";
import { inject, observer } from "mobx-react";
const { deleteUser } = api.people; //TODO: Move to action
@ -38,8 +40,8 @@ class DeleteProfileEverDialogComponent extends React.Component {
};
onReassignDataClick = () => {
const { history, settings, user } = this.props;
history.push(`${settings.homepage}/reassign/${user.userName}`);
const { homepage, user } = this.props;
history.push(`${homepage}/reassign/${user.userName}`);
};
render() {
@ -101,11 +103,11 @@ DeleteProfileEverDialog.propTypes = {
user: PropTypes.object.isRequired,
filter: PropTypes.instanceOf(Filter).isRequired,
fetchPeople: PropTypes.func.isRequired,
settings: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
export default inject(({ auth, peopleStore }) => ({
homepage: auth.settingsStore.homepage,
userCaption: auth.settingsStore.customNames.userCaption,
fetchPeople: peopleStore.usersStore.getUsersList,
filter: peopleStore.filterStore.filter,
}))(observer(withRouter(DeleteProfileEverDialog)));

View File

@ -0,0 +1,60 @@
import { inject, observer } from "mobx-react";
import React from "react";
import {
ChangeEmailDialog,
ChangePasswordDialog,
DeleteSelfProfileDialog,
DeleteProfileEverDialog,
} from "../../../../dialogs";
const Dialogs = ({
changeEmail,
changePassword,
deleteSelfProfile,
deleteProfileEver,
data,
closeDialogs,
filter,
}) => {
return (
<>
{changeEmail && (
<ChangeEmailDialog
visible={changeEmail}
onClose={closeDialogs}
user={data}
/>
)}
{changePassword && (
<ChangePasswordDialog
visible={changePassword}
onClose={closeDialogs}
email={data.email}
/>
)}
{deleteSelfProfile && (
<DeleteSelfProfileDialog
visible={deleteSelfProfile}
onClose={closeDialogs}
email={data.email}
/>
)}
{deleteProfileEver && (
<DeleteProfileEverDialog
visible={deleteProfileEver}
onClose={closeDialogs}
user={data}
/>
)}
</>
);
};
export default inject(({ peopleStore }) => ({
changeEmail: peopleStore.dialogStore.changeEmail,
changePassword: peopleStore.dialogStore.changePassword,
deleteSelfProfile: peopleStore.dialogStore.deleteSelfProfile,
deleteProfileEver: peopleStore.dialogStore.deleteProfileEver,
data: peopleStore.dialogStore.data,
closeDialogs: peopleStore.dialogStore.closeDialogs,
}))(observer(Dialogs));

View File

@ -5,12 +5,22 @@ import IconButton from "@appserver/components/icon-button";
import Link from "@appserver/components/link";
import Box from "@appserver/components/box";
import Grid from "@appserver/components/grid";
import { useTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
const EmptyScreen = ({ resetFilter, isEmptyGroup, setIsLoading }) => {
const { t } = useTranslation("Home");
const EmptyScreen = ({ t, onResetFilter, isEmptyGroup }) => {
const title = isEmptyGroup ? "EmptyGroupTitle" : "NotFoundTitle";
const description = isEmptyGroup
? "EmptyGroupDescription"
: "NotFoundDescription";
const onResetFilter = () => {
setIsLoading(true);
resetFilter(true).finally(() => setIsLoading(false));
};
return (
<EmptyScreenContainer
imageSrc="images/empty_screen_filter.png"
@ -54,4 +64,8 @@ const EmptyScreen = ({ t, onResetFilter, isEmptyGroup }) => {
);
};
export default EmptyScreen;
export default inject(({ peopleStore }) => ({
resetFilter: peopleStore.resetFilter,
isEmptyGroup: peopleStore.selectedGroupStore.isEmptyGroup,
setIsLoading: peopleStore.setIsLoading,
}))(observer(EmptyScreen));

View File

@ -0,0 +1,309 @@
import React from "react";
import Row from "@appserver/components/row";
import Avatar from "@appserver/components/avatar";
import UserContent from "./userContent";
import history from "@appserver/common/history";
import { inject, observer } from "mobx-react";
import { Trans, useTranslation } from "react-i18next";
import toastr from "@appserver/common/components/Toast/toastr";
import { EmployeeStatus } from "@appserver/common/constants";
import { resendUserInvites } from "@appserver/common/api/people"; //TODO: Move to store action
const SimpleUserRow = ({
person,
sectionWidth,
checked,
isAdmin,
isMobile,
selectUser,
selectGroup,
deselectUser,
homepage,
setChangeEmailDialogVisible,
setChangePasswordDialogVisible,
setDeleteProfileDialogVisible,
setDeleteSelfProfileDialogVisible,
setDialogData,
closeDialogs,
updateUserStatus,
}) => {
const { t } = useTranslation("Home");
const isRefetchPeople = true;
const {
email,
role,
displayName,
avatar,
id,
status,
mobilePhone,
options,
userName,
} = person;
const onContentRowSelect = (checked, user) => {
if (checked) {
selectUser(user);
} else {
deselectUser(user);
}
};
const onEmailSentClick = () => {
window.open("mailto:" + email);
};
const onSendMessageClick = () => {
window.open(`sms:${mobilePhone}`);
};
const onEditClick = () => {
history.push(`${homepage}/edit/${userName}`);
};
const toggleChangeEmailDialog = () => {
setDialogData({
email,
id,
});
setChangeEmailDialogVisible(true);
};
const toggleChangePasswordDialog = () => {
setDialogData({
email,
});
setChangePasswordDialogVisible(true);
};
const toggleDeleteSelfProfileDialog = (e) => {
closeDialogs();
setDialogData({
email,
});
setDeleteSelfProfileDialogVisible(true);
};
const toggleDeleteProfileEverDialog = (e) => {
closeDialogs();
setDialogData({
id,
displayName,
userName,
});
setDeleteProfileDialogVisible(true);
};
const onDisableClick = (e) => {
//onLoading(true);
updateUserStatus(EmployeeStatus.Disabled, [id], isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch((error) => toastr.error(error));
//.finally(() => onLoading(false));
};
const onEnableClick = (e) => {
//onLoading(true);
updateUserStatus(EmployeeStatus.Active, [id], isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch((error) => toastr.error(error));
//.finally(() => onLoading(false));
};
const onReassignDataClick = (e) => {
history.push(`${homepage}/reassign/${userName}`);
};
const onDeletePersonalDataClick = (e) => {
toastr.success("Context action: Delete personal data"); //TODO: Implement and add translation
};
const onInviteAgainClick = () => {
//onLoading(true);
resendUserInvites([id])
.then(() =>
toastr.success(
<Trans
i18nKey="MessageEmailActivationInstuctionsSentOnEmail"
ns="Home"
>
The email activation instructions have been sent to the
<strong>{{ email: email }}</strong> email address
</Trans>
)
)
.catch((error) => toastr.error(error));
//.finally(() => onLoading(false));
};
const getUserContextOptions = (options, id) => {
return options.map((option) => {
switch (option) {
case "send-email":
return {
key: option,
label: t("LblSendEmail"),
"data-id": id,
onClick: onEmailSentClick,
};
case "send-message":
return {
key: option,
label: t("LblSendMessage"),
"data-id": id,
onClick: onSendMessageClick,
};
case "separator":
return { key: option, isSeparator: true };
case "edit":
return {
key: option,
label: t("EditButton"),
"data-id": id,
onClick: onEditClick,
};
case "change-password":
return {
key: option,
label: t("PasswordChangeButton"),
"data-id": id,
onClick: toggleChangePasswordDialog,
};
case "change-email":
return {
key: option,
label: t("EmailChangeButton"),
"data-id": id,
onClick: toggleChangeEmailDialog,
};
case "delete-self-profile":
return {
key: option,
label: t("DeleteSelfProfile"),
"data-id": id,
onClick: toggleDeleteSelfProfileDialog,
};
case "disable":
return {
key: option,
label: t("DisableUserButton"),
"data-id": id,
onClick: onDisableClick,
};
case "enable":
return {
key: option,
label: t("EnableUserButton"),
"data-id": id,
onClick: onEnableClick,
};
case "reassign-data":
return {
key: option,
label: t("ReassignData"),
"data-id": id,
onClick: onReassignDataClick,
};
case "delete-personal-data":
return {
key: option,
label: t("RemoveData"),
"data-id": id,
onClick: onDeletePersonalDataClick,
};
case "delete-profile":
return {
key: option,
label: t("DeleteSelfProfile"),
"data-id": id,
onClick: toggleDeleteProfileEverDialog,
};
case "invite-again":
return {
key: option,
label: t("LblInviteAgain"),
"data-id": id,
onClick: onInviteAgainClick,
};
default:
break;
}
return undefined;
});
};
const showContextMenu = options && options.length > 0;
const checkedProps = isAdmin ? { checked } : {};
const element = (
<Avatar size="min" role={role} userName={displayName} source={avatar} />
);
const contextOptionsProps =
(isAdmin && showContextMenu) || (showContextMenu && id === currentUserId)
? {
contextOptions: getUserContextOptions(options, id),
}
: {};
return (
<Row
key={id}
status={status}
data={person}
element={element}
onSelect={onContentRowSelect}
{...checkedProps}
{...contextOptionsProps}
//needForUpdate={this.needForUpdate}
sectionWidth={sectionWidth}
>
<UserContent
isMobile={isMobile}
//widthProp={widthProp}
user={person}
history={history}
selectGroup={selectGroup}
sectionWidth={sectionWidth}
/>
</Row>
);
};
export default inject(({ auth, peopleStore }, { person }) => {
return {
homepage: auth.settingsStore.homepage,
isAdmin: auth.isAdmin,
checked: peopleStore.selectionStore.selection.some(
(el) => el.id === person.id
),
selectUser: peopleStore.selectionStore.selectUser,
deselectUser: peopleStore.selectionStore.deselectUser,
selectGroup: peopleStore.selectedGroupStore.selectGroup,
setChangeEmailDialogVisible:
peopleStore.dialogStore.setChangeEmailDialogVisible,
setChangePasswordDialogVisible:
peopleStore.dialogStore.setChangePasswordDialogVisible,
setDeleteSelfProfileDialogVisible:
peopleStore.dialogStore.setDeleteSelfProfileDialogVisible,
setDeleteProfileDialogVisible:
peopleStore.dialogStore.setDeleteProfileDialogVisible,
setDialogData: peopleStore.dialogStore.setDialogData,
closeDialogs: peopleStore.dialogStore.closeDialogs,
updateUserStatus: peopleStore.usersStore.updateUserStatus,
};
})(observer(SimpleUserRow));

View File

@ -1,44 +1,20 @@
import React from "react";
import { withRouter } from "react-router";
import { withTranslation, Trans } from "react-i18next";
import styled from "styled-components";
import Row from "@appserver/components/row";
import Avatar from "@appserver/components/avatar";
import RowContainer from "@appserver/components/row-container";
import { Consumer } from "@appserver/components/utils/context";
import { isArrayEqual } from "@appserver/components/utils/array";
import UserContent from "./userContent";
import equal from "fast-deep-equal/react";
import { resendUserInvites } from "@appserver/common/api/people";
import toastr from "@appserver/common/components/Toast";
import { EmployeeStatus } from "@appserver/common/constants";
import toastr from "@appserver/common/components/Toast/toastr";
import Loaders from "@appserver/common/components/Loaders";
import {
ChangeEmailDialog,
ChangePasswordDialog,
DeleteSelfProfileDialog,
DeleteProfileEverDialog,
} from "../../../../dialogs";
import EmptyScreen from "./sub-components/EmptyScreen";
import EmptyScreen from "./EmptyScreen";
import { inject, observer } from "mobx-react";
const isRefetchPeople = true;
import SimpleUserRow from "./SimpleUserRow";
import Dialogs from "./Dialogs";
import { isMobile } from "react-device-detect";
class SectionBodyContent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dialogsVisible: {
changeEmail: false,
changePassword: false,
deleteSelfProfile: false,
deleteProfileEver: false,
},
isEmailValid: false,
isLoadedSection: true,
};
}
@ -56,308 +32,11 @@ class SectionBodyContent extends React.PureComponent {
.finally(() => this.setState({ isLoadedSection: isLoaded }));
}
findUserById = (id) => this.props.peopleList.find((man) => man.id === id);
onEmailSentClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
window.open("mailto:" + user.email);
};
onSendMessageClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
window.open(`sms:${user.mobilePhone}`);
};
onEditClick = (e) => {
const { history, settings } = this.props;
const user = this.findUserById(e.currentTarget.dataset.id);
history.push(`${settings.homepage}/edit/${user.userName}`);
};
onDisableClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
const { updateUserStatus, onLoading, t } = this.props;
onLoading(true);
updateUserStatus(EmployeeStatus.Disabled, [user.id], isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch((error) => toastr.error(error))
.finally(() => onLoading(false));
};
onEnableClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
const { updateUserStatus, onLoading, t } = this.props;
onLoading(true);
updateUserStatus(EmployeeStatus.Active, [user.id], isRefetchPeople)
.then(() => toastr.success(t("SuccessChangeUserStatus")))
.catch((error) => toastr.error(error))
.finally(() => onLoading(false));
};
onReassignDataClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
const { history, settings } = this.props;
history.push(`${settings.homepage}/reassign/${user.userName}`);
};
onDeletePersonalDataClick = (e) => {
//const user = this.findUserById(e.currentTarget.dataset.id);
toastr.success("Context action: Delete personal data");
};
onCloseDialog = () => {
this.setState({
dialogsVisible: {
changeEmail: false,
changePassword: false,
deleteSelfProfile: false,
deleteProfileEver: false,
},
});
};
toggleChangeEmailDialog = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
if (!user) return;
const { id, email } = user;
this.setState({
dialogsVisible: {
changeEmail: true,
},
user: {
email,
id,
},
});
};
toggleChangePasswordDialog = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
if (!user) return;
const { email } = user;
this.setState({
dialogsVisible: {
changePassword: true,
},
user: { email },
});
};
toggleDeleteSelfProfileDialog = (e) => {
this.onCloseDialog();
const user = this.findUserById(e.currentTarget.dataset.id);
if (!user) return;
const { email } = user;
this.setState({
dialogsVisible: {
deleteSelfProfile: true,
},
user: { email },
});
};
toggleDeleteProfileEverDialog = (e) => {
this.onCloseDialog();
const user = this.findUserById(e.currentTarget.dataset.id);
if (!user) return;
const { id, displayName, userName } = user;
this.setState({
dialogsVisible: {
deleteProfileEver: true,
},
user: {
id,
displayName,
userName,
},
});
};
onInviteAgainClick = (e) => {
const user = this.findUserById(e.currentTarget.dataset.id);
const { onLoading } = this.props;
onLoading(true);
resendUserInvites([user.id])
.then(() =>
toastr.success(
<Trans
i18nKey="MessageEmailActivationInstuctionsSentOnEmail"
ns="Home"
>
The email activation instructions have been sent to the
<strong>{{ email: user.email }}</strong> email address
</Trans>
)
)
.catch((error) => toastr.error(error))
.finally(() => onLoading(false));
};
getUserContextOptions = (options, id) => {
const { t } = this.props;
return options.map((option) => {
switch (option) {
case "send-email":
return {
key: option,
label: t("LblSendEmail"),
"data-id": id,
onClick: this.onEmailSentClick,
};
case "send-message":
return {
key: option,
label: t("LblSendMessage"),
"data-id": id,
onClick: this.onSendMessageClick,
};
case "separator":
return { key: option, isSeparator: true };
case "edit":
return {
key: option,
label: t("EditButton"),
"data-id": id,
onClick: this.onEditClick,
};
case "change-password":
return {
key: option,
label: t("PasswordChangeButton"),
"data-id": id,
onClick: this.toggleChangePasswordDialog,
};
case "change-email":
return {
key: option,
label: t("EmailChangeButton"),
"data-id": id,
onClick: this.toggleChangeEmailDialog,
};
case "delete-self-profile":
return {
key: option,
label: t("DeleteSelfProfile"),
"data-id": id,
onClick: this.toggleDeleteSelfProfileDialog,
};
case "disable":
return {
key: option,
label: t("DisableUserButton"),
"data-id": id,
onClick: this.onDisableClick,
};
case "enable":
return {
key: option,
label: t("EnableUserButton"),
"data-id": id,
onClick: this.onEnableClick,
};
case "reassign-data":
return {
key: option,
label: t("ReassignData"),
"data-id": id,
onClick: this.onReassignDataClick,
};
case "delete-personal-data":
return {
key: option,
label: t("RemoveData"),
"data-id": id,
onClick: this.onDeletePersonalDataClick,
};
case "delete-profile":
return {
key: option,
label: t("DeleteSelfProfile"),
"data-id": id,
onClick: this.toggleDeleteProfileEverDialog,
};
case "invite-again":
return {
key: option,
label: t("LblInviteAgain"),
"data-id": id,
onClick: this.onInviteAgainClick,
};
default:
break;
}
return undefined;
});
};
onContentRowSelect = (checked, user) => {
if (checked) {
this.props.selectUser(user);
} else {
this.props.deselectUser(user);
}
};
onResetFilter = () => {
const { onLoading, resetFilter } = this.props;
onLoading(true);
resetFilter(true).finally(() => onLoading(false));
};
needForUpdate = (currentProps, nextProps) => {
if (currentProps.checked !== nextProps.checked) {
return true;
}
if (currentProps.status !== nextProps.status) {
return true;
}
if (currentProps.sectionWidth !== nextProps.sectionWidth) {
return true;
}
if (!equal(currentProps.data, nextProps.data)) {
return true;
}
if (!isArrayEqual(currentProps.contextOptions, nextProps.contextOptions)) {
return true;
}
return false;
};
render() {
// console.log("Home SectionBodyContent render()");
const {
isLoaded,
peopleList,
history,
settings,
t,
filter,
widthProp,
isMobile,
selectGroup,
isLoading,
isAdmin,
currentUserId,
isEmptyGroup,
} = this.props;
const { isLoaded, peopleList, isLoading } = this.props;
const { dialogsVisible, user, isLoadedSection } = this.state;
const { isLoadedSection } = this.state;
return !isLoaded || (isMobile && isLoading) || !isLoadedSection ? (
<Loaders.Rows isRectangle={false} />
@ -369,123 +48,30 @@ class SectionBodyContent extends React.PureComponent {
className="people-row-container"
useReactWindow={false}
>
{peopleList.map((man) => {
const {
checked,
role,
displayName,
avatar,
id,
status,
options,
} = man;
const sectionWidth = context.sectionWidth;
const showContextMenu = options && options.length > 0;
const contextOptionsProps =
(isAdmin && showContextMenu) ||
(showContextMenu && id === currentUserId)
? {
contextOptions: this.getUserContextOptions(options, id),
}
: {};
const checkedProps =
checked !== null && isAdmin ? { checked } : {};
const element = (
<Avatar
size="min"
role={role}
userName={displayName}
source={avatar}
/>
);
return (
<Row
key={id}
status={status}
data={man}
element={element}
onSelect={this.onContentRowSelect}
{...checkedProps}
{...contextOptionsProps}
needForUpdate={this.needForUpdate}
sectionWidth={sectionWidth}
>
<UserContent
isMobile={isMobile}
widthProp={widthProp}
user={man}
history={history}
settings={settings}
selectGroup={selectGroup}
sectionWidth={sectionWidth}
/>
</Row>
);
})}
{peopleList.map((person) => (
<SimpleUserRow
key={person.id}
person={person}
sectionWidth={context.sectionWidth}
isMobile={isMobile}
/>
))}
</RowContainer>
)}
</Consumer>
{dialogsVisible.changeEmail && (
<ChangeEmailDialog
visible={dialogsVisible.changeEmail}
onClose={this.onCloseDialog}
user={user}
/>
)}
{dialogsVisible.changePassword && (
<ChangePasswordDialog
visible={dialogsVisible.changePassword}
onClose={this.onCloseDialog}
email={user.email}
/>
)}
{dialogsVisible.deleteSelfProfile && (
<DeleteSelfProfileDialog
visible={dialogsVisible.deleteSelfProfile}
onClose={this.onCloseDialog}
email={user.email}
/>
)}
{dialogsVisible.deleteProfileEver && (
<DeleteProfileEverDialog
visible={dialogsVisible.deleteProfileEver}
onClose={this.onCloseDialog}
user={user}
filter={filter}
settings={settings}
history={history}
/>
)}
<Dialogs />
</>
) : (
<EmptyScreen
t={t}
onResetFilter={this.onResetFilter}
isEmptyGroup={isEmptyGroup}
/>
<EmptyScreen />
);
}
}
export default inject(({ auth, peopleStore }) => ({
settings: auth.settingsStore,
isLoaded: auth.isLoaded,
isAdmin: auth.isAdmin,
currentUserId: auth.userStore.user.id,
fetchPeople: peopleStore.usersStore.getUsersList,
peopleList: peopleStore.usersStore.peopleList,
filter: peopleStore.filterStore.filter,
resetFilter: peopleStore.resetFilter,
selectUser: peopleStore.selectionStore.selectUser,
deselectUser: peopleStore.selectionStore.deselectUser,
selectGroup: peopleStore.selectedGroupStore.selectGroup,
updateUserStatus: peopleStore.usersStore.updateUserStatus,
isLoading: peopleStore.isLoading,
isEmptyGroup: peopleStore.selectedGroupStore.isEmptyGroup,
}))(observer(withRouter(withTranslation("Home")(SectionBodyContent))));
}))(observer(SectionBodyContent));

View File

@ -2,7 +2,7 @@ import React, { useCallback } from "react";
import { withRouter } from "react-router";
import styled from "styled-components";
import RowContent from "@appserver/components/row-container";
import RowContent from "@appserver/components/row-content";
import Link from "@appserver/components/link";
import LinkWithDropdown from "@appserver/components/link-with-dropdown";
import Text from "@appserver/components/text";
@ -80,7 +80,7 @@ const UserContent = ({
history,
homepage,
selectGroup,
widthProp,
//widthProp,
isMobile,
sectionWidth,
}) => {
@ -108,7 +108,7 @@ const UserContent = ({
return (
<RowContent
widthProp={widthProp}
//widthProp={widthProp}
isMobile={isMobile}
sideColor={sideInfoColor}
sectionWidth={sectionWidth}

View File

@ -56,7 +56,7 @@ const getGroup = (filterValues) => {
class SectionFilterContent extends React.Component {
onFilter = (data) => {
const { onLoading, fetchPeople, filter } = this.props;
const { setIsLoading, fetchPeople, filter } = this.props;
const employeeStatus = getEmployeeStatus(data.filterValues);
const activationStatus = getActivationStatus(data.filterValues);
@ -77,8 +77,8 @@ class SectionFilterContent extends React.Component {
newFilter.search = search;
newFilter.group = group;
onLoading(true);
fetchPeople(newFilter).finally(() => onLoading(false));
setIsLoading(true);
fetchPeople(newFilter).finally(() => setIsLoading(false));
};
getData = () => {
@ -264,6 +264,7 @@ export default inject(({ auth, peopleStore }) => {
groups: peopleStore.groupsStore.groups,
fetchPeople: peopleStore.usersStore.getUsersList,
filter: peopleStore.filterStore.filter,
setIsLoading: peopleStore.setIsLoading,
};
})(
observer(

View File

@ -8,7 +8,7 @@ import { inject, observer } from "mobx-react";
const SectionPagingContent = ({
fetchPeople,
filter,
onLoading,
setIsLoading,
selectedCount,
isLoaded,
}) => {
@ -24,10 +24,10 @@ const SectionPagingContent = ({
const newFilter = filter.clone();
newFilter.page++;
onLoading(true);
fetchPeople(newFilter).finally(() => onLoading(false));
setIsLoading(true);
fetchPeople(newFilter).finally(() => setIsLoading(false));
},
[filter, fetchPeople, onLoading]
[filter, fetchPeople, setIsLoading]
);
const onPrevClick = useCallback(
@ -42,10 +42,10 @@ const SectionPagingContent = ({
const newFilter = filter.clone();
newFilter.page--;
onLoading(true);
fetchPeople(newFilter).finally(() => onLoading(false));
setIsLoading(true);
fetchPeople(newFilter).finally(() => setIsLoading(false));
},
[filter, fetchPeople, onLoading]
[filter, fetchPeople, setIsLoading]
);
const onChangePageSize = useCallback(
@ -56,10 +56,10 @@ const SectionPagingContent = ({
newFilter.page = 0;
newFilter.pageCount = pageItem.key;
onLoading(true);
fetchPeople(newFilter).finally(() => onLoading(false));
setIsLoading(true);
fetchPeople(newFilter).finally(() => setIsLoading(false));
},
[filter, fetchPeople, onLoading]
[filter, fetchPeople, setIsLoading]
);
const onChangePage = useCallback(
@ -69,10 +69,10 @@ const SectionPagingContent = ({
const newFilter = filter.clone();
newFilter.page = pageItem.key;
onLoading(true);
fetchPeople(newFilter).finally(() => onLoading(false));
setIsLoading(true);
fetchPeople(newFilter).finally(() => setIsLoading(false));
},
[filter, fetchPeople, onLoading]
[filter, fetchPeople, setIsLoading]
);
const countItems = useMemo(
@ -152,4 +152,5 @@ export default inject(({ auth, peopleStore }) => ({
isLoaded: auth.isLoaded,
fetchPeople: peopleStore.usersStore.getUsersList,
filter: peopleStore.filterStore.filter,
setIsLoading: peopleStore.setIsLoading,
}))(observer(SectionPagingContent));

View File

@ -15,8 +15,8 @@ import {
SectionFilterContent,
SectionPagingContent,
} from "./Section";
import { isMobile } from "react-device-detect";
import { inject, observer } from "mobx-react";
import { isMobile } from "react-device-detect";
class Home extends React.Component {
componentDidUpdate(prevProps) {
@ -29,12 +29,8 @@ class Home extends React.Component {
}
}
onLoading = (status) => {
this.props.setIsLoading(status);
};
render() {
console.log("Home render");
//console.log("Home render");
const { isLoaded, isAdmin, isHeaderVisible } = this.props;
return (
@ -59,19 +55,19 @@ class Home extends React.Component {
</PageLayout.ArticleBody>
<PageLayout.SectionHeader>
<SectionHeaderContent onLoading={this.onLoading} />
<SectionHeaderContent />
</PageLayout.SectionHeader>
<PageLayout.SectionFilter>
<SectionFilterContent onLoading={this.onLoading} />
<SectionFilterContent />
</PageLayout.SectionFilter>
<PageLayout.SectionBody>
<SectionBodyContent isMobile={isMobile} onLoading={this.onLoading} />
<SectionBodyContent />
</PageLayout.SectionBody>
<PageLayout.SectionPaging>
<SectionPagingContent onLoading={this.onLoading} />
<SectionPagingContent />
</PageLayout.SectionPaging>
</PageLayout>
);

View File

@ -51,7 +51,7 @@ i18n
},
react: {
useSuspense: true,
useSuspense: false,
},
});

View File

@ -0,0 +1,43 @@
import { makeAutoObservable } from "mobx";
class DialogStore {
changeEmail = false;
changePassword = false;
deleteSelfProfile = false;
deleteProfileEver = false;
data = {};
constructor() {
makeAutoObservable(this);
}
setChangeEmailDialogVisible = (visible) => {
this.changeEmail = visible;
};
setChangePasswordDialogVisible = (visible) => {
this.changePassword = visible;
};
setDeleteSelfProfileDialogVisible = (visible) => {
this.deleteSelfProfile = visible;
};
setDeleteProfileDialogVisible = (visible) => {
this.deleteProfileEver = visible;
};
setDialogData = (data) => {
this.data = data;
};
closeDialogs = () => {
this.setChangeEmailDialogVisible(false);
this.setChangePasswordDialogVisible(false);
this.setDeleteSelfProfileDialogVisible(false);
this.setDeleteProfileDialogVisible(false);
this.setDialogData({});
};
}
export default DialogStore;

View File

@ -12,6 +12,7 @@ import HeaderMenuStore from "./HeaderMenuStore";
import AvatarEditorStore from "./AvatarEditorStore";
import InviteLinksStore from "./InviteLinksStore";
import store from "studio/store";
import DialogStore from "./DialogStore";
const { auth: authStore } = store;
class PeopleStore {
@ -25,6 +26,7 @@ class PeopleStore {
headerMenuStore = null;
avatarEditorStore = null;
inviteLinksStore = null;
dialogStore = null;
isLoading = false;
isLoaded = false;
@ -40,6 +42,7 @@ class PeopleStore {
this.headerMenuStore = new HeaderMenuStore(this);
this.avatarEditorStore = new AvatarEditorStore(this);
this.inviteLinksStore = new InviteLinksStore(this);
this.dialogStore = new DialogStore();
makeObservable(this, {
isLoading: observable,

View File

@ -235,7 +235,6 @@ class UsersStore {
return {
id,
checked: isViewerAdmin ? this.isUserSelected(user.id) : undefined,
status,
activationStatus,
statusType,

View File

@ -1,25 +1,49 @@
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
.ModuleFederationPlugin;
const path = require("path");
const deps = require("./package.json").dependencies;
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage;
module.exports = {
entry: "./src/index",
var config = {
mode: "development",
devtool: "inline-source-map",
entry: "./src/index",
devServer: {
contentBase: [path.join(__dirname, "public"), path.join(__dirname, "dist")],
contentBasePublicPath: "/products/people/",
publicPath: homepage,
contentBase: [path.join(__dirname, "public")],
contentBasePublicPath: homepage,
port: 5002,
historyApiFallback: true,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
index: homepage,
},
proxy: [
{
context: "/api",
target: "http://localhost:8092",
},
],
hot: false,
hotOnly: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
},
output: {
publicPath: "auto",
publicPath: "auto", //homepage
chunkFilename: "[id].[contenthash].js",
path: path.resolve(process.cwd(), "dist"),
},
resolve: {
@ -28,6 +52,7 @@ module.exports = {
crypto: false,
},
},
module: {
rules: [
{
@ -88,11 +113,12 @@ module.exports = {
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "people",
filename: "remoteEntry.js",
remotes: {
studio: "studio@http://localhost:5001/remoteEntry.js",
studio: "studio@/remoteEntry.js",
},
exposes: {
"./app": "./src/People.jsx",
@ -111,6 +137,30 @@ module.exports = {
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
//base: `${homepage}/`,
}),
new CopyPlugin({
patterns: [
{
from: "public",
globOptions: {
dot: true,
gitignore: true,
ignore: ["**/index.html"],
},
},
],
}),
],
};
module.exports = (env, argv) => {
if (argv.mode === "production") {
config.mode = "production";
} else {
config.devtool = "source-map";
}
return config;
};

View File

@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import styled from "styled-components";
import { Router, Switch } from "react-router-dom";
import { inject, observer } from "mobx-react";
import NavMenu from "@appserver/common/components/NavMenu";
import NavMenu from "./components/NavMenu";
import Main from "@appserver/common/components/Main";
import Box from "@appserver/components/box";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
@ -23,7 +23,8 @@ import "./custom.scss";
import "./i18n";
const Payments = React.lazy(() => import("./components/pages/Payments"));
const Error404 = React.lazy(() => import("@appserver/common/pages/errors/404"));
const Error404 = React.lazy(() => import("studio/Error404"));
const Error401 = React.lazy(() => import("studio/Error401"));
const Home = React.lazy(() => import("./components/pages/Home"));
const Login = React.lazy(() => import("login/app"));
const People = React.lazy(() => import("people/app"));
@ -65,6 +66,13 @@ const Error404Route = (props) => (
</React.Suspense>
);
const Error401Route = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<ErrorBoundary>
<Error401 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const HomeRoute = (props) => (
<React.Suspense fallback={<LoadingShell />}>
<ErrorBoundary>
@ -211,6 +219,7 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
path="/settings"
component={SettingsRoute}
/>
<PrivateRoute path="/error401" component={Error401Route} />
<PrivateRoute component={Error404Route} />
</Switch>
</Main>

View File

@ -0,0 +1,39 @@
import i18n from "i18next";
import Backend from "i18next-http-backend";
import { LANGUAGE } from "@appserver/common/constants";
//import LanguageDetector from "i18next-browser-languagedetector";
// not like to use this?
// have a look at the Quick start guide
// for passing in lng and translations on init
const languages = ["en", "ru"];
const newInstance = i18n.createInstance();
newInstance.use(Backend).init({
lng: localStorage.getItem(LANGUAGE) || "en",
supportedLngs: languages,
whitelist: languages,
fallbackLng: "en",
load: "languageOnly",
//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;
},
},
backend: {
loadPath: `/locales/{{lng}}/NavMenu.json`,
},
react: {
useSuspense: false,
},
});
export default newInstance;

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import Backdrop from "@appserver/components/backdrop";
@ -9,13 +9,13 @@ import Header from "./sub-components/header";
import HeaderNav from "./sub-components/header-nav";
import HeaderUnAuth from "./sub-components/header-unauth";
import { I18nextProvider, withTranslation } from "react-i18next";
import i18n from "./i18n";
import { withRouter } from "react-router";
//import { getLanguage, isDesktopClient } from "../../store/auth/selectors";
import Loaders from "../Loaders";
import { LayoutContextConsumer } from "../Layout/context";
import Loaders from "@appserver/common/components/Loaders";
import { LayoutContextConsumer } from "@appserver/common/components/Layout/context";
import { isMobile } from "react-device-detect";
import { inject, observer } from "mobx-react";
import i18n from "./i18n";
const backgroundColor = "#0F4071";
@ -211,27 +211,7 @@ NavMenu.defaultProps = {
isDesktop: false,
};
const NavMenuTranslationWrapper = withTranslation()(NavMenu);
const NavMenuWrapper = (props) => {
const { language } = props;
useEffect(() => {
i18n.changeLanguage(language);
}, [language]);
return (
<I18nextProvider i18n={i18n}>
<NavMenuTranslationWrapper {...props} />
</I18nextProvider>
);
};
NavMenuWrapper.propTypes = {
language: PropTypes.string.isRequired,
};
export default inject(({ auth }) => {
const NavMenuWrapper = inject(({ auth }) => {
const { settingsStore, isAuthenticated, isLoaded, language } = auth;
const { isDesktopClient: isDesktop } = settingsStore;
return {
@ -240,4 +220,10 @@ export default inject(({ auth }) => {
isDesktop,
language,
};
})(withRouter(observer(NavMenuWrapper)));
})(withRouter(observer(withTranslation("NavMenu")(NavMenu))));
export default () => (
<I18nextProvider i18n={i18n}>
<NavMenuWrapper />
</I18nextProvider>
);

View File

@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import styled from "styled-components";
import NavItem from "./nav-item";
import ProfileActions from "./profile-actions";
import history from "../../../history";
//import history from "@appserver/common/history";
import { useTranslation } from "react-i18next";
import { tablet } from "@appserver/components/utils/device";
@ -44,13 +44,9 @@ const HeaderNav = ({
logout,
isAuthenticated,
}) => {
const { t } = useTranslation();
const { t } = useTranslation("NavMenu");
const onProfileClick = useCallback(() => {
if (homepage == "/products/people") {
history.push("/products/people/view/@self");
} else {
window.open("/products/people/view/@self", "_self");
}
history.push("/products/people/view/@self");
}, []);
const onAboutClick = useCallback(() => history.push("/about"), []);

View File

@ -4,12 +4,12 @@ import PropTypes from "prop-types";
import styled from "styled-components";
import { useLocation, Link as LinkWithoutRedirect } from "react-router-dom";
import NavItem from "./nav-item";
import Headline from "../../Headline";
import Headline from "@appserver/common/components/Headline";
import Nav from "./nav";
import NavLogoItem from "./nav-logo-item";
import Link from "@appserver/components/link";
import Loaders from "../../Loaders";
import history from "../../../history";
import Loaders from "@appserver/common/components/Loaders";
import history from "@appserver/common/history";
import { ReactSVG } from "react-svg";
import { useTranslation } from "react-i18next";

View File

@ -7,7 +7,7 @@ import Badge from "@appserver/components/badge";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import MenuIcon from "../../../../../public/images/menu.react.svg";
import MenuIcon from "../../../../../../public/images/menu.react.svg";
const baseColor = "#7A95B0",
activeColor = "#FFFFFF",

View File

@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import Avatar from "@appserver/components/avatar";
import DropDownItem from "@appserver/components/drop-down-item";
import Link from "@appserver/components/link";
import ProfileMenu from "../../ProfileMenu";
import ProfileMenu from "./profile-menu";
import { inject, observer } from "mobx-react";
class ProfileActions extends React.PureComponent {

View File

@ -2,14 +2,70 @@ import React from "react";
import PropTypes from "prop-types";
import Avatar from "@appserver/components/avatar";
import DropDown from "@appserver/components/drop-down";
import {
AvatarContainer,
LabelContainer,
MainLabelContainer,
MenuContainer,
StyledProfileMenu,
TopArrow,
} from "./StyledProfileMenu";
import styled, { css } from "styled-components";
import DropDownItem from "@appserver/components/drop-down-item";
const commonStyle = css`
font-family: "Open Sans", sans-serif, Arial;
font-style: normal;
color: #ffffff;
margin-left: 60px;
margin-top: -3px;
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
export const StyledProfileMenu = styled(DropDownItem)`
position: relative;
overflow: visible;
padding: 0px;
cursor: pointer;
display: inline-block;
margin-top: -6px;
`;
export const MenuContainer = styled.div`
position: relative;
height: 76px;
background: linear-gradient(200.71deg, #2274aa 0%, #0f4071 100%);
border-radius: 6px 6px 0px 0px;
padding: 16px;
cursor: default;
box-sizing: border-box;
`;
export const AvatarContainer = styled.div`
display: inline-block;
float: left;
`;
export const MainLabelContainer = styled.div`
font-size: 16px;
line-height: 28px;
${commonStyle}
`;
export const LabelContainer = styled.div`
font-weight: normal;
font-size: 11px;
line-height: 16px;
${commonStyle}
`;
export const TopArrow = styled.div`
position: absolute;
cursor: default;
top: -6px;
right: 16px;
width: 24px;
height: 6px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.27954 1.12012C10.8122 -0.295972 13.1759 -0.295971 14.7086 1.12012L18.8406 4.93793C19.5796 5.62078 20.5489 6 21.5551 6H24H0H2.43299C3.4392 6 4.40845 5.62077 5.1475 4.93793L9.27954 1.12012Z' fill='%23206FA4'/%3E%3C/svg%3E");
`;
class ProfileMenu extends React.Component {
constructor(props) {

View File

@ -7,7 +7,7 @@ import Text from "@appserver/components/text";
import toastr from "@appserver/components/toast/toastr";
import UnionIcon from "../svg/union.react.svg";
import RecoverAccessModalDialog from "./recover-access-modal-dialog";
import { sendRecoverRequest } from "../../../api/settings/index";
import { sendRecoverRequest } from "@appserver/common/api/settings/index";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
const StyledUnionIcon = styled(UnionIcon)`

Some files were not shown because too many files have changed in this diff Show More