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
.idea
.vscode
/msb-*
+1 -1
View File
@@ -1,2 +1,2 @@
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
+24 -71
View File
@@ -47,81 +47,34 @@
1. Настройка микрофронтового модуля
Создать `webpack.config.ts` с параметрами конфигурации:
```ts
/** webpack.config.ts */
import type { IWebpackAppConfig } from '@msb/mf-builder';
import { normalizePackageName } from '@msb/mf-builder';
import packageJson from './package.json';
```ts
/** webpack.config.ts */
import type { IWebpackAppConfig } from '@msb/mf-builder';
import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path';
import packageJson from './package.json';
const config: WebpackAppConfig = {
paths: { entryPath: '/', publicPath: '/', outputPath: '/', srcPath: '/', publicUrl: '/' },
devServerOptions: { port: 3001, open: true },
const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../msb-main-page'),
publicUrl: '/msb-main-page/',
},
devServerOptions: {
port: 3002,
},
moduleFederationOptions: {
exposes: {
'./App': path.resolve(__dirname, 'src/exposes/App.tsx'),
},
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/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'],
'./App': {
import: path.resolve(__dirname, 'src/exposes/App.tsx'),
name: `${packageName}_remote`,
},
},
},
};
};
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;
}
}
}
+1595 -24215
View File
File diff suppressed because it is too large Load Diff
+13 -8
View File
@@ -1,10 +1,9 @@
{
"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,
"workspaces": [
"packages/*",
"services/*"
],
"scripts": {
"start": "lerna run start --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",
"build": "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-fix": "lerna run lint-fix --scope=msb-* --stream",
"check-types": "lerna run check-types --scope=msb-* --stream",
@@ -22,9 +22,9 @@
},
"devDependencies": {
"@commitlint/cli": "^16.3.0",
"@commitlint/config-conventional": "^8.3.4",
"@eco/eslint-config": "^21.10.0",
"@eco/eslint-plugin": "^21.7.0",
"@commitlint/config-conventional": "8.3.4",
"@eco/eslint-config": "22.0.0",
"@eco/eslint-plugin": "21.7.0",
"@eco/lint-staged-config": "21.10.0",
"@eco/prettier-config": "21.10.0",
"@eco/stylelint-config": "^22.0.0",
@@ -42,6 +42,11 @@
},
"peerDependencies": {
"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 {
modules: IRemoteRouteConfig[];
settings: {
rateManagementHost: string;
apiPath: string;
authProviderHost: string;
};
}
@@ -89,9 +89,7 @@ const SETTINGS_MOCK = {
},
],
settings: {
rateManagementHost: 'foo',
apiPath: 'bar',
authProviderHost: 'baz',
apiPath: '',
},
};
+3 -3
View File
@@ -1,13 +1,13 @@
import { fetchDepositManagementFundsHandlers } from '../treasury-deals-client';
import { settingsHandlers } from './fetchAppSettings';
import { clientOrganizationsHandlers } from './fetchClientOrganizations';
import { fetchDepositDocumentNewForEditHandlers } from './fetchDepositDocumentNewForEdit';
import { fetchDepositGeneralAgreementsHandlers } from './fetchDepositGeneralAgreements';
import { financialAccountsHandlers } from './fetchFinancialAccounts';
import { fetchProductsHandlers } from './fetchProducts';
import { fetchUserAuthoritiesHandlers } from './fetchUserAuthorities';
import { fetchUserInfoUnionHandlerHandlers } from './fetchUserInfoUnion';
import { statementsHandlers } from './statements';
import { fetchDepositGeneralAgreementsHandlers } from './fetchDepositGeneralAgreements';
import { fetchDepositManagementFundsHandlers } from '../treasury-deals-client';
const handlers = [
...settingsHandlers,
@@ -19,7 +19,7 @@ const handlers = [
...fetchDepositDocumentNewForEditHandlers,
...statementsHandlers,
...fetchDepositGeneralAgreementsHandlers,
...fetchDepositManagementFundsHandlers
...fetchDepositManagementFundsHandlers,
];
export { handlers };
+2 -1
View File
@@ -7,7 +7,8 @@
"@tanstack/react-query": "4.36.1",
"axios": "1.7.9",
"use-sync-external-store": "^1.2.0",
"@tanstack/query-core": "4.36.1"
"@tanstack/query-core": "4.36.1",
"msw": "1.3.5"
},
"devDependencies": {
"msw": "1.3.5"
+1
View File
@@ -5,3 +5,4 @@ export * from './cookies';
export * from './dateTime';
export * from './useRedirect';
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",
"description": "",
"main": "index.ts",
"files": [
"assets",
"constants",
"lib"
],
"files": ["assets", "constants", "lib"],
"dependencies": {
"@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;
if (Array.isArray(otherPlugins) && Array.isArray(this.config.plugins)) {
this.config.plugins.concat(otherPlugins);
this.config.plugins = this.config.plugins.concat(otherPlugins);
}
}
merge(newConfig) {
@@ -15,9 +15,53 @@ const setModuleFederationPlugin = ({ moduleName, moduleFederationOptions, }) =>
singleton: 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,
eager: true,
},
} }, moduleFederationOptions));
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: {
chunks: 'all',
maxInitialRequests: Number.POSITIVE_INFINITY,
minSize: 0,
const setOptimizationRules = (options) => (Object.assign({ minimize: true, minimizer: [new terser_webpack_plugin_1.default()], runtimeChunk: false, splitChunks: {
minSize: 17000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30000,
cacheGroups: {
vendor: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name(module) {
var _a;
const packageName = (_a = module.context.match(/[/\\]node_modules[/\\](.*?)([/\\]|$)/)) !== null && _a !== void 0 ? _a : 'package';
return `npm.${packageName[1].replace('@', '')}`;
name: (module) => {
const { name } = module.resourceResolveData.descriptionFileData;
return name === null || name === void 0 ? void 0 : 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) => {
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));
+1 -5
View File
@@ -4,11 +4,7 @@
"description": "CLI для сборки модулей с помощью Webpack 5",
"main": "lib/index.js",
"types": "types/index.d.ts",
"files": [
"bin",
"lib",
"types"
],
"files": ["bin", "lib", "types"],
"bin": {
"build-app": "bin/build-app.js"
},
@@ -118,7 +118,7 @@ export class WebpackConfigBuilder {
const otherPlugins = this.appConfig.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,
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,
eager: true,
},
},
...moduleFederationOptions,
@@ -1,24 +1,58 @@
import TerserPlugin from 'terser-webpack-plugin';
import type { Configuration } from 'webpack';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
/**
* Установка правил оптимизации сборки.
*/
export const setOptimizationRules = (options?: Configuration['optimization']): Configuration['optimization'] => ({
minimize: true,
minimizer: [new TerserPlugin()],
runtimeChunk: false,
splitChunks: {
chunks: 'all',
maxInitialRequests: Number.POSITIVE_INFINITY,
minSize: 0,
minSize: 17_000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
automaticNameDelimiter: '_',
maxInitialRequests: 30,
enforceSizeThreshold: 30_000,
cacheGroups: {
vendor: {
vendors: {
test: /[/\\]node_modules[/\\]/,
name(module: { context: string }) {
const packageName = module.context.match(/[/\\]node_modules[/\\](.*?)([/\\]|$)/) ?? 'package';
name: (module: Module) => {
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": {
"@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1",
"@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",
"@fractal-ui/core": "30.2.0",
"@fractal-ui/extended": "30.2.0",
"@fractal-ui/library": "30.2.0",
"@fractal-ui/composites": "30.2.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/http": "^1.0.0",
"@msb/shared": "^1.0.0",
@@ -66,9 +68,5 @@
"lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2"
},
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
"browserslist": [">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 packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../build/msb-deposits'),
publicUrl: 'auto',
outputPath: path.resolve(__dirname, '../../msb-deposits'),
publicUrl: '/msb-deposits/',
},
devServerOptions: {
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: {
exposes: {
'./App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
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": {
"@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1",
"@fractal-ui/composites": "30.6.0",
"@fractal-ui/core": "30.6.0",
"@fractal-ui/extended": "30.6.0",
"@fractal-ui/form": "30.6.0",
"@fractal-ui/layout": "30.6.0",
"@fractal-ui/library": "30.6.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",
"@fractal-ui/composites": "30.2.0",
"@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",
"@msb/http": "^1.0.0",
"@msb/mf-utils": "^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>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/manifest.json">
<link rel="manifest" href="./manifest.json">
<title>ГАЗПРОМБАНК | МСБ</title>
</head>
<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 type { ReturnType } from './types';
import { useLayoutEffect } from 'react';
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';
export const useAppSettings = (): ReturnType => {
export const useAppSettings = () => {
const { data, error, isLoading, refetch } = useQuery<IRemoteSettings, Error | undefined>({
queryKey: [QUERY_KEYS_SETTINGS],
queryFn: fetchAppSettings,
@@ -10,14 +11,27 @@ export const useAppSettings = (): ReturnType => {
cacheTime: Number.POSITIVE_INFINITY,
});
const handleClick = (): void => {
refetch();
};
useLayoutEffect(() => {
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 {
modules: data?.modules,
error,
isLoading,
handleClick,
refetch,
};
};
@@ -6,10 +6,10 @@ import LayoutEmptyState from './LayoutEmptyState';
import { StyledThemeProvider } from '@/app/providers';
const Layout: FC = () => {
const { modules, isLoading: isSettingsLoading, error: settingsError, handleClick } = useAppSettings();
const { modules, isLoading: isSettingsLoading, error: settingsError, refetch } = useAppSettings();
if (settingsError || isSettingsLoading)
return <LayoutEmptyState handleClick={handleClick} isLoading={isSettingsLoading} settingsError={settingsError} />;
return <LayoutEmptyState handleClick={refetch} isLoading={isSettingsLoading} settingsError={settingsError} />;
return (
<StyledThemeProvider>
@@ -2,6 +2,5 @@ const ACCOUNTS_NOT_FOUND = 'Счета недоступны';
const NOT_FOUND = 'Информация не загрузилась';
const NOT_FOUND_BUTTON_TEXT = 'Попробовать ещё раз';
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 { LoopArrowIcon } from '@fractal-ui/library';
import { Text } from '@fractal-ui/styling';
@@ -8,11 +9,12 @@ import { Loader } from '@/shared/ui/components';
interface Props {
isLoading: boolean;
disabled: boolean;
buttonWidth?: string;
refetch(): void;
}
const EmptyState: FC<Props> = ({ isLoading, buttonWidth, refetch }) => (
const EmptyState: FC<Props> = ({ disabled, isLoading, buttonWidth, refetch }) => (
<S.EmptyStateContentBox>
{isLoading ? (
<Loader dataName="loader" />
@@ -21,6 +23,7 @@ const EmptyState: FC<Props> = ({ isLoading, buttonWidth, refetch }) => (
<Text.P1>{NOT_FOUND}</Text.P1>
<Button
dataAction="reload"
disabled={disabled}
icon={LoopArrowIcon}
shape="default"
variant="blue"
@@ -1,6 +1,6 @@
import { useState, type ReactElement } from 'react';
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 { EmptyState } from './EmptyState';
import * as S from './Organizations.styles';
@@ -10,6 +10,8 @@ const MobileOrganizations = (): ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const { organizations, isLoading, refetch } = useClientOrganizations();
const { disabledRefetchButton, refetchData } = useRefetchData(refetch);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
@@ -32,7 +34,7 @@ const MobileOrganizations = (): ReactElement => {
</MobileOrganizationItem>
))
) : (
<EmptyState buttonWidth="100%" isLoading={isLoading} refetch={refetch} />
<EmptyState buttonWidth="100%" disabled={disabledRefetchButton} isLoading={isLoading} refetch={refetchData} />
)}
</Drawer>
</>
@@ -1,14 +1,20 @@
import styled from '@emotion/styled';
import { BriefcaseIcon } from '@fractal-ui/library';
const Organizations = styled.div(() => ({
const Organizations = styled.div<{ $isExpaned: boolean }>(({ theme, $isExpaned }) => ({
position: 'relative',
display: 'flex',
alignItems: 'center',
width: '216px',
maxWidth: '216px',
gap: '8px',
padding: '0 8px',
padding: '8px 14px 8px 8px',
borderRadius: '16px',
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)({
@@ -16,15 +22,13 @@ const StyledBriefcaseIcon = styled(BriefcaseIcon)({
});
const ToggleIconBox = styled.div<{ $isExpanded: boolean }>(({ $isExpanded, theme }) => ({
position: 'absolute',
right: '14px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: $isExpanded ? 'rotateX(180deg)' : 'rotateX(0deg)',
svg: {
width: '17px',
height: '17px',
width: '16px',
height: '16px',
color: theme.colors.text.primary,
},
}));
@@ -1,99 +1,17 @@
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 { Drawer, PopupContainer } from '@fractal-ui/overlays';
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';
import { type ReactElement } from 'react';
import { MEDIA, useMediaQuery } from '@msb/shared';
import { DesktopOrganizations } from './DesktopOrganizations';
import { TabletOrganizations } from './TabletOrganizations';
import { useClientOrganizations } from '@/entities/Organization';
const Organizations = (): ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef(null);
const dropdownRef = useRef(null);
const isDesktop = useMediaQuery(MEDIA.desktop);
const { organizations, isLoading, refetch } = useClientOrganizations();
const toggleOpen = () => {
setIsOpen(!isOpen);
};
useOnOutsideClick({
elements: [dropdownRef, containerRef],
handler: toggleOpen,
isActive: isOpen,
});
const { organizations, isLoading, error, refetch } = useClientOrganizations();
return isDesktop ? (
<>
<S.Organizations ref={containerRef} onClick={toggleOpen}>
<S.Amount>
{organizations.length > 1 ? (
<Text.P4>{organizations.length}</Text.P4>
<DesktopOrganizations error={error} isLoading={isLoading} organizations={organizations} refetch={refetch} />
) : (
<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>
</>
) : (
<>
<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>
</>
<TabletOrganizations error={error} isLoading={isLoading} organizations={organizations} refetch={refetch} />
);
};
@@ -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
.filter((item): item is RemoteRouteConfigVisibleOnMobile => 'showOnMobile' in item && item.showOnMobile)
.map(item => (
<MobileMenuItem
key={item.title}
exact={item.exact}
href={item.path}
icon={SIDEBAR_ICONS[item.path as PATHS]}
title={item.shortTitle}
/>
<S.StyledNavLink key={item.title} exact={item.exact} to={item.path}>
<MobileMenuItem icon={SIDEBAR_ICONS[item.path as PATHS]}>{item.shortTitle}</MobileMenuItem>
</S.StyledNavLink>
))}
<S.Divider />
<MobileMenuItem exact href={PATHS.CONTACT} icon={SIDEBAR_ICONS[PATHS.CONTACT]} title={LOCALIZATION.CONTACT_MOBILE} />
<MobileMenuItem exact href={PATHS.SERVICES} icon={SIDEBAR_ICONS[PATHS.SERVICES]} title={LOCALIZATION.SERVICES} />
<S.StyledNavLink exact to={PATHS.CONTACT}>
<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>
) : null}
</S.Navigation>
@@ -1,7 +1,81 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import styled from '@emotion/styled';
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 }) => ({
gridArea: 'sd',
@@ -39,29 +113,6 @@ const Tag = styled.div(({ theme }) => ({
[`@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 }) => ({
height: '1px',
marginTop: '12px',
@@ -71,4 +122,4 @@ const Divider = styled.div(({ theme }) => ({
[`@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 ? (
<S.List>
{modules.map(item => (
<SidebarItem
key={item.title}
exact={item.exact}
href={item.path}
icon={SIDEBAR_ICONS[item.path as PATHS]}
isDesktop={isDesktop}
title={item.title}
/>
<S.StyledNavLink key={item.title} exact={item.exact} to={item.path}>
<SidebarItem icon={SIDEBAR_ICONS[item.path as PATHS]} isDesktop={isDesktop}>
{item.title}
</SidebarItem>
</S.StyledNavLink>
))}
<S.Divider />
<SidebarItem exact href={PATHS.CONTACT} icon={SIDEBAR_ICONS[PATHS.CONTACT]} isDesktop={isDesktop} title={LOCALIZATION.CONTACT} />
<S.NewItem>
<SidebarItem
exact
href={PATHS.SERVICES}
icon={SIDEBAR_ICONS[PATHS.SERVICES]}
isDesktop={isDesktop}
title={LOCALIZATION.SERVICES}
/>
<S.StyledNavLink exact to={PATHS.CONTACT}>
<SidebarItem icon={SIDEBAR_ICONS[PATHS.CONTACT]} isDesktop={isDesktop}>
{LOCALIZATION.CONTACT}
</SidebarItem>
</S.StyledNavLink>
<S.StyledNavLink exact to={PATHS.SERVICES}>
<S.NewServices>
<SidebarItem icon={SIDEBAR_ICONS[PATHS.SERVICES]} isDesktop={isDesktop}>
{LOCALIZATION.SERVICES}
</SidebarItem>
<S.Tag>New</S.Tag>
</S.NewItem>
</S.NewServices>
</S.StyledNavLink>
</S.List>
) : null}
</S.Navigation>
@@ -1,15 +1,16 @@
import type { ReactElement } from 'react';
import type { FC } from 'react';
import { Text } from '@fractal-ui/styling';
import * as S from './SidebarItem.styles';
import type { Props } from './types';
const MobileMenuItem = ({ icon, href, exact, title }: Props): ReactElement => (
<S.StyledNavLink exact={exact} to={href}>
interface Props {
icon: React.ReactNode;
}
const MobileMenuItem: FC<Props> = ({ icon, children }) => (
<S.Box>
{icon}
<Text.P4>{title}</Text.P4>
<Text.P4>{children}</Text.P4>
</S.Box>
</S.StyledNavLink>
);
export { MobileMenuItem };
@@ -1,6 +1,5 @@
import styled from '@emotion/styled';
import { MEDIA } from '@msb/shared';
import { NavLink } from 'react-router-dom';
const IconBox = styled.div({
display: 'flex',
@@ -11,8 +10,10 @@ const Box = styled.div(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: '12px',
height: '40px',
padding: '0 6px',
height: '48px',
borderRadius: '8px',
padding: '0 8px',
color: theme.colors.text.primary,
svg: {
width: '20px',
height: '20px',
@@ -20,6 +21,7 @@ const Box = styled.div(({ theme }) => ({
color: theme.colors.text.secondary,
},
[`@media ${MEDIA.mobile}`]: {
width: '72px',
svg: {
width: '22px',
height: '22px',
@@ -40,29 +42,4 @@ const Box = styled.div(({ theme }) => ({
},
}));
const StyledNavLink = styled(NavLink)(({ theme }) => ({
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 };
export { Box, IconBox };
@@ -1,15 +1,17 @@
import type { ReactElement } from 'react';
import type { FC } from 'react';
import { Text, ExtraText } from '@fractal-ui/styling';
import * as S from './SidebarItem.styles';
import type { Props } from './types';
const SidebarItem = ({ icon, href, exact, title, isDesktop }: Props): ReactElement => (
<S.StyledNavLink exact={exact} to={href}>
interface Props {
icon: React.ReactNode;
isDesktop?: boolean;
}
const SidebarItem: FC<Props> = ({ icon, isDesktop, children }) => (
<S.Box>
{icon}
{isDesktop ? <Text.P3>{title}</Text.P3> : <ExtraText.Description>{title}</ExtraText.Description>}
{isDesktop ? <Text.P3>{children}</Text.P3> : <ExtraText.Description>{children}</ExtraText.Description>}
</S.Box>
</S.StyledNavLink>
);
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 CopyPlugin from 'copy-webpack-plugin';
import path from 'node:path';
import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const outputPath = path.resolve(__dirname, '../../msb-host');
const publicPath = path.resolve(__dirname, 'public');
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../build/msb-host'),
outputPath,
publicUrl: '/msb-host/',
},
devServerOptions: {
port: 3001,
},
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: -5,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
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'],
},
plugins: [
new CopyPlugin({
patterns: [
{
context: publicPath,
from: '**/*.json',
to: outputPath,
},
{
context: publicPath,
from: '**/*.png',
to: outputPath,
},
],
}),
],
};
export default config;
+7 -10
View File
@@ -23,11 +23,12 @@
"dependencies": {
"@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1",
"@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",
"@fractal-ui/core": "30.2.0",
"@fractal-ui/composites": "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",
"@msb/mf-utils": "^1.0.0",
"@msb/shared": "1.0.0",
"@msb/http": "1.0.0",
@@ -68,9 +69,5 @@
"lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2"
},
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
"browserslist": [">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 packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../build/msb-main-page'),
publicUrl: 'auto',
outputPath: path.resolve(__dirname, '../../msb-main-page'),
publicUrl: '/msb-main-page/',
},
devServerOptions: {
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: {
exposes: {
'./App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
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": {
"@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1",
"@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",
"@fractal-ui/core": "30.2.0",
"@fractal-ui/composites": "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",
"@msb/mf-utils": "^1.0.0",
"@msb/http": "^1.0.0",
"@msb/shared": "^1.0.0",
@@ -66,9 +67,5 @@
"lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2"
},
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
"browserslist": [">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 packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../build/msb-payments'),
publicUrl: 'auto',
outputPath: path.resolve(__dirname, '../../msb-payments'),
publicUrl: '/msb-payments/',
},
devServerOptions: {
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: {
exposes: {
'./App': {
@@ -72,66 +21,6 @@ const config: IWebpackAppConfig = {
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": {
"@emotion/react": "11.8.1",
"@emotion/styled": "11.8.1",
"@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",
"@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/overlays": "30.2.0",
"@fractal-ui/table": "30.2.0",
"@fractal-ui/styling": "30.1.0",
"@msb/mf-utils": "^1.0.0",
"@msb/http": "^1.0.0",
"@msb/shared": "^1.0.0",
@@ -68,9 +70,5 @@
"lint-staged": "^12.3.4",
"react-test-renderer": "17.0.2"
},
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
"browserslist": [">0.2%", "not dead", "not op_mini all"]
}
@@ -3,68 +3,17 @@ import { normalizePackageName } from '@msb/mf-builder';
import path from 'node:path';
import packageJson from './package.json';
interface Module {
context: string;
resourceResolveData: { descriptionFileData: Record<string, string> };
}
const packageName = normalizePackageName(packageJson.name);
const config: IWebpackAppConfig = {
moduleName: packageJson.name,
paths: {
outputPath: path.resolve(__dirname, '../../build/msb-statements-and-inquiries'),
publicUrl: 'auto',
outputPath: path.resolve(__dirname, '../../msb-statements-and-inquiries'),
publicUrl: '/msb-statements-and-inquiries/',
},
devServerOptions: {
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: {
exposes: {
'./App': {
@@ -72,70 +21,6 @@ const config: IWebpackAppConfig = {
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'],
},
},
},
};