Big Fix commit:

+ Added new Clients for CRM/Products
+ Removed old fake coming-soon pages
+ Added combineUrl and AppSettingsConfig with proxyUrl
+ Added new fields to modules info on Server
+ Fixed work with icon and image urls
+ Added CRM/Projects to nginx/onlyoffice.conf
+ Applied combineUrl in studio links
+ Added and applied "id" field in package.json of all Clients
+ Added new bat files CrmClient.bat and ProjectsClient.bat
+ Fixed ProductClassName for Files
This commit is contained in:
Alexey Safronov 2021-03-22 00:34:21 +03:00
parent a921bf3f71
commit d568693154
89 changed files with 3092 additions and 394 deletions

2
build/run/CrmClient.bat Normal file
View File

@ -0,0 +1,2 @@
echo "RUN ASC.Web.CRM"
call set BROWSER=none&&npm start --prefix ../../products/ASC.CRM/Client

View File

@ -0,0 +1,2 @@
echo "RUN ASC.Web.Projects"
call set BROWSER=none&&npm start --prefix ../../products/ASC.Projects/Client

View File

@ -7,20 +7,29 @@ namespace ASC.Api.Core
public class Module
{
public Guid Id { get; set; }
public string AppName { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public string ImageUrl { get; set; }
public string Link { get; set; }
public string IconUrl { get; set; }
public string ImageUrl { get; set; }
public string HelpUrl { get; set; }
public string Description { get; set; }
public bool IsPrimary { get; set; }
public Module(Product product)
{
Id = product.ProductID;
AppName = product.ProductClassName;
Title = product.Name;
Description = product.Description;
IconUrl = product.Context.IconFileName;
ImageUrl = product.Context.LargeIconFileName;
Link = product.StartURL;
IsPrimary = product.IsPrimary;
HelpUrl = product.HelpURL;
}
}
}

View File

