diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 5a6e3033..00000000 --- a/.eslintrc +++ /dev/null @@ -1,34 +0,0 @@ -{ - "extends": "airbnb", - "parser": "babel-eslint", - "plugins": [ - "react", - "jsx-a11y", - "import" - ], - "rules": { - "comma-dangle": 0, - "no-restricted-syntax": 1, - "new-cap": 1, - "no-continue": 1, - "no-underscore-dangle": 1, - "global-require": 1, - "react/no-multi-comp": 1, - "react/jsx-filename-extension": 0, - "camelcase": 1, - "import/no-unresolved": 1, - "import/prefer-default-export": 1, - "import/extensions": 1, - "no-return-assign": 1, - "max-len": 1, - "jsx-a11y/no-static-element-interactions": 0, - "jsx-a11y/click-events-have-key-events": 0, - "jsx-a11y/interactive-supports-focus": 0 - }, - "globals": { - "document": false, - "window": false, - "HTMLInputElement": false, - "HTMLDivElement": false - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..4b23f3e5 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,107 @@ +// module.exports = { +// extends: ['airbnb', 'airbnb-base', 'plugin:@typescript-eslint/recommended'], +// // "parser": "babel-eslint", +// parser: '@typescript-eslint/parser', +// parserOptions: { +// ecmaFeatures: { +// jsx: true +// }, +// project: './tsconfig.json' +// }, +// plugins: ['@typescript-eslint', 'react', 'jsx-a11y', 'import', 'react-hooks'], +// rules: { +// indent: ['error', 2], +// '@typescript-eslint/indent': ['error', 2], +// 'comma-dangle': 0, +// 'no-restricted-syntax': 1, +// 'new-cap': 1, +// 'no-continue': 1, +// 'no-underscore-dangle': 1, +// 'global-require': 1, +// 'react/no-multi-comp': 1, +// 'react/jsx-filename-extension': 0, +// camelcase: 1, +// 'import/no-unresolved': 1, +// 'import/prefer-default-export': 1, +// 'import/extensions': 1, +// 'no-return-assign': 1, +// 'max-len': 1, +// 'jsx-a11y/no-static-element-interactions': 0, +// 'jsx-a11y/click-events-have-key-events': 0, +// 'jsx-a11y/interactive-supports-focus': 0, +// 'react-hooks/rules-of-hooks': 'error', +// 'react-hooks/exhaustive-deps': 'warn', +// 'no-nested-ternary': 1 +// }, +// globals: { +// document: false, +// window: false, +// HTMLInputElement: false, +// HTMLDivElement: false +// } +// }; +module.exports = { + extends: ['plugin:@typescript-eslint/recommended'], + plugins: ['import', '@typescript-eslint'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + project: './tsconfig.json', + }, + globals: { + Reactotron: true, + }, + rules: { + curly: ['error', 'all'], + 'valid-jsdoc': 'error', + 'linebreak-style': 'off', + 'no-console': 'off', + 'object-curly-newline': 'off', + 'no-unused-expressions': 'off', + 'no-unused-vars': 'off', + 'prefer-destructuring': [ + 'error', + { + VariableDeclarator: { + object: true, + }, + }, + ], + 'function-paren-newline': ['error', 'consistent'], + 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], + + 'eslint-comments/no-unlimited-disable': 'off', + + 'import/no-unresolved': 'off', + 'import/extensions': 'off', + 'import/prefer-default-export': 'off', + + 'prettier/prettier': [ + 'error', + { + singleQuote: true, + parser: 'flow', + trailingComma: 'all', + printWidth: 100, + }, + '@format', + ], + + '@typescript-eslint/indent': ['error', 2], + '@typescript-eslint/explicit-function-return-type': [ + 'off', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, + }, + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/no-empty-interface': 'off', + }, +}; diff --git a/package-lock.json b/package-lock.json index 224de817..3f0203a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2599,6 +2599,16 @@ "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.7.tgz", "integrity": "sha512-rzOhiQ55WzAiFgXRtitP/ZUT8iVNyllEpylJ5zHzR4vArUvMB39GTk+Zon/uAM0JxEFAWnwsxC2gH8s+tZ3Myg==" }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==" + }, + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==" + }, "@types/node": { "version": "11.11.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.1.tgz", @@ -2624,6 +2634,74 @@ "csstype": "^2.2.0" } }, + "@typescript-eslint/eslint-plugin": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz", + "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==", + "requires": { + "@typescript-eslint/experimental-utils": "1.13.0", + "eslint-utils": "^1.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^2.0.1", + "tsutils": "^3.7.0" + }, + "dependencies": { + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" + }, + "tsutils": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.0.tgz", + "integrity": "sha512-fyveWOtAXfumAxIqkcMHuPaaVyLBKjB8Y00ANZkqh+HITBAQscCbQIHwwBTJdvQq7RykLEbOPcUUnJ16X4NA0g==", + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", + "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -7286,11 +7364,18 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz", + "integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==", + "requires": { + "eslint-visitor-keys": "^1.0.0" + } + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" }, "espree": { "version": "3.5.4", @@ -7321,7 +7406,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, "requires": { "estraverse": "^4.1.0" } @@ -7329,8 +7413,7 @@ "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" }, "esutils": { "version": "2.0.2", @@ -8507,8 +8590,7 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, "gauge": { "version": "2.7.4", @@ -10379,6 +10461,11 @@ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=" + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", diff --git a/package.json b/package.json index a252d778..614618ad 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "@types/classnames": "^2.2.7", "@types/node": "^11.9.0", "@types/react": "16.8.23", + "@typescript-eslint/eslint-plugin": "^1.13.0", + "@typescript-eslint/parser": "^1.13.0", "axios": "^0.18.0", "babel-runtime": "^6.26.0", "classnames": "^2.2.6", diff --git a/src/redux/auth/api.ts b/src/redux/auth/api.ts index 6253169f..7b55fbbd 100644 --- a/src/redux/auth/api.ts +++ b/src/redux/auth/api.ts @@ -7,8 +7,7 @@ import { } from '~/utils/api'; import { API } from '~/constants/api'; import { IResultWithStatus } from '~/redux/types'; -import { authMeTransform, userLoginTransform } from '~/redux/auth/transforms'; -import { IUser } from '~/redux/auth/types'; +import { userLoginTransform } from '~/redux/auth/transforms'; export const apiUserLogin = ({ username, @@ -22,10 +21,3 @@ export const apiUserLogin = ({ .then(resultMiddleware) .catch(errorMiddleware) .then(userLoginTransform); - -export const getAuthSelf = ({ access }): Promise> => - api - .get(API.USER.ME, configWithToken(access)) - .then(resultMiddleware) - .catch(errorMiddleware) - .then(authMeTransform); diff --git a/src/redux/auth/reducer.ts b/src/redux/auth/reducer.ts index 766b0b94..fd7acc69 100644 --- a/src/redux/auth/reducer.ts +++ b/src/redux/auth/reducer.ts @@ -1,14 +1,14 @@ -import {EMPTY_TOKEN, EMPTY_USER, AUTH_USER_ACTIONS} from "~/redux/auth/constants"; +import { EMPTY_USER } from "~/redux/auth/constants"; import { createReducer } from "~/utils/reducer"; -import {IAuthState} from "~/redux/auth/types"; -import {AUTH_USER_HANDLERS} from "~/redux/auth/handlers"; +import { IAuthState } from "~/redux/auth/types"; +import { AUTH_USER_HANDLERS } from "~/redux/auth/handlers"; const HANDLERS = { ...AUTH_USER_HANDLERS, }; const INITIAL_STATE: IAuthState = { - token: { ...EMPTY_TOKEN }, + token: null, user: { ...EMPTY_USER }, login: { error: null, diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts index 2c2cab24..e969ee89 100644 --- a/src/redux/auth/sagas.ts +++ b/src/redux/auth/sagas.ts @@ -1,26 +1,43 @@ -import {call, put, takeLatest } from 'redux-saga/effects'; +import { call, put, takeLatest, select } from 'redux-saga/effects'; import { SagaIterator } from 'redux-saga'; -import {AUTH_USER_ACTIONS} from "~/redux/auth/constants"; +import { AUTH_USER_ACTIONS } from "~/redux/auth/constants"; import * as ActionCreators from '~/redux/auth/actions'; -import {authSetToken, userSetLoginError} from "~/redux/auth/actions"; -import {apiUserLogin, getAuthSelf} from "~/redux/auth/api"; -import {modalSetShown} from "~/redux/modal/actions"; +import { authSetToken, userSetLoginError, authSetUser } from "~/redux/auth/actions"; +import { apiUserLogin } from "~/redux/auth/api"; +import { modalSetShown, modalShowDialog } from "~/redux/modal/actions"; +import { selectToken } from './selectors'; +import { push } from 'connected-react-router'; +import { URLS } from '~/constants/urls'; +import { DIALOGS } from '../modal/constants'; +import { IResultWithStatus } from '../types'; +import { IToken, IUser } from './types'; + +export function* reqWrapper(requestAction, props = {}): ReturnType { + const { access } = yield select(selectToken); + + const result = yield call(requestAction, { access, ...props }); + + if (result && result.status === 401) { + yield put(push(URLS.BASE)); + yield put(modalShowDialog(DIALOGS.LOGIN)); + + return result; + } + + return result; +} function* sendLoginRequestSaga({ username, password }: ReturnType): SagaIterator { if (!username || !password) return; - const { error, data: { access, refresh }} = yield call(apiUserLogin, { username, password }); + const { error, data: { token, user } }: IResultWithStatus<{ token: string, user: IUser }> = yield call(apiUserLogin, { username, password }); - console.log({ access, refresh, error }); + console.log({ token, error }); if (error) return yield put(userSetLoginError(error)); - yield put(authSetToken({ access, refresh })); - - const info = yield call(getAuthSelf); // todo: reqWrapper here - - // todo: get /auth/me - + yield put(authSetToken(token)); + yield put(authSetUser(user)); yield put(modalSetShown(false)); } diff --git a/src/redux/auth/selectors.ts b/src/redux/auth/selectors.ts index 98570e98..483d874f 100644 --- a/src/redux/auth/selectors.ts +++ b/src/redux/auth/selectors.ts @@ -1,4 +1,5 @@ -import {IState} from "~/redux/store"; +import { IState } from "~/redux/store"; export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user; +export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token; export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login; diff --git a/src/redux/auth/transforms.ts b/src/redux/auth/transforms.ts index 43ee722b..1ee063e6 100644 --- a/src/redux/auth/transforms.ts +++ b/src/redux/auth/transforms.ts @@ -1,8 +1,9 @@ -import {IResultWithStatus} from "~/redux/types"; +import { IResultWithStatus } from "~/redux/types"; +import { HTTP_RESPONSES } from "~/utils/api"; -export const userLoginTransform = ({ status, data,error }: IResultWithStatus): IResultWithStatus => { - switch(true) { - case status === 401 || !data.access || data.refresh: +export const userLoginTransform = ({ status, data, error }: IResultWithStatus): IResultWithStatus => { + switch (true) { + case (status === HTTP_RESPONSES.UNAUTHORIZED || !data.token) && status !== HTTP_RESPONSES.CONNECTION_REFUSED: return { status, data, error: 'Пользователь не найден' }; case status === 200: @@ -11,17 +12,4 @@ export const userLoginTransform = ({ status, data,error }: IResultWithStatus): IResultWithStatus => { - switch(true) { - case status === 401: - return { status, data, error: 'Пользователь не авторизован' }; - - case status === 200: - return { status, data, error: null }; - - default: - return { status, data, error: error || 'Неизвестная ошибка' }; - } -}; +}; \ No newline at end of file diff --git a/src/redux/auth/types.ts b/src/redux/auth/types.ts index eb07a06e..03a5cfca 100644 --- a/src/redux/auth/types.ts +++ b/src/redux/auth/types.ts @@ -17,7 +17,7 @@ export interface IUser { export type IAuthState = Readonly<{ user: IUser; - token: IToken; + token: string; login: { error: string; diff --git a/src/redux/node/reducer.ts b/src/redux/node/reducer.ts new file mode 100644 index 00000000..ed59aab9 --- /dev/null +++ b/src/redux/node/reducer.ts @@ -0,0 +1,15 @@ +import { createReducer } from "~/utils/reducer"; + +export type INodeState = { + is_loading: boolean; +} + +const HANDLERS = { + +}; + +const INITIAL_STATE: INodeState = { + is_loading: false, +}; + +export default createReducer(INITIAL_STATE, HANDLERS); \ No newline at end of file diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts index eaad04e4..051b8457 100644 --- a/src/utils/api/index.ts +++ b/src/utils/api/index.ts @@ -38,9 +38,9 @@ export const errorMiddleware = (debug): IResultWithStatus => ? debug.response : { status: HTTP_RESPONSES.CONNECTION_REFUSED, - data: null, + data: {}, debug, - error: 'Network_Disconnected', + error: 'Ошибка сети', }; export const configWithToken = (