diff --git a/.eslintrc.json b/.eslintrc.json index 859eda9..cb21439 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -57,7 +57,9 @@ "react/jsx-fragments": 0, "react/jsx-key": "warn", "react/no-array-index-key": 0, + "react/display-name": 0, "react/destructuring-assignment": 0, + "@typescript-eslint/no-non-null-assertion": 0, "no-console": [ "warn", { diff --git a/src/core/api/CrudAPI.ts b/src/core/api/CrudAPI.ts index d50ac2e..ccaf9ed 100644 --- a/src/core/api/CrudAPI.ts +++ b/src/core/api/CrudAPI.ts @@ -18,10 +18,10 @@ type RequestEntities = { offset?: number; }; -type EntityWithId = T & { +export type EntityWithId = T & { id: string; }; -type EntityWithoutId = Omit; +export type EntityWithoutId = Omit; type ResponseEntities = { data: EntityWithId[]; @@ -68,8 +68,8 @@ export class CrudAPI { return http.post, EntityWithId>(this.endpoint, undefined, entity); } - update = (id: string, entity: T): Promise> => { - return http.patch>(`${this.endpoint}/${id}`, undefined, entity); + update = (id: string, entity: EntityWithoutId): Promise> => { + return http.patch, EntityWithId>(`${this.endpoint}/${id}`, undefined, entity); } replace = (id: string, entity: T): Promise> => { diff --git a/src/core/consts/common.ts b/src/core/consts/common.ts index d7a4aa1..3fa1cba 100644 --- a/src/core/consts/common.ts +++ b/src/core/consts/common.ts @@ -8,6 +8,7 @@ export const ROUTES = { }; export const ENDPOINT = { + AUTH: 'https://localhost:3189/api/auth', USERS: 'https://localhost:3189/api/users', ACTIONS: 'https://localhost:3189/api/bot/actions', CONDITIONS: 'https://localhost:3189/api/bot/conditions', diff --git a/src/core/infrastructure/atom/usersAtom.ts b/src/core/infrastructure/atom/usersAtom.ts index 6631586..d327b80 100644 --- a/src/core/infrastructure/atom/usersAtom.ts +++ b/src/core/infrastructure/atom/usersAtom.ts @@ -11,28 +11,28 @@ export type FieldData = { }; const INIT_USERS: User[] = []; -export const INIT_USER: FieldData[] = [ - {name: 'id', value: ''}, - {name: 'login', value: ''}, - {name: 'password', value: ''}, -]; +export const INIT_USER: User = { + id: '', + login: '', + password: '', +}; export const loadUsersAction = declareAction(); export const usersAtom = declareAtom(INIT_USERS, on => [ on(loadUsersAction, (_state, payload) => payload), ]); -export const loadUserForm = declareAction(); +export const loadUserForm = declareAction(); export const userFormAtom = declareAtom(INIT_USER, on => [ - on(loadUserForm, (_state, payload) => payload), + on(loadUserForm, (state, payload) => payload), ]); export const bindedActions = { loadUsersAction: (users: User[]) => { store.dispatch(loadUsersAction(users)); }, - loadUserForm: (fieldData: FieldData[]) => { - store.dispatch(loadUserForm(fieldData)); + loadUserForm: (user: User) => { + store.dispatch(loadUserForm(user)); } }; diff --git a/src/pages/users/api/UsersAPI.ts b/src/pages/users/api/UsersAPI.ts index 6c2149d..135708b 100644 --- a/src/pages/users/api/UsersAPI.ts +++ b/src/pages/users/api/UsersAPI.ts @@ -1,5 +1,25 @@ import {ENDPOINT} from '_consts/common'; -import {CrudAPI} from '../../../core/api/CrudAPI'; +import {CrudAPI, EntityWithId, EntityWithoutId} from '../../../core/api/CrudAPI'; +import {http} from '../../../core/infrastructure/Http'; import {User} from '../types'; +import {ChangePasswordRequest} from './types'; -export const usersAPI = new CrudAPI(ENDPOINT.USERS); +class UsersAPI extends CrudAPI { + create = (entity: EntityWithoutId): Promise> => { + return http.post, EntityWithId>( + `${ENDPOINT.AUTH}/register-user`, + undefined, + entity, + ); + } + + changePassword = (id: string, password: string): Promise => { + return http.post( + `${ENDPOINT.AUTH}/admin-change-password`, + undefined, + {id, password}, + ); + } +} + +export const usersAPI = new UsersAPI(ENDPOINT.USERS); diff --git a/src/pages/users/api/types.ts b/src/pages/users/api/types.ts new file mode 100644 index 0000000..3771f1c --- /dev/null +++ b/src/pages/users/api/types.ts @@ -0,0 +1,4 @@ +export type ChangePasswordRequest = { + id: string; + password: string; +}; diff --git a/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx b/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx new file mode 100644 index 0000000..4a2672e --- /dev/null +++ b/src/pages/users/components/change-password-modal/ChangePasswordModal.tsx @@ -0,0 +1,44 @@ +import {Input, Modal} from 'antd'; +import React, {FC, memo, useCallback, useState} from 'react'; +import {ROUTES} from '../../../../core/consts/common'; +import {useQuery} from '../../../../core/hooks/useQuery'; +import {routerService} from '../../../../core/services/RouterService'; +import {LABELS} from '../../consts'; +import {ModalType} from '../../enums'; +import {usersService} from '../../services/UsersServices'; +import {queryParsers} from '../../utils'; + +const handleClose = () => { + routerService.pushWithQuery(ROUTES.USERS, {}, {reset: true}); +}; + +const ChangePasswordModal: FC = () => { + const {id, modalType} = useQuery(queryParsers); + const isOpen = modalType === ModalType.ChangePassword; + + const [password, setForm] = useState(''); + + const handleChange = useCallback((event: React.SyntheticEvent) => { + setForm(event.currentTarget.value); + }, [setForm]); + + const handleChangePassword = useCallback(() => { + usersService.changePassword(id, password); + }, [id, password]); + + return ( + +
+ + +
+
+ ); +}; + +export default memo(ChangePasswordModal); diff --git a/src/pages/users/components/change-password-modal/index.ts b/src/pages/users/components/change-password-modal/index.ts new file mode 100644 index 0000000..08a16c2 --- /dev/null +++ b/src/pages/users/components/change-password-modal/index.ts @@ -0,0 +1 @@ +export * from './ChangePasswordModal'; diff --git a/src/pages/users/components/page/Page.tsx b/src/pages/users/components/page/Page.tsx index bba6adf..90109a1 100644 --- a/src/pages/users/components/page/Page.tsx +++ b/src/pages/users/components/page/Page.tsx @@ -1,9 +1,10 @@ import {Button, Layout} from 'antd'; -import React, {FC, memo} from 'react'; +import React, {FC, Fragment, memo} from 'react'; import {createUseStyles} from 'react-jss'; import {ROUTES} from '../../../../core/consts/common'; import {routerService} from '../../../../core/services/RouterService'; import {EntityMode} from '../../../../core/types/EntityModes'; +import ChangePasswordModal from '../change-password-modal/ChangePasswordModal'; import UserSidebar from '../user-sidebar/UserSidebar'; import UsersTable from '../users-table/UsersTable'; @@ -22,20 +23,23 @@ const handleClickNewUser = () => { const Page: FC = () => { const classes = useStyles(); return ( - - - - - - - - - + + + + + + + + + + + + ); }; diff --git a/src/pages/users/components/user-sidebar/UserSidebar.tsx b/src/pages/users/components/user-sidebar/UserSidebar.tsx index 4c88a71..9f0e95e 100644 --- a/src/pages/users/components/user-sidebar/UserSidebar.tsx +++ b/src/pages/users/components/user-sidebar/UserSidebar.tsx @@ -1,59 +1,59 @@ import {useAtom} from '@reatom/react'; -import {Button, Drawer, Form, Input} from 'antd'; -import React, {FC, Fragment, memo, useCallback, useEffect, useMemo} from 'react'; +import {Button, Drawer, Input} from 'antd'; +import React, {FC, Fragment, memo, SyntheticEvent, useCallback, useEffect, useMemo} from 'react'; import {createUseStyles} from 'react-jss'; import {ROUTES} from '../../../../core/consts/common'; import {useQuery} from '../../../../core/hooks/useQuery'; -import {bindedActions, FieldData, userFormAtom} from '../../../../core/infrastructure/atom/usersAtom'; -import {isNotEmpty} from '../../../../core/referers/common'; +import {bindedActions, userFormAtom} from '../../../../core/infrastructure/atom/usersAtom'; import {routerService} from '../../../../core/services/RouterService'; import {EntityMode} from '../../../../core/types/EntityModes'; import {usersService} from '../../services/UsersServices'; -import {User} from '../../types'; import {queryParsers} from '../../utils'; const AVAILABLE_CLOSE_MODES = [EntityMode.Show]; const DISABLED_FORM_MODES = [EntityMode.Show]; -const SHOW_ID_MODES = [EntityMode.Show]; +const SHOW_PASSWORD_MODES = [EntityMode.Create, EntityMode.Copy]; const useStyles = createUseStyles({ button: { marginRight: '8px', - } + }, + input: { + marginBottom: '16px', + }, }); const handleClose = () => { routerService.push(ROUTES.USERS); }; -const onFieldsChange = (_: FieldData[], allFields: FieldData[]) => { - bindedActions.loadUserForm(allFields); -}; - const UserSidebar: FC = () => { const {mode, id} = useQuery(queryParsers); - const fields = useAtom(userFormAtom); + const form = useAtom(userFormAtom); const classes = useStyles(); useEffect(() => { usersService.loadUser(id); }, [id, mode]); + const onChange = (event: SyntheticEvent) => { + const {name, value} = event.currentTarget; + bindedActions.loadUserForm({ + ...form, + [name]: value, + }); + }; + const disabled = useMemo(() => !mode || DISABLED_FORM_MODES.includes(mode), [mode]); - const handleCreate = useCallback(() => { - const user = fields.reduce((acc, {name, value}) => { - if (Array.isArray(name)) { - if (isNotEmpty(name[0])) { - acc[name[0]] = value; - } - } else { - acc[name] = value; - } - return acc; - }, {id: '', login: '', password: ''}); - usersService.createUser(user); - }, [fields]); + const handleCreateUser = useCallback(() => { + const {login, password} = form; + usersService.createUser({login, password}); + }, [form]); + + const handleSaveUser = useCallback(() => { + usersService.updateUser(form); + }, [form]); const handleCopy = useCallback(() => { if (id) { @@ -105,7 +105,7 @@ const UserSidebar: FC = () => { return ( + ), + }; + } return { title: field, dataIndex: field, diff --git a/src/pages/users/consts.ts b/src/pages/users/consts.ts new file mode 100644 index 0000000..f69278c --- /dev/null +++ b/src/pages/users/consts.ts @@ -0,0 +1,3 @@ +export const LABELS = { + CHANGE_PASSWORD: 'Change password', +}; diff --git a/src/pages/users/enums.ts b/src/pages/users/enums.ts new file mode 100644 index 0000000..56e583c --- /dev/null +++ b/src/pages/users/enums.ts @@ -0,0 +1,3 @@ +export enum ModalType { + ChangePassword = 'changePassword', +} diff --git a/src/pages/users/services/UsersServices.ts b/src/pages/users/services/UsersServices.ts index 6cb377b..1e4a041 100644 --- a/src/pages/users/services/UsersServices.ts +++ b/src/pages/users/services/UsersServices.ts @@ -1,7 +1,7 @@ +import {EntityWithoutId} from '../../../core/api/CrudAPI'; import {ROUTES} from '../../../core/consts/common'; -import {bindedActions, FieldData, INIT_USER} from '../../../core/infrastructure/atom/usersAtom'; +import {bindedActions, INIT_USER} from '../../../core/infrastructure/atom/usersAtom'; import {routerService} from '../../../core/services/RouterService'; -import {objectEntries} from '../../../core/utils/objectEntries'; import {usersAPI} from '../api/UsersAPI'; import {User} from '../types'; @@ -19,18 +19,16 @@ class UsersService { usersAPI .find(id) .then(user => { - const fieldData = objectEntries(user).reduce((acc, [name, value]) => { - acc.push({name, value}); - return acc; - }, []); - bindedActions.loadUserForm(fieldData); + bindedActions.loadUserForm({ + ...user, + password: '', + }); }); } bindedActions.loadUserForm(INIT_USER); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - createUser({id, ...user}: User) { + createUser(user: EntityWithoutId) { usersAPI .create(user) .then(() => { @@ -40,6 +38,28 @@ class UsersService { }); } + changePassword(id?: string, password?: string) { + if (id && password) { + usersAPI + .changePassword(id, password) + .then(() => { + this.loadUsers().then(() => { + routerService.push(ROUTES.USERS); + }); + }); + } + } + + updateUser({id, ...user}: User) { + usersAPI + .update(id, user) + .then(() => { + this.loadUsers().then(() => { + routerService.push(ROUTES.USERS); + }); + }); + } + removeUser(id?: string) { if (id) { usersAPI diff --git a/src/pages/users/types.ts b/src/pages/users/types.ts index 9b4dd00..92817b5 100644 --- a/src/pages/users/types.ts +++ b/src/pages/users/types.ts @@ -1,4 +1,5 @@ import {EntityMode} from '../../core/types/EntityModes'; +import {ModalType} from './enums'; export type User = { id: string; @@ -9,4 +10,5 @@ export type User = { export type QueryParams = { id?: string; mode?: EntityMode; + modalType?: ModalType; }; diff --git a/src/pages/users/utils.ts b/src/pages/users/utils.ts index 202631b..58782d1 100644 --- a/src/pages/users/utils.ts +++ b/src/pages/users/utils.ts @@ -1,8 +1,9 @@ -import {QueryParsers} from '../../core/utils/getQueryFromUrl'; +import {EntityMode} from '../../core/types/EntityModes'; import {stringParser} from '../../core/utils/queryParsers'; -import {QueryParams} from './types'; +import {ModalType} from './enums'; -export const queryParsers: QueryParsers = { +export const queryParsers = { id: stringParser(), - mode: stringParser(), + mode: stringParser(), + modalType: stringParser(), }; diff --git a/webpack.config.js b/webpack.config.js index b7abd54..a87948e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,6 +33,11 @@ module.exports = { pathRewrite: { '^/api': '' }, secure: false, }, + '/api/auth': { + target: 'http://vigdorov.ru:3013', + pathRewrite: { '^/api': '' }, + secure: false, + }, '/api/bot': { target: 'http://vigdorov.ru:3012', pathRewrite: { '^/api/bot': '' },