@ -270,33 +270,94 @@ server {
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
}
location ~* (/crm|/mail/|/calendar|/projects|/talk) {
#rewrite products/people/(.*) /$1 break;
proxy_pass http://localhost:5001;
location ~* /crm {
#rewrite products/crm/(.*) /$1 break;
proxy_pass http://localhost:5014;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
location ~* /(sockjs-node|locales) {
rewrite products/people(.*)/(sockjs-node|locales)/(.*) /$2/$3 break;
location ~* /sockjs-node {
rewrite products/crm(.*)/sockjs-node/(.*) /$2/$3 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://localhost:5001;
proxy_pass http://localhost:5014;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /locales {
proxy_pass http://localhost:5014;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /(manifest.json|service-worker.js|appIcon.png|bg-error.png) {
root $public_root;
try_files /$basename /index.html =404;
}
location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) {
proxy_pass http://localhost:5007;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
}
location ~* /projects {
#rewrite products/projects/(.*) /$1 break;
proxy_pass http://localhost:5015;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
location ~* /sockjs-node {
rewrite products/projects(.*)/sockjs-node/(.*) /$2/$3 break;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://localhost:5015;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /locales {
proxy_pass http://localhost:5015;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /(manifest.json|service-worker.js|appIcon.png|bg-error.png) {
root $public_root;
try_files /$basename /index.html =404;
}
location ~* (/httphandlers/filehandler.ashx|ChunkedUploader.ashx) {
proxy_pass http://localhost:5007;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
}
}
location /apisystem {

View File

@ -20,6 +20,14 @@
"name": "🚀 @appserver/people",
"path": "products\\ASC.People\\Client"
},
{
"name": "🚀 @appserver/crm",
"path": "products\\ASC.Crm\\Client"
},
{
"name": "🚀 @appserver/projects",
"path": "products\\ASC.Projects\\Client"
},
{
"name": "🚀 @appserver/studio",
"path": "web\\ASC.Web.Client"

View File

@ -8,7 +8,9 @@
"web/ASC.Web.Client",
"web/ASC.Web.Editor",
"products/ASC.People/Client",
"products/ASC.Files/Client"
"products/ASC.Files/Client",
"products/ASC.CRM/Client",
"products/ASC.Projects/Client"
],
"scripts": {
"wipe": "rimraf node_modules yarn.lock web/**/node_modules products/**/node_modules",

View File

@ -1,16 +1,30 @@
import axios from "axios";
import config from "../package.json";
//import history from "../history";
import { AppServerConfig } from "../constants";
import { combineUrl } from "../utils";
const baseURL = `${window.location.origin}/${config.api.url}`;
const apiTimeout = config.api.timeout;
const { proxyURL, apiPrefixURL, apiTimeout } = AppServerConfig;
const origin = window.location.origin;
const apiBaseURL = combineUrl(origin, proxyURL, apiPrefixURL);
const loginURL = combineUrl(proxyURL, "/login");
const paymentsURL = combineUrl(proxyURL, "/payments");
window.AppServer = {
origin,
proxyURL,
apiPrefixURL,
apiBaseURL,
apiTimeout,
loginURL,
paymentsURL,
};
/**
* @description axios instance for ajax requests
*/
const client = axios.create({
baseURL: baseURL,
baseURL: apiBaseURL,
responseType: "json",
timeout: apiTimeout, // default is `0` (no timeout)
});
@ -25,11 +39,11 @@ client.interceptors.response.use(
switch (true) {
case error.response.status === 401:
setWithCredentialsStatus(false);
window.location.href = "/login";
window.location.href = loginURL;
break;
case error.response.status === 402:
if (!window.location.pathname.includes("payments")) {
window.location.href = "/payments";
window.location.href = paymentsURL;
}
break;
default:

View File

@ -1,4 +1,7 @@
import { request } from "../client";
import { combineUrl } from "../../utils";
import { AppServerConfig } from "../../constants";
const { proxyURL } = AppServerConfig;
export function getModulesList() {
return request({
@ -11,8 +14,8 @@ export function getModulesList() {
const newModules = workingModules.map((m) => {
return {
...m,
iconUrl: m.link + "images/icon.svg",
imageUrl: m.link + m.imageUrl,
iconUrl: combineUrl(proxyURL, m.link, m.iconUrl),
imageUrl: combineUrl(proxyURL, m.link, m.imageUrl),
};
});

View File

@ -1,3 +1,6 @@
import config from "../package.json";
const { api, proxy } = config;
export const LANGUAGE = "language";
export const ARTICLE_PINNED_KEY = "asc_article_pinned_key";
@ -135,3 +138,9 @@ export const LoaderStyle = {
speed: 2,
animate: true,
};
export const AppServerConfig = {
proxyURL: (proxy && proxy.url) || "",
apiPrefixURL: (api && api.url) || "/api/2.0",
apiTimeout: (api && api.timeout) || 30000,
};

View File

@ -9,7 +9,10 @@
"clean": "echo 'skip it'"
},
"api": {
"url": "api/2.0",
"url": "/api/2.0",
"timeout": "30000"
},
"proxy": {
"url": ""
}
}

View File

@ -6,8 +6,10 @@ import ModuleStore from "./ModuleStore";
import SettingsStore from "./SettingsStore";
import UserStore from "./UserStore";
import { logout as logoutDesktop, desktopConstants } from "../desktop";
import { isAdmin } from "../utils";
import { combineUrl, isAdmin } from "../utils";
import isEmpty from "lodash/isEmpty";
import { AppServerConfig } from "../constants";
const { proxyURL } = AppServerConfig;
class AuthStore {
userStore = null;
@ -81,32 +83,36 @@ class AuthStore {
get availableModules() {
const { user } = this.userStore;
const { modules, toModuleWrapper } = this.moduleStore;
const { modules } = this.moduleStore;
if (isEmpty(modules) || isEmpty(this.userStore.user)) {
return [];
}
const isUserAdmin = user.isAdmin;
const customModules = this.getCustomModules(isUserAdmin);
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);
const customProducts = this.getCustomModules(isUserAdmin);
const readyProducts = [];
const inProgressProducts = [];
modules.forEach((p) => {
if (p.appName === "people" || p.appName === "files") {
readyProducts.push(p);
} else {
inProgressProducts.push(p);
}
});
return [
{
separator: true,
id: "nav-products-separator",
},
...primaryProducts,
...customModules,
...readyProducts,
...customProducts,
{
separator: true,
dashed: true,
id: "nav-dummy-products-separator",
},
...dummyProducts,
...inProgressProducts,
];
}
@ -114,16 +120,15 @@ class AuthStore {
if (!isAdmin) {
return [];
}
const settingsModuleWrapper = this.moduleStore.toModuleWrapper(
{
id: "settings",
title: "Settings",
link: "/settings",
},
false,
"SettingsIcon",
"/static/images/settings.react.svg"
);
const settingsModuleWrapper = this.moduleStore.toModuleWrapper({
id: "settings",
title: "Settings",
link: "/settings",
iconUrl: "/static/images/settings.react.svg",
});
settingsModuleWrapper.onClick = this.onClick;
settingsModuleWrapper.onBadgeClick = this.onBadgeClick;
return [settingsModuleWrapper];
};
@ -171,7 +176,7 @@ class AuthStore {
this.init();
if (!withoutRedirect) {
history.push("/login");
history.push(combineUrl(proxyURL, "/login"));
}
};

View File

@ -1,5 +1,8 @@
import api from "../api";
import { makeAutoObservable } from "mobx";
import { combineUrl } from "../utils";
import { AppServerConfig } from "../constants";
const { proxyURL } = AppServerConfig;
class ModuleStore {
isLoading = false;
@ -13,134 +16,34 @@ class ModuleStore {
getModules = async () => {
const list = await api.modules.getModulesList();
const extendedModules = [
...list,
{
id: "2A923037-8B2D-487b-9A22-5AC0918ACF3F",
title: "Mail",
link: "/products/mail/",
originUrl: "/addons/mail/",
helpUrl: "https://helpcenter.onlyoffice.com/userguides/mail.aspx",
description:
"Mail is a tool that allows to work with email messages right on your portal. It provides a variety of the standard capabilities implemented in any other email client.",
isPrimary: false,
},
{
id: "32D24CB5-7ECE-4606-9C94-19216BA42086",
title: "Calendar",
link: "/products/calendar/",
originUrl: "/addons/calendar/",
helpUrl: "https://helpcenter.onlyoffice.com/userguides/calendar.aspx",
description:
"Calendar is a built-in scheduling tool that allows you to always keep track of important events and meetings",
isPrimary: false,
},
{
id: "BF88953E-3C43-4850-A3FB-B1E43AD53A3E",
title: "Talk",
link: "/products/talk/",
originUrl: "/addons/talk/",
helpUrl: "https://helpcenter.onlyoffice.com/userguides/talk.aspx",
description:
"Talk is an instant messenger that provides a real-time communication between the co-workers. It offers all the traditional features you expect from a messenger: history archiving, file transfer, multi-user chat support, search function, emoticons.",
isPrimary: false,
},
].map((m) => this.toModuleWrapper(m));
const extendedModules = list.map((m) => this.toModuleWrapper(m));
this.setModules(extendedModules);
};
toModuleWrapper = (
item,
noAction = true,
iconName = null,
iconUrl = null
) => {
toModuleWrapper = (item) => {
const id =
item.id && typeof item.id === "string" ? item.id.toLowerCase() : null;
const link = item.link
? combineUrl(proxyURL, item.link.toLowerCase())
: null;
const iconUrl = item.iconUrl
? combineUrl(proxyURL, item.iconUrl.toLowerCase())
: null;
const imageUrl = item.imageUrl
? combineUrl(proxyURL, item.imageUrl.toLowerCase())
: null;
const result = {
...item,
id,
appName: "none",
title: item.title,
link: item.link,
originUrl: item.originUrl,
helpUrl: item.helpUrl,
link,
notifications: 0,
iconName: item.iconName || iconName || "/static/images/people.react.svg", //TODO: Change to URL
iconUrl: item.iconUrl || iconUrl,
imageUrl: item.imageUrl,
isolateMode: item.isolateMode,
isPrimary: item.isPrimary,
iconUrl,
imageUrl,
};
switch (id) {
case "6743007c-6f95-4d20-8c88-a8601ce5e76d":
result.appName = "crm";
result.ready = false;
result.iconName = "CrmIcon";
result.iconUrl = "/static/images/crm.react.svg";
result.imageUrl = "/images/crm.svg";
result.helpUrl =
"https://helpcenter.onlyoffice.com/userguides/crm.aspx";
break;
case "1e044602-43b5-4d79-82f3-fd6208a11960":
result.appName = "projects";
result.ready = false;
result.iconName = "ProjectsIcon";
result.iconUrl = "/static/images/projects.react.svg";
result.imageUrl = "/images/projects.svg";
result.helpUrl =
"https://helpcenter.onlyoffice.com/userguides/projects.aspx";
break;
case "2a923037-8b2d-487b-9a22-5ac0918acf3f":
result.appName = "mail";
result.ready = false;
result.iconName = "MailIcon";
result.iconUrl = "/static/images/mail.react.svg";
result.imageUrl = "/images/mail.svg";
break;
case "32d24cb5-7ece-4606-9c94-19216ba42086":
result.appName = "calendar";
result.ready = false;
result.iconName = "CalendarCheckedIcon";
result.iconUrl = "/static/images/calendar.checked.react.svg";
result.imageUrl = "/images/calendar.svg";
break;
case "bf88953e-3c43-4850-a3fb-b1e43ad53a3e":
result.appName = "chat";
result.ready = false;
result.iconName = "ChatIcon";
result.iconUrl = "/static/images/chat.react.svg";
result.imageUrl = "/images/talk.svg";
result.isolateMode = true;
break;
case "e67be73d-f9ae-4ce1-8fec-1880cb518cb4":
result.appName = "files";
result.ready = true;
break;
case "f4d98afd-d336-4332-8778-3c6945c81ea0":
result.appName = "people";
result.ready = true;
break;
default:
result.appName = "none";
result.ready = false;
break;
}
if (!noAction) {
result.onClick = (e) => {
if (e) {
window.open(item.link, "_self");
e.preventDefault();
}
};
result.onBadgeClick = (e) => console.log(iconName + " Badge Clicked", e);
} else {
result.description = item.description;
}
return result;
};

View File

@ -1,6 +1,9 @@
import { action, computed, makeObservable, observable } from "mobx";
import api from "../api";
import { ARTICLE_PINNED_KEY, LANGUAGE } from "../constants";
import { combineUrl } from "../utils";
import { AppServerConfig } from "../constants";
const { proxyURL } = AppServerConfig;
class SettingsStore {
isLoading = false;
@ -15,8 +18,8 @@ class SettingsStore {
timezones = [];
utcOffset = "00:00:00";
utcHoursOffset = 0;
defaultPage = "/"; //"/products/files";
homepage = ""; //config.homepage;
defaultPage = combineUrl(proxyURL, "/");
homepage = "";
datePattern = "M/d/yyyy";
datePatternJQ = "00/00/0000";
dateTimePattern = "dddd, MMMM d, yyyy h:mm:ss tt";
@ -146,7 +149,12 @@ class SettingsStore {
Object.keys(newSettings).map((key) => {
if (key in this) {
this.setValue(key, newSettings[key]);
this.setValue(
key,
key === "defaultPage"
? combineUrl(proxyURL, newSettings[key])
: newSettings[key]
);
if (key === "culture" && !localStorage.getItem(LANGUAGE)) {
localStorage.setItem(LANGUAGE, newSettings[key]);

View File

@ -150,3 +150,18 @@ export function isAdmin(currentUser, currentProductId) {
return currentUser.isAdmin || currentUser.isOwner || isProductAdmin;
}
export function combineUrl(host = "", ...params) {
let url = host.replace(/\/+$/, "");
params.forEach((part) => {
const newPart = part.trim().replace(/^\/+/, "");
url += newPart
? url.length > 0 && url[url.length - 1] === "/"
? newPart
: `/${newPart}`
: "";
});
return url;
}

View File

@ -0,0 +1,7 @@
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -0,0 +1,3 @@
PUBLIC_URL=/products/crm
WDS_SOCKET_PATH=/products/crm/sockjs-node
PORT=5014

21
products/ASC.CRM/Client/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,15 @@
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
EXPOSE 5014
CMD [ "yarn", "build:start" ]

View File

@ -0,0 +1,17 @@
// script to enable webpack-bundle-analyzer
process.env.NODE_ENV = "production";
const webpack = require("webpack");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const webpackConfigProd = require("react-scripts/config/webpack.config")(
"production"
);
webpackConfigProd.plugins.push(new BundleAnalyzerPlugin());
// actually running compilation and waiting for plugin to start explorer
webpack(webpackConfigProd, (err, stats) => {
if (err || stats.hasErrors()) {
console.error(err);
}
});

View File

@ -0,0 +1,84 @@
{
"name": "@appserver/crm",
"version": "0.1.0",
"private": "true",
"homepage": "/products/crm",
"title": "ONLYOFFICE",
"id": "6743007c-6f95-4d20-8c88-a8601ce5e76d",
"scripts": {
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5014",
"build": "webpack --mode production",
"serve": "serve dist -p 5014",
"clean": "rm -rf dist"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^7.1.2",
"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",
"workbox-webpack-plugin": "^6.1.1"
},
"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",
"prop-types": "^15.7.2",
"rc-tree": "^2.1.4",
"re-resizable": "^6.9.0",
"react": "^17.0.1",
"react-autosize-textarea": "^7.1.0",
"react-content-loader": "^5.1.4",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.14.0",
"react-dom": "^17.0.1",
"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",
"react-resize-detector": "^5.2.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-string-format": "^0.1.0",
"react-svg": "^12.0.0",
"react-text-mask": "^5.4.3",
"react-toastify": "^6.1.0",
"react-tooltip": "^4.2.11",
"react-viewer": "^3.2.2",
"react-virtualized-auto-sizer": "^1.0.2",
"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",
"screenfull": "^5.1.0",
"styled-components": "^5.2.1",
"workbox-window": "^6.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="shortcut icon" href="favicon.ico" />
<link rel="manifest" href="manifest.json" />
<!-- Tell the browser it's a PWA -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- Tell iOS it's a PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="appIcon.png" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i"
rel="stylesheet"
type="text/css"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style type="text/css">
body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
font-family: "Open Sans", sans-serif;
font-size: 13px;
-webkit-font-smoothing: antialiased;
}
.temp-header-container {
align-items: center;
background-color: rgb(15, 64, 113);
display: grid;
grid-template-columns: 24px 168px 1fr 36px;
grid-template-rows: 1fr;
grid-column-gap: 16px;
width: calc(100vw - 16px);
height: 56px;
padding-right: 16px;
}
.temp-content-loader {
padding: 16px;
height: calc(100vh - 91px);
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: calc(100vh - 91px);
grid-column-gap: 8px;
}
@media (max-width: 1024px) {
.temp-content-loader {
grid-template-columns: 1fr;
}
.temp-content__article {
display: none;
}
}
.burger-loader-svg {
width: 24px;
height: 24px;
padding-left: 16px;
}
.logo-loader-svg {
width: 168px;
height: 24px;
position: relative;
cursor: pointer;
padding-left: 16px;
}
.avatar-loader-svg {
width: 36px;
height: 36px;
}
</style>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<div id="temp-content">
<header class="temp-header-container">
<div id="burger-loader-svg" class="burger-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria0"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="24"
height="24"
style="fill: url('#fill0');"
></rect>
<defs>
<linearGradient id="fill0">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div id="logo-loader-svg" class="logo-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="168"
height="24"
style="fill: url('#fill01');"
></rect>
<defs>
<linearGradient id="fill01">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div></div>
<div id="avatar-loader-svg" class="avatar-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<circle
cx="18"
cy="18"
r="18"
width="36"
height="36"
style="fill: url('#fill02');"
></circle>
<defs>
<linearGradient id="fill02">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</header>
<div class="temp-content-loader">
<div class="temp-content__article">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria1"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path1)"
style="fill: url('#fill1');"
></rect>
<defs>
<clipPath id="clip-path1">
<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>
</linearGradient>
</defs>
</svg>
</div>
<div>
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria2"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path2)"
style="fill: url('#fill2');"
></rect>
<defs>
<clipPath id="clip-path2">
<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>
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
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 CRM INIT");
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Coming soon",
"ViewWeb": "View web version",
"OpenApp": "Open your {{title}} app",
"LearnMore": "Learn more"
}

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Скоро появится",
"ViewWeb": "Просмотреть веб-версию",
"OpenApp": "Откройте {{title}}",
"LearnMore": "Узнать больше"
}

View File

@ -0,0 +1,15 @@
{
"short_name": "ASC.People",
"name": "ASC.People",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,7 @@
//import "./wdyr";
import React from "react";
import Shell from "studio/shell";
const App = () => <Shell />;
export default App;

View File

@ -0,0 +1,15 @@
import React from "react";
import ReactDOM from "react-dom";
import { MemoryRouter } from "react-router-dom";
import App from "./App";
it("renders without crashing", async () => {
const div = document.createElement("div");
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

View File

@ -0,0 +1,67 @@
import React, { useEffect } from "react";
import { Provider as PeopleProvider, inject, observer } from "mobx-react";
import { Switch } from "react-router-dom";
import CrmStore from "./store/CrmStore";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import toastr from "studio/toastr";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import AppLoader from "@appserver/common/components/AppLoader";
import { updateTempContent } from "@appserver/common/utils";
import config from "../package.json";
import "./custom.scss";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import Home from "./pages/Home";
const homepage = config.homepage;
const Error404 = React.lazy(() => import("studio/Error404"));
const Error404Route = (props) => (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error404 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const CrmContent = (props) => {
const { isLoaded, loadBaseInfo } = props;
useEffect(() => {
loadBaseInfo()
.catch((err) => toastr.error(err))
.finally(() => {
//this.props.setIsLoaded(true);
updateTempContent();
});
}, []);
useEffect(() => {
if (isLoaded) updateTempContent();
}, [isLoaded]);
return (
<Switch>
<PrivateRoute exact path={homepage} component={Home} />
<PrivateRoute component={Error404Route} />
</Switch>
);
};
const Crm = inject(({ auth, crmStore }) => ({
loadBaseInfo: async () => {
await crmStore.init();
auth.setProductVersion(config.version);
},
isLoaded: auth.isLoaded && crmStore.isLoaded,
}))(observer(CrmContent));
const crmStore = new CrmStore();
export default (props) => (
<PeopleProvider crmStore={crmStore}>
<I18nextProvider i18n={i18n}>
<Crm {...props} />
</I18nextProvider>
</PeopleProvider>
);

View File

@ -0,0 +1,47 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import config from "../package.json";
import { Workbox, messageSW } from "workbox-window";
ReactDOM.render(<App />, document.getElementById("root"));
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
const wb = new Workbox(`${config.homepage}/sw.js`);
//TODO: watch https://developers.google.com/web/tools/workbox/guides/advanced-recipes and https://github.com/webmaxru/prog-web-news/blob/5ff94b45c9d317409c21c0fbb7d76e92f064471b/src/app/app-shell/app-shell.component.ts
// const showSkipWaitingPrompt = (event) => {
// let snackBarRef = this.snackBar.open(
// "A new version of the website available",
// "Reload page",
// {
// duration: 5000,
// }
// );
// // Displaying prompt
// snackBarRef.onAction().subscribe(() => {
// // Assuming the user accepted the update, set up a listener
// // that will reload the page as soon as the previously waiting
// // service worker has taken control.
// wb.addEventListener("controlling", () => {
// window.location.reload();
// });
// // This will postMessage() to the waiting service worker.
// wb.messageSkipWaiting();
// });
// };
// // Add an event listener to detect when the registered
// // service worker has installed but is waiting to activate.
// wb.addEventListener("waiting", showSkipWaitingPrompt);
wb.register()
.then((reg) => {
console.log("Successful service worker registration", reg);
})
.catch((err) => console.error("Service worker registration failed", err));
}

View File

@ -0,0 +1,27 @@
// Override default variables before the import
$font-family-base: "Open Sans", sans-serif;
html,
body {
height: 100%;
}
#root {
min-height: 100%;
position: relative;
.pageLoader {
position: absolute;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
overflow: hidden;
}
body.loading * {
cursor: wait !important;
}

View File

@ -0,0 +1,26 @@
import authStore from "@appserver/common/store/AuthStore";
//import store from "../store/store";
//const { getCurrentProduct } = commonStore.auth.selectors;
export const setDocumentTitle = (subTitle = null) => {
// const state = store.getState();
// const { auth: commonState } = state;
const { isAuthenticated, settingsStore, product: currentModule } = authStore;
const { organizationName } = settingsStore;
let title;
if (subTitle) {
if (isAuthenticated && currentModule) {
title = subTitle + " - " + currentModule.title;
} else {
title = subTitle + " - " + organizationName;
}
} else if (currentModule && organizationName) {
title = currentModule.title + " - " + organizationName;
} else {
title = organizationName;
}
document.title = title;
};

View File

@ -0,0 +1,58 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import config from "../package.json";
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"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.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: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
react: {
useSuspense: false,
},
});
export default i18n;

View File

@ -0,0 +1 @@
import("./bootstrap");

View File

@ -0,0 +1,243 @@
import React, { useEffect } from "react";
import { ReactSVG } from "react-svg";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import Badge from "@appserver/components/badge";
import Box from "@appserver/components/box";
import EmptyScreenContainer from "@appserver/components/empty-screen-container";
import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg";
import Loaders from "@appserver/common/components/Loaders";
import toastr from "studio/toastr";
import PageLayout from "@appserver/common/components/PageLayout";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile, isIOS } from "react-device-detect";
import { setDocumentTitle } from "../../helpers/utils";
import { inject } from "mobx-react";
import i18n from "../../i18n";
import { I18nextProvider } from "react-i18next";
const commonStyles = `
.link-box {
margin: 8px 0;
.view-web-link {
margin: 8px;
:focus {
outline: 0;
}
}
}
`;
const ComingSoonPage = styled.div`
padding: ${isMobile ? "62px 0 0 0" : "0"};
width: 336px;
margin: 0 auto;
.module-logo-icon {
float: left;
margin-top: 8px;
margin-right: 16px;
}
.module-title {
margin-top: 14px;
margin-bottom: 14px;
}
.module-info {
margin-bottom: 18px;
.learn-more-link {
white-space: nowrap;
}
}
.coming-soon-badge {
margin-bottom: 26px;
}
${commonStyles}
`;
const StyledDesktopContainer = styled(EmptyScreenContainer)`
img {
width: 150px;
height: 150px;
}
span:first-of-type {
font-size: 24px;
}
span {
font-size: 14px;
> p {
font-size: 14px;
.learn-more-link {
white-space: nowrap;
}
}
}
${commonStyles}
.view-web-link {
font-size: 14px;
}
.coming-soon-badge > div > p {
font-size: 13px;
}
`;
const ExternalLink = ({ label, href }) => (
<Box className="link-box">
<ExternalLinkIcon color="#333333" size={isMobile ? "small" : "medium"} />
<Link
as="a"
href={href}
target="_blank"
className="view-web-link"
color="#555F65"
isBold
isHovered
>
{label}
</Link>
</Box>
);
const Body = ({ modules, match, isLoaded, setCurrentProductId }) => {
const { t } = useTranslation("ComingSoon");
const { error } = match.params;
const { pathname, protocol, hostname } = window.location;
const currentModule = modules.find((m) => m.link === pathname);
const {
id,
title,
description,
imageUrl,
link,
originUrl,
helpUrl,
} = currentModule;
const url = originUrl ? originUrl : link;
const webLink = protocol + "//" + hostname + url;
const appLink = isIOS
? id === "2A923037-8B2D-487b-9A22-5AC0918ACF3F"
? "message:"
: id === "32D24CB5-7ECE-4606-9C94-19216BA42086"
? "calshow:"
: false
: false;
setDocumentTitle();
useEffect(() => {
setCurrentProductId(id);
}, [id, setCurrentProductId]);
useEffect(() => error && toastr.error(error), [error]);
const appButtons = (
<>
<Badge
label={t("ComingSoon")}
maxWidth="150px"
borderRadius="2px"
className="coming-soon-badge"
/>
<ExternalLink label={t("ViewWeb")} href={webLink} />
{appLink && (
<ExternalLink
label={t("OpenApp", {
title: title,
})}
href={appLink}
/>
)}
</>
);
const moduleDescription = (
<Text className="module-info">
{description}{" "}
{helpUrl && (
<Link
as="a"
href={helpUrl}
target="_blank"
className="learn-more-link"
color="#555F65"
isBold
isHovered
>
{t("LearnMore")}...
</Link>
)}
</Text>
);
return !isLoaded ? (
<></>
) : isMobile ? (
<ComingSoonPage>
<ReactSVG
className="module-logo-icon"
loading={() => (
<Loaders.Rectangle
width="100"
height="14"
backgroundColor="#fff"
foregroundColor="#fff"
backgroundOpacity={0.25}
foregroundOpacity={0.2}
/>
)}
src={imageUrl}
/>
<Box displayProp="flex" flexDirection="column" widthProp="220px">
<Text fontWeight="600" fontSize="19px" className="module-title">
{title}
</Text>
{moduleDescription}
{appButtons}
</Box>
</ComingSoonPage>
) : (
<StyledDesktopContainer
imageSrc={imageUrl}
imageAlt={title}
headerText={title}
descriptionText={moduleDescription}
buttons={appButtons}
/>
);
};
const ComingSoon = (props) => {
return (
<PageLayout>
<PageLayout.SectionBody>
<Body {...props} />
</PageLayout.SectionBody>
</PageLayout>
);
};
ComingSoon.propTypes = {
modules: PropTypes.array,
isLoaded: PropTypes.bool,
};
const ComingSoonWrapper = inject(({ auth }) => ({
modules: auth.moduleStore.modules,
isLoaded: auth.isLoaded,
setCurrentProductId: auth.settingsStore.setCurrentProductId,
}))(withRouter(ComingSoon));
export default (props) => (
<I18nextProvider i18n={i18n}>
<ComingSoonWrapper {...props} />
</I18nextProvider>
);

View File

@ -0,0 +1,39 @@
import { action, makeObservable, observable } from "mobx";
import config from "../../package.json";
import store from "studio/store";
const { auth: authStore } = store;
class CrmStore {
isLoading = false;
isLoaded = false;
isInit = false;
constructor() {
makeObservable(this, {
isLoading: observable,
isLoaded: observable,
setIsLoading: action,
setIsLoaded: action,
init: action,
});
}
init = async () => {
if (this.isInit) return;
this.isInit = true;
authStore.settingsStore.setModuleInfo(config.homepage, config.id);
this.setIsLoaded(true);
};
setIsLoading = (loading) => {
this.isLoading = loading;
};
setIsLoaded = (isLoaded) => {
this.isLoaded = isLoaded;
};
}
export default CrmStore;

View File

@ -0,0 +1,116 @@
import {
precacheAndRoute,
cleanupOutdatedCaches,
//createHandlerBoundToURL,
} from "workbox-precaching";
import { setCacheNameDetails } from "workbox-core";
import { clientsClaim } from "workbox-core";
import { /*NavigationRoute,*/ registerRoute } from "workbox-routing";
import { googleFontsCache, imageCache, offlineFallback } from "workbox-recipes";
import {
//CacheFirst,
//NetworkFirst,
StaleWhileRevalidate,
} from "workbox-strategies";
import { ExpirationPlugin } from "workbox-expiration";
//import { BroadcastUpdatePlugin } from "workbox-broadcast-update";
// SETTINGS
// Claiming control to start runtime caching asap
clientsClaim();
// Use to update the app after user triggered refresh (without prompt)
//self.skipWaiting();
// PRECACHING
// Setting custom cache name
setCacheNameDetails({ precache: "wb6-precache", runtime: "wb6-runtime" });
// We inject manifest here using "workbox-build" in workbox-inject.js
precacheAndRoute(self.__WB_MANIFEST);
// Remove cache from the previous WB versions
cleanupOutdatedCaches();
// NAVIGATION ROUTING
// This assumes /index.html has been precached.
// const navHandler = createHandlerBoundToURL("/index.html");
// const navigationRoute = new NavigationRoute(navHandler, {
// denylist: [new RegExp("/out-of-spa/")], // Also might be specified explicitly via allowlist
// });
// registerRoute(navigationRoute);
// STATIC RESOURCES
googleFontsCache({ cachePrefix: "wb6-gfonts" });
// API ROUTING
registerRoute(
// Cache API Request
new RegExp("/api/2.0/(modules|people/@self|(.*)/info(.json|$))"),
new StaleWhileRevalidate({
cacheName: "wb6-api",
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 30 * 60, // 30 Minutes
}),
],
})
);
// TRANSLATIONS
registerRoute(
({ url }) => url.pathname.indexOf("/locales/") !== -1,
// Use cache but update in the background.
new StaleWhileRevalidate({
// Use a custom cache name.
cacheName: "wb6-content-translation",
})
);
// CONTENT
imageCache({ cacheName: "wb6-content-images", maxEntries: 60 });
// APP SHELL UPDATE FLOW
addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
// FALLBACK
offlineFallback({
pageFallback: "/static/offline/offline.html",
imageFallback: "/static/offline/offline.svg",
fontFallback: false,
});
// ALL OTHER EVENTS
// Receive push and show a notification
self.addEventListener("push", function (event) {
console.log("[Service Worker]: Received push event", event);
var notificationData = {};
if (event.data.json()) {
notificationData = event.data.json();
} else {
notificationData = {
title: "Something Has Happened",
message: "Something you might want to check out",
icon: "/assets/img/pwa-logo.png",
};
}
self.registration.showNotification(notificationData.title, notificationData);
});

View File

@ -0,0 +1,12 @@
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = require("@welldone-software/why-did-you-render");
whyDidYouRender(React, {
trackAllPureComponents: true,
collapseGroups: true,
//trackExtraHooks: [
// [ReactRedux, 'useSelector']
//]
});
}

View File

@ -0,0 +1,204 @@
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 TerserPlugin = require("terser-webpack-plugin");
const { InjectManifest } = require("workbox-webpack-plugin");
//const CompressionPlugin = require("compression-webpack-plugin");
const path = require("path");
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage;
const title = pkg.title;
var config = {
mode: "development",
entry: "./src/index",
devServer: {
publicPath: homepage,
contentBase: [path.join(__dirname, "dist")],
contentBasePublicPath: homepage,
port: 5014,
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",
chunkFilename: "static/js/[id].[contenthash].js",
//assetModuleFilename: "static/images/[hash][ext][query]",
path: path.resolve(process.cwd(), "dist"),
filename: "static/js/[name].[contenthash].bundle.js",
},
resolve: {
extensions: [".jsx", ".js", ".json"],
fallback: {
crypto: false,
},
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|ico)$/i,
type: "asset/resource",
generator: {
filename: "static/images/[hash][ext][query]",
},
},
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.react.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
},
},
],
},
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-default-from",
],
},
},
"source-map-loader",
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "crm",
filename: "remoteEntry.js",
remotes: {
studio: "studio@/remoteEntry.js",
},
exposes: {
"./app": "./src/Crm.jsx",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
title: title,
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";
config.optimization = {
splitChunks: { chunks: "all" },
minimize: true,
minimizer: [new TerserPlugin()],
};
config.plugins.push(
new InjectManifest({
mode: "production", //"development",
swSrc: "./src/sw-template.js", // this is your sw template file
swDest: "sw.js", // this will be created in the build step
exclude: [/\.map$/, /manifest$/, /service-worker\.js$/],
})
);
// config.plugins.push(
// new CompressionPlugin({
// filename: "[path][base].gz[query]",
// algorithm: "gzip",
// test: /\.js(\?.*)?$/i,
// threshold: 10240,
// minRatio: 0.8,
// deleteOriginalAssets: true,
// })
// );
} else {
config.devtool = "cheap-module-source-map";
}
return config;
};

View File

@ -95,8 +95,8 @@ namespace ASC.CRM.Configuration
{
//MasterPageFile = String.Concat(PathProvider.BaseVirtualPath, "Masters/BasicTemplate.Master"),
DisabledIconFileName = "product_disabled_logo.png",
IconFileName = "product_logo.png",
LargeIconFileName = "product_logolarge.svg",
IconFileName = "images/crm.menu.svg",
LargeIconFileName = "images/crm.svg",
DefaultSortOrder = 30,
//SubscriptionManager = new ProductSubscriptionManager(),
//SpaceUsageStatManager = new CRMSpaceUsageStatManager(),

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": "true",
"homepage": "/products/files",
"id": "e67be73d-f9ae-4ce1-8fec-1880cb518cb4",
"title": "ONLYOFFICE",
"scripts": {
"start": "webpack-cli serve",

View File

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

View File

@ -99,7 +99,7 @@ class InitFilesStore {
setModuleInfo,
} = auth.settingsStore;
setModuleInfo(config.homepage, "e67be73d-f9ae-4ce1-8fec-1880cb518cb4");
setModuleInfo(config.homepage, config.id);
const requests = [];

View File

@ -95,8 +95,8 @@ namespace ASC.Web.Files.Configuration
_productContext =
new ProductContext
{
DisabledIconFileName = "product_disabled_logo.png",
IconFileName = "product_logo.png",
DisabledIconFileName = "product_disabled_logo.png",
IconFileName = "images/files.menu.svg",
LargeIconFileName = "images/files.svg",
DefaultSortOrder = 10,
//SubscriptionManager = SubscriptionManager,
@ -165,7 +165,7 @@ namespace ASC.Web.Files.Configuration
public override string ProductClassName
{
get { return "documents"; }
get { return "files"; }
}
public override ProductContext Context

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": "true",
"homepage": "/products/people",
"id": "f4d98afd-d336-4332-8778-3c6945c81ea0",
"title": "ONLYOFFICE",
"scripts": {
"start": "webpack-cli serve",

View File

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 340 B

View File

@ -4,6 +4,7 @@ import {
EmployeeStatus,
} from "@appserver/common/constants";
import { isAdmin } from "@appserver/common/utils";
import { id } from "../../package.json";
//const { isAdmin } = utils;
export const getUserStatus = (user) => {
@ -26,7 +27,7 @@ export const getUserStatus = (user) => {
export const getUserRole = (user) => {
if (user.isOwner) return "owner";
else if (isAdmin(user, "f4d98afd-d336-4332-8778-3c6945c81ea0"))
else if (isAdmin(user, id))
//TODO: Change to People Product Id const
return "admin";
//TODO: Need refactoring

View File

@ -69,10 +69,7 @@ class PeopleStore {
if (this.isInit) return;
this.isInit = true;
authStore.settingsStore.setModuleInfo(
config.homepage,
"f4d98afd-d336-4332-8778-3c6945c81ea0"
);
authStore.settingsStore.setModuleInfo(config.homepage, config.id);
await this.groupsStore.getGroupList();
await authStore.settingsStore.getPortalPasswordSettings();

View File

@ -67,14 +67,14 @@ namespace ASC.People
}
}
public override bool IsPrimary { get => true; }
public override bool IsPrimary { get => false; }
public override void Init()
{
_context = new ProductContext
{
DisabledIconFileName = "product_disabled_logo.png",
IconFileName = "product_logo.png",
IconFileName = "images/people.menu.svg",
LargeIconFileName = "images/people.svg",
DefaultSortOrder = 50,
AdminOpportunities = () => PeopleResource.ProductAdminOpportunities.Split('|').ToList(),

View File

@ -0,0 +1,7 @@
{
"presets": ["@babel/preset-react", "@babel/preset-env"],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties"
]
}

View File

@ -0,0 +1,3 @@
PUBLIC_URL=/products/projects
WDS_SOCKET_PATH=/products/projects/sockjs-node
PORT=5015

21
products/ASC.Projects/Client/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,15 @@
FROM node:12
WORKDIR /usr/src/app
COPY package.json ./
COPY yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
EXPOSE 5014
CMD [ "yarn", "build:start" ]

View File

@ -0,0 +1,17 @@
// script to enable webpack-bundle-analyzer
process.env.NODE_ENV = "production";
const webpack = require("webpack");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const webpackConfigProd = require("react-scripts/config/webpack.config")(
"production"
);
webpackConfigProd.plugins.push(new BundleAnalyzerPlugin());
// actually running compilation and waiting for plugin to start explorer
webpack(webpackConfigProd, (err, stats) => {
if (err || stats.hasErrors()) {
console.error(err);
}
});

View File

@ -0,0 +1,84 @@
{
"name": "@appserver/projects",
"version": "0.1.0",
"private": "true",
"homepage": "/products/projects",
"id": "1e044602-43b5-4d79-82f3-fd6208a11960",
"title": "ONLYOFFICE",
"scripts": {
"start": "webpack-cli serve",
"start-prod": "webpack --mode production && serve dist -p 5015",
"build": "webpack --mode production",
"serve": "serve dist -p 5015",
"clean": "rm -rf dist"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-export-default-from": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.10",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^7.1.2",
"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",
"workbox-webpack-plugin": "^6.1.1"
},
"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",
"prop-types": "^15.7.2",
"rc-tree": "^2.1.4",
"re-resizable": "^6.9.0",
"react": "^17.0.1",
"react-autosize-textarea": "^7.1.0",
"react-content-loader": "^5.1.4",
"react-custom-scrollbars": "^4.2.1",
"react-device-detect": "^1.14.0",
"react-dom": "^17.0.1",
"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",
"react-resize-detector": "^5.2.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-string-format": "^0.1.0",
"react-svg": "^12.0.0",
"react-text-mask": "^5.4.3",
"react-toastify": "^6.1.0",
"react-tooltip": "^4.2.11",
"react-viewer": "^3.2.2",
"react-virtualized-auto-sizer": "^1.0.2",
"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",
"screenfull": "^5.1.0",
"styled-components": "^5.2.1",
"workbox-window": "^6.1.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 902 B

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="shortcut icon" href="favicon.ico" />
<link rel="manifest" href="manifest.json" />
<!-- Tell the browser it's a PWA -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- Tell iOS it's a PWA -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="appIcon.png" />
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i"
rel="stylesheet"
type="text/css"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style type="text/css">
body {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
font-family: "Open Sans", sans-serif;
font-size: 13px;
-webkit-font-smoothing: antialiased;
}
.temp-header-container {
align-items: center;
background-color: rgb(15, 64, 113);
display: grid;
grid-template-columns: 24px 168px 1fr 36px;
grid-template-rows: 1fr;
grid-column-gap: 16px;
width: calc(100vw - 16px);
height: 56px;
padding-right: 16px;
}
.temp-content-loader {
padding: 16px;
height: calc(100vh - 91px);
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: calc(100vh - 91px);
grid-column-gap: 8px;
}
@media (max-width: 1024px) {
.temp-content-loader {
grid-template-columns: 1fr;
}
.temp-content__article {
display: none;
}
}
.burger-loader-svg {
width: 24px;
height: 24px;
padding-left: 16px;
}
.logo-loader-svg {
width: 168px;
height: 24px;
position: relative;
cursor: pointer;
padding-left: 16px;
}
.avatar-loader-svg {
width: 36px;
height: 36px;
}
</style>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
<div id="temp-content">
<header class="temp-header-container">
<div id="burger-loader-svg" class="burger-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria0"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="24"
height="24"
style="fill: url('#fill0');"
></rect>
<defs>
<linearGradient id="fill0">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div id="logo-loader-svg" class="logo-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<rect
rx="3"
ry="3"
width="168"
height="24"
style="fill: url('#fill01');"
></rect>
<defs>
<linearGradient id="fill01">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
<div></div>
<div id="avatar-loader-svg" class="avatar-loader-svg">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria01"
preserveAspectRatio="none"
>
<circle
cx="18"
cy="18"
r="18"
width="36"
height="36"
style="fill: url('#fill02');"
></circle>
<defs>
<linearGradient id="fill02">
<stop
offset="0.599964"
stop-color="#fff"
stop-opacity="0.25"
></stop>
</linearGradient>
</defs>
</svg>
</div>
</header>
<div class="temp-content-loader">
<div class="temp-content__article">
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria1"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path1)"
style="fill: url('#fill1');"
></rect>
<defs>
<clipPath id="clip-path1">
<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>
</linearGradient>
</defs>
</svg>
</div>
<div>
<svg
role="img"
width="100%"
height="100%"
aria-labelledby="loading-aria2"
preserveAspectRatio="none"
>
<rect
x="0"
y="0"
width="100%"
height="100%"
clip-path="url(#clip-path2)"
style="fill: url('#fill2');"
></rect>
<defs>
<clipPath id="clip-path2">
<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>
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
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 CRM INIT");
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Coming soon",
"ViewWeb": "View web version",
"OpenApp": "Open your {{title}} app",
"LearnMore": "Learn more"
}

View File

@ -0,0 +1,6 @@
{
"ComingSoon": "Скоро появится",
"ViewWeb": "Просмотреть веб-версию",
"OpenApp": "Откройте {{title}}",
"LearnMore": "Узнать больше"
}

View File

@ -0,0 +1,15 @@
{
"short_name": "ASC.People",
"name": "ASC.People",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,7 @@
//import "./wdyr";
import React from "react";
import Shell from "studio/shell";
const App = () => <Shell />;
export default App;

View File

@ -0,0 +1,15 @@
import React from "react";
import ReactDOM from "react-dom";
import { MemoryRouter } from "react-router-dom";
import App from "./App";
it("renders without crashing", async () => {
const div = document.createElement("div");
ReactDOM.render(
<MemoryRouter>
<App />
</MemoryRouter>,
div
);
await new Promise((resolve) => setTimeout(resolve, 1000));
});

View File

@ -0,0 +1,67 @@
import React, { useEffect } from "react";
import { Provider as PeopleProvider, inject, observer } from "mobx-react";
import { Switch } from "react-router-dom";
import ProjectsStore from "./store/ProjectsStore";
import ErrorBoundary from "@appserver/common/components/ErrorBoundary";
import toastr from "studio/toastr";
import PrivateRoute from "@appserver/common/components/PrivateRoute";
import AppLoader from "@appserver/common/components/AppLoader";
import { updateTempContent } from "@appserver/common/utils";
import config from "../package.json";
import "./custom.scss";
import i18n from "./i18n";
import { I18nextProvider } from "react-i18next";
import Home from "./pages/Home";
const homepage = config.homepage;
const Error404 = React.lazy(() => import("studio/Error404"));
const Error404Route = (props) => (
<React.Suspense fallback={<AppLoader />}>
<ErrorBoundary>
<Error404 {...props} />
</ErrorBoundary>
</React.Suspense>
);
const ProjectsContent = (props) => {
const { isLoaded, loadBaseInfo } = props;
useEffect(() => {
loadBaseInfo()
.catch((err) => toastr.error(err))
.finally(() => {
//this.props.setIsLoaded(true);
updateTempContent();
});
}, []);
useEffect(() => {
if (isLoaded) updateTempContent();
}, [isLoaded]);
return (
<Switch>
<PrivateRoute exact path={homepage} component={Home} />
<PrivateRoute component={Error404Route} />
</Switch>
);
};
const Projects = inject(({ auth, projectsStore }) => ({
loadBaseInfo: async () => {
await projectsStore.init();
auth.setProductVersion(config.version);
},
isLoaded: auth.isLoaded && projectsStore.isLoaded,
}))(observer(ProjectsContent));
const projectsStore = new ProjectsStore();
export default (props) => (
<PeopleProvider projectsStore={projectsStore}>
<I18nextProvider i18n={i18n}>
<Projects {...props} />
</I18nextProvider>
</PeopleProvider>
);

View File

@ -0,0 +1,47 @@
import App from "./App";
import React from "react";
import ReactDOM from "react-dom";
import config from "../package.json";
import { Workbox, messageSW } from "workbox-window";
ReactDOM.render(<App />, document.getElementById("root"));
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
const wb = new Workbox(`${config.homepage}/sw.js`);
//TODO: watch https://developers.google.com/web/tools/workbox/guides/advanced-recipes and https://github.com/webmaxru/prog-web-news/blob/5ff94b45c9d317409c21c0fbb7d76e92f064471b/src/app/app-shell/app-shell.component.ts
// const showSkipWaitingPrompt = (event) => {
// let snackBarRef = this.snackBar.open(
// "A new version of the website available",
// "Reload page",
// {
// duration: 5000,
// }
// );
// // Displaying prompt
// snackBarRef.onAction().subscribe(() => {
// // Assuming the user accepted the update, set up a listener
// // that will reload the page as soon as the previously waiting
// // service worker has taken control.
// wb.addEventListener("controlling", () => {
// window.location.reload();
// });
// // This will postMessage() to the waiting service worker.
// wb.messageSkipWaiting();
// });
// };
// // Add an event listener to detect when the registered
// // service worker has installed but is waiting to activate.
// wb.addEventListener("waiting", showSkipWaitingPrompt);
wb.register()
.then((reg) => {
console.log("Successful service worker registration", reg);
})
.catch((err) => console.error("Service worker registration failed", err));
}

View File

@ -0,0 +1,27 @@
// Override default variables before the import
$font-family-base: "Open Sans", sans-serif;
html,
body {
height: 100%;
}
#root {
min-height: 100%;
position: relative;
.pageLoader {
position: absolute;
left: calc(50% - 20px);
top: 35%;
}
}
body {
margin: 0;
overflow: hidden;
}
body.loading * {
cursor: wait !important;
}

View File

@ -0,0 +1,26 @@
import authStore from "@appserver/common/store/AuthStore";
//import store from "../store/store";
//const { getCurrentProduct } = commonStore.auth.selectors;
export const setDocumentTitle = (subTitle = null) => {
// const state = store.getState();
// const { auth: commonState } = state;
const { isAuthenticated, settingsStore, product: currentModule } = authStore;
const { organizationName } = settingsStore;
let title;
if (subTitle) {
if (isAuthenticated && currentModule) {
title = subTitle + " - " + currentModule.title;
} else {
title = subTitle + " - " + organizationName;
}
} else if (currentModule && organizationName) {
title = currentModule.title + " - " + organizationName;
} else {
title = organizationName;
}
document.title = title;
};

View File

@ -0,0 +1,58 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import config from "../package.json";
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"];
i18n
/*
load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
learn more: https://github.com/i18next/i18next-http-backend
*/
.use(Backend)
/*
detect user language
learn more: https://github.com/i18next/i18next-browser-languageDetector
*/
//.use(LanguageDetector)
/*
pass the i18n instance to react-i18next.
*/
.use(initReactI18next)
/*
init i18next
for all options read: https://www.i18next.com/overview/configuration-options
*/
.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: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
},
react: {
useSuspense: false,
},
});
export default i18n;

View File

@ -0,0 +1 @@
import("./bootstrap");

View File

@ -0,0 +1,243 @@
import React, { useEffect } from "react";
import { ReactSVG } from "react-svg";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import Badge from "@appserver/components/badge";
import Box from "@appserver/components/box";
import EmptyScreenContainer from "@appserver/components/empty-screen-container";
import ExternalLinkIcon from "../../../../../../public/images/external.link.react.svg";
import Loaders from "@appserver/common/components/Loaders";
import toastr from "studio/toastr";
import PageLayout from "@appserver/common/components/PageLayout";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { isMobile, isIOS } from "react-device-detect";
import { setDocumentTitle } from "../../helpers/utils";
import { inject } from "mobx-react";
import i18n from "../../i18n";
import { I18nextProvider } from "react-i18next";
const commonStyles = `
.link-box {
margin: 8px 0;
.view-web-link {
margin: 8px;
:focus {
outline: 0;
}
}
}
`;
const ComingSoonPage = styled.div`
padding: ${isMobile ? "62px 0 0 0" : "0"};
width: 336px;
margin: 0 auto;
.module-logo-icon {
float: left;
margin-top: 8px;
margin-right: 16px;
}
.module-title {
margin-top: 14px;
margin-bottom: 14px;
}
.module-info {
margin-bottom: 18px;
.learn-more-link {
white-space: nowrap;
}
}
.coming-soon-badge {
margin-bottom: 26px;
}
${commonStyles}
`;
const StyledDesktopContainer = styled(EmptyScreenContainer)`
img {
width: 150px;
height: 150px;
}
span:first-of-type {
font-size: 24px;
}
span {
font-size: 14px;
> p {
font-size: 14px;
.learn-more-link {
white-space: nowrap;
}
}
}
${commonStyles}
.view-web-link {
font-size: 14px;
}
.coming-soon-badge > div > p {
font-size: 13px;
}
`;
const ExternalLink = ({ label, href }) => (
<Box className="link-box">
<ExternalLinkIcon color="#333333" size={isMobile ? "small" : "medium"} />
<Link
as="a"
href={href}
target="_blank"
className="view-web-link"
color="#555F65"
isBold
isHovered
>
{label}
</Link>
</Box>
);
const Body = ({ modules, match, isLoaded, setCurrentProductId }) => {
const { t } = useTranslation("ComingSoon");
const { error } = match.params;
const { pathname, protocol, hostname } = window.location;
const currentModule = modules.find((m) => m.link === pathname);
const {
id,
title,
description,
imageUrl,
link,
originUrl,
helpUrl,
} = currentModule;
const url = originUrl ? originUrl : link;
const webLink = protocol + "//" + hostname + url;
const appLink = isIOS
? id === "2A923037-8B2D-487b-9A22-5AC0918ACF3F"
? "message:"
: id === "32D24CB5-7ECE-4606-9C94-19216BA42086"
? "calshow:"
: false
: false;
setDocumentTitle();
useEffect(() => {
setCurrentProductId(id);
}, [id, setCurrentProductId]);
useEffect(() => error && toastr.error(error), [error]);
const appButtons = (
<>
<Badge
label={t("ComingSoon")}
maxWidth="150px"
borderRadius="2px"
className="coming-soon-badge"
/>
<ExternalLink label={t("ViewWeb")} href={webLink} />
{appLink && (
<ExternalLink
label={t("OpenApp", {
title: title,
})}
href={appLink}
/>
)}
</>
);
const moduleDescription = (
<Text className="module-info">
{description}{" "}
{helpUrl && (
<Link
as="a"
href={helpUrl}
target="_blank"
className="learn-more-link"
color="#555F65"
isBold
isHovered
>
{t("LearnMore")}...
</Link>
)}
</Text>
);
return !isLoaded ? (
<></>
) : isMobile ? (
<ComingSoonPage>
<ReactSVG
className="module-logo-icon"
loading={() => (
<Loaders.Rectangle
width="100"
height="14"
backgroundColor="#fff"
foregroundColor="#fff"
backgroundOpacity={0.25}
foregroundOpacity={0.2}
/>
)}
src={imageUrl}
/>
<Box displayProp="flex" flexDirection="column" widthProp="220px">
<Text fontWeight="600" fontSize="19px" className="module-title">
{title}
</Text>
{moduleDescription}
{appButtons}
</Box>
</ComingSoonPage>
) : (
<StyledDesktopContainer
imageSrc={imageUrl}
imageAlt={title}
headerText={title}
descriptionText={moduleDescription}
buttons={appButtons}
/>
);
};
const ComingSoon = (props) => {
return (
<PageLayout>
<PageLayout.SectionBody>
<Body {...props} />
</PageLayout.SectionBody>
</PageLayout>
);
};
ComingSoon.propTypes = {
modules: PropTypes.array,
isLoaded: PropTypes.bool,
};
const ComingSoonWrapper = inject(({ auth }) => ({
modules: auth.moduleStore.modules,
isLoaded: auth.isLoaded,
setCurrentProductId: auth.settingsStore.setCurrentProductId,
}))(withRouter(ComingSoon));
export default (props) => (
<I18nextProvider i18n={i18n}>
<ComingSoonWrapper {...props} />
</I18nextProvider>
);

View File

@ -0,0 +1,39 @@
import { action, makeObservable, observable } from "mobx";
import config from "../../package.json";
import store from "studio/store";
const { auth: authStore } = store;
class ProjectsStore {
isLoading = false;
isLoaded = false;
isInit = false;
constructor() {
makeObservable(this, {
isLoading: observable,
isLoaded: observable,
setIsLoading: action,
setIsLoaded: action,
init: action,
});
}
init = async () => {
if (this.isInit) return;
this.isInit = true;
authStore.settingsStore.setModuleInfo(config.homepage, config.id);
this.setIsLoaded(true);
};
setIsLoading = (loading) => {
this.isLoading = loading;
};
setIsLoaded = (isLoaded) => {
this.isLoaded = isLoaded;
};
}
export default ProjectsStore;

View File

@ -0,0 +1,116 @@
import {
precacheAndRoute,
cleanupOutdatedCaches,
//createHandlerBoundToURL,
} from "workbox-precaching";
import { setCacheNameDetails } from "workbox-core";
import { clientsClaim } from "workbox-core";
import { /*NavigationRoute,*/ registerRoute } from "workbox-routing";
import { googleFontsCache, imageCache, offlineFallback } from "workbox-recipes";
import {
//CacheFirst,
//NetworkFirst,
StaleWhileRevalidate,
} from "workbox-strategies";
import { ExpirationPlugin } from "workbox-expiration";
//import { BroadcastUpdatePlugin } from "workbox-broadcast-update";
// SETTINGS
// Claiming control to start runtime caching asap
clientsClaim();
// Use to update the app after user triggered refresh (without prompt)
//self.skipWaiting();
// PRECACHING
// Setting custom cache name
setCacheNameDetails({ precache: "wb6-precache", runtime: "wb6-runtime" });
// We inject manifest here using "workbox-build" in workbox-inject.js
precacheAndRoute(self.__WB_MANIFEST);
// Remove cache from the previous WB versions
cleanupOutdatedCaches();
// NAVIGATION ROUTING
// This assumes /index.html has been precached.
// const navHandler = createHandlerBoundToURL("/index.html");
// const navigationRoute = new NavigationRoute(navHandler, {
// denylist: [new RegExp("/out-of-spa/")], // Also might be specified explicitly via allowlist
// });
// registerRoute(navigationRoute);
// STATIC RESOURCES
googleFontsCache({ cachePrefix: "wb6-gfonts" });
// API ROUTING
registerRoute(
// Cache API Request
new RegExp("/api/2.0/(modules|people/@self|(.*)/info(.json|$))"),
new StaleWhileRevalidate({
cacheName: "wb6-api",
plugins: [
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 30 * 60, // 30 Minutes
}),
],
})
);
// TRANSLATIONS
registerRoute(
({ url }) => url.pathname.indexOf("/locales/") !== -1,
// Use cache but update in the background.
new StaleWhileRevalidate({
// Use a custom cache name.
cacheName: "wb6-content-translation",
})
);
// CONTENT
imageCache({ cacheName: "wb6-content-images", maxEntries: 60 });
// APP SHELL UPDATE FLOW
addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
// FALLBACK
offlineFallback({
pageFallback: "/static/offline/offline.html",
imageFallback: "/static/offline/offline.svg",
fontFallback: false,
});
// ALL OTHER EVENTS
// Receive push and show a notification
self.addEventListener("push", function (event) {
console.log("[Service Worker]: Received push event", event);
var notificationData = {};
if (event.data.json()) {
notificationData = event.data.json();
} else {
notificationData = {
title: "Something Has Happened",
message: "Something you might want to check out",
icon: "/assets/img/pwa-logo.png",
};
}
self.registration.showNotification(notificationData.title, notificationData);
});

View File

@ -0,0 +1,12 @@
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = require("@welldone-software/why-did-you-render");
whyDidYouRender(React, {
trackAllPureComponents: true,
collapseGroups: true,
//trackExtraHooks: [
// [ReactRedux, 'useSelector']
//]
});
}

View File

@ -0,0 +1,204 @@
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 TerserPlugin = require("terser-webpack-plugin");
const { InjectManifest } = require("workbox-webpack-plugin");
//const CompressionPlugin = require("compression-webpack-plugin");
const path = require("path");
const pkg = require("./package.json");
const deps = pkg.dependencies;
const homepage = pkg.homepage;
const title = pkg.title;
var config = {
mode: "development",
entry: "./src/index",
devServer: {
publicPath: homepage,
contentBase: [path.join(__dirname, "dist")],
contentBasePublicPath: homepage,
port: 5015,
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",
chunkFilename: "static/js/[id].[contenthash].js",
//assetModuleFilename: "static/images/[hash][ext][query]",
path: path.resolve(process.cwd(), "dist"),
filename: "static/js/[name].[contenthash].bundle.js",
},
resolve: {
extensions: [".jsx", ".js", ".json"],
fallback: {
crypto: false,
},
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|ico)$/i,
type: "asset/resource",
generator: {
filename: "static/images/[hash][ext][query]",
},
},
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.react.svg$/,
use: [
{
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
},
},
],
},
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: [
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-default-from",
],
},
},
"source-map-loader",
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: "projects",
filename: "remoteEntry.js",
remotes: {
studio: "studio@/remoteEntry.js",
},
exposes: {
"./app": "./src/Projects.jsx",
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
publicPath: homepage,
title: title,
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";
config.optimization = {
splitChunks: { chunks: "all" },
minimize: true,
minimizer: [new TerserPlugin()],
};
config.plugins.push(
new InjectManifest({
mode: "production", //"development",
swSrc: "./src/sw-template.js", // this is your sw template file
swDest: "sw.js", // this will be created in the build step
exclude: [/\.map$/, /manifest$/, /service-worker\.js$/],
})
);
// config.plugins.push(
// new CompressionPlugin({
// filename: "[path][base].gz[query]",
// algorithm: "gzip",
// test: /\.js(\?.*)?$/i,
// threshold: 10240,
// minRatio: 0.8,
// deleteOriginalAssets: true,
// })
// );
} else {
config.devtool = "cheap-module-source-map";
}
return config;
};

