diff --git a/.eslintrc.js b/.eslintrc.js
index 2c0b736..14c0286 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -18,8 +18,10 @@ module.exports = {
ecmaVersion: 'latest',
sourceType: 'module',
},
- plugins: ['react', '@typescript-eslint', 'i18next', 'react-hooks'],
+ plugins: ['react', '@typescript-eslint', 'i18next', 'react-hooks', 'prettier'],
rules: {
+ 'prettier/prettier': 'error',
+ '@typescript-eslint/consistent-type-imports': 'warn',
'react/jsx-indent': [1, 2],
'react/jsx-indent-props': [1, 2],
indent: [1, 2],
@@ -33,6 +35,13 @@ module.exports = {
'react/function-component-definition': 'off',
'no-shadow': 'off',
'import/extensions': 'off',
+ 'import/order': [
+ 'warn',
+ {
+ groups: ['builtin', 'external', 'internal'],
+ 'newlines-between': 'always',
+ },
+ ],
'import/no-extraneous-dependencies': 'off',
'no-underscore-dangle': 'off',
'i18next/no-literal-string': [
diff --git a/.husky/pre-commit b/.husky/pre-commit
index 15ce104..bda02c5 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,6 +1,8 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
+npm run format || exit 1
+
if [ "$GIT_BRANCH" = "master" ]; then
npm run lint:ts
npm run lint:scss
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_Loading.png b/.loki/reference/chrome_iphone7_features_LoginForm_Loading.png
new file mode 100644
index 0000000..ab73096
Binary files /dev/null and b/.loki/reference/chrome_iphone7_features_LoginForm_Loading.png differ
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_Loading_Dark.png b/.loki/reference/chrome_iphone7_features_LoginForm_Loading_Dark.png
new file mode 100644
index 0000000..fe8ce78
Binary files /dev/null and b/.loki/reference/chrome_iphone7_features_LoginForm_Loading_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_Primary.png b/.loki/reference/chrome_iphone7_features_LoginForm_Primary.png
index 1d67c0b..d25b6ee 100644
Binary files a/.loki/reference/chrome_iphone7_features_LoginForm_Primary.png and b/.loki/reference/chrome_iphone7_features_LoginForm_Primary.png differ
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_Primary_Dark.png b/.loki/reference/chrome_iphone7_features_LoginForm_Primary_Dark.png
new file mode 100644
index 0000000..79778a5
Binary files /dev/null and b/.loki/reference/chrome_iphone7_features_LoginForm_Primary_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_With_Error.png b/.loki/reference/chrome_iphone7_features_LoginForm_With_Error.png
new file mode 100644
index 0000000..770612a
Binary files /dev/null and b/.loki/reference/chrome_iphone7_features_LoginForm_With_Error.png differ
diff --git a/.loki/reference/chrome_iphone7_features_LoginForm_With_Error_Dark.png b/.loki/reference/chrome_iphone7_features_LoginForm_With_Error_Dark.png
new file mode 100644
index 0000000..12ca9e5
Binary files /dev/null and b/.loki/reference/chrome_iphone7_features_LoginForm_With_Error_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_pages_MainPage_Dark.png b/.loki/reference/chrome_iphone7_pages_MainPage_Dark.png
index a058b19..2642e76 100644
Binary files a/.loki/reference/chrome_iphone7_pages_MainPage_Dark.png and b/.loki/reference/chrome_iphone7_pages_MainPage_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_pages_MainPage_Normal.png b/.loki/reference/chrome_iphone7_pages_MainPage_Normal.png
index ae6f64d..c22903d 100644
Binary files a/.loki/reference/chrome_iphone7_pages_MainPage_Normal.png and b/.loki/reference/chrome_iphone7_pages_MainPage_Normal.png differ
diff --git a/.loki/reference/chrome_iphone7_pages_NotFoundPage_Dark.png b/.loki/reference/chrome_iphone7_pages_NotFoundPage_Dark.png
index 88e0c17..30a7ae9 100644
Binary files a/.loki/reference/chrome_iphone7_pages_NotFoundPage_Dark.png and b/.loki/reference/chrome_iphone7_pages_NotFoundPage_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_pages_NotFoundPage_Normal.png b/.loki/reference/chrome_iphone7_pages_NotFoundPage_Normal.png
index 7a049cd..f2c7f1b 100644
Binary files a/.loki/reference/chrome_iphone7_pages_NotFoundPage_Normal.png and b/.loki/reference/chrome_iphone7_pages_NotFoundPage_Normal.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Button_Disabled.png b/.loki/reference/chrome_iphone7_shared_Button_Disabled.png
new file mode 100644
index 0000000..6ed6bef
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Button_Disabled.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Input_Primary.png b/.loki/reference/chrome_iphone7_shared_Input_Primary.png
index c3dd7d6..0895b6b 100644
Binary files a/.loki/reference/chrome_iphone7_shared_Input_Primary.png and b/.loki/reference/chrome_iphone7_shared_Input_Primary.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Error.png b/.loki/reference/chrome_iphone7_shared_Text_Error.png
new file mode 100644
index 0000000..70b3e9a
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Error.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Error_Dark.png b/.loki/reference/chrome_iphone7_shared_Text_Error_Dark.png
new file mode 100644
index 0000000..43716f3
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Error_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Only_Text.png b/.loki/reference/chrome_iphone7_shared_Text_Only_Text.png
new file mode 100644
index 0000000..b3c2d70
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Only_Text.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Only_Title.png b/.loki/reference/chrome_iphone7_shared_Text_Only_Title.png
new file mode 100644
index 0000000..47eb510
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Only_Title.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Primary.png b/.loki/reference/chrome_iphone7_shared_Text_Primary.png
new file mode 100644
index 0000000..3e8d4e0
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Primary.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Primary_Dark.png b/.loki/reference/chrome_iphone7_shared_Text_Primary_Dark.png
new file mode 100644
index 0000000..79eefba
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Primary_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Size_L.png b/.loki/reference/chrome_iphone7_shared_Text_Size_L.png
new file mode 100644
index 0000000..78e4876
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Size_L.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_Text_Size_M.png b/.loki/reference/chrome_iphone7_shared_Text_Size_M.png
new file mode 100644
index 0000000..3e8d4e0
Binary files /dev/null and b/.loki/reference/chrome_iphone7_shared_Text_Size_M.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Dark.png b/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Dark.png
index 13656fa..2d1b6bd 100644
Binary files a/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Dark.png and b/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Normal.png b/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Normal.png
index 957c7df..6915c76 100644
Binary files a/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Normal.png and b/.loki/reference/chrome_iphone7_shared_ThemeSwitcher_Normal.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_ErrorPage_Dark.png b/.loki/reference/chrome_iphone7_widget_ErrorPage_Dark.png
index 3c4feb2..9466d68 100644
Binary files a/.loki/reference/chrome_iphone7_widget_ErrorPage_Dark.png and b/.loki/reference/chrome_iphone7_widget_ErrorPage_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_ErrorPage_Light.png b/.loki/reference/chrome_iphone7_widget_ErrorPage_Light.png
deleted file mode 100644
index 4f1e370..0000000
Binary files a/.loki/reference/chrome_iphone7_widget_ErrorPage_Light.png and /dev/null differ
diff --git a/.loki/reference/chrome_iphone7_widget_ErrorPage_Normal.png b/.loki/reference/chrome_iphone7_widget_ErrorPage_Normal.png
new file mode 100644
index 0000000..cd4b13d
Binary files /dev/null and b/.loki/reference/chrome_iphone7_widget_ErrorPage_Normal.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Navbar_Auth_Navbar.png b/.loki/reference/chrome_iphone7_widget_Navbar_Auth_Navbar.png
new file mode 100644
index 0000000..896d421
Binary files /dev/null and b/.loki/reference/chrome_iphone7_widget_Navbar_Auth_Navbar.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Navbar_Dark.png b/.loki/reference/chrome_iphone7_widget_Navbar_Dark.png
index 42e071b..ca3fe90 100644
Binary files a/.loki/reference/chrome_iphone7_widget_Navbar_Dark.png and b/.loki/reference/chrome_iphone7_widget_Navbar_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Navbar_Dark_Auth_Navbar.png b/.loki/reference/chrome_iphone7_widget_Navbar_Dark_Auth_Navbar.png
new file mode 100644
index 0000000..16b7f43
Binary files /dev/null and b/.loki/reference/chrome_iphone7_widget_Navbar_Dark_Auth_Navbar.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Navbar_Light.png b/.loki/reference/chrome_iphone7_widget_Navbar_Light.png
index 250bf50..bc1b20b 100644
Binary files a/.loki/reference/chrome_iphone7_widget_Navbar_Light.png and b/.loki/reference/chrome_iphone7_widget_Navbar_Light.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Sidebar_Dark.png b/.loki/reference/chrome_iphone7_widget_Sidebar_Dark.png
index ad3778b..2b23d89 100644
Binary files a/.loki/reference/chrome_iphone7_widget_Sidebar_Dark.png and b/.loki/reference/chrome_iphone7_widget_Sidebar_Dark.png differ
diff --git a/.loki/reference/chrome_iphone7_widget_Sidebar_Light.png b/.loki/reference/chrome_iphone7_widget_Sidebar_Light.png
index 2d2426f..958e321 100644
Binary files a/.loki/reference/chrome_iphone7_widget_Sidebar_Light.png and b/.loki/reference/chrome_iphone7_widget_Sidebar_Light.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_Loading.png b/.loki/reference/chrome_laptop_features_LoginForm_Loading.png
new file mode 100644
index 0000000..0c8bb4a
Binary files /dev/null and b/.loki/reference/chrome_laptop_features_LoginForm_Loading.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_Loading_Dark.png b/.loki/reference/chrome_laptop_features_LoginForm_Loading_Dark.png
new file mode 100644
index 0000000..b754f8e
Binary files /dev/null and b/.loki/reference/chrome_laptop_features_LoginForm_Loading_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_Primary.png b/.loki/reference/chrome_laptop_features_LoginForm_Primary.png
index 8e964df..febb121 100644
Binary files a/.loki/reference/chrome_laptop_features_LoginForm_Primary.png and b/.loki/reference/chrome_laptop_features_LoginForm_Primary.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_Primary_Dark.png b/.loki/reference/chrome_laptop_features_LoginForm_Primary_Dark.png
new file mode 100644
index 0000000..bddbffe
Binary files /dev/null and b/.loki/reference/chrome_laptop_features_LoginForm_Primary_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_With_Error.png b/.loki/reference/chrome_laptop_features_LoginForm_With_Error.png
new file mode 100644
index 0000000..3fa43e4
Binary files /dev/null and b/.loki/reference/chrome_laptop_features_LoginForm_With_Error.png differ
diff --git a/.loki/reference/chrome_laptop_features_LoginForm_With_Error_Dark.png b/.loki/reference/chrome_laptop_features_LoginForm_With_Error_Dark.png
new file mode 100644
index 0000000..98ab846
Binary files /dev/null and b/.loki/reference/chrome_laptop_features_LoginForm_With_Error_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_pages_MainPage_Dark.png b/.loki/reference/chrome_laptop_pages_MainPage_Dark.png
index 9f2e058..bb0614c 100644
Binary files a/.loki/reference/chrome_laptop_pages_MainPage_Dark.png and b/.loki/reference/chrome_laptop_pages_MainPage_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_pages_MainPage_Normal.png b/.loki/reference/chrome_laptop_pages_MainPage_Normal.png
index 189fe7f..3296fa6 100644
Binary files a/.loki/reference/chrome_laptop_pages_MainPage_Normal.png and b/.loki/reference/chrome_laptop_pages_MainPage_Normal.png differ
diff --git a/.loki/reference/chrome_laptop_pages_NotFoundPage_Dark.png b/.loki/reference/chrome_laptop_pages_NotFoundPage_Dark.png
index 4dcd133..98325da 100644
Binary files a/.loki/reference/chrome_laptop_pages_NotFoundPage_Dark.png and b/.loki/reference/chrome_laptop_pages_NotFoundPage_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_pages_NotFoundPage_Normal.png b/.loki/reference/chrome_laptop_pages_NotFoundPage_Normal.png
index e2a03fd..e77b50b 100644
Binary files a/.loki/reference/chrome_laptop_pages_NotFoundPage_Normal.png and b/.loki/reference/chrome_laptop_pages_NotFoundPage_Normal.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Button_Disabled.png b/.loki/reference/chrome_laptop_shared_Button_Disabled.png
new file mode 100644
index 0000000..d9bb9e8
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Button_Disabled.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Input_Primary.png b/.loki/reference/chrome_laptop_shared_Input_Primary.png
index bc13232..da12702 100644
Binary files a/.loki/reference/chrome_laptop_shared_Input_Primary.png and b/.loki/reference/chrome_laptop_shared_Input_Primary.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Error.png b/.loki/reference/chrome_laptop_shared_Text_Error.png
new file mode 100644
index 0000000..2131090
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Error.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Error_Dark.png b/.loki/reference/chrome_laptop_shared_Text_Error_Dark.png
new file mode 100644
index 0000000..fb84b26
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Error_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Only_Text.png b/.loki/reference/chrome_laptop_shared_Text_Only_Text.png
new file mode 100644
index 0000000..5616240
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Only_Text.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Only_Title.png b/.loki/reference/chrome_laptop_shared_Text_Only_Title.png
new file mode 100644
index 0000000..2b4cc8e
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Only_Title.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Primary.png b/.loki/reference/chrome_laptop_shared_Text_Primary.png
new file mode 100644
index 0000000..561b419
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Primary.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Primary_Dark.png b/.loki/reference/chrome_laptop_shared_Text_Primary_Dark.png
new file mode 100644
index 0000000..fe9b42d
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Primary_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Size_L.png b/.loki/reference/chrome_laptop_shared_Text_Size_L.png
new file mode 100644
index 0000000..8726587
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Size_L.png differ
diff --git a/.loki/reference/chrome_laptop_shared_Text_Size_M.png b/.loki/reference/chrome_laptop_shared_Text_Size_M.png
new file mode 100644
index 0000000..561b419
Binary files /dev/null and b/.loki/reference/chrome_laptop_shared_Text_Size_M.png differ
diff --git a/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Dark.png b/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Dark.png
index d79373a..3ec10e9 100644
Binary files a/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Dark.png and b/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Normal.png b/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Normal.png
index ce708f9..0997c3e 100644
Binary files a/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Normal.png and b/.loki/reference/chrome_laptop_shared_ThemeSwitcher_Normal.png differ
diff --git a/.loki/reference/chrome_laptop_widget_ErrorPage_Dark.png b/.loki/reference/chrome_laptop_widget_ErrorPage_Dark.png
index 45bec4a..be0f297 100644
Binary files a/.loki/reference/chrome_laptop_widget_ErrorPage_Dark.png and b/.loki/reference/chrome_laptop_widget_ErrorPage_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_widget_ErrorPage_Light.png b/.loki/reference/chrome_laptop_widget_ErrorPage_Light.png
deleted file mode 100644
index b1548e7..0000000
Binary files a/.loki/reference/chrome_laptop_widget_ErrorPage_Light.png and /dev/null differ
diff --git a/.loki/reference/chrome_laptop_widget_ErrorPage_Normal.png b/.loki/reference/chrome_laptop_widget_ErrorPage_Normal.png
new file mode 100644
index 0000000..ea6da3f
Binary files /dev/null and b/.loki/reference/chrome_laptop_widget_ErrorPage_Normal.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Navbar_Auth_Navbar.png b/.loki/reference/chrome_laptop_widget_Navbar_Auth_Navbar.png
new file mode 100644
index 0000000..11c942b
Binary files /dev/null and b/.loki/reference/chrome_laptop_widget_Navbar_Auth_Navbar.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Navbar_Dark.png b/.loki/reference/chrome_laptop_widget_Navbar_Dark.png
index f42c71a..851cc2f 100644
Binary files a/.loki/reference/chrome_laptop_widget_Navbar_Dark.png and b/.loki/reference/chrome_laptop_widget_Navbar_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Navbar_Dark_Auth_Navbar.png b/.loki/reference/chrome_laptop_widget_Navbar_Dark_Auth_Navbar.png
new file mode 100644
index 0000000..8bdd93b
Binary files /dev/null and b/.loki/reference/chrome_laptop_widget_Navbar_Dark_Auth_Navbar.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Navbar_Light.png b/.loki/reference/chrome_laptop_widget_Navbar_Light.png
index 2aeea1c..12801f0 100644
Binary files a/.loki/reference/chrome_laptop_widget_Navbar_Light.png and b/.loki/reference/chrome_laptop_widget_Navbar_Light.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Sidebar_Dark.png b/.loki/reference/chrome_laptop_widget_Sidebar_Dark.png
index 7da010f..4611cec 100644
Binary files a/.loki/reference/chrome_laptop_widget_Sidebar_Dark.png and b/.loki/reference/chrome_laptop_widget_Sidebar_Dark.png differ
diff --git a/.loki/reference/chrome_laptop_widget_Sidebar_Light.png b/.loki/reference/chrome_laptop_widget_Sidebar_Light.png
index 74ff176..b511d23 100644
Binary files a/.loki/reference/chrome_laptop_widget_Sidebar_Light.png and b/.loki/reference/chrome_laptop_widget_Sidebar_Light.png differ
diff --git a/.prettierrc.js b/.prettierrc.js
index 149460f..1e94fa7 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -7,4 +7,5 @@ module.exports = {
trailingComma: 'all',
bracketSpacing: true,
arrowParens: 'always',
+ endOfLine: 'auto',
};
diff --git a/config/build/buildDevServer.ts b/config/build/buildDevServer.ts
index 9f7984c..e3f8445 100644
--- a/config/build/buildDevServer.ts
+++ b/config/build/buildDevServer.ts
@@ -1,5 +1,6 @@
import type { Configuration as DevServerConfiguration } from 'webpack-dev-server';
-import { BuildOptions } from './types/config';
+
+import type { BuildOptions } from './types/config';
export function buildDevServer(options: BuildOptions): DevServerConfiguration {
return {
diff --git a/config/build/buildLoaders.ts b/config/build/buildLoaders.ts
index e98c263..85e0780 100644
--- a/config/build/buildLoaders.ts
+++ b/config/build/buildLoaders.ts
@@ -1,6 +1,7 @@
-import webpack from 'webpack';
+import type webpack from 'webpack';
+
import { buildCssLoader } from './loaders/buildCssLoader';
-import { BuildOptions } from './types/config';
+import type { BuildOptions } from './types/config';
export function buildLoaders({ isDev }: BuildOptions): webpack.RuleSetRule[] {
const svgLoader = {
diff --git a/config/build/buildPlugins.ts b/config/build/buildPlugins.ts
index 9c42960..38f7b78 100644
--- a/config/build/buildPlugins.ts
+++ b/config/build/buildPlugins.ts
@@ -2,7 +2,8 @@ import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
-import { BuildOptions } from './types/config';
+
+import type { BuildOptions } from './types/config';
export function buildPlugins({ paths, isDev }: BuildOptions): webpack.WebpackPluginInstance[] {
const plugins = [
diff --git a/config/build/buildResolvers.ts b/config/build/buildResolvers.ts
index 6e099cc..02c748f 100644
--- a/config/build/buildResolvers.ts
+++ b/config/build/buildResolvers.ts
@@ -1,5 +1,6 @@
-import { ResolveOptions } from 'webpack';
-import { BuildOptions } from './types/config';
+import type { ResolveOptions } from 'webpack';
+
+import type { BuildOptions } from './types/config';
export function buildResolvers(options: BuildOptions): ResolveOptions {
return {
diff --git a/config/build/buildWebpackConfig.ts b/config/build/buildWebpackConfig.ts
index d3430f2..a5ea069 100644
--- a/config/build/buildWebpackConfig.ts
+++ b/config/build/buildWebpackConfig.ts
@@ -1,9 +1,10 @@
-import webpack from 'webpack';
+import type webpack from 'webpack';
+
import { buildDevServer } from './buildDevServer';
import { buildLoaders } from './buildLoaders';
import { buildPlugins } from './buildPlugins';
import { buildResolvers } from './buildResolvers';
-import { BuildOptions } from './types/config';
+import type { BuildOptions } from './types/config';
export function buildWebpackConfig(options: BuildOptions): webpack.Configuration {
const { paths, mode, isDev } = options;
diff --git a/config/storybook/main.js b/config/storybook/main.js
index f7ca392..8d18e8a 100644
--- a/config/storybook/main.js
+++ b/config/storybook/main.js
@@ -1,12 +1,17 @@
module.exports = {
- stories: ['../../src/**/*.stories.@(js|jsx|ts|tsx)'],
- addons: [
- '@storybook/addon-links',
- '@storybook/addon-essentials',
- '@storybook/addon-interactions',
- ],
- framework: '@storybook/react',
- core: {
- builder: 'webpack5',
- },
+ stories: ['../../src/**/*.stories.@(js|jsx|ts|tsx)'],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/addon-interactions',
+ ],
+ framework: '@storybook/react',
+ core: {
+ builder: 'webpack5',
+ },
+ staticDirs: ['../../public'],
+ previewHead: (head) => `
+ ${head}
+
+ `,
};
diff --git a/config/storybook/preview.js b/config/storybook/preview.js
index 77bf2a1..c4f15a9 100644
--- a/config/storybook/preview.js
+++ b/config/storybook/preview.js
@@ -1,19 +1,22 @@
import { addDecorator } from '@storybook/react';
-import { StyleDecorator } from '../../src/shared/config/storybook/StyleDecorator/StyleDecorator';
-import { ThemeDecorator } from '../../src/shared/config/storybook/ThemeDecorator/ThemeDecorator';
+
import { Theme } from '../../src/app/providers/ThemeProvider';
import { RouterDecorator } from '../../src/shared/config/storybook/RouterDecorator/RouterDecorator';
+import { StyleDecorator } from '../../src/shared/config/storybook/StyleDecorator/StyleDecorator';
+import { SuspenseDecorator } from '../../src/shared/config/storybook/SuspenseDecorator/SuspenseDecorator';
+import { ThemeDecorator } from '../../src/shared/config/storybook/ThemeDecorator/ThemeDecorator';
export const parameters = {
- actions: { argTypesRegex: '^on[A-Z].*' },
- controls: {
- matchers: {
- color: /(background|color)$/i,
- date: /Date$/,
- },
- },
+ actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
};
+addDecorator(SuspenseDecorator);
addDecorator(StyleDecorator);
addDecorator(ThemeDecorator(Theme.LIGHT));
addDecorator(RouterDecorator);
diff --git a/config/storybook/webpack.config.ts b/config/storybook/webpack.config.ts
index 8e8867a..fe2455d 100644
--- a/config/storybook/webpack.config.ts
+++ b/config/storybook/webpack.config.ts
@@ -1,32 +1,40 @@
-import webpack, { RuleSetRule } from 'webpack';
import path from 'path';
+
+import type { Configuration } from 'webpack';
+import { DefinePlugin } from 'webpack';
+
import { buildCssLoader } from '../build/loaders/buildCssLoader';
-import { BuildPaths } from '../build/types/config';
+import type { BuildPaths } from '../build/types/config';
-export default ({ config }: { config: webpack.Configuration }) => {
+export default ({ config }: { config: Configuration }) => {
const paths: BuildPaths = {
build: '',
html: '',
entry: '',
src: path.resolve(__dirname, '..', '..', 'src'),
};
- config.resolve.modules.push(paths.src);
- config.resolve.extensions.push('.ts', '.tsx');
+ config.resolve!.modules!.push(paths.src);
+ config.resolve!.extensions!.push('.ts', '.tsx');
- // eslint-disable-next-line no-param-reassign
- config.module.rules = config.module.rules.map((rule: RuleSetRule) => {
- if (/svg/.test(rule.test as string)) {
+ config.module!.rules = config.module!.rules!.map((rule) => {
+ if (rule && typeof rule === 'object' && rule.test && /svg/.test(rule.test as string)) {
return { ...rule, exclude: /\.svg$/i };
}
return rule;
});
- config.module.rules.push({
+ config.module!.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
- config.module.rules.push(buildCssLoader(true));
+ config.module!.rules.push(buildCssLoader(true));
+
+ config.plugins!.push(
+ new DefinePlugin({
+ __IS_DEV__: true,
+ }),
+ );
return config;
};
diff --git a/extractedTranslations/en/translation.json b/extractedTranslations/en/translation.json
index fe6f0f7..5594b06 100644
--- a/extractedTranslations/en/translation.json
+++ b/extractedTranslations/en/translation.json
@@ -1,9 +1,19 @@
{
+ "Loading": {
+ "": {
+ "": {
+ "": ""
+ }
+ }
+ },
"throw error": "throw error",
+ "Авторизация": "",
"Введите username": "Введите username",
"Введите пароль": "Введите пароль",
"Войти": "",
- "Войти": "Войти",
+ "Войти": "Login",
+ "Вы ввели неверный логин или пароль": "",
+ "Выйти": "Выйти",
"Главная": "Главная",
"Главная страница": "Главная страница",
"Короткий язык": "Короткий язык",
diff --git a/extractedTranslations/ru/translation.json b/extractedTranslations/ru/translation.json
index fa6d312..135d49f 100644
--- a/extractedTranslations/ru/translation.json
+++ b/extractedTranslations/ru/translation.json
@@ -1,9 +1,12 @@
{
"throw error": "throw error",
+ "Авторизация": "Авторизация",
"Введите username": "Введите username",
"Введите пароль": "Введите пароль",
"Войти": "Войти",
"Войти": "Войти",
+ "Вы ввели неверный логин или пароль": "Вы ввели неверный логин или пароль",
+ "Выйти": "Выйти",
"Главная": "Главная",
"Главная страница": "Главная страница",
"Короткий язык": "Короткий язык",
diff --git a/json-server/index.js b/json-server/index.js
index a222611..b99f743 100644
--- a/json-server/index.js
+++ b/json-server/index.js
@@ -12,47 +12,47 @@ server.use(jsonServer.bodyParser);
// Нужно для небольшой задержки, чтобы запрос проходил не мгновенно, имитация реального апи
server.use(async (req, res, next) => {
- await new Promise((res) => {
- setTimeout(res, 800);
- });
- next();
+ await new Promise((resolve) => {
+ setTimeout(resolve, 800);
+ });
+ next();
});
// Эндпоинт для логина
server.post('/login', (req, res) => {
- try {
- const { username, password } = req.body;
- const db = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'db.json'), 'UTF-8'));
- const { users = [] } = db;
-
- const userFromBd = users.find(
- (user) => user.username === username && user.password === password,
- );
-
- if (userFromBd) {
- return res.json(userFromBd);
- }
-
- return res.status(403).json({ message: 'User not found' });
- } catch (e) {
- console.log(e);
- return res.status(500).json({ message: e.message });
- }
+ try {
+ const { username, password } = req.body;
+ const db = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'db.json'), 'UTF-8'));
+ const { users = [] } = db;
+
+ const userFromBd = users.find(
+ (user) => user.username === username && user.password === password,
+ );
+
+ if (userFromBd) {
+ return res.json(userFromBd);
+ }
+
+ return res.status(403).json({ message: 'User not found' });
+ } catch (e) {
+ console.log(e);
+ return res.status(500).json({ message: e.message });
+ }
});
// проверяем, авторизован ли пользователь
// eslint-disable-next-line
server.use((req, res, next) => {
- if (!req.headers.authorization) {
- return res.status(403).json({ message: 'AUTH ERROR' });
- }
+ if (!req.headers.authorization) {
+ return res.status(403).json({ message: 'AUTH ERROR' });
+ }
- next();
+ next();
});
server.use(router);
// запуск сервера
server.listen(8000, () => {
- console.log('server is running on 8000 port');
+ console.log('server is running on 8000 port');
});
diff --git a/package-lock.json b/package-lock.json
index b4d38b6..170c20e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@reduxjs/toolkit": "1.8",
+ "axios": "^1.9.0",
"i18next": "^21.6.11",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
@@ -16503,7 +16504,6 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/at-least-node": {
@@ -16622,13 +16622,14 @@
}
},
"node_modules/axios": {
- "version": "0.21.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
- "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
- "dev": true,
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.14.0"
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
@@ -17931,7 +17932,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -18696,7 +18696,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
@@ -20092,7 +20091,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@@ -20463,7 +20461,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -20752,7 +20749,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -20762,7 +20758,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -20806,7 +20801,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -20819,7 +20813,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -22582,7 +22575,6 @@
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "dev": true,
"funding": [
{
"type": "individual",
@@ -22742,7 +22734,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -22884,7 +22875,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -22982,7 +22972,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -23030,7 +23019,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -23310,7 +23298,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -23464,7 +23451,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -23477,7 +23463,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -23621,7 +23606,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -28197,7 +28181,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -28669,7 +28652,6 @@
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -28679,7 +28661,6 @@
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"mime-db": "1.51.0"
@@ -31007,6 +30988,12 @@
"node": ">= 0.10"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -36863,6 +36850,16 @@
"node": ">=8.9.0"
}
},
+ "node_modules/wait-on/node_modules/axios": {
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.14.0"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/package.json b/package.json
index 0065f44..176ba35 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
},
"dependencies": {
"@reduxjs/toolkit": "1.8",
+ "axios": "^1.9.0",
"i18next": "^21.6.11",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
diff --git a/public/locales/en-US/about.json b/public/locales/en-US/about.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/public/locales/en-US/about.json
@@ -0,0 +1 @@
+{}
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
new file mode 100644
index 0000000..75f631e
--- /dev/null
+++ b/public/locales/en-US/translation.json
@@ -0,0 +1,19 @@
+{
+ "Тестовый пример": "Test example",
+ "Перевод": "Translate",
+ "Главная страница": "Main page",
+ "Язык": "English",
+ "новый перевод": "новый перевод",
+ "Страница не найдена": "Page not found",
+ "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка",
+ "Обновить страницу": "Reload page",
+ "throw error": "throw error",
+ "Главная": "Main",
+ "О сайте": "About us",
+ "Переключить": "Переключить",
+ "Короткий язык": "En",
+ "Войти": "Login",
+ "Введите пароль": "Enter password",
+ "Введите username": "Enter username",
+ "Вы ввели неверный логин или пароль": "You entered an invalid username or password"
+}
diff --git a/public/locales/en/about.json b/public/locales/en/about.json
index cabaf62..0967ef4 100644
--- a/public/locales/en/about.json
+++ b/public/locales/en/about.json
@@ -1,3 +1 @@
-{
- "О сайте": "About us"
-}
+{}
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index 6da8186..75f631e 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -1,18 +1,19 @@
{
- "Тестовый пример": "Test example",
- "Перевод": "Translate",
- "Главная страница": "Main page",
- "Язык": "English",
- "новый перевод": "новый перевод",
- "Страница не найдена": "Page not found",
- "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка",
- "Обновить страницу": "Reload page",
- "throw error": "throw error",
- "Главная": "Main",
- "О сайте": "About us",
- "Переключить": "Переключить",
- "Короткий язык": "En",
- "Войти": "Login",
- "Введите пароль": "Enter password",
- "Введите username": "Enter username"
+ "Тестовый пример": "Test example",
+ "Перевод": "Translate",
+ "Главная страница": "Main page",
+ "Язык": "English",
+ "новый перевод": "новый перевод",
+ "Страница не найдена": "Page not found",
+ "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка",
+ "Обновить страницу": "Reload page",
+ "throw error": "throw error",
+ "Главная": "Main",
+ "О сайте": "About us",
+ "Переключить": "Переключить",
+ "Короткий язык": "En",
+ "Войти": "Login",
+ "Введите пароль": "Enter password",
+ "Введите username": "Enter username",
+ "Вы ввели неверный логин или пароль": "You entered an invalid username or password"
}
diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json
index 5cb8a38..152b069 100644
--- a/public/locales/ru/translation.json
+++ b/public/locales/ru/translation.json
@@ -1,18 +1,19 @@
{
- "Тестовый пример": "Тестовый пример",
- "Перевод": "Перевод",
- "Главная страница": "Главная страница",
- "Язык": "Русский",
- "новый перевод": "новый перевод",
- "Страница не найдена": "Страница не найдена",
- "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка",
- "Обновить страницу": "Обновить страницу",
- "throw error": "throw error",
- "Главная": "Главная",
- "О сайте": "О сайте",
- "Переключить": "Переключить",
- "Короткий язык": "Ru",
- "Войти": "Войти",
- "Введите пароль": "Введите пароль",
- "Введите username": "Введите username"
+ "Тестовый пример": "Тестовый пример",
+ "Перевод": "Перевод",
+ "Главная страница": "Главная страница",
+ "Язык": "Русский",
+ "новый перевод": "новый перевод",
+ "Страница не найдена": "Страница не найдена",
+ "Произошла непредвиденная ошибка": "Произошла непредвиденная ошибка",
+ "Обновить страницу": "Обновить страницу",
+ "throw error": "throw error",
+ "Главная": "Главная",
+ "О сайте": "О сайте",
+ "Переключить": "Переключить",
+ "Короткий язык": "Ru",
+ "Войти": "Войти",
+ "Введите пароль": "Введите пароль",
+ "Введите username": "Введите username",
+ "Вы ввели неверный логин или пароль": "Вы ввели неверный логин или пароль"
}
diff --git a/src/app/App.tsx b/src/app/App.tsx
index b47de41..88b9dda 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -1,9 +1,17 @@
import { AppRouter } from 'app/providers/router';
-import { Suspense } from 'react';
+import { Suspense, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+import { userActions } from 'entities/User';
import { Navbar } from 'widgets/Navbar';
import { Sidebar } from 'widgets/Sidebar';
function App() {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(userActions.initAuthData());
+ }, [dispatch]);
+
return (
diff --git a/src/app/providers/ErrorBoundary/ui/ErrorBoundary.tsx b/src/app/providers/ErrorBoundary/ui/ErrorBoundary.tsx
index 67112b3..70f474a 100644
--- a/src/app/providers/ErrorBoundary/ui/ErrorBoundary.tsx
+++ b/src/app/providers/ErrorBoundary/ui/ErrorBoundary.tsx
@@ -1,4 +1,5 @@
-import React, { ErrorInfo, ReactNode, Suspense } from 'react';
+import type { ErrorInfo, ReactNode } from 'react';
+import React, { Suspense } from 'react';
import { ErrorPage } from 'widgets/ErrorPage/ui/ErrorPage';
interface ErrorBoundaryProps {
@@ -22,6 +23,7 @@ class ErrorBoundary extends React.Component = {
counter: counterReducer,
user: userReducer,
+ loginForm: loginReducer,
};
return configureStore({
diff --git a/src/app/providers/StoreProvider/ui/StoreProvider.tsx b/src/app/providers/StoreProvider/ui/StoreProvider.tsx
index b1c9346..cdd43fd 100644
--- a/src/app/providers/StoreProvider/ui/StoreProvider.tsx
+++ b/src/app/providers/StoreProvider/ui/StoreProvider.tsx
@@ -1,7 +1,7 @@
-import { DeepPartial } from '@reduxjs/toolkit';
-import { StateSchema } from 'app/providers/StoreProvider/config/StateSchema';
+import type { DeepPartial } from '@reduxjs/toolkit';
+import type { StateSchema } from 'app/providers/StoreProvider/config/StateSchema';
import { createReduxStore } from 'app/providers/StoreProvider/config/store';
-import { ReactNode } from 'react';
+import type { ReactNode } from 'react';
import { Provider } from 'react-redux';
interface StoreProviderProps {
diff --git a/src/app/providers/ThemeProvider/lib/useTheme.ts b/src/app/providers/ThemeProvider/lib/useTheme.ts
index 95a71b6..99c7b92 100644
--- a/src/app/providers/ThemeProvider/lib/useTheme.ts
+++ b/src/app/providers/ThemeProvider/lib/useTheme.ts
@@ -1,4 +1,5 @@
import { useContext, useEffect } from 'react';
+
import { LOCAL_STORAGE_THEME_KEY, Theme, ThemeContext } from './ThemeContext';
interface UseThemeResult {
diff --git a/src/app/providers/ThemeProvider/ui/ThemeProvider.tsx b/src/app/providers/ThemeProvider/ui/ThemeProvider.tsx
index a04e097..b7837bc 100644
--- a/src/app/providers/ThemeProvider/ui/ThemeProvider.tsx
+++ b/src/app/providers/ThemeProvider/ui/ThemeProvider.tsx
@@ -1,4 +1,6 @@
-import { FC, useMemo, useState } from 'react';
+import type { FC } from 'react';
+import { useMemo, useState } from 'react';
+
import { LOCAL_STORAGE_THEME_KEY, Theme, ThemeContext } from '../lib/ThemeContext';
interface ThemeProviderProps {
diff --git a/src/app/styles/themes/dark.scss b/src/app/styles/themes/dark.scss
index 58f6f94..1317ca8 100644
--- a/src/app/styles/themes/dark.scss
+++ b/src/app/styles/themes/dark.scss
@@ -5,4 +5,8 @@
--secondary-color: #04ff04;
--inverted-primary-color: #0452ff;
--inverted-secondary-color: #0232c2;
+ --primary-color-rgb: 4, 150, 4;
+ --secondary-color-rgb: 4, 255, 4;
+ --inverted-primary-color-rgb: 4, 82, 255;
+ --inverted-secondary-color-rgb: 2, 50, 194;
}
diff --git a/src/app/styles/themes/normal.scss b/src/app/styles/themes/normal.scss
index 461b90d..88f75aa 100644
--- a/src/app/styles/themes/normal.scss
+++ b/src/app/styles/themes/normal.scss
@@ -5,4 +5,8 @@
--secondary-color: #0449e0;
--inverted-primary-color: #04ff04;
--inverted-secondary-color: #049604;
+ --primary-color-rgb: 2, 50, 194;
+ --secondary-color-rgb: 4, 73, 224;
+ --inverted-primary-color-rgb: 4, 255, 4;
+ --inverted-secondary-color-rgb: 4, 150, 4;
}
diff --git a/src/app/styles/variables/global.scss b/src/app/styles/variables/global.scss
index a925df1..2a4b064 100644
--- a/src/app/styles/variables/global.scss
+++ b/src/app/styles/variables/global.scss
@@ -20,4 +20,6 @@
// Цвета
--overlay-color: rgba(0 0 0 / 60%);
+ --red-light: #f00;
+ --red-dark: #d32f2f;
}
diff --git a/src/app/types/global.d.ts b/src/app/types/global.d.ts
index b76b63e..92dc27b 100644
--- a/src/app/types/global.d.ts
+++ b/src/app/types/global.d.ts
@@ -11,7 +11,7 @@ declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.svg' {
- import React from 'react';
+ import type React from 'react';
const SVG: React.VFC>;
export default SVG;
diff --git a/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts b/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts
index 9ab1713..bd7f4ff 100644
--- a/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts
+++ b/src/entities/Counter/model/selectors/getCounter/getCounter.test.ts
@@ -1,5 +1,6 @@
-import { DeepPartial } from '@reduxjs/toolkit';
-import { StateSchema } from 'app/providers/StoreProvider';
+import type { DeepPartial } from '@reduxjs/toolkit';
+import type { StateSchema } from 'app/providers/StoreProvider';
+
import { getCounter } from './getCounter';
describe('getCounter', () => {
diff --git a/src/entities/Counter/model/selectors/getCounter/getCounter.ts b/src/entities/Counter/model/selectors/getCounter/getCounter.ts
index 4168ac3..68af2f9 100644
--- a/src/entities/Counter/model/selectors/getCounter/getCounter.ts
+++ b/src/entities/Counter/model/selectors/getCounter/getCounter.ts
@@ -1,3 +1,3 @@
-import { StateSchema } from 'app/providers/StoreProvider';
+import type { StateSchema } from 'app/providers/StoreProvider';
export const getCounter = (state: StateSchema) => state.counter;
diff --git a/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.test.ts b/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.test.ts
index 7cf103b..2f924c0 100644
--- a/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.test.ts
+++ b/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.test.ts
@@ -1,5 +1,6 @@
-import { DeepPartial } from '@reduxjs/toolkit';
-import { StateSchema } from 'app/providers/StoreProvider';
+import type { DeepPartial } from '@reduxjs/toolkit';
+import type { StateSchema } from 'app/providers/StoreProvider';
+
import { getCounterValue } from './getCounterValue';
describe('getCounterValue', () => {
diff --git a/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.ts b/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.ts
index 3e1eba9..92a58c5 100644
--- a/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.ts
+++ b/src/entities/Counter/model/selectors/getCounterValue/getCounterValue.ts
@@ -1,5 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
-import { CounterSchema } from '../../types/counterSchema';
+
+import type { CounterSchema } from '../../types/counterSchema';
import { getCounter } from '../getCounter/getCounter';
export const getCounterValue = createSelector(
diff --git a/src/entities/Counter/model/slice/counterSlice.test.ts b/src/entities/Counter/model/slice/counterSlice.test.ts
index 46bc55d..ac215d8 100644
--- a/src/entities/Counter/model/slice/counterSlice.test.ts
+++ b/src/entities/Counter/model/slice/counterSlice.test.ts
@@ -1,4 +1,4 @@
-import { CounterSchema } from '../types/counterSchema';
+import type { CounterSchema } from '../types/counterSchema';
import { counterActions, counterReducer } from './counterSlice';
describe('counterSlice', () => {
diff --git a/src/entities/Counter/model/slice/counterSlice.ts b/src/entities/Counter/model/slice/counterSlice.ts
index 100abb4..fa5762d 100644
--- a/src/entities/Counter/model/slice/counterSlice.ts
+++ b/src/entities/Counter/model/slice/counterSlice.ts
@@ -1,5 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
-import { CounterSchema } from '../types/counterSchema';
+
+import type { CounterSchema } from '../types/counterSchema';
const initialState: CounterSchema = {
value: 0,
diff --git a/src/entities/Counter/ui/Counter.test.tsx b/src/entities/Counter/ui/Counter.test.tsx
index e93d064..a48f9a6 100644
--- a/src/entities/Counter/ui/Counter.test.tsx
+++ b/src/entities/Counter/ui/Counter.test.tsx
@@ -1,6 +1,7 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { componentRender } from 'shared/lib/tests/componentRender/componentRender';
+
import { Counter } from './Counter';
describe('Counter', () => {
diff --git a/src/entities/Counter/ui/Counter.tsx b/src/entities/Counter/ui/Counter.tsx
index 9789db9..877f146 100644
--- a/src/entities/Counter/ui/Counter.tsx
+++ b/src/entities/Counter/ui/Counter.tsx
@@ -1,5 +1,6 @@
import { useDispatch, useSelector } from 'react-redux';
import { Button } from 'shared/ui/Button/Button';
+
import { getCounterValue } from '../model/selectors/getCounterValue/getCounterValue';
import { counterActions } from '../model/slice/counterSlice';
diff --git a/src/entities/User/index.ts b/src/entities/User/index.ts
index cfa3d28..9013bf2 100644
--- a/src/entities/User/index.ts
+++ b/src/entities/User/index.ts
@@ -1,2 +1,3 @@
export { userActions, userReducer } from './model/slice/userSlice';
export { User, UserSchema } from './model/types/user';
+export { getUserAuthData } from './model/selectors/getUserAuthData/getUserAuthData';
diff --git a/src/entities/User/model/selectors/getUserAuthData/getUserAuthData.ts b/src/entities/User/model/selectors/getUserAuthData/getUserAuthData.ts
new file mode 100644
index 0000000..915c8d0
--- /dev/null
+++ b/src/entities/User/model/selectors/getUserAuthData/getUserAuthData.ts
@@ -0,0 +1,3 @@
+import type { StateSchema } from 'app/providers/StoreProvider';
+
+export const getUserAuthData = (state: StateSchema) => state.user.authData;
diff --git a/src/entities/User/model/slice/userSlice.ts b/src/entities/User/model/slice/userSlice.ts
index c0322bc..3621ce8 100644
--- a/src/entities/User/model/slice/userSlice.ts
+++ b/src/entities/User/model/slice/userSlice.ts
@@ -1,15 +1,32 @@
-import { createSlice } from '@reduxjs/toolkit';
-import { UserSchema } from '../types/user';
+import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
+import { USER_LOCALSTORAGE_KEY } from 'shared/const/localstorage';
+
+import type { User, UserSchema } from '../types/user';
const initialState: UserSchema = {
authData: undefined,
isLoading: false,
+ error: undefined,
};
export const userSlice = createSlice({
name: 'user',
initialState,
- reducers: {},
+ reducers: {
+ setAuthData: (state, action: PayloadAction) => {
+ state.authData = action.payload;
+ },
+ initAuthData: (state) => {
+ const user = localStorage.getItem(USER_LOCALSTORAGE_KEY);
+ if (user) {
+ state.authData = JSON.parse(user);
+ }
+ },
+ logout: (state) => {
+ state.authData = undefined;
+ localStorage.removeItem(USER_LOCALSTORAGE_KEY);
+ },
+ },
});
export const { actions: userActions, reducer: userReducer } = userSlice;
diff --git a/src/entities/User/model/types/user.ts b/src/entities/User/model/types/user.ts
index 10d0fdd..47cb51a 100644
--- a/src/entities/User/model/types/user.ts
+++ b/src/entities/User/model/types/user.ts
@@ -6,4 +6,5 @@ export interface User {
export interface UserSchema {
authData?: User;
isLoading: boolean;
+ error?: string;
}
diff --git a/src/features/AuthByUsername/index.ts b/src/features/AuthByUsername/index.ts
index 7f7bd4b..23670a8 100644
--- a/src/features/AuthByUsername/index.ts
+++ b/src/features/AuthByUsername/index.ts
@@ -1 +1,3 @@
export { LoginModal } from './ui/LoginModal/LoginModal';
+export type { LoginSchema } from './model/types/loginSchema';
+export { loginReducer } from './model/slice/loginSlice';
diff --git a/src/features/AuthByUsername/model/selectors/getLoginState/getLoginState.ts b/src/features/AuthByUsername/model/selectors/getLoginState/getLoginState.ts
new file mode 100644
index 0000000..3f8fb64
--- /dev/null
+++ b/src/features/AuthByUsername/model/selectors/getLoginState/getLoginState.ts
@@ -0,0 +1,3 @@
+import type { StateSchema } from 'app/providers/StoreProvider';
+
+export const getLoginState = (state: StateSchema) => state.loginForm;
diff --git a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.test.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts
new file mode 100644
index 0000000..5e1d9c9
--- /dev/null
+++ b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts
@@ -0,0 +1,37 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
+import { type User, userActions } from 'entities/User';
+import { USER_LOCALSTORAGE_KEY } from 'shared/const/localstorage';
+
+interface LoginByUsernameProps {
+ username: string;
+ password: string;
+}
+
+/**
+ * Сервис для авторизации пользователя по логину и паролю
+ */
+export const loginByUsername = createAsyncThunk<
+ User,
+ LoginByUsernameProps,
+ { rejectValue: string }
+>('login/loginByUsername', async (authData, thunkAPI) => {
+ const { dispatch, rejectWithValue } = thunkAPI;
+
+ try {
+ const response = await axios.post('http://localhost:8000/login', authData);
+
+ if (!response.data) {
+ throw new Error('Нет данных пользователя');
+ }
+
+ localStorage.setItem(USER_LOCALSTORAGE_KEY, JSON.stringify(response.data));
+ dispatch(userActions.setAuthData(response.data));
+
+ return response.data;
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.log(e);
+ return rejectWithValue('error');
+ }
+});
diff --git a/src/features/AuthByUsername/model/slice/loginSlice.ts b/src/features/AuthByUsername/model/slice/loginSlice.ts
new file mode 100644
index 0000000..10aab59
--- /dev/null
+++ b/src/features/AuthByUsername/model/slice/loginSlice.ts
@@ -0,0 +1,46 @@
+import type { PayloadAction } from '@reduxjs/toolkit';
+import { createSlice } from '@reduxjs/toolkit';
+
+import type { LoginSchema } from '../types/loginSchema';
+import { loginByUsername } from '../services/loginByUsername/loginByUsername';
+
+const initialState: LoginSchema = {
+ username: '',
+ password: '',
+ isLoading: false,
+ error: undefined,
+};
+
+export const loginSlice = createSlice({
+ name: 'login',
+ initialState,
+ reducers: {
+ setUsername: (state, action: PayloadAction) => {
+ state.username = action.payload;
+ },
+ setPassword: (state, action: PayloadAction) => {
+ state.password = action.payload;
+ },
+ setIsLoading: (state, action: PayloadAction) => {
+ state.isLoading = action.payload;
+ },
+ setError: (state, action: PayloadAction) => {
+ state.error = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder.addCase(loginByUsername.pending, (state) => {
+ state.error = undefined;
+ state.isLoading = true;
+ });
+ builder.addCase(loginByUsername.fulfilled, (state) => {
+ state.isLoading = false;
+ });
+ builder.addCase(loginByUsername.rejected, (state, action) => {
+ state.isLoading = false;
+ state.error = action.payload;
+ });
+ },
+});
+
+export const { actions: loginActions, reducer: loginReducer } = loginSlice;
diff --git a/src/features/AuthByUsername/model/types/loginSchema.ts b/src/features/AuthByUsername/model/types/loginSchema.ts
new file mode 100644
index 0000000..e8c6e71
--- /dev/null
+++ b/src/features/AuthByUsername/model/types/loginSchema.ts
@@ -0,0 +1,6 @@
+export interface LoginSchema {
+ username: string;
+ password: string;
+ isLoading: boolean;
+ error?: string;
+}
diff --git a/src/features/AuthByUsername/ui/LoginForm/LoginForm.module.scss b/src/features/AuthByUsername/ui/LoginForm/LoginForm.module.scss
index a9bd007..7727682 100644
--- a/src/features/AuthByUsername/ui/LoginForm/LoginForm.module.scss
+++ b/src/features/AuthByUsername/ui/LoginForm/LoginForm.module.scss
@@ -1,6 +1,7 @@
.LoginForm {
display: flex;
flex-direction: column;
+ gap: 8px;
width: 400px;
padding: 20px;
box-shadow: 0 0 10px rgb(0 0 0 / 10%);
diff --git a/src/features/AuthByUsername/ui/LoginForm/LoginForm.stories.tsx b/src/features/AuthByUsername/ui/LoginForm/LoginForm.stories.tsx
index d32b4c8..6c977a4 100644
--- a/src/features/AuthByUsername/ui/LoginForm/LoginForm.stories.tsx
+++ b/src/features/AuthByUsername/ui/LoginForm/LoginForm.stories.tsx
@@ -1,4 +1,7 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Theme } from 'app/providers/ThemeProvider';
+import { StoreDecorator } from 'shared/config/storybook/StoreDecorator/StoreDecorator';
+import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { LoginForm } from './LoginForm';
@@ -13,7 +16,49 @@ export default {
const Template: ComponentStory = (args) => ;
export const Primary = Template.bind({});
-Primary.args = {
- placeholder: 'Type text',
- value: '123',
-};
+Primary.args = {};
+Primary.decorators = [
+ StoreDecorator({
+ loginForm: { username: 'admin', password: '123' },
+ }),
+ ThemeDecorator(Theme.LIGHT),
+];
+
+export const PrimaryDark = Template.bind({});
+PrimaryDark.args = {};
+PrimaryDark.decorators = [
+ StoreDecorator({ loginForm: { username: 'admin', password: '123' } }),
+ ThemeDecorator(Theme.DARK),
+];
+
+export const WithError = Template.bind({});
+WithError.args = {};
+WithError.decorators = [
+ StoreDecorator({
+ loginForm: { username: 'admin', password: '12345', error: 'Ошибка авторизации' },
+ }),
+ ThemeDecorator(Theme.LIGHT),
+];
+
+export const WithErrorDark = Template.bind({});
+WithErrorDark.args = {};
+WithErrorDark.decorators = [
+ StoreDecorator({
+ loginForm: { username: 'admin', password: '12345', error: 'Ошибка авторизации' },
+ }),
+ ThemeDecorator(Theme.DARK),
+];
+
+export const Loading = Template.bind({});
+Loading.args = {};
+Loading.decorators = [
+ StoreDecorator({ loginForm: { username: 'admin', password: '123', isLoading: true } }),
+ ThemeDecorator(Theme.LIGHT),
+];
+
+export const LoadingDark = Template.bind({});
+LoadingDark.args = {};
+LoadingDark.decorators = [
+ StoreDecorator({ loginForm: { username: 'admin', password: '123', isLoading: true } }),
+ ThemeDecorator(Theme.DARK),
+];
diff --git a/src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx b/src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx
index b63a013..969a75a 100644
--- a/src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx
+++ b/src/features/AuthByUsername/ui/LoginForm/LoginForm.tsx
@@ -1,23 +1,105 @@
+import { loginByUsername } from 'features/AuthByUsername/model/services/loginByUsername/loginByUsername';
+import type { FormEvent, KeyboardEvent } from 'react';
+import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
import { classNames } from 'shared/lib/classNames/classNames';
import { Button, ButtonTheme } from 'shared/ui/Button/Button';
import { Input } from 'shared/ui/Input/Input';
+import { Text, TextAlign, TextSize, TextTheme } from 'shared/ui/Text/Text';
+
+import { getLoginState } from '../../model/selectors/getLoginState/getLoginState';
+import { loginActions } from '../../model/slice/loginSlice';
import styles from './LoginForm.module.scss';
+// eslint-disable-next-line import/order
+import i18n from 'shared/config/i18n/i18n';
+
interface LoginFormProps {
className?: string;
}
-export const LoginForm = ({ className }: LoginFormProps) => {
+export const LoginForm = memo(({ className }: LoginFormProps) => {
const { t } = useTranslation();
+ const dispatch = useDispatch();
+ const { username, password, isLoading, error } = useSelector(getLoginState);
+
+ const onChangeUsername = useCallback(
+ (value: string) => {
+ dispatch(loginActions.setUsername(value));
+ },
+ [dispatch],
+ );
+
+ const onChangePassword = useCallback(
+ (value: string) => {
+ dispatch(loginActions.setPassword(value));
+ },
+ [dispatch],
+ );
+
+ const onLoginClick = useCallback(() => {
+ dispatch(loginByUsername({ username, password }));
+ }, [dispatch, username, password]);
+
+ const onSubmit = useCallback(
+ (e: FormEvent) => {
+ e.preventDefault();
+ onLoginClick();
+ },
+ [onLoginClick],
+ );
+
+ const onKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ onLoginClick();
+ }
+ },
+ [onLoginClick],
+ );
return (
-
-
-
-
+
);
-};
+});
diff --git a/src/features/AuthByUsername/ui/LoginModal/LoginModal.tsx b/src/features/AuthByUsername/ui/LoginModal/LoginModal.tsx
index 9ad1668..af16165 100644
--- a/src/features/AuthByUsername/ui/LoginModal/LoginModal.tsx
+++ b/src/features/AuthByUsername/ui/LoginModal/LoginModal.tsx
@@ -1,5 +1,6 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { Modal } from 'shared/ui/Modal/Modal';
+
import { LoginForm } from '../LoginForm/LoginForm';
interface LoginModalProps {
diff --git a/src/index.tsx b/src/index.tsx
index da585ad..88b322e 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -2,6 +2,7 @@ import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from 'app/providers/ThemeProvider';
import { StoreProvider } from 'app/providers/StoreProvider';
+
import App from './app/App';
import 'app/styles/index.scss';
diff --git a/src/pages/AboutPage/ui/AboutPage.stories.tsx b/src/pages/AboutPage/ui/AboutPage.stories.tsx
index 2cbf881..d127cad 100644
--- a/src/pages/AboutPage/ui/AboutPage.stories.tsx
+++ b/src/pages/AboutPage/ui/AboutPage.stories.tsx
@@ -1,7 +1,8 @@
import React from 'react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Theme } from 'app/providers/ThemeProvider';
+
import AboutPage from './AboutPage';
export default {
diff --git a/src/pages/MainPage/ui/MainPage.stories.tsx b/src/pages/MainPage/ui/MainPage.stories.tsx
index e0e66a8..0f913a7 100644
--- a/src/pages/MainPage/ui/MainPage.stories.tsx
+++ b/src/pages/MainPage/ui/MainPage.stories.tsx
@@ -1,7 +1,7 @@
-import React from 'react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { Theme } from 'app/providers/ThemeProvider';
+import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
+
import MainPage from './MainPage';
export default {
@@ -12,7 +12,7 @@ export default {
},
} as ComponentMeta;
-const Template: ComponentStory = (args) => ;
+const Template: ComponentStory = () => ;
export const Normal = Template.bind({});
Normal.args = {};
diff --git a/src/pages/NotFoundPage/ui/NotFoundPage.stories.tsx b/src/pages/NotFoundPage/ui/NotFoundPage.stories.tsx
index a4e9262..4db11a7 100644
--- a/src/pages/NotFoundPage/ui/NotFoundPage.stories.tsx
+++ b/src/pages/NotFoundPage/ui/NotFoundPage.stories.tsx
@@ -1,7 +1,8 @@
import React from 'react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Theme } from 'app/providers/ThemeProvider';
+
import { NotFoundPage } from './NotFoundPage';
export default {
diff --git a/src/pages/NotFoundPage/ui/NotFoundPage.tsx b/src/pages/NotFoundPage/ui/NotFoundPage.tsx
index ddd8c63..4afa2a8 100644
--- a/src/pages/NotFoundPage/ui/NotFoundPage.tsx
+++ b/src/pages/NotFoundPage/ui/NotFoundPage.tsx
@@ -1,5 +1,6 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { useTranslation } from 'react-i18next';
+
import cls from './NotFoundPage.module.scss';
interface NotFoundPageProps {
diff --git a/src/shared/config/i18n/i18n.ts b/src/shared/config/i18n/i18n.ts
index 95298a2..a1f16bf 100644
--- a/src/shared/config/i18n/i18n.ts
+++ b/src/shared/config/i18n/i18n.ts
@@ -1,15 +1,18 @@
import i18n from 'i18next';
-import { initReactI18next } from 'react-i18next';
-
-import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
+import Backend from 'i18next-http-backend';
+import { initReactI18next } from 'react-i18next';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
- fallbackLng: 'en',
+ fallbackLng: {
+ 'en-US': ['en'],
+ 'ru-RU': ['ru'],
+ default: ['en'],
+ },
debug: __IS_DEV__,
interpolation: {
diff --git a/src/shared/config/routeConfig/routeConfig.tsx b/src/shared/config/routeConfig/routeConfig.tsx
index 5a9823c..be24a9e 100644
--- a/src/shared/config/routeConfig/routeConfig.tsx
+++ b/src/shared/config/routeConfig/routeConfig.tsx
@@ -1,4 +1,4 @@
-import { RouteProps } from 'react-router-dom';
+import type { RouteProps } from 'react-router-dom';
import { MainPage } from 'pages/MainPage';
import { AboutPage } from 'pages/AboutPage';
import { NotFoundPage } from 'pages/NotFoundPage';
diff --git a/src/shared/config/storybook/RouterDecorator/RouterDecorator.tsx b/src/shared/config/storybook/RouterDecorator/RouterDecorator.tsx
index 9ca127d..bc4fb15 100644
--- a/src/shared/config/storybook/RouterDecorator/RouterDecorator.tsx
+++ b/src/shared/config/storybook/RouterDecorator/RouterDecorator.tsx
@@ -1,4 +1,4 @@
-import { Story } from '@storybook/react';
+import type { Story } from '@storybook/react';
import { BrowserRouter } from 'react-router-dom';
export const RouterDecorator = (story: () => Story) => {story()};
diff --git a/src/shared/config/storybook/StoreDecorator/StoreDecorator.tsx b/src/shared/config/storybook/StoreDecorator/StoreDecorator.tsx
new file mode 100644
index 0000000..bfecfa6
--- /dev/null
+++ b/src/shared/config/storybook/StoreDecorator/StoreDecorator.tsx
@@ -0,0 +1,11 @@
+import type { DeepPartial } from '@reduxjs/toolkit';
+import type { Story } from '@storybook/react';
+import { StoreProvider } from 'app/providers/StoreProvider';
+import type { StateSchema } from 'app/providers/StoreProvider';
+
+export const StoreDecorator =
+ (initialState: DeepPartial) => (StoryComponent: Story) => (
+
+
+
+ );
diff --git a/src/shared/config/storybook/StyleDecorator/StyleDecorator.ts b/src/shared/config/storybook/StyleDecorator/StyleDecorator.ts
index f758fd6..9527298 100644
--- a/src/shared/config/storybook/StyleDecorator/StyleDecorator.ts
+++ b/src/shared/config/storybook/StyleDecorator/StyleDecorator.ts
@@ -1,4 +1,4 @@
import 'app/styles/index.scss';
-import { Story } from '@storybook/react';
+import type { Story } from '@storybook/react';
export const StyleDecorator = (story: () => Story) => story();
diff --git a/src/shared/config/storybook/SuspenseDecorator/SuspenseDecorator.tsx b/src/shared/config/storybook/SuspenseDecorator/SuspenseDecorator.tsx
new file mode 100644
index 0000000..d174c51
--- /dev/null
+++ b/src/shared/config/storybook/SuspenseDecorator/SuspenseDecorator.tsx
@@ -0,0 +1,8 @@
+import { type Story } from '@storybook/react';
+import { Suspense } from 'react';
+
+export const SuspenseDecorator = (StoryComponent: Story) => (
+
+
+
+);
diff --git a/src/shared/config/storybook/ThemeDecorator/ThemeDecorator.tsx b/src/shared/config/storybook/ThemeDecorator/ThemeDecorator.tsx
index f2b2c5d..cf578fa 100644
--- a/src/shared/config/storybook/ThemeDecorator/ThemeDecorator.tsx
+++ b/src/shared/config/storybook/ThemeDecorator/ThemeDecorator.tsx
@@ -1,5 +1,6 @@
-import { Story } from '@storybook/react';
-import { Theme, ThemeProvider } from 'app/providers/ThemeProvider';
+import type { Story } from '@storybook/react';
+import type { Theme } from 'app/providers/ThemeProvider';
+import { ThemeProvider } from 'app/providers/ThemeProvider';
export const ThemeDecorator = (theme: Theme) => (StoryComponent: Story) => (
diff --git a/src/shared/const/localstorage.ts b/src/shared/const/localstorage.ts
new file mode 100644
index 0000000..8a61f39
--- /dev/null
+++ b/src/shared/const/localstorage.ts
@@ -0,0 +1 @@
+export const USER_LOCALSTORAGE_KEY = 'user';
diff --git a/src/shared/lib/tests/componentRender/componentRender.tsx b/src/shared/lib/tests/componentRender/componentRender.tsx
index 3b4627f..1da1958 100644
--- a/src/shared/lib/tests/componentRender/componentRender.tsx
+++ b/src/shared/lib/tests/componentRender/componentRender.tsx
@@ -1,7 +1,8 @@
-import { DeepPartial } from '@reduxjs/toolkit';
+import type { DeepPartial } from '@reduxjs/toolkit';
import { render } from '@testing-library/react';
-import { StateSchema, StoreProvider } from 'app/providers/StoreProvider';
-import { ReactNode } from 'react';
+import type { StateSchema } from 'app/providers/StoreProvider';
+import { StoreProvider } from 'app/providers/StoreProvider';
+import type { ReactNode } from 'react';
import { I18nextProvider } from 'react-i18next';
import { MemoryRouter } from 'react-router-dom';
import i18nForTests from 'shared/config/i18n/i18nForTests';
diff --git a/src/shared/lib/tests/renderWithTranslation/renderWithTranslation.tsx b/src/shared/lib/tests/renderWithTranslation/renderWithTranslation.tsx
index b591d67..c8ca4c1 100644
--- a/src/shared/lib/tests/renderWithTranslation/renderWithTranslation.tsx
+++ b/src/shared/lib/tests/renderWithTranslation/renderWithTranslation.tsx
@@ -1,4 +1,4 @@
-import { ReactNode } from 'react';
+import type { ReactNode } from 'react';
import { render } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import i18nForTests from 'shared/config/i18n/i18nForTests';
diff --git a/src/shared/ui/AppLink/AppLink.stories.tsx b/src/shared/ui/AppLink/AppLink.stories.tsx
index 9c836da..efb5f58 100644
--- a/src/shared/ui/AppLink/AppLink.stories.tsx
+++ b/src/shared/ui/AppLink/AppLink.stories.tsx
@@ -1,7 +1,8 @@
import React from 'react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Theme } from 'app/providers/ThemeProvider';
+
import { AppLink, AppLinkTheme } from './AppLink';
export default {
diff --git a/src/shared/ui/AppLink/AppLink.tsx b/src/shared/ui/AppLink/AppLink.tsx
index 8fe5ece..25bcf37 100644
--- a/src/shared/ui/AppLink/AppLink.tsx
+++ b/src/shared/ui/AppLink/AppLink.tsx
@@ -1,6 +1,8 @@
-import { FC } from 'react';
-import { Link, LinkProps } from 'react-router-dom';
+import type { FC } from 'react';
+import type { LinkProps } from 'react-router-dom';
+import { Link } from 'react-router-dom';
import { classNames } from 'shared/lib/classNames/classNames';
+
import cls from './AppLink.module.scss';
export enum AppLinkTheme {
diff --git a/src/shared/ui/Button/Button.module.scss b/src/shared/ui/Button/Button.module.scss
index 7df9edb..08df5cb 100644
--- a/src/shared/ui/Button/Button.module.scss
+++ b/src/shared/ui/Button/Button.module.scss
@@ -64,3 +64,7 @@
.size_xl {
font: var(--font-xl);
}
+
+.disabled {
+ opacity: 0.5;
+}
diff --git a/src/shared/ui/Button/Button.stories.tsx b/src/shared/ui/Button/Button.stories.tsx
index a732268..dbce691 100644
--- a/src/shared/ui/Button/Button.stories.tsx
+++ b/src/shared/ui/Button/Button.stories.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ThemeDecorator } from 'shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Theme } from 'app/providers/ThemeProvider';
+
import { Button, ButtonSize, ButtonTheme } from './Button';
export default {
@@ -93,3 +93,10 @@ SquareSizeXL.args = {
square: true,
size: ButtonSize.XL,
};
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ children: 'Click',
+ theme: ButtonTheme.OUTLINE,
+ disabled: true,
+};
diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx
index da3795f..df563ee 100644
--- a/src/shared/ui/Button/Button.tsx
+++ b/src/shared/ui/Button/Button.tsx
@@ -1,6 +1,7 @@
/* eslint-disable no-unused-vars */
-import { ButtonHTMLAttributes, FC } from 'react';
+import type { ButtonHTMLAttributes, FC } from 'react';
import { classNames } from 'shared/lib/classNames/classNames';
+
import cls from './Button.module.scss';
export enum ButtonTheme {
@@ -22,18 +23,29 @@ interface ButtonProps extends ButtonHTMLAttributes {
theme?: ButtonTheme;
square?: boolean;
size?: ButtonSize;
+ disabled?: boolean;
}
export const Button: FC = (props) => {
- const { className, children, theme, square, size = ButtonSize.M, ...otherProps } = props;
+ const {
+ className,
+ children,
+ theme,
+ square,
+ disabled,
+ size = ButtonSize.M,
+ ...otherProps
+ } = props;
const mods: Record = {
[cls.square]: square,
+ [cls.disabled]: disabled,
};
return (