From c1b81dfd6d19061e67452e6b15296c5128f41c51 Mon Sep 17 00:00:00 2001 From: theAshbringer Date: Mon, 8 Sep 2025 05:24:25 +0000 Subject: [PATCH 1/4] axios instance created and passed to thunk --- .../StoreProvider/config/StateSchema.ts | 29 +++++++++++++--- .../providers/StoreProvider/config/store.ts | 7 ++-- src/app/providers/StoreProvider/index.ts | 8 +++-- .../loginByUsername/loginByUsername.ts | 34 ++++++++++--------- src/shared/api/api.ts | 7 ++++ 5 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 src/shared/api/api.ts diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index 15ee7fc..fb5e6dd 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -1,18 +1,23 @@ import { - AnyAction, CombinedState, EnhancedStore, Reducer, ReducersMapObject, + AnyAction, + CombinedState, + EnhancedStore, + Reducer, + ReducersMapObject, } from '@reduxjs/toolkit'; +import { AxiosInstance } from 'axios'; import type { CounterSchema } from 'entities/Counter'; import { ProfileSchema } from 'entities/Profile'; import { UserSchema } from 'entities/User'; import { LoginSchema } from 'features/AuthByUsername'; export interface StateSchema { - counter: CounterSchema, - user: UserSchema, + counter: CounterSchema; + user: UserSchema; // Async reducers - login?: LoginSchema, - profile?: ProfileSchema, + login?: LoginSchema; + profile?: ProfileSchema; } export type StateSchemaKey = keyof StateSchema; @@ -27,3 +32,17 @@ export interface ReducerManager { export interface ReduxStoreWithManager extends EnhancedStore { reducerManager: ReducerManager; } + +/** Тип для extraArgument миддлвара thunk в createReduxStore() */ +export interface ThunkApiArg { + api: AxiosInstance; +} + +/** + * Тип конфига для всех thunk, включает тип для данных при ошибке и тип для прокинутого extraArgument. + * @template T - Тип данных, которые могут быть возвращены в случае отказа (reject). + */ +export interface ThunkConfig { + rejectValue: T; + extra: ThunkApiArg; +} diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index b089da3..5142c4d 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -1,12 +1,13 @@ import { configureStore, ReducersMapObject } from '@reduxjs/toolkit'; import { counterReducer } from 'entities/Counter'; import { userReducer } from 'entities/User'; +import { $api } from 'shared/api/api'; import { createReducerManager } from './reducerManager'; import type { StateSchema } from './StateSchema'; export function createReduxStore( initialState?: StateSchema, - asyncReducers?: ReducersMapObject, + asyncReducers?: ReducersMapObject ) { const rootReducer: ReducersMapObject = { ...asyncReducers, @@ -16,10 +17,12 @@ export function createReduxStore( const reducerManager = createReducerManager(rootReducer); - const store = configureStore({ + const store = configureStore({ reducer: reducerManager.reduce, devTools: __IS_DEV__, preloadedState: initialState, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ thunk: { extraArgument: { api: $api } } }), }); // @ts-ignore diff --git a/src/app/providers/StoreProvider/index.ts b/src/app/providers/StoreProvider/index.ts index 3a9ac72..31d2e89 100644 --- a/src/app/providers/StoreProvider/index.ts +++ b/src/app/providers/StoreProvider/index.ts @@ -1,3 +1,7 @@ -export type { StateSchema, ReduxStoreWithManager } from 'app/providers/StoreProvider/config/StateSchema'; +export type { + ReduxStoreWithManager, + StateSchema, + ThunkConfig, +} from 'app/providers/StoreProvider/config/StateSchema'; +export { AppDispatch, createReduxStore } from './config/store'; export { StoreProvider } from './ui/StoreProvider'; -export { createReduxStore, AppDispatch } from './config/store'; diff --git a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts index e432f75..779593b 100644 --- a/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts +++ b/src/features/AuthByUsername/model/services/loginByUsername/loginByUsername.ts @@ -1,5 +1,5 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import axios from 'axios'; +import { ThunkConfig } from 'app/providers/StoreProvider'; import { User, userActions } from 'entities/User'; import { USER_LOCAL_STORAGE_KEY } from 'shared/const/localStorage'; @@ -8,19 +8,21 @@ interface LoginByUsernameProps { password: string; } -export const loginByUsername = createAsyncThunk( - 'login/loginByUsername', - async (authData, { rejectWithValue, dispatch }) => { - try { - const response = await axios.post('http://localhost:8000/login', authData); - if (!response.data) { - throw new Error(); - } - localStorage.setItem(USER_LOCAL_STORAGE_KEY, JSON.stringify(response.data)); - dispatch(userActions.setAuthData(response.data)); - return response.data; - } catch (error) { - return rejectWithValue('error'); +export const loginByUsername = createAsyncThunk< + User, + LoginByUsernameProps, + ThunkConfig +>('login/loginByUsername', async (authData, thunkApi) => { + const { rejectWithValue, dispatch, extra } = thunkApi; + try { + const response = await extra.api.post('/login', authData); + if (!response.data) { + throw new Error(); } - }, -); + localStorage.setItem(USER_LOCAL_STORAGE_KEY, JSON.stringify(response.data)); + dispatch(userActions.setAuthData(response.data)); + return response.data; + } catch (error) { + return rejectWithValue('error'); + } +}); diff --git a/src/shared/api/api.ts b/src/shared/api/api.ts new file mode 100644 index 0000000..49bb3bf --- /dev/null +++ b/src/shared/api/api.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; +import { USER_LOCAL_STORAGE_KEY } from 'shared/const/localStorage'; + +export const $api = axios.create({ + baseURL: 'http://localhost:8100/', + headers: { authorization: localStorage.getItem(USER_LOCAL_STORAGE_KEY) }, +}); From 99123fa32463bf653df652827e950b42413389c3 Mon Sep 17 00:00:00 2001 From: theAshbringer Date: Mon, 8 Sep 2025 07:15:16 +0000 Subject: [PATCH 2/4] api url via env --- .eslintrc.js | 1 + config/build/buildPlugins.ts | 3 ++- config/build/types/config.ts | 24 +++++++++++++----------- config/jest/jest.config.ts | 1 + config/storybook/webpack.config.ts | 1 + src/app/types/global.d.ts | 1 + src/shared/api/api.ts | 2 +- webpack.config.ts | 2 ++ 8 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 876dc5c..bb6703c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -74,5 +74,6 @@ module.exports = { }, globals: { __IS_DEV__: true, + __API__: true, }, }; diff --git a/config/build/buildPlugins.ts b/config/build/buildPlugins.ts index 98fb29b..0a45aed 100644 --- a/config/build/buildPlugins.ts +++ b/config/build/buildPlugins.ts @@ -4,7 +4,7 @@ import { DefinePlugin, ProgressPlugin, WebpackPluginInstance } from 'webpack'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import { BuildOptions } from './types/config'; -export function buildPlugins({ paths, isDev }: BuildOptions): WebpackPluginInstance[] { +export function buildPlugins({ paths, isDev, apiUrl }: BuildOptions): WebpackPluginInstance[] { const plugins = [ new HtmlWebpackPlugin({ template: paths.html, @@ -16,6 +16,7 @@ export function buildPlugins({ paths, isDev }: BuildOptions): WebpackPluginInsta }), new DefinePlugin({ __IS_DEV__: JSON.stringify(isDev), + __API__: JSON.stringify(apiUrl), }), ]; diff --git a/config/build/types/config.ts b/config/build/types/config.ts index 5cf5148..74a4501 100644 --- a/config/build/types/config.ts +++ b/config/build/types/config.ts @@ -1,20 +1,22 @@ -export type BuildMode = 'production' | 'development' +export type BuildMode = 'production' | 'development'; export interface BuildPaths { - entry: string, - build: string, - html: string, - src: string, + entry: string; + build: string; + html: string; + src: string; } export interface BuildEnv { - port: number, - mode: BuildMode, + port: number; + mode: BuildMode; + api: string; } export interface BuildOptions { - mode: BuildMode, - paths: BuildPaths, - isDev: boolean, - port: number, + mode: BuildMode; + paths: BuildPaths; + isDev: boolean; + port: number; + apiUrl: string; } diff --git a/config/jest/jest.config.ts b/config/jest/jest.config.ts index ba43009..3494b33 100644 --- a/config/jest/jest.config.ts +++ b/config/jest/jest.config.ts @@ -53,6 +53,7 @@ export default { // A set of global variables that need to be available in all test environments globals: { __IS_DEV__: true, + __API__: true, }, transformIgnorePatterns: ['node_modules/(?!axios)'], diff --git a/config/storybook/webpack.config.ts b/config/storybook/webpack.config.ts index cc98340..c537e9b 100644 --- a/config/storybook/webpack.config.ts +++ b/config/storybook/webpack.config.ts @@ -32,6 +32,7 @@ export default ({ config }: {config: webpack.Configuration}) => { config.plugins!.push(new DefinePlugin({ __IS_DEV__: true, + __API__: true, })); return config; diff --git a/src/app/types/global.d.ts b/src/app/types/global.d.ts index ad48cc7..9c43e59 100644 --- a/src/app/types/global.d.ts +++ b/src/app/types/global.d.ts @@ -11,3 +11,4 @@ declare module '*.svg' { } declare const __IS_DEV__: boolean; +declare const __API__: string; diff --git a/src/shared/api/api.ts b/src/shared/api/api.ts index 49bb3bf..021fc42 100644 --- a/src/shared/api/api.ts +++ b/src/shared/api/api.ts @@ -2,6 +2,6 @@ import axios from 'axios'; import { USER_LOCAL_STORAGE_KEY } from 'shared/const/localStorage'; export const $api = axios.create({ - baseURL: 'http://localhost:8100/', + baseURL: __API__, headers: { authorization: localStorage.getItem(USER_LOCAL_STORAGE_KEY) }, }); diff --git a/webpack.config.ts b/webpack.config.ts index fbce96f..2ea5f18 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -14,11 +14,13 @@ export default (env: BuildEnv) => { const mode = env.mode || 'development'; const PORT = env.port || 3001; const isDev = mode === 'development'; + const apiUrl = isDev ? 'http://localhost:8100' : 'http://production.com'; const config: Configuration = buildWebpackConfig({ mode, paths, isDev, + apiUrl, port: PORT, }); From 546c6eb0526fe961816e259e9a1710ecbf16fd12 Mon Sep 17 00:00:00 2001 From: theAshbringer Date: Mon, 8 Sep 2025 07:15:33 +0000 Subject: [PATCH 3/4] update vscode settings --- .vscode/settings.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0faa680..f5becee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,13 +2,11 @@ "i18n-ally.localesPaths": ["public/locales", "src/shared/config/i18n"], "i18n-ally.keystyle": "nested", "i18n-ally.sourceLanguage": "ru", - // "editor.defaultFormatter": "dbaeumer.vscode-eslint", - // "editor.formatOnSave": true, - // "eslint.format.enable": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit", "source.fixAll.prettier": "explicit", + "source.organizeImports": "explicit" }, "eslint.validate": ["javascript"], "css.validate": false, @@ -17,4 +15,11 @@ "editor.formatOnSave": true, "jest.jestCommandLine": "npm run test:unit --", "prettier.configPath": ".prettierrc", + "typescript.inlayHints.parameterNames.enabled": "all", + "typescript.inlayHints.parameterTypes.enabled": true, + "typescript.inlayHints.variableTypes.enabled": true, + "typescript.inlayHints.functionLikeReturnTypes.enabled": true, + "editor.inlayHints.fontSize": 13, + "editor.inlayHints.maximumLength": 55, + "editor.inlayHints.enabled": "onUnlessPressed" } From d114d5560bdbbb3f8fd830cfbcc15f761180384f Mon Sep 17 00:00:00 2001 From: theAshbringer Date: Mon, 8 Sep 2025 07:30:14 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=82=D1=83=D0=BF=D0=BD=D1=8F=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 2 +- config/build/types/config.ts | 2 +- config/jest/jest.config.ts | 2 +- config/storybook/webpack.config.ts | 6 +++--- webpack.config.ts | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index bb6703c..3745e60 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -74,6 +74,6 @@ module.exports = { }, globals: { __IS_DEV__: true, - __API__: true, + __API__: '', }, }; diff --git a/config/build/types/config.ts b/config/build/types/config.ts index 74a4501..0046f1b 100644 --- a/config/build/types/config.ts +++ b/config/build/types/config.ts @@ -10,7 +10,7 @@ export interface BuildPaths { export interface BuildEnv { port: number; mode: BuildMode; - api: string; + apiUrl: string; } export interface BuildOptions { diff --git a/config/jest/jest.config.ts b/config/jest/jest.config.ts index 3494b33..cd79c4e 100644 --- a/config/jest/jest.config.ts +++ b/config/jest/jest.config.ts @@ -53,7 +53,7 @@ export default { // A set of global variables that need to be available in all test environments globals: { __IS_DEV__: true, - __API__: true, + __API__: '', }, transformIgnorePatterns: ['node_modules/(?!axios)'], diff --git a/config/storybook/webpack.config.ts b/config/storybook/webpack.config.ts index c537e9b..8d2f9ae 100644 --- a/config/storybook/webpack.config.ts +++ b/config/storybook/webpack.config.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import webpack, { DefinePlugin } from 'webpack'; import path from 'path'; +import webpack, { DefinePlugin } from 'webpack'; import { buildCssLoader } from '../build/loaders/buildCssLoader'; import { BuildPaths } from '../build/types/config'; @@ -31,8 +31,8 @@ export default ({ config }: {config: webpack.Configuration}) => { config.module!.rules!.push(buildCssLoader(true)); config.plugins!.push(new DefinePlugin({ - __IS_DEV__: true, - __API__: true, + __IS_DEV__: JSON.stringify(true), + __API__: JSON.stringify(''), })); return config; diff --git a/webpack.config.ts b/webpack.config.ts index 2ea5f18..5e7e2fa 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -13,8 +13,9 @@ export default (env: BuildEnv) => { const mode = env.mode || 'development'; const PORT = env.port || 3001; + const apiUrl = env.apiUrl || 'http://localhost:8100'; + const isDev = mode === 'development'; - const apiUrl = isDev ? 'http://localhost:8100' : 'http://production.com'; const config: Configuration = buildWebpackConfig({ mode,