View File

@ -122,8 +122,8 @@ namespace ASC.Projects.Configuration
{
//MasterPageFile = String.Concat(PathProvider.BaseVirtualPath, "Masters/BasicTemplate.Master"),
DisabledIconFileName = "product_disabled_logo.png",
IconFileName = "product_logo.png",
LargeIconFileName = "product_logolarge.svg",
IconFileName = "images/projects.menu.svg",
LargeIconFileName = "images/projects.svg",
//SubscriptionManager = new ProductSubscriptionManager(),
DefaultSortOrder = 20,
//SpaceUsageStatManager = new ProjectsSpaceUsageStatManager(),

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 16 16">
<title>calendar</title>
<g>
<path d="M10 10v-1.5c0.459-0.185 0.815-0.541 0.996-0.988l0.004-0.012v-3c0-1-1-2.5-2-2.5h-2c-1 0-2 1.5-2 2.5v3c0.185 0.459 0.541 0.815 0.988 0.996l0.012 0.004v1.5s-4 2-4 3v1h12v-1c0-1-4-3-4-3z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 340 B

View File

@ -11,7 +11,7 @@ import Layout from "./components/Layout";
import ScrollToTop from "./components/Layout/ScrollToTop";
import history from "@appserver/common/history";
import toastr from "studio/toastr";
import { updateTempContent } from "@appserver/common/utils";
import { combineUrl, updateTempContent } from "@appserver/common/utils";
import { Provider as MobxProvider } from "mobx-react";
import ThemeProvider from "@appserver/components/theme-provider";
import { Base } from "@appserver/components/themes";
@ -22,9 +22,57 @@ import { I18nextProvider } from "react-i18next";
import i18n from "./i18n";
import AppLoader from "@appserver/common/components/AppLoader";
import System from "./components/System";
import { AppServerConfig } from "@appserver/common/constants";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
const PROXY_HOMEPAGE_URL = combineUrl(proxyURL, homepage);
const HOME_URLS = [
combineUrl(PROXY_HOMEPAGE_URL, "/") || "/",
combineUrl(PROXY_HOMEPAGE_URL, "/error=:error"),
];
const WIZARD_URL = combineUrl(PROXY_HOMEPAGE_URL, "/wizard");
const ABOUT_URL = combineUrl(PROXY_HOMEPAGE_URL, "/about");
const LOGIN_URLS = [
combineUrl(PROXY_HOMEPAGE_URL, "/login"),
combineUrl(PROXY_HOMEPAGE_URL, "/login/error=:error"),
combineUrl(PROXY_HOMEPAGE_URL, "/login/confirmed-email=:confirmedEmail"),
];
const CONFIRM_URL = combineUrl(PROXY_HOMEPAGE_URL, "/confirm");
const COMING_SOON_URLS = [
combineUrl(PROXY_HOMEPAGE_URL, "/coming-soon"),
//combineUrl(PROXY_HOMEPAGE_URL, "/products/mail"),
//combineUrl(PROXY_HOMEPAGE_URL, "/products/projects"),
//combineUrl(PROXY_HOMEPAGE_URL, "/products/crm"),
//combineUrl(PROXY_HOMEPAGE_URL, "/products/calendar"),
//combineUrl(PROXY_HOMEPAGE_URL, "/products/talk/"),
];
const THIRD_PARTY_RESPONSE_URL = combineUrl(
PROXY_HOMEPAGE_URL,
"/thirdparty/:provider"
);
const PAYMENTS_URL = combineUrl(PROXY_HOMEPAGE_URL, "/payments");
const SETTINGS_URL = combineUrl(PROXY_HOMEPAGE_URL, "/settings");
const ERROR_401_URL = combineUrl(PROXY_HOMEPAGE_URL, "/error401");
if (!window.AppServer) {
window.AppServer = {};
}
window.AppServer.studio = {
HOME_URLS,
WIZARD_URL,
ABOUT_URL,
LOGIN_URLS,
CONFIRM_URL,
COMING_SOON_URLS,
THIRD_PARTY_RESPONSE_URL,
PAYMENTS_URL,
SETTINGS_URL,
ERROR_401_URL,
};
const Payments = React.lazy(() => import("./components/pages/Payments"));
const Error404 = React.lazy(() => import("studio/Error404"));
const Error401 = React.lazy(() => import("studio/Error401"));
@ -126,40 +174,6 @@ const ThirdPartyResponseRoute = (props) => (
);
const Shell = ({ items = [], page = "home", ...rest }) => {
// useEffect(() => {
// //utils.removeTempContent();
// const {
// getPortalSettings,
// getUser,
// getModules,
// setIsLoaded,
// getIsAuthenticated,
// } = rest;
// getIsAuthenticated()
// .then((isAuthenticated) => {
// if (isAuthenticated) updateTempContent(isAuthenticated);
// const requests = [];
// if (!isAuthenticated) {
// requests.push(getPortalSettings());
// } else if (
// !window.location.pathname.includes("confirm/EmailActivation")
// ) {
// requests.push(getUser());
// requests.push(getPortalSettings());
// requests.push(getModules());
// }
// return Promise.all(requests).finally(() => {
// updateTempContent();
// setIsLoaded();
// });
// })
// .catch((err) => toastr.error(err.message));
// }, []);
const { isLoaded, loadBaseInfo, isThirdPartyResponse, modules } = rest;
useEffect(() => {
@ -176,29 +190,45 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
useEffect(() => {
console.log("Current page ", page);
// setModuleInfo(page, "e67be73d-f9ae-4ce1-8fec-1880cb518cb4");
}, [page]);
const pathname = window.location.pathname.toLowerCase();
const isEditor = pathname.indexOf("doceditor") !== -1;
const dynamicRoutes = modules
.filter((m) => m.ready)
.map((m) => {
const system = {
url: `${window.location.origin}${m.link}remoteEntry.js`,
scope: m.appName,
module: "./app",
};
return (
<PrivateRoute
key={m.id}
path={`${homepage}${m.link}`}
component={System}
system={system}
/>
);
});
if (!window.AppServer.studio) {
window.AppServer.studio = {};
}
window.AppServer.studio.modules = {};
const dynamicRoutes = modules.map((m) => {
const appURL = m.link;
const remoteEntryURL = combineUrl(
window.location.origin,
appURL,
"remoteEntry.js"
);
const system = {
url: remoteEntryURL,
scope: m.appName,
module: "./app",
};
window.AppServer.studio.modules[m.appName] = {
appURL,
remoteEntryURL,
};
return (
<PrivateRoute
key={m.id}
path={appURL}
component={System}
system={system}
/>
);
});
return (
<Layout>
@ -208,56 +238,27 @@ const Shell = ({ items = [], page = "home", ...rest }) => {
<ScrollToTop />
<Main>
<Switch>
<PrivateRoute exact path={HOME_URLS} component={HomeRoute} />
<PublicRoute exact path={WIZARD_URL} component={WizardRoute} />
<PrivateRoute path={ABOUT_URL} component={AboutRoute} />
<PublicRoute exact path={LOGIN_URLS} component={LoginRoute} />
<Route path={CONFIRM_URL} component={ConfirmRoute} />
<PrivateRoute
exact
path={[`${homepage}/`, `${homepage}/error=:error`]}
component={HomeRoute}
/>
<PublicRoute
exact
path={`${homepage}/wizard`}
component={WizardRoute}
/>
<PrivateRoute path={`${homepage}/about`} component={AboutRoute} />
<PublicRoute
exact
path={[
`${homepage}/login`,
`${homepage}/login/error=:error`,
`${homepage}/login/confirmed-email=:confirmedEmail`,
]}
component={LoginRoute}
/>
<Route path={`${homepage}/confirm`} component={ConfirmRoute} />
<PrivateRoute
path={[
`${homepage}/coming-soon`,
`${homepage}/products/mail`,
`${homepage}/products/projects`,
`${homepage}/products/crm`,
`${homepage}/products/calendar`,
`${homepage}/products/talk/`,
]}
path={COMING_SOON_URLS}
component={ComingSoonRoute}
/>
<PrivateRoute
path={`${homepage}/thirdparty/:provider`}
path={THIRD_PARTY_RESPONSE_URL}
component={ThirdPartyResponseRoute}
/>
<PrivateRoute
path={`${homepage}/payments`}
component={PaymentsRoute}
/>
<PrivateRoute path={PAYMENTS_URL} component={PaymentsRoute} />
<PrivateRoute
restricted
path={`${homepage}/settings`}
path={SETTINGS_URL}
component={SettingsRoute}
/>
{dynamicRoutes}
<PrivateRoute
path={`${homepage}/error401`}
component={Error401Route}
/>
<PrivateRoute path={ERROR_401_URL} component={Error401Route} />
<PrivateRoute component={Error404Route} />
</Switch>
</Main>

View File

@ -3,12 +3,23 @@ import PropTypes from "prop-types";
import styled from "styled-components";
import NavItem from "./nav-item";
import ProfileActions from "./profile-actions";
//import history from "@appserver/common/history";
import { useTranslation } from "react-i18next";
import { tablet } from "@appserver/components/utils/device";
import { combineUrl } from "@appserver/common/utils";
import { inject, observer } from "mobx-react";
import { withRouter } from "react-router";
import { AppServerConfig } from "@appserver/common/constants";
import config from "../../../../package.json";
const { proxyURL } = AppServerConfig;
const homepage = config.homepage;
const PROXY_HOMEPAGE_URL = combineUrl(proxyURL, homepage);
const ABOUT_URL = combineUrl(PROXY_HOMEPAGE_URL, "/about");
const PROFILE_URL = combineUrl(
PROXY_HOMEPAGE_URL,
"/products/people/view/@self"
);
const StyledNav = styled.nav`
display: flex;
@ -41,10 +52,10 @@ const StyledNav = styled.nav`
const HeaderNav = ({ history, modules, user, logout, isAuthenticated }) => {
const { t } = useTranslation("NavMenu");
const onProfileClick = useCallback(() => {
history.push("/products/people/view/@self");
history.push(PROFILE_URL);
}, []);
const onAboutClick = useCallback(() => history.push("/about"), []);
const onAboutClick = useCallback(() => history.push(ABOUT_URL), []);
const onLogoutClick = useCallback(() => logout && logout(), [logout]);
@ -54,13 +65,13 @@ const HeaderNav = ({ history, modules, user, logout, isAuthenticated }) => {
key: "ProfileBtn",
label: t("Profile"),
onClick: onProfileClick,
url: "/products/people/view/@self",
url: PROFILE_URL,
},
{
key: "AboutBtn",
label: t("AboutCompanyTitle"),
onClick: onAboutClick,
url: "/about",
url: ABOUT_URL,
},
{
key: "LogoutBtn",

View File

@ -15,6 +15,9 @@ import Box from "@appserver/components/box";
import Text from "@appserver/components/text";
import { desktop } from "@appserver/components/utils/device";
import i18n from "../i18n";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
const { proxyURL } = AppServerConfig;
const backgroundColor = "#0F4071";
@ -115,10 +118,7 @@ const HeaderComponent = ({
const onLogoClick = () => {
history.push(defaultPage);
};
const onLinkClick = () => {
history.push("/about");
backdropClick();
};
const onBadgeClick = (e) => {
@ -139,34 +139,34 @@ const HeaderComponent = ({
};
//TODO: getCustomModules
const getCustomModules = () => {
if (!isAdmin) {
return [];
} // Temporarily hiding the settings module
// const getCustomModules = () => {
// if (!isAdmin) {
// return [];
// } // Temporarily hiding the settings module
return (
<>
<NavItem
separator={true}
key={"nav-modules-separator"}
data-id={"nav-modules-separator"}
/>
<NavItem
separator={false}
key={"settings"}
data-id={"settings"}
data-link="/settings"
opened={isNavOpened}
active={"settings" == currentProductId}
iconName={"SettingsIcon"}
onClick={onItemClick}
url="/settings"
>
{t("Settings")}
</NavItem>
</>
);
};
// return (
// <>
// <NavItem
// separator={true}
// key={"nav-modules-separator"}
// data-id={"nav-modules-separator"}
// />
// <NavItem
// separator={false}
// key={"settings"}
// data-id={"settings"}
// data-link="/settings"
// opened={isNavOpened}
// active={"settings" == currentProductId}
// iconName={"SettingsIcon"}
// onClick={onItemClick}
// url="/settings"
// >
// {t("Settings")}
// </NavItem>
// </>
// );
// };
const isMainPage = pathname === "/";
return (
@ -206,8 +206,7 @@ const HeaderComponent = ({
{mainModules.map(
({
id,
separator,
iconName,
separator, //iconName,
iconUrl,
notifications,
link,
@ -221,7 +220,7 @@ const HeaderComponent = ({
data-link={link}
opened={isNavOpened}
active={isMainPage ? false : id == currentProductId}
iconName={iconName}
//iconName={iconName}
iconUrl={iconUrl}
badgeNumber={notifications}
onClick={onItemClick}
@ -248,7 +247,10 @@ const HeaderComponent = ({
-{" "}
</Text>
<StyledLink>
<LinkWithoutRedirect to="/about" className="nav-menu-header_link">
<LinkWithoutRedirect
to={combineUrl(proxyURL, "/about")}
className="nav-menu-header_link"
>
{t("AboutShort")}
</LinkWithoutRedirect>
</StyledLink>
@ -293,13 +295,13 @@ export default inject(({ auth }) => {
const { logoUrl, defaultPage, currentProductId } = settingsStore;
const { totalNotifications } = moduleStore;
const mainModules = availableModules.filter((m) => !m.isolateMode);
//TODO: restore when chat will complete -> const mainModules = availableModules.filter((m) => !m.isolateMode);
return {
isAdmin,
defaultPage,
logoUrl,
mainModules,
mainModules: availableModules,
totalNotifications,
isLoaded,
version,

View File

@ -17,8 +17,11 @@ import PasswordInput from "@appserver/components/password-input";
import toastr from "@appserver/components/toast/toastr";
import Loader from "@appserver/components/loader";
import PageLayout from "@appserver/common/components/PageLayout";
import { EmployeeActivationStatus } from "@appserver/common/constants";
import { createPasswordHash } from "@appserver/common/utils";
import {
AppServerConfig,
EmployeeActivationStatus,
} from "@appserver/common/constants";
import { combineUrl, createPasswordHash } from "@appserver/common/utils";
const inputWidth = "400px";
@ -192,7 +195,7 @@ class Confirm extends React.PureComponent {
axios.all(requests).catch((e) => {
console.error("get settings error", e);
history.push(`/login/error=${e}`);
history.push(combineUrl(AppServerConfig.proxyURL, `/login/error=${e}`));
});
window.addEventListener("keydown", this.onKeyPress);

View File

@ -14,7 +14,8 @@ import toastr from "@appserver/components/toast/toastr";
import Loader from "@appserver/components/loader";
import EmailInput from "@appserver/components/email-input";
import PageLayout from "@appserver/common/components/PageLayout";
import { createPasswordHash } from "@appserver/common/utils";
import { combineUrl, createPasswordHash } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
const inputWidth = "400px";
@ -185,7 +186,7 @@ class Confirm extends React.PureComponent {
axios.all(requests).catch((e) => {
console.error("get settings error", e);
history.push(`/login/error=${e}`);
history.push(combineUrl(AppServerConfig.proxyURL, `/login/error=${e}`));
});
window.addEventListener("keydown", this.onKeyPress);

View File

@ -12,6 +12,8 @@ import {
getTKeyByKey,
checkPropertyByLink,
} from "../../../utils";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
const HeaderContainer = styled.div`
position: relative;
@ -86,7 +88,7 @@ class SectionHeaderContent extends React.Component {
let newArrayOfParams = this.getArrayOfParams();
newArrayOfParams.splice(-1, 1);
const newPath = "/settings/" + newArrayOfParams.join("/");
this.props.history.push(newPath);
this.props.history.push(combineUrl(AppServerConfig.proxyURL, newPath));
};
getArrayOfParams = () => {

View File

@ -8,8 +8,9 @@ import Link from "@appserver/components/link";
import ArrowRightIcon from "../../../../../../public/images/arrow.right.react.svg";
import { setDocumentTitle } from "../../../../../helpers/utils";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { showLoader, hideLoader } from "@appserver/common/utils";
import { showLoader, hideLoader, combineUrl } from "@appserver/common/utils";
import { inject, observer } from "mobx-react";
import { AppServerConfig } from "@appserver/common/constants";
const mapCulturesToArray = (cultures, t) => {
return cultures.map((culture) => {
@ -213,7 +214,10 @@ class Customization extends React.Component {
className="inherit-title-link header"
onClick={this.onClickLink}
truncate={true}
href="/settings/common/customization/language-and-time-zone"
href={combineUrl(
AppServerConfig.proxyURL,
"/settings/common/customization/language-and-time-zone"
)}
>
{t("StudioTimeLanguageSettings")}
</Link>
@ -234,7 +238,10 @@ class Customization extends React.Component {
truncate={true}
className="inherit-title-link header"
onClick={this.onClickLink}
href="/settings/common/customization/custom-titles"
href={combineUrl(
AppServerConfig.proxyURL,
"/settings/common/customization/custom-titles"
)}
>
{t("CustomTitles")}
</Link>

View File

@ -10,6 +10,8 @@ import AdminsSettings from "./sub-components/admins";
import { setDocumentTitle } from "../../../../../helpers/utils";
import { inject } from "mobx-react";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
const MainContainer = styled.div`
padding-bottom: 16px;
@ -51,13 +53,23 @@ class PureAccessRights extends Component {
switch (page.key) {
case "0":
history.push("/settings/security/accessrights/owner");
history.push(
combineUrl(
AppServerConfig.proxyURL,
"/settings/security/accessrights/owner"
)
);
break;
case "1":
history.push("/settings/security/accessrights/admins");
history.push(
combineUrl(
AppServerConfig.proxyURL,
"/settings/security/accessrights/admins"
)
);
break;
// case "2":
// history.push("/settings/security/accessrights/modules");
// history.push(combineUrl(AppServerConfig.proxyURL, "/settings/security/accessrights/modules"));
// break;
default:
break;

View File

@ -8,7 +8,7 @@ import axios from "axios";
import PageLayout from "@appserver/common/components/PageLayout";
import ErrorContainer from "@appserver/common/components/ErrorContainer";
import history from "@appserver/common/history";
import { createPasswordHash } from "@appserver/common/utils";
import { combineUrl, createPasswordHash } from "@appserver/common/utils";
import Loader from "@appserver/components/loader";
import { tablet } from "@appserver/components/utils/device";
import { EmailSettings } from "@appserver/components/utils/email";
@ -21,6 +21,7 @@ import ModalContainer from "./sub-components/modal-dialog-container";
import { setDocumentTitle } from "../../../helpers/utils";
import { inject, observer } from "mobx-react";
import { AppServerConfig } from "@appserver/common/constants";
const emailSettings = new EmailSettings();
emailSettings.allowDomainPunycode = true;
@ -95,7 +96,7 @@ class Body extends Component {
window.addEventListener("keyup", this.onKeyPressHandler);
if (!wizardToken) {
history.push("/");
history.push(combineUrl(AppServerConfig.proxyURL, "/"));
} else {
await axios
.all([
@ -236,7 +237,9 @@ class Body extends Component {
setWizardComplete();
getPortalSettings();
})
.then(() => history.push("/login"))
.then(() =>
history.push(combineUrl(AppServerConfig.proxyURL, "/login"))
)
.catch((e) =>
this.setState({
errorLoading: true,

View File

@ -5,8 +5,9 @@ import { withRouter } from "react-router";
import Loader from "@appserver/components/loader";
import PageLayout from "@appserver/common/components/PageLayout";
import { checkConfirmLink } from "@appserver/common/api/user"; //TODO: Move AuthStore
import { getObjectByLocation } from "@appserver/common/utils";
import { combineUrl, getObjectByLocation } from "@appserver/common/utils";
import { inject, observer } from "mobx-react";
import { AppServerConfig } from "@appserver/common/constants";
class ConfirmRoute extends React.Component {
constructor(props) {
@ -55,18 +56,26 @@ class ConfirmRoute extends React.Component {
});
break;
case ValidationResult.Invalid:
history.push(`${path}/error=Invalid link`);
history.push(
combineUrl(AppServerConfig.proxyURL, path, "/error=Invalid link")
);
break;
case ValidationResult.Expired:
history.push(`${path}/error=Expired link`);
history.push(
combineUrl(AppServerConfig.proxyURL, path, "/error=Expired link")
);
break;
default:
history.push(`${path}/error=Unknown error`);
history.push(
combineUrl(AppServerConfig.proxyURL, path, "/error=Unknown error")
);
break;
}
})
.catch((error) => {
history.push(`${path}/error=${error}`);
history.push(
combineUrl(AppServerConfig.proxyURL, path, `/error=${error}`)
);
});
}

122
yarn.lock
View File

@ -3504,9 +3504,9 @@
loader-utils "^2.0.0"
"@tanem/svg-injector@^9.0.1":
version "9.0.3"
resolved "https://registry.yarnpkg.com/@tanem/svg-injector/-/svg-injector-9.0.3.tgz#0aaa6664ce5ab9322b7e6c7ad2632cd991f5345f"
integrity sha512-iRNGHmtL0k/gp0sAPTPYCadMnewZQpXIuFL+fGAAKpF1MkmROMCu7ZFR4uV9SqWYT/jelCcmSAFGc5OPqJc07A==
version "9.0.4"
resolved "https://registry.yarnpkg.com/@tanem/svg-injector/-/svg-injector-9.0.4.tgz#0b0382f1569ca464da98f3a9be02fd0395e41bf4"
integrity sha512-Urcvygj12r/xXwFfidmfGGepwkENyeLyjmQbAU19mF+sHgOL7dxd2pBC7iXgiaJo+4yYMBPa/Mqx+p/gQI2U6Q==
dependencies:
"@babel/runtime" "^7.13.10"
content-type "^1.0.4"
@ -3559,9 +3559,9 @@
integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==
"@types/babel__core@^7.1.0":
version "7.1.13"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.13.tgz#bc6eea53975fdf163aff66c086522c6f293ae4cf"
integrity sha512-CC6amBNND16pTk4K3ZqKIaba6VGKAQs3gMjEY17FVd56oI/ZWt9OhS6riYiWv9s8ENbYUi7p8lgqb0QHQvUKQQ==
version "7.1.14"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.14.tgz#faaeefc4185ec71c389f4501ee5ec84b170cc402"
integrity sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
@ -3597,9 +3597,9 @@
integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==
"@types/cheerio@^0.22.22":
version "0.22.27"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.27.tgz#3a44d9b06fa40ca43599380cd0f3e2a1ceb98a57"
integrity sha512-UpmYZewEWNEE6Ya24RzAQ2X2OYwz32AaLyzYinpM8qqFGRyYufqKSvxPjjZkvS+h16bajfXl7VojrAxWzG/+mA==
version "0.22.28"
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.28.tgz#90808aabb44fec40fa2950f4c72351e3e4eb065b"
integrity sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw==
dependencies:
"@types/node" "*"
@ -5839,9 +5839,9 @@ can-use-dom@^0.1.0:
integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=
caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001181:
version "1.0.30001202"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001202.tgz#4cb3bd5e8a808e8cd89e4e66c549989bc8137201"
integrity sha512-ZcijQNqrcF8JNLjzvEiXqX4JUYxoZa7Pvcsd9UD8Kz4TvhTonOSNRsK+qtvpVL4l6+T1Rh4LFtLfnNWg6BGWCQ==
version "1.0.30001203"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001203.tgz#a7a34df21a387d9deffcd56c000b8cf5ab540580"
integrity sha512-/I9tvnzU/PHMH7wBPrfDMSuecDeUKerjCPX7D0xBbaJZPxoT9m+yYxt0zCTkcijCkjTdim3H56Zm0i5Adxch4w==
capture-exit@^2.0.0:
version "2.0.0"
@ -6348,7 +6348,7 @@ compression@^1.7.4:
safe-buffer "5.1.2"
vary "~1.1.2"
compute-scroll-into-view@^1.0.16:
compute-scroll-into-view@^1.0.17:
version "1.0.17"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
@ -7160,10 +7160,10 @@ detect-newline@^2.1.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
detect-node-es@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.0.0.tgz#c0318b9e539a5256ca780dd9575c9345af05b8ed"
integrity sha512-S4AHriUkTX9FoFvL4G8hXDcx6t3gp2HpfCza3Q0v6S78gul2hKWifLQbeW+ZF89+hSm2ZIc/uF3J97ZgytgTRg==
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detect-node@^2.0.4:
version "2.0.5"
@ -7429,12 +7429,12 @@ dotenv@^8.0.0:
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
downshift@^6.0.6:
version "6.1.0"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.0.tgz#f008063d9b63935910d9db12ead07979ab51ce66"
integrity sha512-MnEJERij+1pTVAsOPsH3q9MJGNIZuu2sT90uxOCEOZYH6sEzkVGtUcTBVDRQkE8y96zpB7uEbRn24aE9VpHnZg==
version "6.1.1"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.1.tgz#3c8f5a64cc678e1b45a87b80647ea5351af13e5e"
integrity sha512-ch8Sh/j7gVqQd7Kcv3A5TkGfldmxmlQrRPZJYWEhzh24+h7WA4vXssuhcGNJrD8YPJlZYQGHcaX8BNhS0IcOvg==
dependencies:
"@babel/runtime" "^7.12.5"
compute-scroll-into-view "^1.0.16"
compute-scroll-into-view "^1.0.17"
prop-types "^15.7.2"
react-is "^17.0.1"
@ -7479,9 +7479,9 @@ ejs@^3.1.2:
jake "^10.6.1"
electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.649:
version "1.3.690"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.690.tgz#54df63ec42fba6b8e9e05fe4be52caeeedb6e634"
integrity sha512-zPbaSv1c8LUKqQ+scNxJKv01RYFkVVF1xli+b+3Ty8ONujHjAMg+t/COmdZqrtnS1gT+g4hbSodHillymt1Lww==
version "1.3.693"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.693.tgz#5089c506a925c31f93fcb173a003a22e341115dd"
integrity sha512-vUdsE8yyeu30RecppQtI+XTz2++LWLVEIYmzeCaCRLSdtKZ2eXqdJcrs85KwLiPOPVc6PELgWyXBsfqIvzGZag==
element-resize-detector@^1.2.1:
version "1.2.2"
@ -7982,9 +7982,9 @@ events@^3.0.0, events@^3.2.0:
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
eventsource@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
version "1.1.0"
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf"
integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==
dependencies:
original "^1.0.0"
@ -9202,10 +9202,10 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
highlight.js@^10.1.1, highlight.js@~10.6.0:
version "10.6.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.6.0.tgz#0073aa71d566906965ba6e1b7be7b2682f5e18b6"
integrity sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==
highlight.js@^10.1.1, highlight.js@~10.7.0:
version "10.7.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955"
integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA==
highlight.js@~9.13.0:
version "9.13.1"
@ -9245,10 +9245,10 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.7.1:
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
hosted-git-info@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.0.tgz#9f06639a90beff66cacae6e77f8387b431d61ddc"
integrity sha512-fqhGdjk4av7mT9fU/B01dUtZ+WZSc/XEXMoLXDVZukiQRXxeHSSz3AqbeWRJHtF8EQYHlAgB1NSAHU0Cm7aqZA==
hosted-git-info@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.1.tgz#710ef5452ea429a844abc33c981056e7371edab7"
integrity sha512-eT7NrxAsppPRQEBSwKSosReE+v8OzABwEScQYk5d4uxaEPlzxTIku7LINXtBGalthkLhJnq5lBI89PfK43zAKg==
dependencies:
lru-cache "^6.0.0"
@ -11369,12 +11369,12 @@ lower-case@^2.0.2:
tslib "^2.0.3"
lowlight@^1.14.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.19.0.tgz#b8544199cafcf10c5731b21c7458c358f79a2a97"
integrity sha512-NIskvQ1d1ovKyUytkMpT8+8Bhq3Ub54os1Xp4RAC9uNbXH1YVRf5NERq7JNzapEe5BzUc1Cj4F0I+eLBBFj6hA==
version "1.20.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
dependencies:
fault "^1.0.0"
highlight.js "~10.6.0"
highlight.js "~10.7.0"
lowlight@~1.11.0:
version "1.11.0"
@ -12225,13 +12225,13 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-
validate-npm-package-license "^3.0.1"
normalize-package-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.1.tgz#98dc56dfe6755d99b1c53f046e1e3d2dde55a1c7"
integrity sha512-D/ttLdxo71msR4FF3VgSwK4blHfE3/vGByz1NCeE7/Dh8reQOKNJJjk5L10mLq9jxa+ZHzT1/HLgxljzbXE7Fw==
version "3.0.2"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.2.tgz#cae5c410ae2434f9a6c1baa65d5bc3b9366c8699"
integrity sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==
dependencies:
hosted-git-info "^4.0.0"
resolve "^1.17.0"
semver "^7.3.2"
hosted-git-info "^4.0.1"
resolve "^1.20.0"
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
normalize-path@^2.1.1:
@ -13453,9 +13453,11 @@ qs@6.7.0:
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.6.0, qs@^6.9.4:
version "6.9.6"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==
version "6.10.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.0.tgz#8b6519121ab291c316a3e4d49cecf6d13d8c7fe5"
integrity sha512-yjACOWijC6L/kmPZZAsVBNY2zfHSIbpdpL977quseu56/8BZ2LoF5axK2bGhbzhVKt7V9xgWTtpyLbxwIoER0Q==
dependencies:
side-channel "^1.0.4"
qs@~6.5.2:
version "6.5.2"
@ -14693,7 +14695,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0:
resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@ -14784,9 +14786,9 @@ rollup-pluginutils@^2.8.2:
estree-walker "^0.6.1"
rollup@^2.25.0:
version "2.41.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.41.4.tgz#2a674d64db4322482d440699acb060dc6dd9e65f"
integrity sha512-f9IHfMO8p2Y8OdisI7Oj3oKkPuaQ6cgSwYqAi0TDvP3w2p+oX1VejX/w28a1h8WTnrapzfO5d4Uqhww+gL0b0g==
version "2.42.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.42.1.tgz#6d675b7971e3bee510935326a0f7e556bb7d43de"
integrity sha512-/y7M2ULg06JOXmMpPzhTeQroJSchy8lX8q6qrjqil0jmLz6ejCWbQzVnWTsdmMQRhfU0QcwtiW8iZlmrGXWV4g==
optionalDependencies:
fsevents "~2.3.1"
@ -14974,7 +14976,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2:
semver@^7.3.2, semver@^7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
@ -16113,9 +16115,9 @@ terser@^4.1.2, terser@^4.6.3, terser@^4.8.0:
source-map-support "~0.5.12"
terser@^5.0.0, terser@^5.5.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2"
integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==
version "5.6.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.1.tgz#a48eeac5300c0a09b36854bf90d9c26fb201973c"
integrity sha512-yv9YLFQQ+3ZqgWCUk+pvNJwgUTdlIxUk1WTN+RnaFJe2L7ipG2csPT0ra2XRm7Cs8cxN7QXmK1rFzEwYEQkzXw==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"
@ -16768,11 +16770,11 @@ use-latest@^1.0.0:
use-isomorphic-layout-effect "^1.0.0"
use-sidecar@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"
integrity sha512-A5ggIS3/qTdxCAlcy05anO2/oqXOfpmxnpRE1Jm+fHHtCvUvNSZDGqgOSAXPriBVAcw2fMFFkh5v5KqrFFhCMA==
version "1.0.5"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==
dependencies:
detect-node-es "^1.0.0"
detect-node-es "^1.1.0"
tslib "^1.9.3"
use@^3.1.0: