feat(TEAMMSBMOB-16123): запуск приложения в режиме prod

This commit is contained in:
Онуфрийчук Егор
2025-07-15 18:48:18 +03:00
parent c9bcad96eb
commit 4cbca1a3c1
59 changed files with 2444 additions and 25204 deletions
+8
View File
@@ -0,0 +1,8 @@
.npmrc
jest.config.ts
commitlint.config.js
Dockerfile
docker-compose.yml
prettier.config.js
.stylelintrc.js
.lintstagedrc.js
+1
View File
@@ -5,3 +5,4 @@ build
lerna-debug.log lerna-debug.log
.idea .idea
.vscode .vscode
/msb-*
+1 -1
View File
@@ -1,2 +1,2 @@
registry=https://nexus-npm.gboteam.ru/ registry=https://nexus-npm.gboteam.ru/
//nexus.gboteam.ru/repository/:_auth="" //nexus.gboteam.ru/repository/:_auth=${NPM_AUTH_TOKEN}
+9
View File
@@ -0,0 +1,9 @@
FROM nginx:1.27.4-alpine
COPY msb-host /opt/site/msb-host
COPY msb-main-page /opt/site/msb-main-page
COPY msb-deposits /opt/site/msb-deposits
COPY msb-payments /opt/site/msb-payments
COPY msb-statements-and-inquiries /opt/site/msb-statements-and-inquiries
COPY nginx.conf /etc/nginx/nginx.conf
+30 -77
View File
@@ -47,81 +47,34 @@
1. Настройка микрофронтового модуля 1. Настройка микрофронтового модуля
Создать `webpack.config.ts` с параметрами конфигурации: Создать `webpack.config.ts` с параметрами конфигурации:
```ts ```ts
/** webpack.config.ts */ /** webpack.config.ts */
import type { IWebpackAppConfig } from '@msb/mf-builder'; import type { IWebpackAppConfig } from '@msb/mf-builder';
import { normalizePackageName } from '@msb/mf-builder'; import { normalizePackageName } from '@msb/mf-builder';
import packageJson from './package.json'; import path from 'node:path';
import packageJson from './package.json';
const config: WebpackAppConfig = {
paths: { entryPath: '/', publicPath: '/', outputPath: '/', srcPath: '/', publicUrl: '/' }, const packageName = normalizePackageName(packageJson.name);
devServerOptions: { port: 3001, open: true },
moduleFederationOptions: { const config: IWebpackAppConfig = {
exposes: { moduleName: packageJson.name,
'./App': path.resolve(__dirname, 'src/exposes/App.tsx'), paths: {
}, outputPath: path.resolve(__dirname, '../../msb-main-page'),
shared: { publicUrl: '/msb-main-page/',
'@fractal-ui/styling': { },
singleton: true, devServerOptions: {
requiredVersion: packageJson.dependencies['@fractal-ui/styling'], port: 3002,
}, },
'@fractal-ui/core': { moduleFederationOptions: {
singleton: true, exposes: {
requiredVersion: packageJson.dependencies['@fractal-ui/core'], './App': {
}, import: path.resolve(__dirname, 'src/exposes/App.tsx'),
'@fractal-ui/extended': { name: `${packageName}_remote`,
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/composites': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/composites'],
},
'@fractal-ui/form': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/form'],
},
'@fractal-ui/layout': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/layout'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'@fractal-ui/table': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/table'],
},
'@fractal-ui/visualization': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/visualization'],
},
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
'styled-components': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-components'],
},
},
}, },
}; },
},
export default config; };
```
export default config;
```
1. Для того, чтобы поднять приложение интернет-банка в режиме 'production', необходимо запустить команду npm run start:prod. В последующих итерациях нужно будет перед запуском скрипта удалить ранее созданный докер контейнер и образ.
+13
View File
@@ -0,0 +1,13 @@
services:
nginx-msb:
build:
context: ./
dockerfile: Dockerfile
container_name: nginx-msb
ports:
- 3001:80
networks:
- network-local
networks:
network-local:
driver: bridge
+19
View File
@@ -0,0 +1,19 @@
{
"automatedSystemCode": "MSB",
"projectCodeDevOps": "MSB",
"name": "msb-platform-monorepo",
"title": "Интернет-банк МСБ",
"type": "application",
"valueStreamMaintainer": "ДБО (Цифровые каналы МСБ)",
"valueStreamContributors": null,
"jiraDevProject": "TEAMMSBMOB",
"jiraDevComponent": "msb-platform-monorepo",
"confluenceUrl": "https://confluence.int.gazprombank.ru",
"emailItLeader": "sergey.barykin@gazprombank.ru",
"emailTechDevLeader": "egor.onufriychuk@gazprombank.ru",
"emailDevOps": "nikita.pa.filatov@gazprombank.ru",
"emailEnterpriseArchitect": null,
"emailOperations": "eco_ops@gazprombank.ru",
"emailSolutionArchitect": null,
"description": "Монорепозиторий интернет-банка МСБ"
}
+37
View File
@@ -0,0 +1,37 @@
worker_processes auto;
events {
worker_connections 8000;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
root /opt/site;
index index.html;
location / {
root /opt/site/msb-host;
try_files $uri $uri/ /index.html =404;
}
location /msb-host/ {
try_files $uri $uri/ =404;
}
location /msb-main-page/ {
try_files $uri $uri/ =404;
}
location /msb-deposits/ {
try_files $uri $uri/ =404;
}
location /msb-payments/ {
try_files $uri $uri/ =404;
}
location /msb-statements-and-inquiries/ {
try_files $uri $uri/ =404;
}
}
}
+1596 -24216
View File
File diff suppressed because it is too large Load Diff
+13 -8
View File
@@ -1,10 +1,9 @@
{ {
"name": "msb-platform-monorepo", "name": "msb-platform-monorepo",
"version": "1.0.0-beta.2",
"files": ["msb-host", "msb-main-page", "msb-deposits", "msb-payments", "msb-statements-and-inquiries"],
"workspaces": ["packages/*", "services/*"],
"private": true, "private": true,
"workspaces": [
"packages/*",
"services/*"
],
"scripts": { "scripts": {
"start": "lerna run start --stream", "start": "lerna run start --stream",
"start:statements": "lerna run start --scope=msb-host --scope=msb-statements-and-inquiries --stream", "start:statements": "lerna run start --scope=msb-host --scope=msb-statements-and-inquiries --stream",
@@ -14,6 +13,7 @@
"start:service": "lerna run start --scope=msb-host --stream", "start:service": "lerna run start --scope=msb-host --stream",
"build": "lerna run build --scope=msb-* --stream", "build": "lerna run build --scope=msb-* --stream",
"build:packages": "lerna run build --scope=@msb/* --stream", "build:packages": "lerna run build --scope=@msb/* --stream",
"start:prod": "npm run build && docker-compose up",
"lint": "lerna run lint --scope=msb-* --stream", "lint": "lerna run lint --scope=msb-* --stream",
"lint-fix": "lerna run lint-fix --scope=msb-* --stream", "lint-fix": "lerna run lint-fix --scope=msb-* --stream",
"check-types": "lerna run check-types --scope=msb-* --stream", "check-types": "lerna run check-types --scope=msb-* --stream",
@@ -22,9 +22,9 @@
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.3.0", "@commitlint/cli": "^16.3.0",
"@commitlint/config-conventional": "^8.3.4", "@commitlint/config-conventional": "8.3.4",
"@eco/eslint-config": "^21.10.0", "@eco/eslint-config": "22.0.0",
"@eco/eslint-plugin": "^21.7.0", "@eco/eslint-plugin": "21.7.0",
"@eco/lint-staged-config": "21.10.0", "@eco/lint-staged-config": "21.10.0",
"@eco/prettier-config": "21.10.0", "@eco/prettier-config": "21.10.0",
"@eco/stylelint-config": "^22.0.0", "@eco/stylelint-config": "^22.0.0",
@@ -42,6 +42,11 @@
}, },
"peerDependencies": { "peerDependencies": {
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2" "react-dom": "17.0.2",
"@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "30.2.0",
"@fractal-ui/library": "30.2.0",
"@fractal-ui/overlays": "30.2.0",
"@fractal-ui/styling": "30.1.0"
} }
} }
@@ -18,9 +18,7 @@ type IRemoteRouteConfig = RemoteRouteConfigBase | RemoteRouteConfigVisibleOnMobi
interface IRemoteSettings { interface IRemoteSettings {
modules: IRemoteRouteConfig[]; modules: IRemoteRouteConfig[];
settings: { settings: {
rateManagementHost: string;
apiPath: string; apiPath: string;
authProviderHost: string;
}; };
} }
+1 -1
View File
@@ -2,4 +2,4 @@ export * from './endpoints';
export * from './network'; export * from './network';
export * from './mocks'; export * from './mocks';
export * from './treasury-deals-client'; export * from './treasury-deals-client';
export * from '@tanstack/react-query'; export * from '@tanstack/react-query';
@@ -89,9 +89,7 @@ const SETTINGS_MOCK = {
}, },
], ],
settings: { settings: {
rateManagementHost: 'foo', apiPath: '',
apiPath: 'bar',
authProviderHost: 'baz',
}, },
}; };
+3 -3
View File
@@ -1,13 +1,13 @@
import { fetchDepositManagementFundsHandlers } from '../treasury-deals-client';
import { settingsHandlers } from './fetchAppSettings'; import { settingsHandlers } from './fetchAppSettings';
import { clientOrganizationsHandlers } from './fetchClientOrganizations'; import { clientOrganizationsHandlers } from './fetchClientOrganizations';
import { fetchDepositDocumentNewForEditHandlers } from './fetchDepositDocumentNewForEdit'; import { fetchDepositDocumentNewForEditHandlers } from './fetchDepositDocumentNewForEdit';
import { fetchDepositGeneralAgreementsHandlers } from './fetchDepositGeneralAgreements';
import { financialAccountsHandlers } from './fetchFinancialAccounts'; import { financialAccountsHandlers } from './fetchFinancialAccounts';
import { fetchProductsHandlers } from './fetchProducts'; import { fetchProductsHandlers } from './fetchProducts';
import { fetchUserAuthoritiesHandlers } from './fetchUserAuthorities'; import { fetchUserAuthoritiesHandlers } from './fetchUserAuthorities';
import { fetchUserInfoUnionHandlerHandlers } from './fetchUserInfoUnion'; import { fetchUserInfoUnionHandlerHandlers } from './fetchUserInfoUnion';
import { statementsHandlers } from './statements'; import { statementsHandlers } from './statements';
import { fetchDepositGeneralAgreementsHandlers } from './fetchDepositGeneralAgreements';
import { fetchDepositManagementFundsHandlers } from '../treasury-deals-client';
const handlers = [ const handlers = [
...settingsHandlers, ...settingsHandlers,
@@ -19,7 +19,7 @@ const handlers = [
...fetchDepositDocumentNewForEditHandlers, ...fetchDepositDocumentNewForEditHandlers,
...statementsHandlers, ...statementsHandlers,
...fetchDepositGeneralAgreementsHandlers, ...fetchDepositGeneralAgreementsHandlers,
...fetchDepositManagementFundsHandlers ...fetchDepositManagementFundsHandlers,
]; ];
export { handlers }; export { handlers };
+2 -1
View File
@@ -7,7 +7,8 @@
"@tanstack/react-query": "4.36.1", "@tanstack/react-query": "4.36.1",
"axios": "1.7.9", "axios": "1.7.9",
"use-sync-external-store": "^1.2.0", "use-sync-external-store": "^1.2.0",
"@tanstack/query-core": "4.36.1" "@tanstack/query-core": "4.36.1",
"msw": "1.3.5"
}, },
"devDependencies": { "devDependencies": {
"msw": "1.3.5" "msw": "1.3.5"
+1
View File
@@ -5,3 +5,4 @@ export * from './cookies';
export * from './dateTime'; export * from './dateTime';
export * from './useRedirect'; export * from './useRedirect';
export * from './useElementHeight'; export * from './useElementHeight';
export * from './useRefetchData';
@@ -0,0 +1,3 @@
const REFETCH_DELAY = 10_000;
export { REFETCH_DELAY };
@@ -0,0 +1 @@
export * from './useRefetchData';
@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react';
import { REFETCH_DELAY } from './constants';
/**
* Хук, для управления состоянием повторного запроса данных.
* @param refetch Функция повторного запроса данных.
* @returns Состояние блокировки кнопки повторного запроса данных.
*/
function useRefetchData(refetch: () => void) {
const [disabledRefetchButton, setDisabled] = useState(false);
const [isAbleToRefetchData, setIsAbleToRefetchData] = useState(true);
const refetchData = () => {
refetch();
setIsAbleToRefetchData(false);
};
useEffect(() => {
if (isAbleToRefetchData) {
return;
}
setDisabled(true);
const timer = setTimeout(() => {
setDisabled(false);
setIsAbleToRefetchData(true);
}, REFETCH_DELAY);
return () => clearTimeout(timer);
}, [isAbleToRefetchData]);
return { disabledRefetchButton, refetchData };
}
export { useRefetchData };
+16 -5
View File
@@ -3,12 +3,23 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.ts", "main": "index.ts",
"files": [ "files": ["assets", "constants", "lib"],
"assets",
"constants",
"lib"
],
"dependencies": { "dependencies": {
"@msb/http": "^1.0.0" "@msb/http": "^1.0.0"
},
"devDependencies": {
"react": "17.0.2",
"react-dom": "17.0.2",
"styled-system": "5.1.5",
"@emotion/styled": "11.8.1",
"@fractal-ui/composites": "30.2.0",
"@fractal-ui/library": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"react-router-dom": "5.2.0",
"react-animate-height": "2.0.23"
},
"peerDependencies": {
"react": "17.0.2",
"react-dom": "17.0.2"
} }
} }
@@ -100,7 +100,7 @@ class WebpackConfigBuilder {
}) || []; }) || [];
const otherPlugins = this.appConfig.plugins; const otherPlugins = this.appConfig.plugins;
if (Array.isArray(otherPlugins) && Array.isArray(this.config.plugins)) { if (Array.isArray(otherPlugins) && Array.isArray(this.config.plugins)) {
this.config.plugins.concat(otherPlugins); this.config.plugins = this.config.plugins.concat(otherPlugins);
} }
} }
merge(newConfig) { merge(newConfig) {
@@ -15,9 +15,53 @@ const setModuleFederationPlugin = ({ moduleName, moduleFederationOptions, }) =>
singleton: true, singleton: true,
eager: true, eager: true,
}, },
'styled-components': { 'react-router-dom': {
singleton: true,
},
'@msb/http': {
singleton: true,
},
'@msb/shared': {
singleton: true,
},
'@fractal-ui/composites': {
singleton: true,
},
'@fractal-ui/styling': {
singleton: true,
},
'@fractal-ui/library': {
singleton: true,
},
'@fractal-ui/core': {
singleton: true,
},
'@fractal-ui/extended': {
singleton: true,
},
'@fractal-ui/overlays': {
singleton: true,
},
'styled-system': {
singleton: true,
},
'@styled-system/css': {
singleton: true,
},
'@emotion/react': {
singleton: true,
},
'@emotion/styled': {
singleton: true,
},
'react-animate-height': {
singleton: true,
},
'react-dnd': {
singleton: true,
},
'react-dnd-html5-backend': {
singleton: true, singleton: true,
eager: true,
}, },
} }, moduleFederationOptions)); } }, moduleFederationOptions));
exports.setModuleFederationPlugin = setModuleFederationPlugin; exports.setModuleFederationPlugin = setModuleFederationPlugin;
@@ -8,18 +8,44 @@ const terser_webpack_plugin_1 = __importDefault(require("terser-webpack-plugin")
/** /**
* Установка правил оптимизации сборки. * Установка правил оптимизации сборки.
*/ */
const setOptimizationRules = (options) => (Object.assign({ minimize: true, minimizer: [new terser_webpack_plugin_1.default()], splitChunks: { const setOptimizationRules = (options) => (Object.assign({ minimize: true, minimizer: [new terser_webpack_plugin_1.default()], runtimeChunk: false, splitChunks: {
chunks: 'all', minSize: 17000,
maxInitialRequests: Number.POSITIVE_INFINITY, minRemainingSize: 0,
minSize: 0, minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30000,
cacheGroups: { cacheGroups: {
vendor: { vendors: {
test: /[/\\]node_modules[/\\]/, test: /[/\\]node_modules[/\\]/,
name(module) { name: (module) => {
var _a; const { name } = module.resourceResolveData.descriptionFileData;
const packageName = (_a = module.context.match(/[/\\]node_modules[/\\](.*?)([/\\]|$)/)) !== null && _a !== void 0 ? _a : 'package'; return name === null || name === void 0 ? void 0 : name.replace('@', '').replace('/', '_');
return `npm.${packageName[1].replace('@', '')}`;
}, },
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name === null || name === void 0 ? void 0 : name.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
}, },
}, },
} }, options)); } }, options));
+1 -5
View File
@@ -4,11 +4,7 @@
"description": "CLI для сборки модулей с помощью Webpack 5", "description": "CLI для сборки модулей с помощью Webpack 5",
"main": "lib/index.js", "main": "lib/index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
"files": [ "files": ["bin", "lib", "types"],
"bin",
"lib",
"types"
],
"bin": { "bin": {
"build-app": "bin/build-app.js" "build-app": "bin/build-app.js"
}, },
@@ -118,7 +118,7 @@ export class WebpackConfigBuilder {
const otherPlugins = this.appConfig.plugins; const otherPlugins = this.appConfig.plugins;
if (Array.isArray(otherPlugins) && Array.isArray(this.config.plugins)) { if (Array.isArray(otherPlugins) && Array.isArray(this.config.plugins)) {
this.config.plugins.concat(otherPlugins); this.config.plugins = this.config.plugins.concat(otherPlugins);
} }
} }
@@ -21,9 +21,53 @@ export const setModuleFederationPlugin = ({
singleton: true, singleton: true,
eager: true, eager: true,
}, },
'styled-components': { 'react-router-dom': {
singleton: true,
},
'@msb/http': {
singleton: true,
},
'@msb/shared': {
singleton: true,
},
'@fractal-ui/composites': {
singleton: true,
},
'@fractal-ui/styling': {
singleton: true,
},
'@fractal-ui/library': {
singleton: true,
},
'@fractal-ui/core': {
singleton: true,
},
'@fractal-ui/extended': {
singleton: true,
},
'@fractal-ui/overlays': {
singleton: true,
},
'styled-system': {
singleton: true,
},
'@styled-system/css': {
singleton: true,
},
'@emotion/react': {
singleton: true,
},
'@emotion/styled': {
singleton: true,
},
'react-animate-height': {
singleton: true,
},
'react-dnd': {
singleton: true,
},
'react-dnd-html5-backend': {
singleton: true, singleton: true,
eager: true,
}, },
}, },
...moduleFederationOptions, ...moduleFederationOptions,
@@ -1,24 +1,58 @@
import TerserPlugin from 'terser-webpack-plugin'; import TerserPlugin from 'terser-webpack-plugin';
import type { Configuration } from 'webpack'; import type { Configuration } from 'webpack';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
/** /**
* Установка правил оптимизации сборки. * Установка правил оптимизации сборки.
*/ */
export const setOptimizationRules = (options?: Configuration['optimization']): Configuration['optimization'] => ({ export const setOptimizationRules = (options?: Configuration['optimization']): Configuration['optimization'] => ({
minimize: true, minimize: true,
minimizer: [new TerserPlugin()], minimizer: [new TerserPlugin()],
runtimeChunk: false,
splitChunks: { splitChunks: {
chunks: 'all', minSize: 17_000,
maxInitialRequests: Number.POSITIVE_INFINITY, minRemainingSize: 0,
minSize: 0, minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: { cacheGroups: {
vendor: { vendors: {
test: /[/\\]node_modules[/\\]/, test: /[/\\]node_modules[/\\]/,
name(module: { context: string }) { name: (module: Module) => {
const packageName = module.context.match(/[/\\]node_modules[/\\](.*?)([/\\]|$)/) ?? 'package'; const { name } = module.resourceResolveData.descriptionFileData;
return `npm.${packageName[1].replace('@', '')}`; return name?.replace('@', '').replace('/', '_');
}, },
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
}, },
}, },
}, },
@@ -1 +1 @@
{"version":3,"file":"setModuleFederationPlugin.d.ts","sourceRoot":"","sources":["../../../src/config/webpack/setModuleFederationPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;GAEG;AACH,eAAO,MAAM,yBAAyB,6CAGnC,KAAK,cAAc,EAAE,yBAAyB,GAAG,YAAY,CAAC,qCAmB7D,CAAC"} {"version":3,"file":"setModuleFederationPlugin.d.ts","sourceRoot":"","sources":["../../../src/config/webpack/setModuleFederationPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;GAEG;AACH,eAAO,MAAM,yBAAyB,6CAGnC,KAAK,cAAc,EAAE,yBAAyB,GAAG,YAAY,CAAC,qCA+D7D,CAAC"}
@@ -1 +1 @@
{"version":3,"file":"setOptimizationRules.d.ts","sourceRoot":"","sources":["../../../src/config/webpack/setOptimizationRules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,oBAAoB,aAAc,aAAa,CAAC,cAAc,CAAC,KAAG,aAAa,CAAC,cAAc,CAmBzG,CAAC"} {"version":3,"file":"setOptimizationRules.d.ts","sourceRoot":"","sources":["../../../src/config/webpack/setOptimizationRules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAO7C;;GAEG;AACH,eAAO,MAAM,oBAAoB,aAAc,aAAa,CAAC,cAAc,CAAC,KAAG,aAAa,CAAC,cAAc,CAgDzG,CAAC"}
+8 -10
View File
@@ -23,11 +23,13 @@
"dependencies": { "dependencies": {
"@emotion/react": "11.8.1", "@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1", "@emotion/styled": "11.8.1",
"@fractal-ui/core": "^30.2.0", "@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "^30.2.0", "@fractal-ui/extended": "30.2.0",
"@fractal-ui/library": "^30.2.0", "@fractal-ui/library": "30.2.0",
"@fractal-ui/overlays": "^30.2.0", "@fractal-ui/composites": "30.2.0",
"@fractal-ui/styling": "^30.1.0", "@fractal-ui/form": "30.2.0",
"@fractal-ui/overlays": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"@msb/mf-utils": "^1.0.0", "@msb/mf-utils": "^1.0.0",
"@msb/http": "^1.0.0", "@msb/http": "^1.0.0",
"@msb/shared": "^1.0.0", "@msb/shared": "^1.0.0",
@@ -66,9 +68,5 @@
"lint-staged": "^12.3.4", "lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2" "react-test-renderer": "17.0.2"
}, },
"browserslist": [ "browserslist": [">0.2%", "not dead", "not op_mini all"]
">0.2%",
"not dead",
"not op_mini all"
]
} }
+2 -113
View File
@@ -3,68 +3,17 @@ import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path'; import path from 'node:path';
import packageJson from './package.json'; import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name); const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = { const config: IWebpackAppConfig = {
moduleName: packageJson.name, moduleName: packageJson.name,
paths: { paths: {
outputPath: path.resolve(__dirname, '../../build/msb-deposits'), outputPath: path.resolve(__dirname, '../../msb-deposits'),
publicUrl: 'auto', publicUrl: '/msb-deposits/',
}, },
devServerOptions: { devServerOptions: {
port: 3007, port: 3007,
}, },
optimizationOptions: {
runtimeChunk: false,
splitChunks: {
minSize: 17_000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
},
},
},
},
moduleFederationOptions: { moduleFederationOptions: {
exposes: { exposes: {
'./App': { './App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
name: `${packageName}_remote`, name: `${packageName}_remote`,
}, },
}, },
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
'@msb/http': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/http'],
},
'@msb/shared': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/shared'],
},
'@fractal-ui/styling': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/styling'],
},
'@fractal-ui/core': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/core'],
},
'@fractal-ui/extended': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'styled-system': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-system'],
},
'@styled-system/css': {
singleton: true,
requiredVersion: packageJson.dependencies['@styled-system/css'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@emotion/react': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/react'],
},
'@emotion/styled': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/styled'],
},
},
}, },
}; };
+6 -11
View File
@@ -23,17 +23,12 @@
"dependencies": { "dependencies": {
"@emotion/react": "11.8.1", "@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1", "@emotion/styled": "11.8.1",
"@fractal-ui/composites": "30.6.0", "@fractal-ui/composites": "30.2.0",
"@fractal-ui/core": "30.6.0", "@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "30.6.0", "@fractal-ui/extended": "30.2.0",
"@fractal-ui/form": "30.6.0", "@fractal-ui/library": "30.2.0",
"@fractal-ui/layout": "30.6.0", "@fractal-ui/overlays": "30.2.0",
"@fractal-ui/library": "30.6.0", "@fractal-ui/styling": "30.1.0",
"@fractal-ui/overlays": "30.6.0",
"@fractal-ui/styling": "30.6.0",
"@fractal-ui/svg-generator": "30.3.1",
"@fractal-ui/table": "30.6.0",
"@fractal-ui/visualization": "30.6.0",
"@msb/http": "^1.0.0", "@msb/http": "^1.0.0",
"@msb/mf-utils": "^1.0.0", "@msb/mf-utils": "^1.0.0",
"@msb/shared": "1.0.0", "@msb/shared": "1.0.0",
@@ -0,0 +1,94 @@
{
"modules": [
{
"id": 1,
"remoteEntryUrl": "/msb-main-page/remoteEntry.js",
"remoteName": "msb-main-page",
"moduleName": "App",
"path": "/",
"title": "Главная",
"showOnMobile": true,
"shortTitle": "Главная",
"exact": true
},
{
"id": 2,
"remoteEntryUrl": "/msb-transaction-history/remoteEntry.js",
"remoteName": "msb-transaction-history",
"moduleName": "App",
"path": "/transaction-history",
"title": "История операций",
"showOnMobile": true,
"shortTitle": "История",
"exact": false
},
{
"id": 3,
"remoteEntryUrl": "/msb-payments/remoteEntry.js",
"remoteName": "msb-payments",
"moduleName": "App",
"path": "/payments",
"title": "Платежи",
"showOnMobile": true,
"shortTitle": "Платежи",
"exact": false
},
{
"id": 4,
"remoteEntryUrl": "/msb-statements-and-inquiries/remoteEntry.js",
"remoteName": "msb-statements-and-inquiries",
"moduleName": "App",
"path": "/statements-and-inquiries",
"title": "Выписки и\u00A0справки",
"exact": false
},
{
"id": 5,
"remoteEntryUrl": "/msb-accounts/remoteEntry.js",
"remoteName": "msb-accounts",
"moduleName": "App",
"path": "/accounts",
"title": "Счета",
"exact": false
},
{
"id": 6,
"remoteEntryUrl": "/msb-deposits/remoteEntry.js",
"remoteName": "msb-deposits",
"moduleName": "App",
"path": "/deposits",
"title": "Депозиты и\u00A0МНО",
"exact": false
},
{
"id": 7,
"remoteEntryUrl": "/msb-fea/remoteEntry.js",
"remoteName": "msb-fea",
"moduleName": "App",
"path": "/fea",
"title": "ВЭД",
"exact": false
},
{
"id": 8,
"remoteEntryUrl": "/msb-credit-account/remoteEntry.js",
"remoteName": "msb-credit-account",
"moduleName": "App",
"path": "/credit-account",
"title": "Кредитный кабинет",
"exact": false
},
{
"id": 9,
"remoteEntryUrl": "/msb-acquiring/remoteEntry.js",
"remoteName": "msb-acquiring",
"moduleName": "App",
"path": "/acquiring",
"title": "Эквайринг",
"exact": false
}
],
"settings": {
"apiPath": "/api"
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="./manifest.json">
<title>ГАЗПРОМБАНК | МСБ</title> <title>ГАЗПРОМБАНК | МСБ</title>
</head> </head>
<body> <body>
@@ -0,0 +1,3 @@
const MOCK_SERVER_API_PATH_KEY = 'mock_server_api_path';
export { MOCK_SERVER_API_PATH_KEY };
@@ -1,8 +0,0 @@
import type { IRemoteSettings } from '@msb/http';
export interface ReturnType {
modules: IRemoteSettings['modules'] | undefined;
error: Error | null | undefined;
isLoading: boolean;
handleClick(): void;
}
@@ -1,8 +1,9 @@
import { useQuery, type IRemoteSettings } from '@msb/http'; import { useLayoutEffect } from 'react';
import type { ReturnType } from './types'; import { network, useQuery, type IRemoteSettings } from '@msb/http';
import { MOCK_SERVER_API_PATH_KEY } from './constants';
import { fetchAppSettings, QUERY_KEYS_SETTINGS } from '@/shared/api/fetchAppSettings'; import { fetchAppSettings, QUERY_KEYS_SETTINGS } from '@/shared/api/fetchAppSettings';
export const useAppSettings = (): ReturnType => { export const useAppSettings = () => {
const { data, error, isLoading, refetch } = useQuery<IRemoteSettings, Error | undefined>({ const { data, error, isLoading, refetch } = useQuery<IRemoteSettings, Error | undefined>({
queryKey: [QUERY_KEYS_SETTINGS], queryKey: [QUERY_KEYS_SETTINGS],
queryFn: fetchAppSettings, queryFn: fetchAppSettings,
@@ -10,14 +11,27 @@ export const useAppSettings = (): ReturnType => {
cacheTime: Number.POSITIVE_INFINITY, cacheTime: Number.POSITIVE_INFINITY,
}); });
const handleClick = (): void => { useLayoutEffect(() => {
refetch(); if (!data) {
}; return;
}
const { apiPath } = data.settings;
const mockServerApiPath = localStorage.getItem(MOCK_SERVER_API_PATH_KEY);
// для перенаправления запросов на моковый сервер для тестирования
if (mockServerApiPath) {
network.client.defaults.baseURL = mockServerApiPath;
} else if (apiPath) {
network.client.defaults.baseURL = apiPath;
}
}, [data]);
return { return {
modules: data?.modules, modules: data?.modules,
error, error,
isLoading, isLoading,
handleClick, refetch,
}; };
}; };
@@ -6,10 +6,10 @@ import LayoutEmptyState from './LayoutEmptyState';
import { StyledThemeProvider } from '@/app/providers'; import { StyledThemeProvider } from '@/app/providers';
const Layout: FC = () => { const Layout: FC = () => {
const { modules, isLoading: isSettingsLoading, error: settingsError, handleClick } = useAppSettings(); const { modules, isLoading: isSettingsLoading, error: settingsError, refetch } = useAppSettings();
if (settingsError || isSettingsLoading) if (settingsError || isSettingsLoading)
return <LayoutEmptyState handleClick={handleClick} isLoading={isSettingsLoading} settingsError={settingsError} />; return <LayoutEmptyState handleClick={refetch} isLoading={isSettingsLoading} settingsError={settingsError} />;
return ( return (
<StyledThemeProvider> <StyledThemeProvider>
@@ -2,6 +2,5 @@ const ACCOUNTS_NOT_FOUND = 'Счета недоступны';
const NOT_FOUND = 'Информация не загрузилась'; const NOT_FOUND = 'Информация не загрузилась';
const NOT_FOUND_BUTTON_TEXT = 'Попробовать ещё раз'; const NOT_FOUND_BUTTON_TEXT = 'Попробовать ещё раз';
const ORGANIZATIONS = 'Организации'; const ORGANIZATIONS = 'Организации';
const ALL_ORGANIZATIONS = 'Все организации';
export { ACCOUNTS_NOT_FOUND, NOT_FOUND, NOT_FOUND_BUTTON_TEXT, ORGANIZATIONS, ALL_ORGANIZATIONS }; export { ACCOUNTS_NOT_FOUND, NOT_FOUND, NOT_FOUND_BUTTON_TEXT, ORGANIZATIONS };
@@ -0,0 +1,85 @@
import { useRef, useState, type ReactElement } from 'react';
import { DropdownBase, DropdownItem } from '@fractal-ui/composites';
import { useOnOutsideClick } from '@fractal-ui/core';
import { BriefcaseIcon, DownIcon } from '@fractal-ui/library';
import { PopupContainer } from '@fractal-ui/overlays';
import { Text } from '@fractal-ui/styling';
import type { OrganizationDto } from '@msb/http';
import { ACCOUNTS, pluralize, useRefetchData } from '@msb/shared';
import { ACCOUNTS_NOT_FOUND, ORGANIZATIONS } from '../constants';
import { EmptyState } from './EmptyState';
import * as S from './Organizations.styles';
interface Props {
organizations: OrganizationDto[];
isLoading: boolean;
refetch(): void;
error: Error | null | undefined;
}
const DesktopOrganizations = ({ organizations, error, isLoading, refetch }: Props): ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef(null);
const dropdownRef = useRef(null);
const { disabledRefetchButton, refetchData } = useRefetchData(refetch);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
useOnOutsideClick({
elements: [dropdownRef, containerRef],
handler: toggleOpen,
isActive: isOpen,
});
return (
<>
<S.Organizations ref={containerRef} $isExpaned={isOpen} onClick={toggleOpen}>
<S.Amount>
{organizations.length > 1 ? (
<Text.P4>{organizations.length}</Text.P4>
) : (
<div>
<BriefcaseIcon />
</div>
)}
</S.Amount>
<Text.P2 minWidth={132} overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" width="100%">
{organizations.length === 1 ? organizations[0].shortName : ORGANIZATIONS}
</Text.P2>
{(isLoading || Boolean(error) || organizations.length > 1) && (
<S.ToggleIconBox $isExpanded={isOpen}>
<DownIcon />
</S.ToggleIconBox>
)}
</S.Organizations>
<PopupContainer anchorEl={containerRef} isOpen={isOpen} offsetX={0} offsetY={4}>
<div ref={dropdownRef}>
<DropdownBase width={328}>
{organizations.length > 0 ? (
organizations.map(item => (
<DropdownItem
key={item.id}
description={
item.accounts && item.accounts.length > 0
? `${item.accounts.length} ${pluralize(item.accounts.length, ACCOUNTS)}`
: ACCOUNTS_NOT_FOUND
}
size="M"
>
{item.shortName}
</DropdownItem>
))
) : (
<EmptyState disabled={disabledRefetchButton} isLoading={isLoading} refetch={refetchData} />
)}
</DropdownBase>
</div>
</PopupContainer>
</>
);
};
export { DesktopOrganizations };
@@ -1,4 +1,5 @@
import type { FC } from 'react'; /* eslint-disable @typescript-eslint/no-confusing-void-expression */
import { type FC } from 'react';
import { Button } from '@fractal-ui/core'; import { Button } from '@fractal-ui/core';
import { LoopArrowIcon } from '@fractal-ui/library'; import { LoopArrowIcon } from '@fractal-ui/library';
import { Text } from '@fractal-ui/styling'; import { Text } from '@fractal-ui/styling';
@@ -8,11 +9,12 @@ import { Loader } from '@/shared/ui/components';
interface Props { interface Props {
isLoading: boolean; isLoading: boolean;
disabled: boolean;
buttonWidth?: string; buttonWidth?: string;
refetch(): void; refetch(): void;
} }
const EmptyState: FC<Props> = ({ isLoading, buttonWidth, refetch }) => ( const EmptyState: FC<Props> = ({ disabled, isLoading, buttonWidth, refetch }) => (
<S.EmptyStateContentBox> <S.EmptyStateContentBox>
{isLoading ? ( {isLoading ? (
<Loader dataName="loader" /> <Loader dataName="loader" />
@@ -21,6 +23,7 @@ const EmptyState: FC<Props> = ({ isLoading, buttonWidth, refetch }) => (
<Text.P1>{NOT_FOUND}</Text.P1> <Text.P1>{NOT_FOUND}</Text.P1>
<Button <Button
dataAction="reload" dataAction="reload"
disabled={disabled}
icon={LoopArrowIcon} icon={LoopArrowIcon}
shape="default" shape="default"
variant="blue" variant="blue"
@@ -1,6 +1,6 @@
import { useState, type ReactElement } from 'react'; import { useState, type ReactElement } from 'react';
import { Drawer } from '@fractal-ui/overlays'; import { Drawer } from '@fractal-ui/overlays';
import { ACCOUNTS, pluralize } from '@msb/shared'; import { ACCOUNTS, pluralize, useRefetchData } from '@msb/shared';
import { ACCOUNTS_NOT_FOUND, ORGANIZATIONS } from '../constants'; import { ACCOUNTS_NOT_FOUND, ORGANIZATIONS } from '../constants';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import * as S from './Organizations.styles'; import * as S from './Organizations.styles';
@@ -10,6 +10,8 @@ const MobileOrganizations = (): ReactElement => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { organizations, isLoading, refetch } = useClientOrganizations(); const { organizations, isLoading, refetch } = useClientOrganizations();
const { disabledRefetchButton, refetchData } = useRefetchData(refetch);
const toggleOpen = () => { const toggleOpen = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
}; };
@@ -32,7 +34,7 @@ const MobileOrganizations = (): ReactElement => {
</MobileOrganizationItem> </MobileOrganizationItem>
)) ))
) : ( ) : (
<EmptyState buttonWidth="100%" isLoading={isLoading} refetch={refetch} /> <EmptyState buttonWidth="100%" disabled={disabledRefetchButton} isLoading={isLoading} refetch={refetchData} />
)} )}
</Drawer> </Drawer>
</> </>
@@ -1,14 +1,20 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { BriefcaseIcon } from '@fractal-ui/library'; import { BriefcaseIcon } from '@fractal-ui/library';
const Organizations = styled.div(() => ({ const Organizations = styled.div<{ $isExpaned: boolean }>(({ theme, $isExpaned }) => ({
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
width: '216px', maxWidth: '216px',
gap: '8px', gap: '8px',
padding: '0 8px', padding: '8px 14px 8px 8px',
borderRadius: '16px',
cursor: 'pointer', cursor: 'pointer',
border: `1px solid ${theme.colors.control.bg}`,
...($isExpaned && { '&&&': { borderColor: theme.colors.control.borderFocus } }),
'&:hover': {
borderColor: theme.colors.control.border,
},
})); }));
const StyledBriefcaseIcon = styled(BriefcaseIcon)({ const StyledBriefcaseIcon = styled(BriefcaseIcon)({
@@ -16,15 +22,13 @@ const StyledBriefcaseIcon = styled(BriefcaseIcon)({
}); });
const ToggleIconBox = styled.div<{ $isExpanded: boolean }>(({ $isExpanded, theme }) => ({ const ToggleIconBox = styled.div<{ $isExpanded: boolean }>(({ $isExpanded, theme }) => ({
position: 'absolute',
right: '14px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
transform: $isExpanded ? 'rotateX(180deg)' : 'rotateX(0deg)', transform: $isExpanded ? 'rotateX(180deg)' : 'rotateX(0deg)',
svg: { svg: {
width: '17px', width: '16px',
height: '17px', height: '16px',
color: theme.colors.text.primary, color: theme.colors.text.primary,
}, },
})); }));
@@ -1,99 +1,17 @@
import { useRef, useState, type ReactElement } from 'react'; import { type ReactElement } from 'react';
import { DropdownBase, DropdownItem } from '@fractal-ui/composites'; import { MEDIA, useMediaQuery } from '@msb/shared';
import { useOnOutsideClick } from '@fractal-ui/core'; import { DesktopOrganizations } from './DesktopOrganizations';
import { BriefcaseIcon, DownIcon } from '@fractal-ui/library'; import { TabletOrganizations } from './TabletOrganizations';
import { Drawer, PopupContainer } from '@fractal-ui/overlays'; import { useClientOrganizations } from '@/entities/Organization';
import { Text } from '@fractal-ui/styling';
import { ACCOUNTS, MEDIA, pluralize, useMediaQuery } from '@msb/shared';
import { ACCOUNTS_NOT_FOUND, ALL_ORGANIZATIONS, ORGANIZATIONS } from '../constants';
import { EmptyState } from './EmptyState';
import * as S from './Organizations.styles';
import { TabletOrganizationItem, useClientOrganizations } from '@/entities/Organization';
const Organizations = (): ReactElement => { const Organizations = (): ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef(null);
const dropdownRef = useRef(null);
const isDesktop = useMediaQuery(MEDIA.desktop); const isDesktop = useMediaQuery(MEDIA.desktop);
const { organizations, isLoading, refetch } = useClientOrganizations(); const { organizations, isLoading, error, refetch } = useClientOrganizations();
const toggleOpen = () => {
setIsOpen(!isOpen);
};
useOnOutsideClick({
elements: [dropdownRef, containerRef],
handler: toggleOpen,
isActive: isOpen,
});
return isDesktop ? ( return isDesktop ? (
<> <DesktopOrganizations error={error} isLoading={isLoading} organizations={organizations} refetch={refetch} />
<S.Organizations ref={containerRef} onClick={toggleOpen}>
<S.Amount>
{organizations.length > 1 ? (
<Text.P4>{organizations.length}</Text.P4>
) : (
<div>
<BriefcaseIcon />
</div>
)}
</S.Amount>
<Text.P2 overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
{organizations.length === 1 ? organizations[0].shortName : ALL_ORGANIZATIONS}
</Text.P2>
<S.ToggleIconBox $isExpanded={isOpen}>
<DownIcon />
</S.ToggleIconBox>
</S.Organizations>
<PopupContainer anchorEl={containerRef} isOpen={isOpen} offsetX={-8} offsetY={12}>
<div ref={dropdownRef}>
<DropdownBase width={328}>
{organizations.length > 0 ? (
organizations.map(item => (
<DropdownItem
key={item.id}
description={
item.accounts && item.accounts.length > 0
? `${item.accounts.length} ${pluralize(item.accounts.length, ACCOUNTS)}`
: ACCOUNTS_NOT_FOUND
}
size="M"
>
{item.shortName}
</DropdownItem>
))
) : (
<EmptyState isLoading={isLoading} refetch={refetch} />
)}
</DropdownBase>
</div>
</PopupContainer>
</>
) : ( ) : (
<> <TabletOrganizations error={error} isLoading={isLoading} organizations={organizations} refetch={refetch} />
<S.StyledBriefcaseIcon onClick={toggleOpen} />
<Drawer preventCloseOnOutside header={ORGANIZATIONS} isOpen={isOpen} onClose={toggleOpen}>
<S.DrawerContentBox>
{organizations.length > 0 ? (
organizations.map(item => (
<TabletOrganizationItem
key={item.id}
description={
item.accounts && item.accounts.length > 0
? `${item.accounts.length} ${pluralize(item.accounts.length, ACCOUNTS)}`
: ACCOUNTS_NOT_FOUND
}
>
{item.shortName}
</TabletOrganizationItem>
))
) : (
<EmptyState buttonWidth="100%" isLoading={isLoading} refetch={refetch} />
)}
</S.DrawerContentBox>
</Drawer>
</>
); );
}; };
@@ -0,0 +1,52 @@
import { useState, type ReactElement } from 'react';
import { Drawer } from '@fractal-ui/overlays';
import type { OrganizationDto } from '@msb/http';
import { ACCOUNTS, pluralize, useRefetchData } from '@msb/shared';
import { ACCOUNTS_NOT_FOUND, ORGANIZATIONS } from '../constants';
import { EmptyState } from './EmptyState';
import * as S from './Organizations.styles';
import { TabletOrganizationItem } from '@/entities/Organization';
interface Props {
organizations: OrganizationDto[];
isLoading: boolean;
refetch(): void;
error: Error | null | undefined;
}
const TabletOrganizations = ({ organizations, isLoading, refetch }: Props): ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const { disabledRefetchButton, refetchData } = useRefetchData(refetch);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<>
<S.StyledBriefcaseIcon onClick={toggleOpen} />
<Drawer preventCloseOnOutside header={ORGANIZATIONS} isOpen={isOpen} onClose={toggleOpen}>
<S.DrawerContentBox>
{organizations.length > 0 ? (
organizations.map(item => (
<TabletOrganizationItem
key={item.id}
description={
item.accounts && item.accounts.length > 0
? `${item.accounts.length} ${pluralize(item.accounts.length, ACCOUNTS)}`
: ACCOUNTS_NOT_FOUND
}
>
{item.shortName}
</TabletOrganizationItem>
))
) : (
<EmptyState buttonWidth="100%" disabled={disabledRefetchButton} isLoading={isLoading} refetch={refetchData} />
)}
</S.DrawerContentBox>
</Drawer>
</>
);
};
export { TabletOrganizations };
@@ -15,17 +15,17 @@ const MobileMenu: FC<Props> = ({ modules = [] }) => (
{modules {modules
.filter((item): item is RemoteRouteConfigVisibleOnMobile => 'showOnMobile' in item && item.showOnMobile) .filter((item): item is RemoteRouteConfigVisibleOnMobile => 'showOnMobile' in item && item.showOnMobile)
.map(item => ( .map(item => (
<MobileMenuItem <S.StyledNavLink key={item.title} exact={item.exact} to={item.path}>
key={item.title} <MobileMenuItem icon={SIDEBAR_ICONS[item.path as PATHS]}>{item.shortTitle}</MobileMenuItem>
exact={item.exact} </S.StyledNavLink>
href={item.path}
icon={SIDEBAR_ICONS[item.path as PATHS]}
title={item.shortTitle}
/>
))} ))}
<S.Divider /> <S.Divider />
<MobileMenuItem exact href={PATHS.CONTACT} icon={SIDEBAR_ICONS[PATHS.CONTACT]} title={LOCALIZATION.CONTACT_MOBILE} /> <S.StyledNavLink exact to={PATHS.CONTACT}>
<MobileMenuItem exact href={PATHS.SERVICES} icon={SIDEBAR_ICONS[PATHS.SERVICES]} title={LOCALIZATION.SERVICES} /> <MobileMenuItem icon={SIDEBAR_ICONS[PATHS.CONTACT]}>{LOCALIZATION.CONTACT_MOBILE}</MobileMenuItem>
</S.StyledNavLink>
<S.StyledNavLink exact to={PATHS.SERVICES}>
<MobileMenuItem icon={SIDEBAR_ICONS[PATHS.SERVICES]}>{LOCALIZATION.SERVICES}</MobileMenuItem>
</S.StyledNavLink>
</S.List> </S.List>
) : null} ) : null}
</S.Navigation> </S.Navigation>
@@ -1,7 +1,81 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { MEDIA } from '@msb/shared'; import { MEDIA } from '@msb/shared';
import { StyledNavLink, Box } from './SidebarItem/SidebarItem.styles'; import { NavLink } from 'react-router-dom';
import { Box } from './SidebarItem/SidebarItem.styles';
const NewServices = styled.div(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 16px 12px 12px',
marginTop: '16px',
position: 'relative',
borderRadius: '16px',
backgroundColor: theme.colors.bg.primary,
cursor: 'pointer',
'&&&': {
[`${Box}`]: {
padding: 0,
color: theme.colors.text.accentBrand,
gap: '16px',
svg: {
width: '16px',
height: '16px',
color: theme.colors.text.accentBrand,
},
},
},
[`@media ${MEDIA.tablet}`]: { padding: '8px 0', left: 0, width: '100%', justifyContent: 'center' },
}));
const StyledNavLink = styled(NavLink)(({ theme }) => ({
display: 'block',
textDecoration: 'none',
'&.active': {
[`${Box}`]: {
fontWeight: 700,
svg: {
color: theme.colors.text.primary,
},
},
[`${NewServices}`]: {
color: theme.colors.text.accentBrand,
svg: {
color: theme.colors.text.accentBrand,
},
},
},
[`@media ${MEDIA.desktop}`]: {
'&:hover': {
[`${NewServices}`]: {
backgroundColor: theme.colors.control.secondary.white.bgHover,
},
'&&&': {
[`${Box}`]: {
backgroundColor: theme.colors.control.secondary.white.bgHover,
color: theme.colors.control.secondary.white.typoHover,
svg: {
color: theme.colors.control.secondary.white.typoHover,
},
},
},
},
},
[`@media ${MEDIA.mobile}`]: {
'&&&': {
fontWeight: 'normal',
},
'&.active': {
[`${Box}`]: {
color: theme.colors.text.accentBrand,
svg: {
color: theme.colors.text.accentBrand,
},
},
},
},
}));
const Navigation = styled.nav(({ theme }) => ({ const Navigation = styled.nav(({ theme }) => ({
gridArea: 'sd', gridArea: 'sd',
@@ -39,29 +113,6 @@ const Tag = styled.div(({ theme }) => ({
[`@media ${MEDIA.tablet}`]: { display: 'none' }, [`@media ${MEDIA.tablet}`]: { display: 'none' },
})); }));
const NewItem = styled.div(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 16px 12px 12px',
marginTop: '16px',
position: 'relative',
width: '224px',
left: '-5px',
borderRadius: '16px',
backgroundColor: theme.colors.bg.primary,
[`${Box}`]: {
padding: 0,
},
[`${StyledNavLink}`]: {
color: theme.colors.text.accentBrand,
svg: {
color: theme.colors.text.accentBrand,
},
},
[`@media ${MEDIA.tablet}`]: { padding: '8px 0', left: 0, width: '100%', justifyContent: 'center' },
}));
const Divider = styled.div(({ theme }) => ({ const Divider = styled.div(({ theme }) => ({
height: '1px', height: '1px',
marginTop: '12px', marginTop: '12px',
@@ -71,4 +122,4 @@ const Divider = styled.div(({ theme }) => ({
[`@media ${MEDIA.mobile}`]: { display: 'none' }, [`@media ${MEDIA.mobile}`]: { display: 'none' },
})); }));
export { Navigation, NewItem, List, Divider, Tag }; export { Navigation, NewServices, List, Divider, Tag, StyledNavLink };
@@ -16,27 +16,26 @@ const Sidebar: FC<Props> = ({ modules = [] }) => {
{modules?.length > 0 ? ( {modules?.length > 0 ? (
<S.List> <S.List>
{modules.map(item => ( {modules.map(item => (
<SidebarItem <S.StyledNavLink key={item.title} exact={item.exact} to={item.path}>
key={item.title} <SidebarItem icon={SIDEBAR_ICONS[item.path as PATHS]} isDesktop={isDesktop}>
exact={item.exact} {item.title}
href={item.path} </SidebarItem>
icon={SIDEBAR_ICONS[item.path as PATHS]} </S.StyledNavLink>
isDesktop={isDesktop}
title={item.title}
/>
))} ))}
<S.Divider /> <S.Divider />
<SidebarItem exact href={PATHS.CONTACT} icon={SIDEBAR_ICONS[PATHS.CONTACT]} isDesktop={isDesktop} title={LOCALIZATION.CONTACT} /> <S.StyledNavLink exact to={PATHS.CONTACT}>
<S.NewItem> <SidebarItem icon={SIDEBAR_ICONS[PATHS.CONTACT]} isDesktop={isDesktop}>
<SidebarItem {LOCALIZATION.CONTACT}
exact </SidebarItem>
href={PATHS.SERVICES} </S.StyledNavLink>
icon={SIDEBAR_ICONS[PATHS.SERVICES]} <S.StyledNavLink exact to={PATHS.SERVICES}>
isDesktop={isDesktop} <S.NewServices>
title={LOCALIZATION.SERVICES} <SidebarItem icon={SIDEBAR_ICONS[PATHS.SERVICES]} isDesktop={isDesktop}>
/> {LOCALIZATION.SERVICES}
<S.Tag>New</S.Tag> </SidebarItem>
</S.NewItem> <S.Tag>New</S.Tag>
</S.NewServices>
</S.StyledNavLink>
</S.List> </S.List>
) : null} ) : null}
</S.Navigation> </S.Navigation>
@@ -1,15 +1,16 @@
import type { ReactElement } from 'react'; import type { FC } from 'react';
import { Text } from '@fractal-ui/styling'; import { Text } from '@fractal-ui/styling';
import * as S from './SidebarItem.styles'; import * as S from './SidebarItem.styles';
import type { Props } from './types';
const MobileMenuItem = ({ icon, href, exact, title }: Props): ReactElement => ( interface Props {
<S.StyledNavLink exact={exact} to={href}> icon: React.ReactNode;
<S.Box> }
{icon}
<Text.P4>{title}</Text.P4> const MobileMenuItem: FC<Props> = ({ icon, children }) => (
</S.Box> <S.Box>
</S.StyledNavLink> {icon}
<Text.P4>{children}</Text.P4>
</S.Box>
); );
export { MobileMenuItem }; export { MobileMenuItem };
@@ -1,6 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { MEDIA } from '@msb/shared'; import { MEDIA } from '@msb/shared';
import { NavLink } from 'react-router-dom';
const IconBox = styled.div({ const IconBox = styled.div({
display: 'flex', display: 'flex',
@@ -11,8 +10,10 @@ const Box = styled.div(({ theme }) => ({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '12px', gap: '12px',
height: '40px', height: '48px',
padding: '0 6px', borderRadius: '8px',
padding: '0 8px',
color: theme.colors.text.primary,
svg: { svg: {
width: '20px', width: '20px',
height: '20px', height: '20px',
@@ -20,6 +21,7 @@ const Box = styled.div(({ theme }) => ({
color: theme.colors.text.secondary, color: theme.colors.text.secondary,
}, },
[`@media ${MEDIA.mobile}`]: { [`@media ${MEDIA.mobile}`]: {
width: '72px',
svg: { svg: {
width: '22px', width: '22px',
height: '22px', height: '22px',
@@ -40,29 +42,4 @@ const Box = styled.div(({ theme }) => ({
}, },
})); }));
const StyledNavLink = styled(NavLink)(({ theme }) => ({ export { Box, IconBox };
display: 'block',
color: theme.colors.text.primary,
textDecoration: 'none',
'&.active': { fontWeight: 700 },
'&:hover': { fontWeight: 700 },
[`@media ${MEDIA.mobile}`]: {
'&&&': {
fontWeight: 'normal',
},
'&.active': {
color: theme.colors.text.accentBrand,
svg: {
color: theme.colors.text.accentBrand,
},
},
'&:hover': {
color: theme.colors.text.accentBrand,
svg: {
color: theme.colors.text.accentBrand,
},
},
},
}));
export { Box, IconBox, StyledNavLink };
@@ -1,15 +1,17 @@
import type { ReactElement } from 'react'; import type { FC } from 'react';
import { Text, ExtraText } from '@fractal-ui/styling'; import { Text, ExtraText } from '@fractal-ui/styling';
import * as S from './SidebarItem.styles'; import * as S from './SidebarItem.styles';
import type { Props } from './types';
const SidebarItem = ({ icon, href, exact, title, isDesktop }: Props): ReactElement => ( interface Props {
<S.StyledNavLink exact={exact} to={href}> icon: React.ReactNode;
<S.Box> isDesktop?: boolean;
{icon} }
{isDesktop ? <Text.P3>{title}</Text.P3> : <ExtraText.Description>{title}</ExtraText.Description>}
</S.Box> const SidebarItem: FC<Props> = ({ icon, isDesktop, children }) => (
</S.StyledNavLink> <S.Box>
{icon}
{isDesktop ? <Text.P3>{children}</Text.P3> : <ExtraText.Description>{children}</ExtraText.Description>}
</S.Box>
); );
export { SidebarItem }; export { SidebarItem };
@@ -1,9 +0,0 @@
interface Props {
href: string;
icon: React.ReactNode;
title: string;
isDesktop?: boolean;
exact: boolean;
}
export type { Props };
+19 -121
View File
@@ -1,138 +1,36 @@
import type { IWebpackAppConfig } from '@msb/mf-builder'; import type { IWebpackAppConfig } from '@msb/mf-builder';
import CopyPlugin from 'copy-webpack-plugin';
import path from 'node:path'; import path from 'node:path';
import packageJson from './package.json'; import packageJson from './package.json';
interface Module { const outputPath = path.resolve(__dirname, '../../msb-host');
context: string; const publicPath = path.resolve(__dirname, 'public');
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const config: IWebpackAppConfig = { const config: IWebpackAppConfig = {
moduleName: packageJson.name, moduleName: packageJson.name,
paths: { paths: {
outputPath: path.resolve(__dirname, '../../build/msb-host'), outputPath,
publicUrl: '/msb-host/',
}, },
devServerOptions: { devServerOptions: {
port: 3001, port: 3001,
}, },
optimizationOptions: { plugins: [
runtimeChunk: false, new CopyPlugin({
splitChunks: { patterns: [
minSize: 17_000, {
minRemainingSize: 0, context: publicPath,
minChunks: 1, from: '**/*.json',
maxAsyncRequests: 30, to: outputPath,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
minSize: 0,
priority: -5,
reuseExistingChunk: true,
}, },
default: { {
minChunks: 2, context: publicPath,
priority: -20, from: '**/*.png',
reuseExistingChunk: true, to: outputPath,
}, },
fractalPackages: { ],
test: /[/\\]node_modules[/\\](@fractal-ui)\//, }),
reuseExistingChunk: true, ],
chunks: 'all',
name: (module: Module) => module.resourceResolveData.descriptionFileData.name.replace('@', '').replace('/', '_'),
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
chunks: 'all',
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
},
},
},
},
moduleFederationOptions: {
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
'@msb/http': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/http'],
},
'@msb/shared': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/shared'],
},
'@fractal-ui/styling': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/styling'],
},
'@fractal-ui/core': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/core'],
},
'@fractal-ui/extended': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'styled-system': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-system'],
},
'@styled-system/css': {
singleton: true,
requiredVersion: packageJson.dependencies['@styled-system/css'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@emotion/react': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/react'],
},
'@emotion/styled': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/styled'],
},
'react-animate-height': {
singleton: true,
requiredVersion: packageJson.dependencies['react-animate-height'],
},
'react-dnd': {
singleton: true,
requiredVersion: packageJson.dependencies['react-dnd'],
},
'react-dnd-html5-backend': {
singleton: true,
requiredVersion: packageJson.dependencies['react-dnd-html5-backend'],
},
},
},
}; };
export default config; export default config;
+7 -10
View File
@@ -23,11 +23,12 @@
"dependencies": { "dependencies": {
"@emotion/react": "11.8.1", "@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1", "@emotion/styled": "11.8.1",
"@fractal-ui/core": "^30.2.0", "@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "^30.2.0", "@fractal-ui/composites": "30.2.0",
"@fractal-ui/library": "^30.2.0", "@fractal-ui/extended": "30.2.0",
"@fractal-ui/overlays": "^30.2.0", "@fractal-ui/library": "30.2.0",
"@fractal-ui/styling": "^30.1.0", "@fractal-ui/overlays": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"@msb/mf-utils": "^1.0.0", "@msb/mf-utils": "^1.0.0",
"@msb/shared": "1.0.0", "@msb/shared": "1.0.0",
"@msb/http": "1.0.0", "@msb/http": "1.0.0",
@@ -68,9 +69,5 @@
"lint-staged": "^12.3.4", "lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2" "react-test-renderer": "17.0.2"
}, },
"browserslist": [ "browserslist": [">0.2%", "not dead", "not op_mini all"]
">0.2%",
"not dead",
"not op_mini all"
]
} }
+2 -113
View File
@@ -3,68 +3,17 @@ import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path'; import path from 'node:path';
import packageJson from './package.json'; import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name); const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = { const config: IWebpackAppConfig = {
moduleName: packageJson.name, moduleName: packageJson.name,
paths: { paths: {
outputPath: path.resolve(__dirname, '../../build/msb-main-page'), outputPath: path.resolve(__dirname, '../../msb-main-page'),
publicUrl: 'auto', publicUrl: '/msb-main-page/',
}, },
devServerOptions: { devServerOptions: {
port: 3002, port: 3002,
}, },
optimizationOptions: {
runtimeChunk: false,
splitChunks: {
minSize: 17_000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
},
},
},
},
moduleFederationOptions: { moduleFederationOptions: {
exposes: { exposes: {
'./App': { './App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
name: `${packageName}_remote`, name: `${packageName}_remote`,
}, },
}, },
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'@msb/http': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/http'],
},
'@msb/shared': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/shared'],
},
'@fractal-ui/styling': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/styling'],
},
'@fractal-ui/core': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/core'],
},
'@fractal-ui/extended': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'styled-system': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-system'],
},
'@styled-system/css': {
singleton: true,
requiredVersion: packageJson.dependencies['@styled-system/css'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@emotion/react': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/react'],
},
'@emotion/styled': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/styled'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
},
}, },
}; };
+7 -10
View File
@@ -23,11 +23,12 @@
"dependencies": { "dependencies": {
"@emotion/react": "11.8.1", "@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1", "@emotion/styled": "11.8.1",
"@fractal-ui/core": "^30.2.0", "@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "^30.2.0", "@fractal-ui/composites": "30.2.0",
"@fractal-ui/library": "^30.2.0", "@fractal-ui/extended": "30.2.0",
"@fractal-ui/overlays": "^30.2.0", "@fractal-ui/library": "30.2.0",
"@fractal-ui/styling": "^30.1.0", "@fractal-ui/overlays": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"@msb/mf-utils": "^1.0.0", "@msb/mf-utils": "^1.0.0",
"@msb/http": "^1.0.0", "@msb/http": "^1.0.0",
"@msb/shared": "^1.0.0", "@msb/shared": "^1.0.0",
@@ -66,9 +67,5 @@
"lint-staged": "^12.3.4", "lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2" "react-test-renderer": "17.0.2"
}, },
"browserslist": [ "browserslist": [">0.2%", "not dead", "not op_mini all"]
">0.2%",
"not dead",
"not op_mini all"
]
} }
+2 -113
View File
@@ -3,68 +3,17 @@ import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path'; import path from 'node:path';
import packageJson from './package.json'; import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name); const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = { const config: IWebpackAppConfig = {
moduleName: packageJson.name, moduleName: packageJson.name,
paths: { paths: {
outputPath: path.resolve(__dirname, '../../build/msb-payments'), outputPath: path.resolve(__dirname, '../../msb-payments'),
publicUrl: 'auto', publicUrl: '/msb-payments/',
}, },
devServerOptions: { devServerOptions: {
port: 3004, port: 3004,
}, },
optimizationOptions: {
runtimeChunk: false,
splitChunks: {
minSize: 17_000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
},
},
},
},
moduleFederationOptions: { moduleFederationOptions: {
exposes: { exposes: {
'./App': { './App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
name: `${packageName}_remote`, name: `${packageName}_remote`,
}, },
}, },
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
'@msb/http': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/http'],
},
'@msb/shared': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/shared'],
},
'@fractal-ui/styling': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/styling'],
},
'@fractal-ui/core': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/core'],
},
'@fractal-ui/extended': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'styled-system': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-system'],
},
'@styled-system/css': {
singleton: true,
requiredVersion: packageJson.dependencies['@styled-system/css'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@emotion/react': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/react'],
},
'@emotion/styled': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/styled'],
},
},
}, },
}; };
@@ -23,11 +23,13 @@
"dependencies": { "dependencies": {
"@emotion/react": "11.8.1", "@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1", "@emotion/styled": "11.8.1",
"@fractal-ui/core": "^30.2.0", "@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "^30.2.0", "@fractal-ui/extended": "30.2.0",
"@fractal-ui/library": "^30.2.0", "@fractal-ui/composites": "30.2.0",
"@fractal-ui/overlays": "^30.2.0", "@fractal-ui/library": "30.2.0",
"@fractal-ui/styling": "^30.1.0", "@fractal-ui/overlays": "30.2.0",
"@fractal-ui/table": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"@msb/mf-utils": "^1.0.0", "@msb/mf-utils": "^1.0.0",
"@msb/http": "^1.0.0", "@msb/http": "^1.0.0",
"@msb/shared": "^1.0.0", "@msb/shared": "^1.0.0",
@@ -68,9 +70,5 @@
"lint-staged": "^12.3.4", "lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2" "react-test-renderer": "17.0.2"
}, },
"browserslist": [ "browserslist": [">0.2%", "not dead", "not op_mini all"]
">0.2%",
"not dead",
"not op_mini all"
]
} }
@@ -3,68 +3,17 @@ import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path'; import path from 'node:path';
import packageJson from './package.json'; import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name); const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = { const config: IWebpackAppConfig = {
moduleName: packageJson.name, moduleName: packageJson.name,
paths: { paths: {
outputPath: path.resolve(__dirname, '../../build/msb-statements-and-inquiries'), outputPath: path.resolve(__dirname, '../../msb-statements-and-inquiries'),
publicUrl: 'auto', publicUrl: '/msb-statements-and-inquiries/',
}, },
devServerOptions: { devServerOptions: {
port: 3005, port: 3005,
}, },
optimizationOptions: {
runtimeChunk: false,
splitChunks: {
minSize: 17_000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
minSize: 0,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
fractalPackages: {
test: /[/\\]node_modules[/\\](@fractal-ui)\//,
reuseExistingChunk: true,
name: (module: Module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name?.replace('@', '').replace('/', '_');
},
priority: 10,
},
reactPackages: {
test: /[/\\]node_modules[/\\](react|react-dom|react-router-dom|react-animate-height|react-dnd|react-dnd-html5-backend)\//,
reuseExistingChunk: true,
name: (module: Module) => module.resourceResolveData.descriptionFileData.name,
priority: 20,
},
},
},
},
moduleFederationOptions: { moduleFederationOptions: {
exposes: { exposes: {
'./App': { './App': {
@@ -72,70 +21,6 @@ const config: IWebpackAppConfig = {
name: `${packageName}_remote`, name: `${packageName}_remote`,
}, },
}, },
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: packageJson.dependencies['react-dom'],
},
'react-router-dom': {
singleton: true,
requiredVersion: packageJson.dependencies['react-router-dom'],
},
'@msb/http': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/http'],
},
'@msb/shared': {
singleton: true,
requiredVersion: packageJson.dependencies['@msb/shared'],
},
'@fractal-ui/styling': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/styling'],
},
'@fractal-ui/core': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/core'],
},
'@fractal-ui/extended': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/extended'],
},
'@fractal-ui/overlays': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/overlays'],
},
'styled-system': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-system'],
},
'@styled-system/css': {
singleton: true,
requiredVersion: packageJson.dependencies['@styled-system/css'],
},
'@fractal-ui/library': {
singleton: true,
requiredVersion: packageJson.dependencies['@fractal-ui/library'],
},
'@emotion/react': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/react'],
},
'@emotion/styled': {
singleton: true,
requiredVersion: packageJson.dependencies['@emotion/styled'],
},
'styled-components': {
singleton: true,
requiredVersion: packageJson.dependencies['styled-components'],
},
},
}, },
}; };