mirror of
https://github.com/elyby/accounts-frontend.git
synced 2026-10-20 12:48:09 +05:30
Upgrade project's build infrastructure: Node 22, Webpack 5, TypeScript 5, Storybook 8
This commit is contained in:
100 files changed
+7512
-10487
No files matched your search
@@ -1,5 +1,6 @@
|
||||
[production]
|
||||
> 0.25%
|
||||
not iOS < 14
|
||||
not dead
|
||||
|
||||
[development]
|
||||
|
||||
@@ -9,7 +9,6 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
|
||||
|
||||
+3
-5
@@ -1,11 +1,9 @@
|
||||
variables:
|
||||
NODE_IMAGE: node:14-alpine
|
||||
NODE_E2E_IMAGE: circleci/node:14-buster-browsers
|
||||
NODE_IMAGE: node:22-alpine
|
||||
DEPLOY_IMAGE: docksal/ssh-agent:1.3
|
||||
# To cache both npm modules and Cypress binary we use environment variables
|
||||
# to point at the folders we can list as paths in "cache" job settings
|
||||
# To cache npm modules we use an environment variable
|
||||
# to point at the folder we list as a path in "cache" job settings
|
||||
YARN_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/yarn"
|
||||
CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/cache/cypress"
|
||||
|
||||
GIT_DEPTH: 10
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import 'storybook-addon-intl/register';
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
||||
import '@storybook/addon-viewport/register';
|
||||
@@ -1,13 +0,0 @@
|
||||
import { configure, addDecorator } from '@storybook/react';
|
||||
|
||||
import storyDecorator from './storyDecorator';
|
||||
|
||||
const req = require.context('../packages/app', true, /\.story\.[tj]sx?$/);
|
||||
|
||||
function loadStories() {
|
||||
req.keys().forEach((filename) => req(filename));
|
||||
}
|
||||
|
||||
addDecorator(storyDecorator);
|
||||
|
||||
configure(loadStories, module);
|
||||
@@ -1,36 +1,19 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Channel } from '@storybook/channels';
|
||||
import { setIntlConfig } from 'storybook-addon-intl';
|
||||
import { EVENT_SET_LOCALE_ID, EVENT_GET_LOCALE_ID } from 'storybook-addon-intl/dist/shared';
|
||||
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from 'app/services/i18n';
|
||||
|
||||
import { setLocale } from 'app/components/i18n/actions';
|
||||
import { useReduxDispatch } from 'app/functions';
|
||||
|
||||
setIntlConfig({
|
||||
locales: SUPPORTED_LANGUAGES,
|
||||
defaultLocale: DEFAULT_LANGUAGE,
|
||||
});
|
||||
interface Props {
|
||||
locale: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const IntlDecorator: React.ComponentType<{
|
||||
channel: Channel;
|
||||
}> = ({ channel, children }) => {
|
||||
const dispatch = useDispatch();
|
||||
const IntlDecorator: React.ComponentType<Props> = ({ locale, children }) => {
|
||||
const dispatch = useReduxDispatch();
|
||||
|
||||
React.useEffect(() => {
|
||||
const onLocaleChange = (locale: string) => {
|
||||
dispatch(setLocale(locale));
|
||||
};
|
||||
|
||||
// Listen for change of locale
|
||||
channel.on(EVENT_SET_LOCALE_ID, onLocaleChange);
|
||||
|
||||
// Request the current locale
|
||||
channel.emit(EVENT_GET_LOCALE_ID);
|
||||
|
||||
return () => {
|
||||
channel.removeListener(EVENT_SET_LOCALE_ID, onLocaleChange);
|
||||
};
|
||||
}, [channel]);
|
||||
dispatch(setLocale(locale));
|
||||
}, [dispatch, locale]);
|
||||
|
||||
return children as React.ReactElement;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
import { ContextReplacementPlugin } from 'webpack';
|
||||
|
||||
import rootConfig from '../webpack.config';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../packages/app/**/*.story.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-toolbars',
|
||||
'@storybook/addon-viewport',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {},
|
||||
},
|
||||
webpackFinal: (config) => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
...rootConfig.resolve,
|
||||
alias: {
|
||||
// Storybook's own presets contribute aliases we must keep — e.g. @storybook/react-dom-shim's
|
||||
// preset aliases itself to the React 16/17-compatible shim when it detects React < 18
|
||||
// (see @storybook/react-dom-shim/dist/preset.js). Overwriting the whole `resolve` (and thus
|
||||
// dropping this alias) makes webpack fall back to the React 18 shim, which imports the
|
||||
// nonexistent (in React 17) `react-dom/client` module.
|
||||
...config.resolve?.alias,
|
||||
...rootConfig.resolve.alias,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
...config.module,
|
||||
rules: [
|
||||
...rootConfig.module.rules,
|
||||
// Our webpack config's rules above fully replace Storybook's own module.rules, which drops
|
||||
// the fix Storybook normally applies for strict ESM resolution of .mjs files (see
|
||||
// @storybook/preset-react-webpack's framework-preset-cra.js). Without it, webpack requires
|
||||
// an explicit extension for every bare import inside real ESM files such as
|
||||
// @storybook/react/dist/entry-preview.mjs, and fails to resolve its `react-dom/test-utils`
|
||||
// import (react-dom doesn't declare an "exports" map to compensate). Restore it explicitly.
|
||||
{ test: /\.m?js$/, resolve: { fullySpecified: false } },
|
||||
],
|
||||
},
|
||||
resolveLoader: rootConfig.resolveLoader,
|
||||
plugins: [
|
||||
...(config.plugins || []),
|
||||
...rootConfig.plugins.filter((plugin) => plugin instanceof ContextReplacementPlugin),
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Preview } from '@storybook/react';
|
||||
|
||||
import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from 'app/services/i18n';
|
||||
|
||||
import storyDecorator from './storyDecorator';
|
||||
|
||||
function getLanguageTitle(locale: string): string {
|
||||
try {
|
||||
return new Intl.DisplayNames(['en'], { type: 'language' }).of(locale) ?? locale;
|
||||
} catch {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [storyDecorator],
|
||||
globalTypes: {
|
||||
locale: {
|
||||
name: 'Locale',
|
||||
description: 'Application locale (react-intl)',
|
||||
defaultValue: DEFAULT_LANGUAGE,
|
||||
toolbar: {
|
||||
icon: 'globe',
|
||||
dynamicTitle: true,
|
||||
items: SUPPORTED_LANGUAGES.map((locale) => ({
|
||||
value: locale,
|
||||
title: getLanguageTitle(locale),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import addons, { DecoratorFunction } from '@storybook/addons';
|
||||
import type { Decorator } from '@storybook/react';
|
||||
|
||||
import { ContextProvider } from 'app/shell';
|
||||
import { browserHistory } from 'app/services/history';
|
||||
import storeFactory from 'app/storeFactory';
|
||||
@@ -9,12 +10,10 @@ import { IntlDecorator } from './decorators';
|
||||
|
||||
const store = storeFactory();
|
||||
|
||||
export default ((story) => {
|
||||
const channel = addons.getChannel();
|
||||
const storyDecorator: Decorator = (story, context) => (
|
||||
<ContextProvider store={store} history={browserHistory}>
|
||||
<IntlDecorator locale={context.globals.locale}>{story()}</IntlDecorator>
|
||||
</ContextProvider>
|
||||
);
|
||||
|
||||
return (
|
||||
<ContextProvider store={store} history={browserHistory}>
|
||||
<IntlDecorator channel={channel}>{story()}</IntlDecorator>
|
||||
</ContextProvider>
|
||||
);
|
||||
}) as DecoratorFunction<React.ReactElement>;
|
||||
export default storyDecorator;
|
||||
@@ -1,23 +0,0 @@
|
||||
// @ts-nocheck
|
||||
const { ContextReplacementPlugin } = require('webpack');
|
||||
|
||||
const rootConfig = require('../webpack.config');
|
||||
|
||||
module.exports = async ({ config }) => ({
|
||||
...config,
|
||||
resolve: rootConfig.resolve,
|
||||
|
||||
module: {
|
||||
...config.module,
|
||||
// our rules should satisfy all storybook needs,
|
||||
// so replace all storybook defaults with our rules
|
||||
rules: rootConfig.module.rules,
|
||||
},
|
||||
|
||||
resolveLoader: rootConfig.resolveLoader,
|
||||
|
||||
plugins: [
|
||||
...config.plugins,
|
||||
...rootConfig.plugins.filter((plugin) => plugin instanceof ContextReplacementPlugin),
|
||||
],
|
||||
});
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
# test directories
|
||||
__tests__
|
||||
test
|
||||
tests
|
||||
powered-test
|
||||
|
||||
# asset directories
|
||||
docs
|
||||
doc
|
||||
website
|
||||
images
|
||||
assets
|
||||
|
||||
# examples
|
||||
example
|
||||
examples
|
||||
|
||||
# code coverage directories
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# build scripts
|
||||
Makefile
|
||||
Gulpfile.js
|
||||
Gruntfile.js
|
||||
|
||||
# configs
|
||||
.tern-project
|
||||
.gitattributes
|
||||
.editorconfig
|
||||
.*ignore
|
||||
.eslintrc
|
||||
.jshintrc
|
||||
.flowconfig
|
||||
.documentup.json
|
||||
.yarn-metadata.json
|
||||
.*.yml
|
||||
*.yml
|
||||
!.svgo.yml
|
||||
|
||||
# misc
|
||||
*.gz
|
||||
*.md
|
||||
+2
-4
@@ -3,6 +3,7 @@
|
||||
module.exports = function (api) {
|
||||
const env = api.env();
|
||||
const isProduction = api.env((envName) => envName.includes('production'));
|
||||
const { version: corejsVersion } = require('core-js/package.json');
|
||||
|
||||
api.cache.using(() => env);
|
||||
|
||||
@@ -16,7 +17,7 @@ module.exports = function (api) {
|
||||
ignoreBrowserslistConfig: false,
|
||||
modules: false,
|
||||
useBuiltIns: 'usage', // or "entry"
|
||||
corejs: 3,
|
||||
corejs: corejsVersion,
|
||||
include: ['proposal-class-properties'],
|
||||
},
|
||||
],
|
||||
@@ -52,9 +53,6 @@ module.exports = function (api) {
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-proposal-function-bind',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-transform-runtime',
|
||||
[
|
||||
'react-intl-auto',
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const { env } = process;
|
||||
|
||||
module.exports = {
|
||||
version: env.VERSION || env.NODE_ENV,
|
||||
apiHost: env.API_HOST || 'https://account.dev.ely.by',
|
||||
ga: env.GA_ID && { id: env.GA_ID },
|
||||
sentryDSN: env.SENTRY_DSN,
|
||||
crowdin: {
|
||||
apiKey: env.CROWDIN_API_KEY,
|
||||
projectId: 350687,
|
||||
filePath: 'accounts/site.json',
|
||||
sourceLang: 'en',
|
||||
basePath: `${__dirname}/packages/app/i18n`,
|
||||
minTranslated: 80, // Minimal ready percent before translation can be published
|
||||
},
|
||||
};
|
||||
+53
-79
@@ -16,14 +16,14 @@
|
||||
"license": "Apache-2.0",
|
||||
"repository": "https://github.com/elyby/accounts-frontend",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"tests-e2e"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "yarn run clean && yarn run build:dll && webpack-dev-server --colors",
|
||||
"start": "yarn run clean && webpack serve",
|
||||
"clean": "rm -rf ./build && mkdir ./build",
|
||||
"e2e": "yarn --cwd ./tests-e2e test",
|
||||
"test": "jest",
|
||||
@@ -40,13 +40,11 @@
|
||||
"i18n:pull": "babel-node --extensions \".ts\" ./packages/scripts/i18n-crowdin.ts pull",
|
||||
"build": "yarn run clean && yarn run build:webpack",
|
||||
"build:install": "yarn install",
|
||||
"build:webpack": "NODE_ENV=production webpack --colors -p --bail",
|
||||
"build:webpack": "NODE_ENV=production webpack --bail",
|
||||
"build:quiet": "yarn run clean && yarn run build:webpack --quiet",
|
||||
"build:dll": "babel-node --extensions '.ts,.d.ts' ./packages/scripts/build-dll.ts",
|
||||
"build:serve": "http-server --proxy https://account.dev.ely.by ./build",
|
||||
"sb": "APP_ENV=storybook start-storybook -p 9009 --ci",
|
||||
"sb:build": "APP_ENV=storybook build-storybook",
|
||||
"postinstall": "node -e \"try{require('./postinstall.js')}catch(e){}\""
|
||||
"sb": "APP_ENV=storybook storybook dev -p 9009",
|
||||
"sb:build": "APP_ENV=storybook storybook build"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{json,scss,css,md}": [
|
||||
@@ -60,6 +58,7 @@
|
||||
"roots": [
|
||||
"<rootDir>/packages/app"
|
||||
],
|
||||
"testEnvironment": "jsdom",
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/jest/setupAfterEnv.js"
|
||||
],
|
||||
@@ -87,92 +86,67 @@
|
||||
"react": "^17",
|
||||
"react-dom": "^17",
|
||||
"react-hot-loader": "^4",
|
||||
"react-intl": "^4.5.7",
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
"react-intl": "^4.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.10.5",
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/node": "^7.10.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.10.4",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.10.4",
|
||||
"@babel/plugin-proposal-function-bind": "^7.10.5",
|
||||
"@babel/plugin-proposal-function-sent": "^7.10.4",
|
||||
"@babel/plugin-proposal-json-strings": "^7.10.4",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.10.4",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.10.5",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.10.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/plugin-transform-runtime": "^7.10.5",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@babel/runtime-corejs3": "^7.10.5",
|
||||
"@storybook/addon-actions": "^5.3.18",
|
||||
"@storybook/addon-links": "^5.3.18",
|
||||
"@storybook/addon-viewport": "^5.3.18",
|
||||
"@storybook/addons": "^5.3.18",
|
||||
"@storybook/react": "^5.3.18",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@babel/cli": "^7",
|
||||
"@babel/core": "^7",
|
||||
"@babel/node": "^7",
|
||||
"@babel/plugin-transform-runtime": "^7",
|
||||
"@babel/preset-env": "^7",
|
||||
"@babel/preset-react": "^7",
|
||||
"@babel/preset-typescript": "^7",
|
||||
"@babel/runtime-corejs3": "^7",
|
||||
"@storybook/addon-actions": "^8.2.9",
|
||||
"@storybook/addon-links": "^8.2.9",
|
||||
"@storybook/addon-toolbars": "^8.2.9",
|
||||
"@storybook/addon-viewport": "^8.2.9",
|
||||
"@storybook/react": "^8.2.9",
|
||||
"@storybook/react-webpack5": "^8.2.9",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/sinon": "^9.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^3.0.0",
|
||||
"@typescript-eslint/parser": "^3.0.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"@types/webpack-env": "^1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"babel-loader": "^10",
|
||||
"babel-plugin-react-intl-auto": "https://github.com/elyby/babel-plugin-react-intl-auto.git#build",
|
||||
"core-js": "3.6.5",
|
||||
"csp-webpack-plugin": "^2.0.2",
|
||||
"css-loader": "^3.5.3",
|
||||
"core-js": "^3",
|
||||
"css-loader": "^7.1.2",
|
||||
"cssnano": "^4.1.10",
|
||||
"dotenv": "^8.2.0",
|
||||
"eager-imports-webpack-plugin": "^1.0.0",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-jsdoc": "^25.4.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-jsdoc": "^46.10.1",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"file-loader": "^6.0.0",
|
||||
"file-replace-loader": "^1.2.0",
|
||||
"html-loader": "^1.1.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"html-loader": "^5.1.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"imports-loader": "^0.8.0",
|
||||
"jest": "^26.0.1",
|
||||
"jest-watch-typeahead": "^0.6.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"lint-staged": "^10.2.4",
|
||||
"loader-utils": "^2.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"postcss": "^8.2.8",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-watch-typeahead": "^2.2.2",
|
||||
"lint-staged": "^15.2.7",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"postcss": "^8",
|
||||
"postcss-import": "^14.0.0",
|
||||
"postcss-loader": "^4.2.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss-logical-properties-polyfill": "^0.1.0",
|
||||
"postcss-scss": "^3.0.5",
|
||||
"prettier": "^2.0.5",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass-loader": "^10.2.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.77.8",
|
||||
"sass-loader": "^14.2.1",
|
||||
"sinon": "^9.0.2",
|
||||
"sitemap-webpack-plugin": "^0.8.0",
|
||||
"speed-measure-webpack-plugin": "^1.3.3",
|
||||
"storybook-addon-intl": "^2.4.1",
|
||||
"style-loader": "~1.2.1",
|
||||
"typescript": "^3.9.3",
|
||||
"sitemap-webpack-plugin": "^1",
|
||||
"storybook": "^8.2.9",
|
||||
"style-loader": "^4.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"unexpected": "^11.14.0",
|
||||
"unexpected-sinon": "^10.5.1",
|
||||
"url-loader": "^4.1.0",
|
||||
"wait-on": "^5.0.0",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-bundle-analyzer": "^3.8.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.10.1",
|
||||
"webpackbar": "^4.0.0"
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-cli": "^7",
|
||||
"webpack-dev-server": "^5"
|
||||
}
|
||||
}
|
||||
@@ -169,24 +169,6 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user authenticated during oauth', () => {
|
||||
beforeEach(() => {
|
||||
(getState as any).returns({
|
||||
accounts: {
|
||||
available: [],
|
||||
active: null,
|
||||
},
|
||||
user: {},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by',
|
||||
prompt: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when one account available', () => {
|
||||
beforeEach(() => {
|
||||
(getState as any).returns({
|
||||
|
||||
@@ -4,6 +4,11 @@ import { useHistory, useLocation, useRouteMatch } from 'react-router';
|
||||
import { Factory } from './factory';
|
||||
import PanelTransition from './PanelTransition';
|
||||
|
||||
export default {
|
||||
title: 'Components/Auth',
|
||||
excludeStories: /.*Presenter$/,
|
||||
};
|
||||
|
||||
interface AuthPresenterProps {
|
||||
factory: Factory;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import { AuthPresenter } from 'app/components/auth/Auth.story';
|
||||
|
||||
import AcceptRules from './AcceptRules';
|
||||
|
||||
storiesOf('Components/Auth', module).add('AcceptRules', () => <AuthPresenter factory={AcceptRules} />);
|
||||
export default { title: 'Components/Auth' };
|
||||
|
||||
export const AcceptRulesStory = () => <AuthPresenter factory={AcceptRules} />;
|
||||
AcceptRulesStory.storyName = 'AcceptRules';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import { AuthPresenter } from 'app/components/auth/Auth.story';
|
||||
|
||||
import Activation from './Activation';
|
||||
|
||||
export default { title: 'Components/Auth' };
|
||||
|
||||
// TODO: add case with provided key
|
||||
storiesOf('Components/Auth', module).add('Activation', () => <AuthPresenter factory={Activation} />);
|
||||
export const ActivationStory = () => <AuthPresenter factory={Activation} />;
|
||||
ActivationStory.storyName = 'Activation';
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.description {
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use 'sass:color';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.appInfo {
|
||||
max-width: 270px;
|
||||
@@ -43,7 +44,7 @@
|
||||
margin-top: 7px;
|
||||
|
||||
a {
|
||||
color: lighten($font-color, 10%);
|
||||
color: color.adjust($font-color, $lightness: 10%, $space: hsl);
|
||||
border-bottom-color: #666;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import { AuthPresenter } from 'app/components/auth/Auth.story';
|
||||
|
||||
import ChooseAccount from './ChooseAccount';
|
||||
|
||||
export default { title: 'Components/Auth' };
|
||||
|
||||
// TODO: provide accounts list
|
||||
// TODO: provide application name
|
||||
storiesOf('Components/Auth', module).add('ChooseAccount', () => <AuthPresenter factory={ChooseAccount} />);
|
||||
export const ChooseAccountStory = () => <AuthPresenter factory={ChooseAccount} />;
|
||||
ChooseAccountStory.storyName = 'ChooseAccount';
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.accountSwitcher {
|
||||
background: $black;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/panel.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/panel.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.accountSwitcherContainer {
|
||||
margin-left: -$bodyLeftRightPadding;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.finishPage {
|
||||
font-family: $font-family-title;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.avatar {
|
||||
width: 90px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.authInfo {
|
||||
// Отступы сверху и снизу разные т.к. мы ужимаем высоту линии строки с логином на 2 пикселя и из-за этого теряем отступ снизу
|
||||
@@ -10,7 +10,7 @@
|
||||
.authInfoAvatar {
|
||||
$size: 30px;
|
||||
|
||||
float: start;
|
||||
float: inline-start;
|
||||
height: $size;
|
||||
width: $size;
|
||||
font-size: $size;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.description {
|
||||
font-family: $font-family-title;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import ContactFormPopup from './ContactFormPopup';
|
||||
|
||||
storiesOf('Components/Popups', module).add('ContactFormPopup', () => (
|
||||
export default { title: 'Components/Popups' };
|
||||
|
||||
export const ContactFormPopupStory = () => (
|
||||
<ContactFormPopup
|
||||
onSubmit={(params) => {
|
||||
action('onSubmit')(params);
|
||||
@@ -13,4 +14,5 @@ storiesOf('Components/Popups', module).add('ContactFormPopup', () => (
|
||||
}}
|
||||
onClose={action('onClose')}
|
||||
/>
|
||||
));
|
||||
);
|
||||
ContactFormPopupStory.storyName = 'ContactFormPopup';
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import SuccessContactFormPopup from './SuccessContactFormPopup';
|
||||
|
||||
storiesOf('Components/Popups', module).add('SuccessContactFormPopup', () => (
|
||||
export default { title: 'Components/Popups' };
|
||||
|
||||
export const SuccessContactFormPopupStory = () => (
|
||||
<SuccessContactFormPopup email="email@ely.by" onClose={action('onClose')} />
|
||||
));
|
||||
);
|
||||
SuccessContactFormPopupStory.storyName = 'SuccessContactFormPopup';
|
||||
@@ -1,6 +1,6 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/popup/popup.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/popup/popup.scss' as *;
|
||||
|
||||
/* Form state */
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { ComponentType, ComponentProps } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import ApplicationsIndex from './ApplicationsIndex';
|
||||
@@ -15,7 +14,7 @@ export const DevLayout: ComponentType = ({ children }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const sampleApp: OauthAppResponse = {
|
||||
const sampleApp: OauthAppResponse = {
|
||||
clientId: 'my-application',
|
||||
clientSecret: 'cL1eNtS3cRE7xNJqfWQdqrMRKURfW1ssP4kiX6JDW0_szM-n-q',
|
||||
type: TYPE_WEB_APPLICATION,
|
||||
@@ -34,22 +33,32 @@ const commonProps: Omit<ComponentProps<typeof ApplicationsIndex>, 'isLoading' |
|
||||
deleteApp: async (clientId) => action('deleteApp')(clientId),
|
||||
};
|
||||
|
||||
storiesOf('Components/Dev/Apps/ApplicationsIndex', module)
|
||||
.addDecorator((storyFn) => <DevLayout>{storyFn()}</DevLayout>)
|
||||
.add('Guest', () => <ApplicationsIndex isLoading={false} displayForGuest applications={[]} {...commonProps} />)
|
||||
.add('Loading', () => <ApplicationsIndex isLoading displayForGuest={false} applications={[]} {...commonProps} />)
|
||||
.add('Empty', () => (
|
||||
<ApplicationsIndex isLoading={false} displayForGuest={false} applications={[]} {...commonProps} />
|
||||
))
|
||||
.add('With apps', () => (
|
||||
<ApplicationsIndex
|
||||
isLoading={false}
|
||||
displayForGuest={false}
|
||||
applications={[
|
||||
sampleApp,
|
||||
{ ...sampleApp, clientId: 'my-application1', countUsers: 10 },
|
||||
{ ...sampleApp, clientId: 'my-application2', countUsers: 1 },
|
||||
]}
|
||||
{...commonProps}
|
||||
/>
|
||||
));
|
||||
export default {
|
||||
title: 'Components/Dev/Apps/ApplicationsIndex',
|
||||
decorators: [(storyFn: () => React.ReactElement) => <DevLayout>{storyFn()}</DevLayout>],
|
||||
excludeStories: /.*Layout$/,
|
||||
};
|
||||
|
||||
export const Guest = () => <ApplicationsIndex isLoading={false} displayForGuest applications={[]} {...commonProps} />;
|
||||
|
||||
export const Loading = () => (
|
||||
<ApplicationsIndex isLoading displayForGuest={false} applications={[]} {...commonProps} />
|
||||
);
|
||||
|
||||
export const Empty = () => (
|
||||
<ApplicationsIndex isLoading={false} displayForGuest={false} applications={[]} {...commonProps} />
|
||||
);
|
||||
|
||||
export const WithApps = () => (
|
||||
<ApplicationsIndex
|
||||
isLoading={false}
|
||||
displayForGuest={false}
|
||||
applications={[
|
||||
sampleApp,
|
||||
{ ...sampleApp, clientId: 'my-application1', countUsers: 10 },
|
||||
{ ...sampleApp, clientId: 'my-application2', countUsers: 1 },
|
||||
]}
|
||||
{...commonProps}
|
||||
/>
|
||||
);
|
||||
WithApps.storyName = 'With apps';
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { OauthAppResponse } from 'app/services/api/oauth';
|
||||
@@ -28,78 +27,91 @@ const blankApp: OauthAppResponse = {
|
||||
|
||||
const onSubmit = async (form: FormModel) => action('onSubmit')(form);
|
||||
|
||||
storiesOf('Components/Dev/Apps/ApplicationForm', module)
|
||||
.addDecorator((storyFn) => <DevLayout>{storyFn()}</DevLayout>)
|
||||
.add('Create', () => {
|
||||
const [currentType, setType] = useState<ApplicationType | null>(null);
|
||||
export default {
|
||||
title: 'Components/Dev/Apps/ApplicationForm',
|
||||
decorators: [(storyFn: () => React.ReactElement) => <DevLayout>{storyFn()}</DevLayout>],
|
||||
};
|
||||
|
||||
return (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={currentType}
|
||||
setType={(type) => {
|
||||
action('setType')(type);
|
||||
setType(type);
|
||||
}}
|
||||
app={blankApp}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.add('Create website', () => (
|
||||
export const Create = () => {
|
||||
const [currentType, setType] = useState<ApplicationType | null>(null);
|
||||
|
||||
return (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_WEB_APPLICATION}
|
||||
setType={action('setType')}
|
||||
type={currentType}
|
||||
setType={(type) => {
|
||||
action('setType')(type);
|
||||
setType(type);
|
||||
}}
|
||||
app={blankApp}
|
||||
/>
|
||||
))
|
||||
.add('Create desktop application', () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_DESKTOP_APPLICATION}
|
||||
setType={action('setType')}
|
||||
app={blankApp}
|
||||
/>
|
||||
))
|
||||
.add('Create Minecraft server', () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_MINECRAFT_SERVER}
|
||||
setType={action('setType')}
|
||||
app={{
|
||||
...blankApp,
|
||||
type: TYPE_MINECRAFT_SERVER,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
.add('Update website', () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
type={TYPE_WEB_APPLICATION}
|
||||
app={{
|
||||
...blankApp,
|
||||
clientId: 'already-registered',
|
||||
}}
|
||||
/>
|
||||
))
|
||||
.add('Update Minecraft server', () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
type={TYPE_MINECRAFT_SERVER}
|
||||
app={{
|
||||
...blankApp,
|
||||
type: TYPE_MINECRAFT_SERVER,
|
||||
clientId: 'already-registered',
|
||||
}}
|
||||
/>
|
||||
));
|
||||
);
|
||||
};
|
||||
|
||||
export const CreateWebsite = () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_WEB_APPLICATION}
|
||||
setType={action('setType')}
|
||||
app={blankApp}
|
||||
/>
|
||||
);
|
||||
CreateWebsite.storyName = 'Create website';
|
||||
|
||||
export const CreateDesktopApplication = () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_DESKTOP_APPLICATION}
|
||||
setType={action('setType')}
|
||||
app={blankApp}
|
||||
/>
|
||||
);
|
||||
CreateDesktopApplication.storyName = 'Create desktop application';
|
||||
|
||||
export const CreateMinecraftServer = () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
displayTypeSwitcher
|
||||
type={TYPE_MINECRAFT_SERVER}
|
||||
setType={action('setType')}
|
||||
app={{
|
||||
...blankApp,
|
||||
type: TYPE_MINECRAFT_SERVER,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
CreateMinecraftServer.storyName = 'Create Minecraft server';
|
||||
|
||||
export const UpdateWebsite = () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
type={TYPE_WEB_APPLICATION}
|
||||
app={{
|
||||
...blankApp,
|
||||
clientId: 'already-registered',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
UpdateWebsite.storyName = 'Update website';
|
||||
|
||||
export const UpdateMinecraftServer = () => (
|
||||
<ApplicationForm
|
||||
form={new FormModel()}
|
||||
onSubmit={onSubmit}
|
||||
type={TYPE_MINECRAFT_SERVER}
|
||||
app={{
|
||||
...blankApp,
|
||||
type: TYPE_MINECRAFT_SERVER,
|
||||
clientId: 'already-registered',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
UpdateMinecraftServer.storyName = 'Update Minecraft server';
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.container {
|
||||
max-width: 500px;
|
||||
|
||||
@@ -114,7 +114,6 @@ export default class ApplicationItem extends React.Component<
|
||||
key="publickOauthApplicationDescription"
|
||||
defaultMessage="This is a public client and it can't have any secrets by design. To secure authorization flow, use the PKCE challenge code. <link>Read more</link>."
|
||||
values={{
|
||||
// @ts-expect-error those typings seems to be invalid in the current version of react-intl, but might be fixed later
|
||||
link: (nodes) => (
|
||||
<a href="https://www.oauth.com/oauth2-servers/pkce/" target="_blank">
|
||||
{nodes}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { ComponentType, CSSProperties } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import FooterMenu from './FooterMenu';
|
||||
|
||||
@@ -7,7 +6,9 @@ const PreviewWrapper: ComponentType<{ style?: CSSProperties }> = ({ style, child
|
||||
<div style={{ padding: '25px', width: 320, boxSizing: 'border-box', ...style }}>{children}</div>
|
||||
);
|
||||
|
||||
storiesOf('Components', module).add('FooterMenu', () => (
|
||||
export default { title: 'Components' };
|
||||
|
||||
export const FooterMenuStory = () => (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<PreviewWrapper>
|
||||
<FooterMenu />
|
||||
@@ -16,4 +17,5 @@ storiesOf('Components', module).add('FooterMenu', () => (
|
||||
<FooterMenu />
|
||||
</PreviewWrapper>
|
||||
</div>
|
||||
));
|
||||
);
|
||||
FooterMenuStory.storyName = 'FooterMenu';
|
||||
@@ -22,18 +22,19 @@ export function getCountriesList(): string[] {
|
||||
/**
|
||||
* Возвращает для указанной локали её флаг с учётом всех нюансов загрузки флага
|
||||
* и подбора соответствующего локали флага.
|
||||
*
|
||||
* @param {string} locale
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getLocaleIconUrl(locale: string): string {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
return require(`flag-icon-css/flags/4x3/${localeToCountryCode[locale] || locale}.svg`).default;
|
||||
return require(`./flags/${locale}.svg`);
|
||||
} catch (err) {
|
||||
if (!err.message.startsWith('Cannot find module')) {
|
||||
throw err;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
return require(`flag-icons/flags/4x3/${localeToCountryCode[locale] || locale}.svg`);
|
||||
} catch (err) {
|
||||
if (!err.message.startsWith('Cannot find module')) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import LanguageSwitcherPopup from './LanguageSwitcherPopup';
|
||||
|
||||
storiesOf('Components/Popups', module).add('LanguageSwitcherPopup', () => {
|
||||
export default { title: 'Components/Popups' };
|
||||
|
||||
export const LanguageSwitcherPopupStory = () => {
|
||||
const [activeLocale, setActiveLocale] = useState('en');
|
||||
|
||||
return (
|
||||
@@ -73,4 +74,5 @@ storiesOf('Components/Popups', module).add('LanguageSwitcherPopup', () => {
|
||||
onClose={action('onClose')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
LanguageSwitcherPopupStory.storyName = 'LanguageSwitcherPopup';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.languageLink {
|
||||
position: relative;
|
||||
@@ -28,7 +28,7 @@
|
||||
display: inline-block;
|
||||
margin-inline-end: 4px;
|
||||
height: $height;
|
||||
width: $height * 4 / 3;
|
||||
width: calc($height * 4 / 3);
|
||||
box-shadow: 0 0 1px rgba(#000, 0.2);
|
||||
|
||||
background-size: cover;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/popup/popup.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/popup/popup.scss' as *;
|
||||
|
||||
@mixin hideFooter {
|
||||
@media (max-height: 455px) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import AccountDeleted from './AccountDeleted';
|
||||
|
||||
storiesOf('Components/Profile', module).add('AccountDeleted', () => (
|
||||
export default { title: 'Components/Profile' };
|
||||
|
||||
export const AccountDeletedStory = () => (
|
||||
<ProfileLayout>
|
||||
<AccountDeleted
|
||||
onRestore={() =>
|
||||
@@ -17,4 +18,5 @@ storiesOf('Components/Profile', module).add('AccountDeleted', () => (
|
||||
}
|
||||
/>
|
||||
</ProfileLayout>
|
||||
));
|
||||
);
|
||||
AccountDeletedStory.storyName = 'AccountDeleted';
|
||||
@@ -1,18 +1,22 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React, { ComponentType, PropsWithChildren } from 'react';
|
||||
|
||||
import rootStyles from 'app/pages/root/root.scss';
|
||||
import profileStyles from 'app/pages/profile/profile.scss';
|
||||
|
||||
import Profile from './Profile';
|
||||
|
||||
export const ProfileLayout: ComponentType = ({ children }) => (
|
||||
<div className={rootStyles.wrapper}>
|
||||
<div className={profileStyles.container}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
import Profile from './Profile';
|
||||
export default {
|
||||
title: 'Components/Profile',
|
||||
excludeStories: /.*Layout$/,
|
||||
};
|
||||
|
||||
storiesOf('Components/Profile', module).add('Profile', () => (
|
||||
export const ProfileStory = () => (
|
||||
<ProfileLayout>
|
||||
<Profile
|
||||
user={{
|
||||
@@ -33,4 +37,5 @@ storiesOf('Components/Profile', module).add('Profile', () => (
|
||||
activeLocale={'en'}
|
||||
/>
|
||||
</ProfileLayout>
|
||||
));
|
||||
);
|
||||
ProfileStory.storyName = 'Profile';
|
||||
@@ -1,69 +1,80 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import ChangeEmail from './ChangeEmail';
|
||||
|
||||
storiesOf('Components/Profile/ChangeEmail', module)
|
||||
.addDecorator((story) => <ProfileLayout>{story()}</ProfileLayout>)
|
||||
.add('First step', () => (
|
||||
<ChangeEmail
|
||||
step={0}
|
||||
email="initial-email@ely.by"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
export default {
|
||||
title: 'Components/Profile/ChangeEmail',
|
||||
decorators: [(story: () => React.ReactElement) => <ProfileLayout>{story()}</ProfileLayout>],
|
||||
};
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Second step', () => (
|
||||
<ChangeEmail
|
||||
step={1}
|
||||
email="email-from-prev-step@ely.by"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
export const FirstStep = () => (
|
||||
<ChangeEmail
|
||||
step={0}
|
||||
email="initial-email@ely.by"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Second step with code', () => (
|
||||
<ChangeEmail
|
||||
step={1}
|
||||
code="I7SP06BUTLLM8MA03O"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
FirstStep.storyName = 'First step';
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Third step', () => (
|
||||
<ChangeEmail
|
||||
step={2}
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
export const SecondStep = () => (
|
||||
<ChangeEmail
|
||||
step={1}
|
||||
email="email-from-prev-step@ely.by"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Third step with code', () => (
|
||||
<ChangeEmail
|
||||
step={2}
|
||||
code="I7SP06BUTLLM8MA03O"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
SecondStep.storyName = 'Second step';
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
));
|
||||
export const SecondStepWithCode = () => (
|
||||
<ChangeEmail
|
||||
step={1}
|
||||
code="I7SP06BUTLLM8MA03O"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
SecondStepWithCode.storyName = 'Second step with code';
|
||||
|
||||
export const ThirdStep = () => (
|
||||
<ChangeEmail
|
||||
step={2}
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
ThirdStep.storyName = 'Third step';
|
||||
|
||||
export const ThirdStepWithCode = () => (
|
||||
<ChangeEmail
|
||||
step={2}
|
||||
code="I7SP06BUTLLM8MA03O"
|
||||
onSubmit={(step, form) => {
|
||||
action('onSubmit')(step, form);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
ThirdStepWithCode.storyName = 'Third step with code';
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { FormModel } from 'app/components/ui/form';
|
||||
@@ -7,7 +6,9 @@ import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import ChangePassword from './ChangePassword';
|
||||
|
||||
storiesOf('Components/Profile', module).add('ChangePassword', () => (
|
||||
export default { title: 'Components/Profile' };
|
||||
|
||||
export const ChangePasswordStory = () => (
|
||||
<ProfileLayout>
|
||||
<ChangePassword
|
||||
form={new FormModel()}
|
||||
@@ -18,4 +19,5 @@ storiesOf('Components/Profile', module).add('ChangePassword', () => (
|
||||
}}
|
||||
/>
|
||||
</ProfileLayout>
|
||||
));
|
||||
);
|
||||
ChangePasswordStory.storyName = 'ChangePassword';
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { FormModel } from 'app/components/ui/form';
|
||||
@@ -7,7 +6,9 @@ import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import ChangeUsername from './ChangeUsername';
|
||||
|
||||
storiesOf('Components/Profile', module).add('ChangeUsername', () => (
|
||||
export default { title: 'Components/Profile' };
|
||||
|
||||
export const ChangeUsernameStory = () => (
|
||||
<ProfileLayout>
|
||||
<ChangeUsername
|
||||
form={new FormModel()}
|
||||
@@ -20,4 +21,5 @@ storiesOf('Components/Profile', module).add('ChangeUsername', () => (
|
||||
}}
|
||||
/>
|
||||
</ProfileLayout>
|
||||
));
|
||||
);
|
||||
ChangeUsernameStory.storyName = 'ChangeUsername';
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
|
||||
storiesOf('Components/Profile', module).add('DeleteAccount', () => (
|
||||
export default { title: 'Components/Profile' };
|
||||
|
||||
export const DeleteAccountStory = () => (
|
||||
<ProfileLayout>
|
||||
<DeleteAccount
|
||||
onSubmit={async () => {
|
||||
@@ -14,4 +15,5 @@ storiesOf('Components/Profile', module).add('DeleteAccount', () => (
|
||||
}}
|
||||
/>
|
||||
</ProfileLayout>
|
||||
));
|
||||
);
|
||||
DeleteAccountStory.storyName = 'DeleteAccount';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.removableDataRow {
|
||||
composes: formRow from '~app/components/profile/profileForm.scss';
|
||||
|
||||
@@ -1,62 +1,70 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { ProfileLayout } from 'app/components/profile/Profile.story';
|
||||
|
||||
import MultiFactorAuth from './MultiFactorAuth';
|
||||
|
||||
storiesOf('Components/Profile/MultiFactorAuth', module)
|
||||
.addDecorator((story) => <ProfileLayout>{story()}</ProfileLayout>)
|
||||
.add('First step', () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={0}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
export default {
|
||||
title: 'Components/Profile/MultiFactorAuth',
|
||||
decorators: [(story: () => React.ReactElement) => <ProfileLayout>{story()}</ProfileLayout>],
|
||||
};
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Second step', () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={1}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
export const FirstStep = () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={0}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Third step', () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={2}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
FirstStep.storyName = 'First step';
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
))
|
||||
.add('Enabled', () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled
|
||||
step={0}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
export const SecondStep = () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={1}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
));
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
SecondStep.storyName = 'Second step';
|
||||
|
||||
export const ThirdStep = () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled={false}
|
||||
step={2}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
ThirdStep.storyName = 'Third step';
|
||||
|
||||
export const Enabled = () => (
|
||||
<MultiFactorAuth
|
||||
isMfaEnabled
|
||||
step={0}
|
||||
onSubmit={(form, sendData) => {
|
||||
action('onSubmit')(form, sendData);
|
||||
|
||||
return Promise.resolve();
|
||||
}}
|
||||
onComplete={action('onComplete')}
|
||||
onChangeStep={action('onChangeStep')}
|
||||
/>
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.instructionTitle {
|
||||
font-family: $font-family-title;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.mfaTitle {
|
||||
composes: sectionTitle from '~app/components/profile/profileForm.scss';
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { FormModel } from 'app/components/ui/form';
|
||||
|
||||
import PasswordRequestForm from './PasswordRequestForm';
|
||||
|
||||
storiesOf('Components/Popups/PasswordRequestForm', module)
|
||||
.add('empty', () => <PasswordRequestForm form={new FormModel()} onSubmit={action('onSubmit')} />)
|
||||
.add('with error', () => {
|
||||
const form = new FormModel();
|
||||
form.bindField('password');
|
||||
form.errors['password'] = 'error.password_incorrect';
|
||||
export default { title: 'Components/Popups/PasswordRequestForm' };
|
||||
|
||||
return <PasswordRequestForm form={form} onSubmit={action('onSubmit')} />;
|
||||
});
|
||||
export const Empty = () => <PasswordRequestForm form={new FormModel()} onSubmit={action('onSubmit')} />;
|
||||
|
||||
export const WithError = () => {
|
||||
const form = new FormModel();
|
||||
form.bindField('password');
|
||||
form.errors['password'] = 'error.password_incorrect';
|
||||
|
||||
return <PasswordRequestForm form={form} onSubmit={action('onSubmit')} />;
|
||||
};
|
||||
WithError.storyName = 'with error';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/popup/popup.scss';
|
||||
@use '~app/components/ui/popup/popup.scss' as *;
|
||||
|
||||
.boundings {
|
||||
@include popupBounding(280px);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
$formColumnWidth: 416px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.contentWithBackButton {
|
||||
position: relative;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import SourceCodePopup from './SourceCodePopup';
|
||||
|
||||
storiesOf('Components/Popups', module).add('SourceCodePopup', () => <SourceCodePopup onClose={action('onClose')} />);
|
||||
export default { title: 'Components/Popups' };
|
||||
|
||||
export const SourceCodePopupStory = () => <SourceCodePopup onClose={action('onClose')} />;
|
||||
SourceCodePopupStory.storyName = 'SourceCodePopup';
|
||||
@@ -1,6 +1,6 @@
|
||||
@import '~app/components/ui/popup/popup.scss';
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/popup/popup.scss' as *;
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.boundings {
|
||||
@include popupBounding(420px);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import BSoD from './BSoD';
|
||||
|
||||
storiesOf('UI', module).add('BSoD', () => <BSoD />);
|
||||
export default { title: 'UI' };
|
||||
|
||||
export const BSoDStory = () => <BSoD />;
|
||||
BSoDStory.storyName = 'BSoD';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono', monospace;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import './colors.scss';
|
||||
@import './fonts.scss';
|
||||
@use './colors.scss' as *;
|
||||
@use './fonts.scss' as *;
|
||||
|
||||
@mixin button-theme($themeName, $backgroundColor) {
|
||||
.#{$themeName} {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.overflow {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:map';
|
||||
|
||||
$green: #207e5c;
|
||||
$blue: #5b9aa9;
|
||||
$red: #e66c69;
|
||||
@@ -25,10 +28,10 @@ $whiteButtonDark: #f5f5f5; // TODO: найти оптимальный цвет
|
||||
$black: #1c1c1c,
|
||||
);
|
||||
|
||||
@if (map_has_key($elyColorsMap, $color)) {
|
||||
@return map-get($elyColorsMap, $color);
|
||||
@if (map.has-key($elyColorsMap, $color)) {
|
||||
@return map.get($elyColorsMap, $color);
|
||||
} @else {
|
||||
@return darken($color, 4%);
|
||||
@return color.adjust($color, $lightness: -4%, $space: hsl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +47,9 @@ $whiteButtonDark: #f5f5f5; // TODO: найти оптимальный цвет
|
||||
$black: #2e2e2e,
|
||||
);
|
||||
|
||||
@if (map_has_key($elyColorsMap, $color)) {
|
||||
@return map-get($elyColorsMap, $color);
|
||||
@if (map.has-key($elyColorsMap, $color)) {
|
||||
@return map.get($elyColorsMap, $color);
|
||||
} @else {
|
||||
@return lighten($color, 3%);
|
||||
@return color.adjust($color, $lightness: 3%, $space: hsl);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import Button from './Button';
|
||||
|
||||
storiesOf('UI/Form', module).add('Button', () => (
|
||||
export default { title: 'UI/Form' };
|
||||
|
||||
export const ButtonStory = () => (
|
||||
<>
|
||||
<div>
|
||||
<Button>Green Button</Button> <Button color="blue">Blue Button</Button>{' '}
|
||||
@@ -79,4 +80,5 @@ storiesOf('UI/Form', module).add('Button', () => (
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
));
|
||||
);
|
||||
ButtonStory.storyName = 'Button';
|
||||
@@ -131,7 +131,7 @@ export default class Form extends React.Component<Props, State> {
|
||||
result = (this.props as PropsWithoutForm).onSubmit!(new FormData(form));
|
||||
}
|
||||
|
||||
if (result && result.then) {
|
||||
if (result) {
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
result
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
import TextareaAutosize, { TextareaAutosizeProps } from 'react-textarea-autosize';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { uniqueId, omit } from 'app/functions';
|
||||
import { SKIN_DARK, COLOR_GREEN, Skin, Color } from 'app/components/ui';
|
||||
|
||||
@@ -11,8 +12,8 @@ import FormInputComponent from './FormInputComponent';
|
||||
interface OwnProps {
|
||||
placeholder?: string | MessageDescriptor;
|
||||
label?: string | MessageDescriptor;
|
||||
skin: Skin;
|
||||
color: Color;
|
||||
skin?: Skin;
|
||||
color?: Color;
|
||||
}
|
||||
|
||||
export default class TextArea extends FormInputComponent<OwnProps & Omit<TextareaAutosizeProps, keyof OwnProps>> {
|
||||
@@ -57,7 +58,7 @@ export default class TextArea extends FormInputComponent<OwnProps & Omit<Textare
|
||||
{label}
|
||||
<div className={styles.textAreaContainer}>
|
||||
<TextareaAutosize
|
||||
inputRef={this.elRef}
|
||||
ref={this.elRef}
|
||||
className={clsx(styles.textArea, styles[`${skin}TextField`], styles[`${color}TextField`])}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
$dropdownPadding: 15px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
@mixin form-transition() {
|
||||
// Анимация фона должна быть быстрее анимации рамки, т.к. визуально фон заполняется медленнее
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.componentLoader {
|
||||
text-align: center;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.panel {
|
||||
background: $black;
|
||||
@@ -25,7 +25,7 @@ $headerHeight: 60px;
|
||||
.headerControl {
|
||||
composes: black from '~app/components/ui/buttons.scss';
|
||||
|
||||
float: start;
|
||||
float: inline-start;
|
||||
overflow: hidden;
|
||||
height: $headerHeight - 1px;
|
||||
width: 49px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
$popupPadding: 20px; // Default content padding
|
||||
$popupMargin: 20px; // Outer popup margins
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.pseudoAvatarWrapper {
|
||||
position: relative;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.steps {
|
||||
height: 40px;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import LoggedInPanel from './LoggedInPanel';
|
||||
@@ -13,33 +12,39 @@ const activeAccount = {
|
||||
isDeleted: false,
|
||||
};
|
||||
|
||||
storiesOf('Components/Userbar', module)
|
||||
.addDecorator((storyFn) => (
|
||||
<div style={{ background: '#207e5c', paddingRight: '10px', textAlign: 'right' }}>{storyFn()}</div>
|
||||
))
|
||||
.add('LoggedInPanel', () => (
|
||||
<LoggedInPanel
|
||||
activeAccount={activeAccount}
|
||||
accounts={[
|
||||
activeAccount,
|
||||
{
|
||||
id: 2,
|
||||
username: 'AnotherMockUser',
|
||||
email: 'mock-user2@ely.by',
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
isDeleted: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'DeletedUser',
|
||||
email: 'i-am-deleted@ely.by',
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
isDeleted: true,
|
||||
},
|
||||
]}
|
||||
onSwitchAccount={async (account) => action('onSwitchAccount')(account)}
|
||||
onRemoveAccount={async (account) => action('onRemoveAccount')(account)}
|
||||
/>
|
||||
));
|
||||
export default {
|
||||
title: 'Components/Userbar',
|
||||
decorators: [
|
||||
(storyFn: () => React.ReactElement) => (
|
||||
<div style={{ background: '#207e5c', paddingRight: '10px', textAlign: 'right' }}>{storyFn()}</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const LoggedInPanelStory = () => (
|
||||
<LoggedInPanel
|
||||
activeAccount={activeAccount}
|
||||
accounts={[
|
||||
activeAccount,
|
||||
{
|
||||
id: 2,
|
||||
username: 'AnotherMockUser',
|
||||
email: 'mock-user2@ely.by',
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
isDeleted: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'DeletedUser',
|
||||
email: 'i-am-deleted@ely.by',
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
isDeleted: true,
|
||||
},
|
||||
]}
|
||||
onSwitchAccount={async (account) => action('onSwitchAccount')(account)}
|
||||
onRemoveAccount={async (account) => action('onRemoveAccount')(account)}
|
||||
/>
|
||||
);
|
||||
LoggedInPanelStory.storyName = 'LoggedInPanel';
|
||||
@@ -1,8 +1,8 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
// TODO: эту константу можно заимпортить из panel.scss, но это приводит к странным ошибкам
|
||||
//@import '~app/components/ui/panel.scss';
|
||||
//@use '~app/components/ui/panel.scss' as *;
|
||||
$bodyLeftRightPadding: 20px;
|
||||
|
||||
$lightBorderColor: #eee;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.loggedInPanel {
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default `
|
||||
</script>
|
||||
|
||||
<style>
|
||||
${require('!!css-loader!postcss-loader!sass-loader!app/index.scss')[0][1]}
|
||||
${require('!!css-loader!postcss-loader!sass-loader!app/components/ui/loader/loader.scss')[0][1]}
|
||||
${require('!!css-loader?{"esModule":false}!postcss-loader!sass-loader!app/index.scss').toString()}
|
||||
${require('!!css-loader?{"esModule":false}!postcss-loader!sass-loader!app/components/ui/loader/loader.scss').toString()}
|
||||
</style>
|
||||
`;
|
||||
@@ -1,7 +1,3 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg version="1.2" baseProfile="tiny" id="Слой_1"
|
||||
xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="17px" height="19px"
|
||||
viewBox="0 0 17 19">
|
||||
<polygon fill="#FFFFFF" points="17,11.93 10.927,11.93 10.927,18 6.073,18 6.073,11.93 0,11.93 0,7.064 6.073,7.064 6.073,1
|
||||
10.927,1 10.927,7.064 17,7.064 "/>
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" width="17px" height="19px" viewBox="0 0 17 19">
|
||||
<polygon points="17,11.93 10.927,11.93 10.927,18 6.073,18 6.073,11.93 0,11.93 0,7.064 6.073,7.064 6.073,1 10.927,1 10.927,7.064 17,7.064 "/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 387 B After Width: | Height: | Size: 254 B |
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg version="1.2" baseProfile="tiny" id="Слой_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="17px" height="19px" viewBox="0 0 17 19" xml:space="preserve">
|
||||
<path fill="#FFFFFF" d="M6.909,3.659c-2.346,0-4.25,1.903-4.25,4.25c0,2.347,1.903,4.248,4.25,4.248c2.347,0,4.249-1.9,4.249-4.248
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" width="17px" height="19px" viewBox="0 0 17 19">
|
||||
<path d="M6.909,3.659c-2.346,0-4.25,1.903-4.25,4.25c0,2.347,1.903,4.248,4.25,4.248c2.347,0,4.249-1.9,4.249-4.248
|
||||
C11.157,5.562,9.254,3.659,6.909,3.659z M16.831,16.617l-1.214,1.215c-0.222,0.223-0.586,0.223-0.809,0l-2.429-2.428
|
||||
c-0.224-0.223-0.224-0.588,0-0.813l0.067-0.064l-1.144-1.141c-0.029-0.029-0.044-0.066-0.066-0.098
|
||||
c-1.185,0.953-2.689,1.527-4.329,1.527C3.093,14.816,0,11.725,0,7.909S3.093,1,6.909,1s6.909,3.092,6.909,6.909
|
||||
|
||||
|
Before Width: | Height: | Size: 875 B After Width: | Height: | Size: 724 B |
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
html,
|
||||
body,
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
"clsx": "^1.1.0",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"debounce": "^1.0.0",
|
||||
"flag-icon-css": "^3.4.6",
|
||||
"flag-icons": "^7",
|
||||
"intl": "^1.2.5",
|
||||
"promise.prototype.finally": "3.1.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"raf": "^3.4.1",
|
||||
"raven-js": "^3.27.0",
|
||||
"react": "^17",
|
||||
"react-click-away-listener": "^1.4.3",
|
||||
@@ -25,23 +23,19 @@
|
||||
"react-motion": "^0.5.0",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-textarea-autosize": "^7.1.2",
|
||||
"react-textarea-autosize": "^8",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-localstorage": "^0.4.1",
|
||||
"redux-thunk": "^2.0.0",
|
||||
"rtl-detect": "^1.0.2",
|
||||
"url-search-params-polyfill": "^8.1.0",
|
||||
"webfontloader": "^1.6.26",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
"webfontloader": "^1.6.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.8.0",
|
||||
"@testing-library/react": "^12",
|
||||
"@types/debounce": "^1.2.0",
|
||||
"@types/intl": "^1.2.0",
|
||||
"@types/promise.prototype.finally": "^2.0.4",
|
||||
"@types/raf": "^3.4.0",
|
||||
"@types/react-dom": "^17",
|
||||
"@types/react-helmet": "^6.0.0",
|
||||
"@types/react-motion": "^0.0.29",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
// Original: http://codepen.io/vanderlanth/pen/rxpNMY
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
$sidebar-width: 320px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
|
||||
.page {
|
||||
border-top: 50px solid #ddd8ce;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
$toolbarHeight: 50px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
@use '~app/components/ui/colors.scss' as *;
|
||||
@use '~app/components/ui/fonts.scss' as *;
|
||||
|
||||
.rules {
|
||||
max-width: 500px;
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'url-search-params-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
import { shim as shimPromiseFinally } from 'promise.prototype.finally';
|
||||
import { polyfill as rafPolyfill } from 'raf';
|
||||
|
||||
rafPolyfill();
|
||||
shimPromiseFinally();
|
||||
|
||||
// allow :active styles in mobile Safary
|
||||
document.addEventListener('touchstart', () => {}, true);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default class RegisterState extends AbstractState {
|
||||
return context
|
||||
.run('register', payload)
|
||||
.then(() => context.setState(new CompleteState()))
|
||||
.catch((err) => err.errors || logger.warn('Error registering', err));
|
||||
.catch((err) => err?.errors || logger.warn('Error registering', err));
|
||||
}
|
||||
|
||||
reject(context: AuthContext, payload: Record<string, any>): void {
|
||||
|
||||
@@ -14,7 +14,7 @@ export default class ResendActivationState extends AbstractState {
|
||||
return context
|
||||
.run('resendActivation', payload)
|
||||
.then(() => context.setState(new ActivationState()))
|
||||
.catch((err) => err.errors || logger.warn('Error resending activation', err));
|
||||
.catch((err) => err?.errors || logger.warn('Error resending activation', err));
|
||||
}
|
||||
|
||||
reject(context: AuthContext): void {
|
||||
|
||||
@@ -1,35 +1,26 @@
|
||||
import logger from 'app/services/logger';
|
||||
|
||||
const needs = {
|
||||
intl: !window.Intl,
|
||||
plural: !window?.Intl?.PluralRules,
|
||||
// @ts-ignore ts has a little bit outdated definitions
|
||||
relative: !window?.Intl?.RelativeTimeFormat,
|
||||
};
|
||||
|
||||
/**
|
||||
* All modern browsers currently do support all required Intl APIs,
|
||||
* for the outdated browsers we will polyfill this api on demand
|
||||
*/
|
||||
|
||||
async function polyfill(locale: string): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (!needs.intl && Intl.DateTimeFormat.supportedLocalesOf([locale]).length === 0) {
|
||||
// fallback to polyfill in case, when browser does not support locale we need
|
||||
needs.intl = true;
|
||||
needs.plural = true;
|
||||
needs.relative = true;
|
||||
}
|
||||
let intlSupported = !!window.Intl;
|
||||
let pluralSupported = !!window.Intl?.PluralRules;
|
||||
let relativeSupported = !!window.Intl?.RelativeTimeFormat;
|
||||
let localeSupported = intlSupported && Intl.DateTimeFormat.supportedLocalesOf([locale]).length > 0;
|
||||
|
||||
if (needs.intl) {
|
||||
// If locale isn't supported, then we need to fully replace native intl in order to introduce a new locale
|
||||
if (!intlSupported || !localeSupported) {
|
||||
promises.push(polyfillIntl(locale));
|
||||
} else {
|
||||
if (needs.plural) {
|
||||
if (!pluralSupported) {
|
||||
promises.push(polyfillPlural(locale));
|
||||
}
|
||||
|
||||
if (needs.relative) {
|
||||
if (!relativeSupported) {
|
||||
promises.push(polyfillRelative(locale));
|
||||
}
|
||||
}
|
||||
@@ -62,16 +53,26 @@ async function polyfillIntl(locale: string): Promise<void> {
|
||||
|
||||
// MUST be loaded in series with the main polyfill
|
||||
await Promise.all([
|
||||
import(
|
||||
// WARNING: .js extension is required for proper function of ContextReplacementPlugin
|
||||
/* webpackChunkName: "intl-[request]" */
|
||||
`intl/locale-data/jsonp/${locale}.js`
|
||||
),
|
||||
polyfillLocale(locale),
|
||||
polyfillPlural(locale),
|
||||
polyfillRelative(locale),
|
||||
]);
|
||||
}
|
||||
|
||||
async function polyfillLocale(locale: string): Promise<void> {
|
||||
try {
|
||||
await import(
|
||||
/* webpackChunkName: "intl-[request]" */
|
||||
`./overrides/intl/${locale}.js`
|
||||
);
|
||||
} catch {
|
||||
await import(
|
||||
/* webpackChunkName: "intl-[request]" */
|
||||
`intl/locale-data/jsonp/${locale}.js`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function polyfillPlural(locale: string): Promise<void> {
|
||||
await import(
|
||||
/* webpackChunkName: "intl-pluralrules" */
|
||||
@@ -79,11 +80,19 @@ async function polyfillPlural(locale: string): Promise<void> {
|
||||
);
|
||||
|
||||
// MUST be loaded in series with the main polyfill
|
||||
await import(
|
||||
// WARNING: .js extension is required for proper function of ContextReplacementPlugin
|
||||
/* webpackChunkName: "intl-pluralrules-[request]" */
|
||||
`@formatjs/intl-pluralrules/dist/locale-data/${locale}.js`
|
||||
);
|
||||
try {
|
||||
await import(
|
||||
// WARNING: .js extension is required for proper function of ContextReplacementPlugin
|
||||
/* webpackChunkName: "intl-pluralrules-[request]" */
|
||||
`./overrides/pluralrules/${locale}.js`
|
||||
);
|
||||
} catch {
|
||||
await import(
|
||||
// WARNING: .js extension is required for proper function of ContextReplacementPlugin
|
||||
/* webpackChunkName: "intl-pluralrules-[request]" */
|
||||
`@formatjs/intl-pluralrules/dist/locale-data/${locale}.js`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function polyfillRelative(locale: string): Promise<void> {
|
||||
@@ -93,11 +102,17 @@ async function polyfillRelative(locale: string): Promise<void> {
|
||||
);
|
||||
|
||||
// MUST be loaded in series with the main polyfill
|
||||
await import(
|
||||
// WARNING: .js extension is required for proper function of ContextReplacementPlugin
|
||||
/* webpackChunkName: "intl-relativetimeformat-[request]" */
|
||||
`@formatjs/intl-relativetimeformat/dist/locale-data/${locale}.js`
|
||||
);
|
||||
try {
|
||||
await import(
|
||||
/* webpackChunkName: "intl-relativetimeformat-[request]" */
|
||||
`./overrides/relativetimeformat/${locale}.js`
|
||||
);
|
||||
} catch {
|
||||
await import(
|
||||
/* webpackChunkName: "intl-relativetimeformat-[request]" */
|
||||
`@formatjs/intl-relativetimeformat/dist/locale-data/${locale}.js`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default polyfill;
|
||||
@@ -1,68 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import type { Stats } from 'fs';
|
||||
import fs from 'fs';
|
||||
import webpack, { MultiCompiler } from 'webpack';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import webpackConfig from './../../webpack.dll.config';
|
||||
|
||||
// @ts-ignore
|
||||
const compiler: MultiCompiler = webpack(webpackConfig);
|
||||
|
||||
Promise.all([stat(`${__dirname}/../../yarn.lock`), stat(`${__dirname}/../../dll/vendor.json`)])
|
||||
.then(([lockFileStats, dllFileStats]) => {
|
||||
const lockFile = new Date(lockFileStats.mtime);
|
||||
const dll = new Date(dllFileStats.mtime);
|
||||
|
||||
if (dll < lockFile) {
|
||||
return Promise.reject({
|
||||
code: 'OUTDATED',
|
||||
});
|
||||
}
|
||||
|
||||
logResult(chalk.green('Current dlls are up to date!'));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.code !== 'ENOENT' && err.code !== 'OUTDATED') {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
console.log('Rebuilding dlls...');
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
logResult(
|
||||
chalk.green('Dll was successfully build in %s ms'),
|
||||
// @ts-expect-error - something wrong with webpack types
|
||||
stats.endTime - stats.startTime,
|
||||
);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logResult(chalk.red('Unexpected error checking dll state'), err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function logResult(...args: any[]): void {
|
||||
console.log('\n');
|
||||
console.log(...args);
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
function stat(path: string): Promise<Stats> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
err ? reject(err) : resolve(stats);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -16,22 +16,25 @@ import { prompt, DistinctQuestion } from 'inquirer';
|
||||
|
||||
import git from 'git-rev-sync';
|
||||
|
||||
import { crowdin as config } from './../../config';
|
||||
import 'dotenv/config';
|
||||
|
||||
if (!config.apiKey) {
|
||||
const CROWDIN_API_KEY = process.env.CROWDIN_API_KEY;
|
||||
|
||||
if (!CROWDIN_API_KEY) {
|
||||
console.error(ch.red`crowdinApiKey is required`);
|
||||
process.exit(126);
|
||||
}
|
||||
|
||||
const PROJECT_ID = config.projectId;
|
||||
const CROWDIN_FILE_PATH = config.filePath;
|
||||
const SOURCE_LANG = config.sourceLang;
|
||||
const LANG_DIR = config.basePath;
|
||||
// @ts-ignore
|
||||
const PROJECT_ID = parseInt(process.env.CROWDIN_PROJECT_ID || 350687);
|
||||
const CROWDIN_FILE_PATH = 'accounts/site.json';
|
||||
const SOURCE_LANG = 'en';
|
||||
const LANG_DIR = path.resolve(__dirname, '../app/i18n');
|
||||
const INDEX_FILE_NAME = 'index.js';
|
||||
const MIN_RELEASE_PROGRESS = config.minTranslated;
|
||||
const MIN_RELEASE_PROGRESS = 80; // Minimal ready percent before translation can be published
|
||||
|
||||
const crowdin = new Crowdin({
|
||||
token: config.apiKey,
|
||||
token: CROWDIN_API_KEY,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -62,6 +65,7 @@ const NATIVE_NAMES_MAP: Record<string, string> = {
|
||||
pt: 'Português do Brasil',
|
||||
sr: 'Српски',
|
||||
ro: 'Română',
|
||||
hu: 'Magyar',
|
||||
udm: 'Удмурт',
|
||||
zh: '简体中文',
|
||||
};
|
||||
|
||||
@@ -26,6 +26,6 @@
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/json5": "^0.0.30",
|
||||
"@types/progress": "^2.0.3",
|
||||
"@types/webpack": "^4.41.13"
|
||||
"@types/webpack": "^5.28.5"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
* https://github.com/DragonsInn/fontgen-loader
|
||||
*/
|
||||
|
||||
const loaderUtils = require('loader-utils');
|
||||
const crypto = require('crypto');
|
||||
const fontgen = require('webfonts-generator');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
@@ -69,12 +69,15 @@ function getFilesAndDeps(patterns, context) {
|
||||
|
||||
module.exports = function (content) {
|
||||
this.cacheable();
|
||||
const params = loaderUtils.getOptions(this) || {};
|
||||
const params = this.getOptions({}) || {};
|
||||
let config;
|
||||
try {
|
||||
config = JSON.parse(content);
|
||||
} catch (ex) {
|
||||
config = this.exec(content, this.resourcePath);
|
||||
// eslint-disable-next-line no-new-func
|
||||
const mod = { exports: {} };
|
||||
new Function('module', 'exports', content)(mod, mod.exports);
|
||||
config = mod.exports;
|
||||
}
|
||||
|
||||
config.__dirname = path.dirname(this.resourcePath);
|
||||
@@ -167,16 +170,13 @@ module.exports = function (content) {
|
||||
const format = formats[i];
|
||||
|
||||
if (embed) {
|
||||
urls[format] = `data:${mimeTypes[format]};charset=utf-8;base64,${new Buffer(res[format]).toString(
|
||||
'base64',
|
||||
)}`;
|
||||
urls[format] = `data:${mimeTypes[format]};charset=utf-8;base64,${Buffer.from(res[format]).toString('base64')}`;
|
||||
} else {
|
||||
let filename = config.fileName || params.fileName || '[hash]-[fontname][ext]';
|
||||
filename = filename.replace('[fontname]', fontconf.fontName).replace('[ext]', `.${format}`);
|
||||
|
||||
const url = loaderUtils.interpolateName(this.context, filename, {
|
||||
content: res[format],
|
||||
});
|
||||
const contentHash = crypto.createHash('sha1').update(res[format]).digest('hex').slice(0, 8);
|
||||
const url = filename.replace('[hash]', contentHash);
|
||||
|
||||
urls[format] = path.join(pub, url).replace(/\\/g, '/');
|
||||
this.emitFile(url, res[format]);
|
||||
|
||||
+48
-63
@@ -1,73 +1,58 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
const path = require('path');
|
||||
const loaderUtils = require('loader-utils');
|
||||
const fileCache = {};
|
||||
const isProduction = process.argv.some((arg) => arg === '-p');
|
||||
const rootPath = path.resolve('./src');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = ({ webpackLoaderContext: loader }) => ({
|
||||
syntax: 'postcss-scss',
|
||||
plugins: {
|
||||
'postcss-import': {
|
||||
addModulesDirectories: ['./src'],
|
||||
module.exports = (ctx) => {
|
||||
// postcss-loader v5+ uses ctx.webpack, v4 used ctx.webpackLoaderContext
|
||||
const loader = ctx.webpack || ctx.webpackLoaderContext;
|
||||
|
||||
resolve: (
|
||||
(defaultResolve) => (url, basedir, importOptions) =>
|
||||
defaultResolve(
|
||||
// mainly to remove '~' from request
|
||||
loaderUtils.urlToRequest(url),
|
||||
basedir,
|
||||
importOptions,
|
||||
)
|
||||
)(require('postcss-import/lib/resolve-id')),
|
||||
return {
|
||||
syntax: 'postcss-scss',
|
||||
plugins: [
|
||||
require('postcss-import')({
|
||||
addModulesDirectories: ['./src'],
|
||||
|
||||
load: ((defaultLoad) => (filename, importOptions) => {
|
||||
if (/\.font.(js|json)$/.test(filename)) {
|
||||
// separately process calls to font loader
|
||||
// e.g. `@import '~app/icons.font.json';`
|
||||
if (!fileCache[filename] || !isProduction) {
|
||||
// do not execute loader on the same file twice
|
||||
// this is an overcome for a bug with ExtractTextPlugin, for isProduction === true
|
||||
// when @imported files may be processed multiple times
|
||||
fileCache[filename] = new Promise((resolve, reject) =>
|
||||
loader.loadModule(filename, (err, source) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
resolve: (
|
||||
(defaultResolve) => (url, basedir, importOptions) =>
|
||||
defaultResolve(
|
||||
// mainly to remove '~' from request
|
||||
url.replace(/^~/, ''),
|
||||
basedir,
|
||||
importOptions,
|
||||
)
|
||||
)(require('postcss-import/lib/resolve-id')),
|
||||
|
||||
return;
|
||||
}
|
||||
load: ((defaultLoad) => (filename, importOptions) => {
|
||||
if (/\.font.(js|json)$/.test(filename)) {
|
||||
// separately process calls to font loader
|
||||
// e.g. `@import '~app/icons.font.json';`
|
||||
if (!fileCache[filename] || !isProduction) {
|
||||
fileCache[filename] = new Promise((resolve, reject) =>
|
||||
loader.loadModule(filename, (err, source) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
|
||||
resolve(loader.exec(source, rootPath));
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// loader.exec() was removed in webpack 5; evaluate the CommonJS module manually
|
||||
const mod = { exports: {} };
|
||||
|
||||
// eslint-disable-next-line no-new-func
|
||||
new Function('module', 'exports', 'require', source)(mod, mod.exports, require);
|
||||
resolve(mod.exports);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return fileCache[filename];
|
||||
}
|
||||
|
||||
return fileCache[filename];
|
||||
}
|
||||
|
||||
return defaultLoad(filename, importOptions);
|
||||
})(require('postcss-import/lib/load-content')),
|
||||
},
|
||||
'postcss-logical-properties-polyfill': {},
|
||||
// TODO: for some reason cssnano strips out @mixin declarations
|
||||
// cssnano: {
|
||||
// /**
|
||||
// * TODO: cssnano options
|
||||
// */
|
||||
// // autoprefixer: {
|
||||
// // add: true,
|
||||
// // remove: true,
|
||||
// // browsers: ['last 2 versions']
|
||||
// // },
|
||||
// // safe: true,
|
||||
// // // отключаем минификацию цветов, что бы она не ломала такие выражения:
|
||||
// // // composes: black from '~./buttons.scss';
|
||||
// // colormin: false,
|
||||
// // discardComments: {
|
||||
// // removeAll: true
|
||||
// // }
|
||||
// preset: 'default'
|
||||
// }
|
||||
},
|
||||
});
|
||||
return defaultLoad(filename, importOptions);
|
||||
})(require('postcss-import/lib/load-content')),
|
||||
}),
|
||||
require('postcss-logical-properties-polyfill').default(),
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const overrideMap = {
|
||||
// Override some intl definitions
|
||||
'packages/app/services/i18n/overrides/intl': 'node_modules/intl/locale-data/jsonp',
|
||||
'packages/app/services/i18n/overrides/pluralrules': 'node_modules/@formatjs/intl-pluralrules/dist/locale-data',
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
'packages/app/services/i18n/overrides/relativetimeformat': 'node_modules/@formatjs/intl-relativetimeformat/dist/locale-data',
|
||||
|
||||
// Override country flags
|
||||
'packages/app/components/i18n/flags': 'node_modules/flag-icon-css/flags/4x3',
|
||||
};
|
||||
|
||||
Object.entries(overrideMap).forEach(([sourceDir, targetDir]) => {
|
||||
fs.readdirSync(sourceDir).forEach((localeFile) => {
|
||||
fs.copyFileSync(path.join(sourceDir, localeFile), path.join(targetDir, localeFile));
|
||||
});
|
||||
});
|
||||
+2
-1
@@ -18,7 +18,8 @@
|
||||
// *.intl.json overrides (@see `webpack-loaders.d.ts`)
|
||||
"resolveJsonModule": false,
|
||||
"jsx": "preserve",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"useUnknownInCatchVariables": false
|
||||
},
|
||||
"include": ["@types", "packages", "config", ".storybook"],
|
||||
"exclude": ["packages/app/services/i18n/overrides"]
|
||||
|
||||
+88
-100
@@ -5,17 +5,15 @@ require('@babel/register')({
|
||||
extensions: ['.es6', '.es', '.jsx', '.js', '.mjs', '.ts', '.tsx'],
|
||||
});
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const SitemapPlugin = require('sitemap-webpack-plugin').default;
|
||||
const CSPPlugin = require('csp-webpack-plugin');
|
||||
const WebpackBar = require('webpackbar');
|
||||
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||
const EagerImportsPlugin = require('eager-imports-webpack-plugin').default;
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const config = require('./config');
|
||||
const SUPPORTED_LANGUAGES = Object.keys(require('app/i18n').default);
|
||||
const { getCountriesList } = require('app/components/i18n/localeFlags');
|
||||
const rootPath = path.resolve('./packages');
|
||||
@@ -28,19 +26,20 @@ const isDockerized = !!process.env.DOCKERIZED;
|
||||
const isStorybook = process.env.APP_ENV === 'storybook';
|
||||
const isCI = !!process.env.CI;
|
||||
const isSilent = isCI || process.argv.some((arg) => /quiet/.test(arg));
|
||||
const isCspEnabled = false;
|
||||
const enableDll = !isProduction && !isStorybook;
|
||||
const webpackEnv = isProduction ? 'production' : 'development';
|
||||
|
||||
process.env.NODE_ENV = webpackEnv;
|
||||
|
||||
const smp = new SpeedMeasurePlugin();
|
||||
// Collect our custom language flags in order to exclude them from packaged
|
||||
const customFlags = fs
|
||||
.readdirSync(path.resolve(rootPath, 'app/components/i18n/flags'))
|
||||
.filter((file) => file.endsWith('.svg'))
|
||||
.map((file) => path.basename(file, '.svg'));
|
||||
const flagIconCountries = getCountriesList().filter((country) => !customFlags.includes(country));
|
||||
|
||||
const webpackConfig = {
|
||||
mode: webpackEnv,
|
||||
|
||||
cache: true,
|
||||
|
||||
entry: {
|
||||
app: path.join(__dirname, 'packages/app'),
|
||||
},
|
||||
@@ -48,9 +47,18 @@ const webpackConfig = {
|
||||
output: {
|
||||
path: outputPath,
|
||||
publicPath: '/',
|
||||
filename: `[name].js?[${isProduction ? 'chunkhash' : 'hash'}]`,
|
||||
filename: `[name].js?[${isProduction ? 'contenthash' : 'fullhash'}]`,
|
||||
clean: true,
|
||||
},
|
||||
|
||||
// intl-messageformat@8.4.1 incorrectly declares ^5 parser dependency but uses ^6 exports.
|
||||
// These functions are never called since the app doesn't use skeleton date/number patterns.
|
||||
// Will be resolve when we will migrate to the newer intl version
|
||||
ignoreWarnings: [
|
||||
/export 'parseDateTimeSkeleton'.*was not found in 'intl-messageformat-parser'/,
|
||||
/export 'convertNumberSkeletonToNumberFormatOptions'.*was not found in 'intl-messageformat-parser'/,
|
||||
],
|
||||
|
||||
resolve: {
|
||||
modules: [rootPath, 'node_modules'],
|
||||
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
|
||||
@@ -59,7 +67,7 @@ const webpackConfig = {
|
||||
},
|
||||
},
|
||||
|
||||
devtool: 'cheap-module-source-map',
|
||||
devtool: isProduction ? 'hidden-source-map' : 'eval-source-map',
|
||||
|
||||
stats: {
|
||||
hash: false,
|
||||
@@ -78,47 +86,36 @@ const webpackConfig = {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new WebpackBar(),
|
||||
new webpack.DefinePlugin({
|
||||
'window.SENTRY_DSN': config.sentryDSN ? JSON.stringify(config.sentryDSN) : undefined,
|
||||
'window.GA_ID': config.ga && config.ga.id ? JSON.stringify(config.ga.id) : undefined,
|
||||
'window.SENTRY_DSN': process.env.SENTRY_DSN ? JSON.stringify(process.env.SENTRY_DSN) : undefined,
|
||||
'window.GA_ID': process.env.GA_ID ? JSON.stringify(process.env.GA_ID) : undefined,
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
__VERSION__: config.version || '',
|
||||
__VERSION__: process.env.VERSION || process.env.NODE_ENV,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'packages/app/index.ejs',
|
||||
favicon: 'packages/app/favicon.ico',
|
||||
scripts: enableDll ? ['/dll/vendor.dll.js'] : [],
|
||||
hash: false, // webpack does this for all our assets automagically
|
||||
filename: 'index.html',
|
||||
inject: false,
|
||||
minify: {
|
||||
collapseWhitespace: isProduction,
|
||||
},
|
||||
isCspEnabled,
|
||||
}),
|
||||
new SitemapPlugin(
|
||||
'https://account.ely.by',
|
||||
['/', '/register', '/resend-activation', '/activation', '/forgot-password', '/rules'],
|
||||
{
|
||||
lastMod: true,
|
||||
changeFreq: 'weekly',
|
||||
},
|
||||
),
|
||||
// restrict webpack import context, to create chunks only for supported locales
|
||||
// @see services/i18n/intlPolyfill.js
|
||||
new webpack.ContextReplacementPlugin(/locale-data/, new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.js$`)),
|
||||
// @see components/i18n/localeFlags.js
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/flag-icon-css\/flags\/4x3/,
|
||||
new RegExp(`/(${getCountriesList().join('|')})\\.svg`),
|
||||
/flag-icons\/flags\/4x3/,
|
||||
new RegExp(`/(${flagIconCountries.join('|')})\\.svg`),
|
||||
),
|
||||
// @see components/i18n/localeFlags.js
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/app\/components\/i18n\/flags/,
|
||||
new RegExp(`/(${getCountriesList().join('|')})\\.svg`),
|
||||
new RegExp(`/(${SUPPORTED_LANGUAGES.join('|')})\\.svg`),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -129,28 +126,35 @@ const webpackConfig = {
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
options: {
|
||||
// style-loader@1.1.2 is still buggy. It breaks our icon styles
|
||||
// (vertical align is broken and styles applied multiple times)
|
||||
// so we can not use it and it's new `esModule` options
|
||||
// esModule: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: isProduction ? '[hash:base64:5]' : '[path][name]-[local]',
|
||||
// css-loader v7 changed the default to 'camel-case-only', which broke
|
||||
// composes: some-dashed-class from '...', restore the v3 behaviour
|
||||
exportLocalsConvention: 'as-is',
|
||||
},
|
||||
esModule: true,
|
||||
esModule: false,
|
||||
importLoaders: 2,
|
||||
sourceMap: !isProduction,
|
||||
// Skip resolving absolute URLs (e.g. /assets/fonts/... from fontgen-loader)
|
||||
url: {
|
||||
filter: (url) => !url.startsWith('/'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
api: 'modern',
|
||||
sourceMap: !isProduction,
|
||||
sassOptions: {
|
||||
sourceMapIncludeSources: !isProduction,
|
||||
// icons.scss uses @import for fontgen-loader integration via postcss-import
|
||||
silenceDeprecations: ['import'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -176,21 +180,25 @@ const webpackConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|gif|jpg|svg)$/,
|
||||
loader: 'file-loader',
|
||||
query: {
|
||||
name: 'assets/[name].[ext]?[contenthash]',
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'assets/[name][ext]?[contenthash]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|ttf)$/,
|
||||
loader: 'file-loader',
|
||||
query: {
|
||||
name: 'assets/fonts/[name].[ext]?[contenthash]',
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'assets/fonts/[name][ext]?[contenthash]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
// first-render.js uses require('...html') in a template literal — needs a plain string
|
||||
esModule: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
// this is consumed by postcss-import
|
||||
@@ -234,19 +242,36 @@ if (isProduction) {
|
||||
filename: '[name].css?[contenthash]',
|
||||
}),
|
||||
);
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new SitemapPlugin({
|
||||
base: 'https://account.ely.by',
|
||||
paths: [
|
||||
'/',
|
||||
'/register',
|
||||
'/resend-activation',
|
||||
'/activation',
|
||||
'/forgot-password',
|
||||
'/rules',
|
||||
'/dev/applications',
|
||||
],
|
||||
options: {
|
||||
lastmod: true,
|
||||
changefreq: 'weekly',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
webpackConfig.devtool = 'hidden-source-map';
|
||||
|
||||
webpackConfig.optimization = {
|
||||
moduleIds: 'hashed',
|
||||
moduleIds: 'deterministic',
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
name: 'vendors',
|
||||
priority: 0,
|
||||
test: (m) => String(m.context).includes('node_modules'),
|
||||
test: /node_modules/,
|
||||
chunks: 'all',
|
||||
},
|
||||
polyfills: {
|
||||
@@ -256,12 +281,6 @@ if (isProduction) {
|
||||
enforce: true,
|
||||
},
|
||||
...SUPPORTED_LANGUAGES.reduce((acc, locale) => {
|
||||
const localePolyfills = [
|
||||
`intl-${locale}-js`,
|
||||
`intl-pluralrules-${locale}-js`,
|
||||
`intl-relativetimeformat-${locale}-js`,
|
||||
];
|
||||
|
||||
acc[locale] = {
|
||||
name: `locale-${locale}`,
|
||||
priority: 1,
|
||||
@@ -271,7 +290,15 @@ if (isProduction) {
|
||||
acc[`${locale}Polyfill`] = {
|
||||
name: `locale-${locale}-polyfill`,
|
||||
priority: 2,
|
||||
chunks: ({ name }) => localePolyfills.includes(name),
|
||||
// Matches the upstream locale-data chunks (intl-<locale>-js,
|
||||
// intl-pluralrules-<locale>-js, intl-relativetimeformat-<locale>-js)
|
||||
// as well as our ./overrides chunks. The latter live under a
|
||||
// resolve.modules root, so their [request] chunk name expands to the
|
||||
// full module path — but it still starts with `intl` and ends with
|
||||
// `-<locale>-js`, so a prefix/suffix match folds every variant in here
|
||||
// (and keeps that long, structure-leaking name out of the emitted files).
|
||||
chunks: ({ name }) =>
|
||||
typeof name === 'string' && name.startsWith('intl') && name.endsWith(`-${locale}-js`),
|
||||
enforce: true,
|
||||
};
|
||||
|
||||
@@ -281,61 +308,22 @@ if (isProduction) {
|
||||
},
|
||||
};
|
||||
} else {
|
||||
webpackConfig.plugins.push(
|
||||
// force webpack to use mode: eager chunk imports in dev mode
|
||||
// this will improve build performance
|
||||
// this mode will be default for dev builds in webpack 5
|
||||
new EagerImportsPlugin(),
|
||||
);
|
||||
|
||||
if (enableDll) {
|
||||
webpackConfig.plugins.push(
|
||||
new webpack.DllReferencePlugin({
|
||||
context: __dirname,
|
||||
manifest: require('./dll/vendor.json'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
webpackConfig.devServer = {
|
||||
host: 'localhost',
|
||||
port: 8080,
|
||||
proxy: config.apiHost && {
|
||||
'/api': {
|
||||
target: config.apiHost,
|
||||
changeOrigin: true, // add host http-header, based on target
|
||||
secure: false, // allow self-signed certs
|
||||
proxy: [
|
||||
{
|
||||
context: ['/api'],
|
||||
target: process.env.API_HOST || 'https://account.dev.ely.by',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
hot: true,
|
||||
inline: true,
|
||||
historyApiFallback: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (isCspEnabled) {
|
||||
webpackConfig.plugins.push(
|
||||
new CSPPlugin({
|
||||
'default-src': "'none'",
|
||||
'style-src': ["'self'", "'unsafe-inline'"],
|
||||
'script-src': [
|
||||
"'self'",
|
||||
"'nonce-edge-must-die'",
|
||||
"'unsafe-inline'",
|
||||
'https://www.google-analytics.com',
|
||||
'https://recaptcha.net/recaptcha/',
|
||||
'https://www.gstatic.com/recaptcha/',
|
||||
'https://www.gstatic.cn/recaptcha/',
|
||||
],
|
||||
'img-src': ["'self'", 'data:', 'www.google-analytics.com'],
|
||||
'font-src': ["'self'", 'data:'],
|
||||
'connect-src': ["'self'", 'https://sentry.ely.by'].concat(isProduction ? [] : ['ws://localhost:8080']),
|
||||
'frame-src': ['https://www.google.com/recaptcha/', 'https://recaptcha.net/recaptcha/'],
|
||||
'report-uri': 'https://sentry.ely.by/api/2/csp-report/?sentry_key=088e7718236a4f91937a81fb319a93f6',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (isDockerized) {
|
||||
webpackConfig.watchOptions = {
|
||||
poll: 2000,
|
||||
@@ -361,4 +349,4 @@ if (isSilent) {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = smp.wrap(webpackConfig);
|
||||
module.exports = webpackConfig;
|
||||
@@ -1,46 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const vendor = Object.keys(require('./packages/app/package.json').dependencies);
|
||||
const baseConfig = require('./babel.config');
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
throw new Error('Dll plugin should be used for dev mode only');
|
||||
}
|
||||
|
||||
const outputPath = path.join(__dirname, 'dll');
|
||||
|
||||
const webpackConfig = {
|
||||
mode: 'development',
|
||||
|
||||
entry: {
|
||||
vendor: vendor.filter(
|
||||
(item) =>
|
||||
// if we include rhl in dll. It won't work for some reason
|
||||
!item.includes('react-hot-loader'),
|
||||
),
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: '[name].dll.js?[hash]',
|
||||
path: outputPath,
|
||||
library: '[name]',
|
||||
publicPath: '/',
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
resolve: baseConfig.resolve,
|
||||
|
||||
plugins: [
|
||||
new webpack.DllPlugin({
|
||||
name: '[name]',
|
||||
path: path.join(outputPath, '[name].json'),
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = webpackConfig;
|
||||
Reference in new issue
Block a user