diff --git a/src/components/avatar-modal-component/AvatarModal.js b/src/components/avatar-modal-component/AvatarModal.js
index 37021b5..6cdd0c8 100644
--- a/src/components/avatar-modal-component/AvatarModal.js
+++ b/src/components/avatar-modal-component/AvatarModal.js
@@ -53,7 +53,7 @@ class AvatarModal extends Modal {
}
init = async () => {
- const user = await usersServiceApi.getMe();
+ const user = await usersServiceApi.getSelfInfo();
this.changeAvatar(this.url || user.avatar);
}
diff --git a/src/components/client-logs-view-form/ClientLogsViewForm.js b/src/components/client-logs-view-form/ClientLogsViewForm.js
index bbe8d26..d160378 100644
--- a/src/components/client-logs-view-form/ClientLogsViewForm.js
+++ b/src/components/client-logs-view-form/ClientLogsViewForm.js
@@ -3,6 +3,7 @@ import FormControl from '../form-control';
import {FORM_TYPES} from '../../consts';
import ModalSidebar from '../modal-sidebar';
import './ClientLogsViewForm.css';
+import {INPUT_IDS, LABELS, CLASSNAMES} from './consts';
class ClientLogsViewForm extends Component {
constructor (parentNode) {
@@ -15,44 +16,50 @@ class ClientLogsViewForm extends Component {
this.title = this.mainNode.querySelector('.h2');
this.form = this.mainNode.querySelector('form');
- this.title.textContent = 'Просмотр клиентского запроса';
+ this.title.textContent = LABELS.VIEW_CLIENT_REQUEST;
const inputs = [
this.idInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-id',
- label: 'id',
+ id: INPUT_IDS.ID,
+ label: LABELS.ID,
}),
this.resultInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-result',
- label: 'Результат',
+ id: INPUT_IDS.RESULT,
+ label: LABELS.RESULT,
}),
this.startTimeInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-startTime',
- label: 'Время запроса',
+ id: INPUT_IDS.START_TIME,
+ label: LABELS.TIME_REQUEST,
}),
this.headersInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-headers',
- label: 'Заголовки запроса',
+ id: INPUT_IDS.HEADERS,
+ label: LABELS.HEADERS_REQUEST,
type: FORM_TYPES.TEXTAREA,
- className: 'ClientLogsViewForm__headersInput',
+ className: CLASSNAMES.HEADERS_INPUT,
+ }),
+ this.bodyRequest = this.createComponent(FormControl, this.form, {
+ id: INPUT_IDS.BODY,
+ label: LABELS.BODY_REQUEST,
+ type: FORM_TYPES.TEXTAREA,
+ className: CLASSNAMES.HEADERS_INPUT,
}),
this.urlInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-url',
- label: 'URL запроса',
+ id: INPUT_IDS.URL,
+ label: LABELS.URL_REQUEST,
}),
this.methodInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-method',
- label: 'Метод запроса',
+ id: INPUT_IDS.METHOD,
+ label: LABELS.METHOD_REQUEST,
}),
this.endTimeInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-endTime',
- label: 'Время ответа',
+ id: INPUT_IDS.END_TIME,
+ label: LABELS.TIME_RESPONSE,
}),
this.responseInput = this.createComponent(FormControl, this.form, {
- id: 'client-logs-view-form-response',
- label: 'Ответ сервера',
+ id: INPUT_IDS.RESPONSE,
+ label: LABELS.SERVER_RESPONSE,
type: FORM_TYPES.TEXTAREA,
- className: 'ClientLogsViewForm__responseInput',
+ className: CLASSNAMES.RESPONSE_INPUT,
}),
];
inputs.forEach((input) => {
@@ -65,8 +72,13 @@ class ClientLogsViewForm extends Component {
return JSON.stringify(object, false, 4);
}
- setForm ({_id, type, request, response, startTime, endTime}) {
- const {headers, url, method} = JSON.parse(request) ?? {};
+ setRequestBody = (body) => {
+ this.bodyRequest.setValue(this.prepareStringJSON(body));
+ this.bodyRequest.mainNode.style.display = body ? 'block' : 'none';
+ }
+
+ setForm = ({_id, type, request, response, startTime, endTime}) => {
+ const {headers, url, method, body} = JSON.parse(request) ?? {};
this.idInput.setValue(_id);
this.resultInput.setValue(type);
this.startTimeInput.setValue(startTime);
@@ -75,6 +87,7 @@ class ClientLogsViewForm extends Component {
this.methodInput.setValue(method);
this.endTimeInput.setValue(endTime);
this.responseInput.setValue(this.prepareStringJSON(response));
+ this.setRequestBody(body);
this.sidebar.show();
}
}
diff --git a/src/components/client-logs-view-form/consts.js b/src/components/client-logs-view-form/consts.js
new file mode 100644
index 0000000..3b96ba1
--- /dev/null
+++ b/src/components/client-logs-view-form/consts.js
@@ -0,0 +1,29 @@
+export const INPUT_IDS = {
+ ID: 'client-logs-view-form-id',
+ RESULT: 'client-logs-view-form-result',
+ START_TIME: 'client-logs-view-form-startTime',
+ HEADERS: 'client-logs-view-form-headers',
+ BODY: 'client-logs-view-form-body-request',
+ URL: 'client-logs-view-form-url',
+ METHOD: 'client-logs-view-form-method',
+ END_TIME: 'client-logs-view-form-endTime',
+ RESPONSE: 'client-logs-view-form-response',
+};
+
+export const LABELS = {
+ ID: 'id',
+ RESULT: 'Результат',
+ TIME_REQUEST: 'Время запроса',
+ HEADERS_REQUEST: 'Заголовки запроса',
+ BODY_REQUEST: 'Тело запроса',
+ URL_REQUEST: 'URL запроса',
+ METHOD_REQUEST: 'Метод запроса',
+ TIME_RESPONSE: 'Время ответа',
+ SERVER_RESPONSE: 'Ответ сервера',
+ VIEW_CLIENT_REQUEST: 'Просмотр клиентского запроса',
+};
+
+export const CLASSNAMES = {
+ HEADERS_INPUT: 'ClientLogsViewForm__headersInput',
+ RESPONSE_INPUT: 'ClientLogsViewForm__responseInput',
+};
diff --git a/src/components/form-control/FormControl.js b/src/components/form-control/FormControl.js
index 4eaa4bd..afd6599 100644
--- a/src/components/form-control/FormControl.js
+++ b/src/components/form-control/FormControl.js
@@ -1,5 +1,5 @@
import Component from '../component';
-import {FORM_TYPES} from '../../consts';
+import {FORM_TYPES, EVENTS, TAG_NAME} from '../../consts';
class FormControl extends Component {
constructor (parentNode, {
@@ -7,9 +7,11 @@ class FormControl extends Component {
id,
type = FORM_TYPES.TEXT,
placeholder = '',
+ pattern,
initValue = '',
className = '',
required = false,
+ options = [],
} = {}) {
super('#form-control', parentNode);
@@ -18,11 +20,12 @@ class FormControl extends Component {
this.input = this.createElement({
tagName: this.getInputTagName(type),
options: {
- className: `form-control ${className}`,
+ className: `${this.getInputBaseClassName(type)} ${className}`,
},
args: {
type: type === FORM_TYPES.PASSWORD ? 'password' : 'text',
...(required ? {required: ''} : {}),
+ ...(pattern ? {pattern} : {}),
}
});
this.input.placeholder = placeholder;
@@ -33,12 +36,31 @@ class FormControl extends Component {
this.label.textContent = label;
this.label.setAttribute('for', id);
- this.addEventListener(this.input, 'focus', this.clearError);
- this.addEventListener(this.input, 'click', this.clearError);
- this.addEventListener(this.input, 'keydown', this.clearError);
- this.addEventListener(this.input, 'input', (evt) => {
- this.next('input', evt);
+ this.addEventListener(this.input, EVENTS.FOCUS, this.clearError);
+ this.addEventListener(this.input, EVENTS.CLICK, this.clearError);
+ this.addEventListener(this.input, EVENTS.KEYDOWN, this.clearError);
+ this.addEventListener(this.input, EVENTS.INPUT, (evt) => {
+ this.next(EVENTS.INPUT, evt);
});
+
+ if (type === FORM_TYPES.SELECT) {
+ options.forEach(({value, text}) => {
+ this.createElement({
+ tagName: TAG_NAME.OPTION,
+ parentNode: this.input,
+ options: {
+ textContent: text,
+ },
+ args: {
+ value,
+ },
+ });
+ });
+
+ this.addEventListener(this.input, EVENTS.CHANGE, (evt) => {
+ this.next(EVENTS.CHANGE, evt);
+ });
+ }
}
disabled = (value) => {
@@ -61,20 +83,29 @@ class FormControl extends Component {
this.errorText.textContent = '';
}
- getInputTagName (type) {
+ getInputTagName = (type) => {
switch (type) {
case FORM_TYPES.TEXT:
case FORM_TYPES.PASSWORD:
- return 'input';
+ return TAG_NAME.INPUT;
case FORM_TYPES.SELECT:
- return 'select';
+ return TAG_NAME.SELECT;
case FORM_TYPES.TEXTAREA:
- return 'textarea';
+ return TAG_NAME.TEXTAREA;
default: {
throw new Error(`Тип формы ${type} не поддерживается`);
}
}
}
+
+ getInputBaseClassName = (type) => {
+ switch (type) {
+ case FORM_TYPES.SELECT:
+ return 'form-select';
+ default:
+ return 'form-control';
+ }
+ }
}
export default FormControl;
diff --git a/src/components/profile-page/ProfilePage.js b/src/components/profile-page/ProfilePage.js
index 74bc3ce..f57fac1 100644
--- a/src/components/profile-page/ProfilePage.js
+++ b/src/components/profile-page/ProfilePage.js
@@ -23,7 +23,7 @@ class ProfilePage extends Component {
}
init = async () => {
- this.user = await usersServiceApi.getMe();
+ this.user = await usersServiceApi.getSelfInfo();
this.form.initProfile(this.user);
}
}
diff --git a/src/components/user-view-form/UserViewForm.js b/src/components/user-view-form/UserViewForm.js
new file mode 100644
index 0000000..6a54915
--- /dev/null
+++ b/src/components/user-view-form/UserViewForm.js
@@ -0,0 +1,236 @@
+import Component from '../component';
+import ModalSidebar from '../modal-sidebar';
+import FormControl from '../form-control';
+import {FORM_TYPES, EVENTS, MODES} from '../../consts';
+import routeService from '../../services/RouteService';
+import usersServiceApi from '../../api/UsersServiceAPI';
+import userInfoService from '../../services/UserInfoService';
+
+const TITLE_MODES = {
+ [MODES.Create]: 'Создание пользователя',
+ [MODES.View]: 'Просмотр пользователя',
+ [MODES.Edit]: 'Редактирование пользователя',
+};
+
+const EDIT_MODES = [MODES.Create, MODES.Edit];
+
+const TRUE = 'true';
+
+class UserViewForm extends Component {
+ constructor (parentNode) {
+ super('#user-view-form', parentNode);
+
+ this.sidebar = this.createComponent(ModalSidebar, {
+ content: this.mainNode,
+ });
+
+ this.title = this.mainNode.querySelector('.h2');
+ this.form = this.mainNode.querySelector('form');
+
+ this.inputs = [
+ this.login = this.createComponent(FormControl, this.form, {
+ id: 'user-view-form-login',
+ label: 'Логин пользователя',
+ pattern: '^[a-zA-Z][a-zA-Z0-9_-]{3,}$',
+ required: true,
+ offComplete: true,
+ }),
+ this.password = this.createComponent(FormControl, this.form, {
+ id: 'user-view-form-password',
+ label: 'Пароль',
+ type: FORM_TYPES.PASSWORD,
+ }),
+ this.avatar = this.createComponent(FormControl, this.form, {
+ id: 'user-view-form-avatar',
+ label: 'Аватар',
+ }),
+ this.isAdmin = this.createComponent(FormControl, this.form, {
+ id: 'user-view-form-is-admin',
+ label: 'Админ',
+ type: FORM_TYPES.SELECT,
+ options: [
+ {value: true, text: 'Да'},
+ {value: false, text: 'Нет'},
+ ]
+ }),
+ ];
+
+ this.password.input.setAttribute('autocomplete', 'new-password');
+
+ this.buttons = [
+ {
+ button: this.createButton = this.mainNode.querySelector('.UserViewForm__create'),
+ modes: [MODES.Create],
+ onlyAdmin: true,
+ },
+ {
+ button: this.saveButton = this.mainNode.querySelector('.UserViewForm__save'),
+ modes: [MODES.Edit],
+ onlyAdmin: true,
+ },
+ {
+ button: this.editButton = this.mainNode.querySelector('.UserViewForm__edit'),
+ modes: [MODES.View],
+ onlyAdmin: true,
+ },
+ {
+ button: this.cancelButton = this.mainNode.querySelector('.UserViewForm__cancel'),
+ modes: [MODES.Create, MODES.Edit, MODES.View],
+ },
+ {
+ button: this.deleteButton = this.mainNode.querySelector('.UserViewForm__delete'),
+ modes: [MODES.View, MODES.Edit],
+ onlyAdmin: true,
+ }
+ ];
+
+ this.addEventListener(this.cancelButton, 'click', () => {
+ routeService.pushQuery({}, true);
+ });
+
+ this.addEventListener(this.editButton, 'click', () => {
+ routeService.pushQuery({mode: MODES.Edit});
+ });
+
+ this.addEventListener(this.form, 'submit', (event) => {
+ event.preventDefault();
+ });
+ this.addEventListener(this.createButton, 'click', this.createUser);
+ this.addEventListener(this.saveButton, 'click', this.saveUser);
+ this.addEventListener(this.deleteButton, 'click', this.deleteUser);
+
+ this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
+ const {mode, login} = query;
+ this.setForm(login, mode);
+ });
+
+ const {query: {mode, login}} = routeService.getUrlData();
+ if (mode) {
+ this.setForm(login, mode);
+ }
+
+ this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => {
+ this.createButton.disabled = !is_admin;
+ this.createButton.saveButton = !is_admin;
+ });
+ }
+
+ setSidebarTitle = (mode, userName) => {
+ this.title.textContent = [TITLE_MODES[mode], userName].filter(Boolean).join(' ');
+ }
+
+ setInputDisabled = (mode) => {
+ const disabled = !EDIT_MODES.includes(mode);
+ this.inputs.forEach((input) => {
+ const disabledLogin = this.login === input && mode === MODES.Edit;
+ input.disabled(disabled ? disabled : disabledLogin);
+ });
+ }
+
+ validateInputs = () => {
+ this.form.classList.add('was-validated');
+ const login = this.login.getValue();
+
+ const loginErrorMessage = (() => {
+ if (!login) {
+ return 'Заполните логин';
+ }
+ if (login.length < 4) {
+ return 'Длинна логина минимум 4 символа';
+ }
+
+ if (!/^[a-z][a-z0-9_-]*$/.test(login)) {
+ return 'Логин может содержать только латинские буквы, цифры, тире и нижнее подчеркивание';
+ }
+
+ return '';
+ })();
+
+ this.login.setError(loginErrorMessage);
+
+ return this.form.checkValidity();
+ }
+
+ createUser = () => {
+ if (this.validateInputs()) {
+ this.next(EVENTS.CREATE_USER, {
+ login: this.login.getValue(),
+ avatar: this.avatar.getValue(),
+ password: this.password.getValue(),
+ is_admin: this.isAdmin.getValue() === TRUE,
+ });
+ routeService.pushQuery({}, true);
+ }
+ }
+
+ saveUser = () => {
+ if (this.validateInputs()) {
+ this.next(EVENTS.SAVE_USER, {
+ login: this.login.getValue(),
+ avatar: this.avatar.getValue(),
+ is_admin: this.isAdmin.getValue() === TRUE,
+ });
+ routeService.pushQuery({}, true);
+ }
+ }
+
+ deleteUser = () => {
+ const {query: {login}} = routeService.getUrlData();
+ routeService.pushQuery({}, true);
+ this.next(EVENTS.DELETE_USER, login);
+ }
+
+ loadUser = async (login) => {
+ if (login) {
+ return await usersServiceApi.find(login);
+ }
+ return {
+ login: '',
+ avatar: '',
+ is_admin: '',
+ };
+ }
+
+ showButtons = async (mode) => {
+ const user = await userInfoService.getUserInfo();
+ this.buttons.forEach(({button, modes, onlyAdmin}) => {
+ const isShow = modes.includes(mode) && (onlyAdmin ? user.is_admin : true);
+ button.style.display = isShow ? 'inline-block' : 'none';
+ });
+ }
+
+ setPassword = (mode) => {
+ const isShow = mode === MODES.Create;
+
+ this.password.setValue('');
+
+ this.password.mainNode.style.display = isShow ? 'block' : 'none';
+ }
+
+ setLogin = (mode, login) => {
+ const disabled = mode !== MODES.Create;
+
+ this.login.disabled(disabled);
+
+ this.login.setValue(login);
+ }
+
+ setForm = async (userLogin, mode) => {
+ const {login, avatar, is_admin} = await this.loadUser(userLogin);
+ this.setSidebarTitle(mode, login);
+ this.setLogin(mode, login);
+ this.avatar.setValue(avatar);
+ this.isAdmin.setValue(!!is_admin);
+ this.setInputDisabled(mode);
+ this.showButtons(mode);
+ this.setPassword(mode);
+
+ if (mode) {
+ this.sidebar.show();
+ } else {
+ this.sidebar.hide();
+ }
+ }
+}
+
+export default UserViewForm;
diff --git a/src/components/user-view-form/index.js b/src/components/user-view-form/index.js
new file mode 100644
index 0000000..c06b617
--- /dev/null
+++ b/src/components/user-view-form/index.js
@@ -0,0 +1,3 @@
+import UserViewForm from './UserViewForm';
+
+export default UserViewForm;
diff --git a/src/components/users-page/UsersPage.js b/src/components/users-page/UsersPage.js
index e2794f5..db8d049 100644
--- a/src/components/users-page/UsersPage.js
+++ b/src/components/users-page/UsersPage.js
@@ -1,16 +1,69 @@
import Component from '../component';
import UsersTable from '../users-table';
import usersServiceApi from '../../api/UsersServiceAPI';
+import UserViewForm from '../user-view-form';
+import {EVENTS, MODES} from '../../consts';
+import routeService from '../../services/RouteService';
+import userInfoService from '../../services/UserInfoService';
class UsersPage extends Component {
constructor (mainNodeSelector, parentNode) {
super(mainNodeSelector, parentNode);
+ this.usersForm = this.createComponent(UserViewForm);
+
+ this.createElement({tagName: 'div', parentNode: this.mainNode});
+
+ this.createUserButton = this.createElement({
+ tagName: 'button',
+ parentNode: this.mainNode,
+ options: {
+ className: 'btn btn-primary m-3',
+ textContent: 'Создать пользователя',
+ },
+ args: {
+ type: 'button',
+ },
+ });
+
+ this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => {
+ this.createUserButton.disabled = !is_admin;
+ });
+
+ this.addEventListener(this.createUserButton, 'click', () => {
+ routeService.pushQuery({mode: MODES.Create}, true);
+ });
+
+ this.addSubscribe(this.usersForm, EVENTS.CREATE_USER, async (user) => {
+ await usersServiceApi.create(user);
+ this.initPage();
+ });
+
+ this.addSubscribe(this.usersForm, EVENTS.SAVE_USER, async (user) => {
+ await usersServiceApi.update(user);
+ this.initPage();
+ });
+
+ this.addSubscribe(this.usersForm, EVENTS.DELETE_USER, async (login) => {
+ await usersServiceApi.remove(login);
+ this.initPage();
+ });
+
this.usersTable = this.createComponent(UsersTable, this.mainNode);
+
+ this.addSubscribe(this.usersTable, EVENTS.ROW_DOUBLE_CLICK, (_, row) => {
+ routeService.pushQuery({
+ mode: MODES.View,
+ login: row.login,
+ });
+ });
+
this.initPage();
}
initPage = async () => {
+ const user = await userInfoService.getUserInfo();
+ this.createUserButton.disabled = !user.is_admin;
this.userList = await usersServiceApi.request();
this.renderTable();
}
diff --git a/src/consts.js b/src/consts.js
index b93d2b5..6accf9b 100644
--- a/src/consts.js
+++ b/src/consts.js
@@ -55,7 +55,14 @@ export const EVENTS = {
CHANGE_USER_AVATAR: 'changeUserAvatar',
OPEN_MODAL: 'openModal',
CLICK: 'click',
- SUBMIT: 'submit'
+ SUBMIT: 'submit',
+ FOCUS: 'focus',
+ KEYDOWN: 'keydown',
+ INPUT: 'input',
+ CHANGE: 'change',
+ CREATE_USER: 'createUser',
+ SAVE_USER: 'saveUser',
+ DELETE_USER: 'deleteUser',
};
export const FORM_TYPES = {
@@ -64,3 +71,17 @@ export const FORM_TYPES = {
TEXTAREA: 'TEXTAREA',
PASSWORD: 'PASSWORD',
};
+
+export const MODES = {
+ Create: 'create',
+ View: 'view',
+ Edit: 'edit',
+};
+
+export const TAG_NAME = {
+ OPTION: 'option',
+ DIV: 'div',
+ INPUT: 'input',
+ SELECT: 'select',
+ TEXTAREA: 'textarea',
+};
diff --git a/src/services/UserInfoService.js b/src/services/UserInfoService.js
index 38f2cca..767c7c7 100644
--- a/src/services/UserInfoService.js
+++ b/src/services/UserInfoService.js
@@ -2,21 +2,31 @@ import usersServiceApi from '../api/UsersServiceAPI';
import {EVENTS} from '../consts';
import EmitService from './EmitService';
+const NOT_USER = 'not_user';
+const DEFAULT_AVATAR = 'https://d5qmjlya0ygtg.cloudfront.net/569/c5295/f9ad/47c8/96a0/66a65609b38d/original/331698.jpg';
class UserInfoService extends EmitService {
constructor () {
super();
this.userInfo = {
- login: 'not_user',
- avatar: 'https://d5qmjlya0ygtg.cloudfront.net/569/c5295/f9ad/47c8/96a0/66a65609b38d/original/331698.jpg',
+ login: NOT_USER,
+ avatar: DEFAULT_AVATAR,
};
}
setUserLogin = async () => {
- this.userInfo = await usersServiceApi.getMe();
+ this.userInfo = await usersServiceApi.getSelfInfo();
this.next(EVENTS.CHANGE_USER_INFO, {...this.userInfo});
}
+ getUserInfo = async () => {
+ if (this.userInfo.login === NOT_USER) {
+ this.userInfo = await usersServiceApi.getSelfInfo();
+ }
+
+ return {...this.userInfo};
+ }
+
changeAllAvatars () {
this.next(EVENTS.CHANGE_USER_AVATAR);
}