diff --git a/src/api/ChatApi.ts b/src/api/ChatApi.ts index 4e7fd94..6ab2569 100644 --- a/src/api/ChatApi.ts +++ b/src/api/ChatApi.ts @@ -9,12 +9,7 @@ class ChatAPI { } public async createChat(title: string): Promise { - return chats.post('/', { - data: { title }, - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - }); + return chats.post('/', {data: { title }}); } public async getUserToken(chatId: number): Promise<{ token: string }> { @@ -26,22 +21,22 @@ class ChatAPI { return response; } - public async getChatUsers(chatId: number): Promise { + public async getChatUsers(chatId: number | undefined): Promise { return chats.get(`/${chatId}/users`); } - public async addUsers(chatId: number, users: any): Promise { + public async addUsers(data: any): Promise { return chats.put('/users', { - data: { chatId, users }, + data: data, headers: { 'Content-type': 'application/json; charset=UTF-8', }, }); } - public async removeUsers(chatId: number, users: any): Promise { + public async removeUsers(data: any): Promise { return chats.delete('/users', { - data: { chatId, users }, + data: data, headers: { 'Content-type': 'application/json; charset=UTF-8', } @@ -56,6 +51,15 @@ class ChatAPI { } }); } + + public async uploadAvatar(data: any): Promise { + return chats.put('/avatar', { + data: data, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + } } export default new ChatAPI(); diff --git a/src/components/add-user-modal/user-modal.hbs b/src/components/add-user-modal/user-modal.hbs index 1b4b41e..43caa6e 100644 --- a/src/components/add-user-modal/user-modal.hbs +++ b/src/components/add-user-modal/user-modal.hbs @@ -1,6 +1,6 @@ {{#if isAddUserOpen }}
-

Добавить пользователя в чат

+

{{#if isUserSearchEnabled}}Add user to chat{{else}}Delete user from chat{{/if}}

{{#if isUserSearchEnabled}} {{{ Search users=users class="search" placeholder="Search users" }}} {{/if}} diff --git a/src/components/add-user-modal/user-modal.less b/src/components/add-user-modal/user-modal.less index 0fe2693..d078e30 100644 --- a/src/components/add-user-modal/user-modal.less +++ b/src/components/add-user-modal/user-modal.less @@ -10,13 +10,25 @@ background: @main__background-color; padding: 3rem 2rem 1rem 2rem; display: flex; + width: 30%; flex-direction: column; justify-content: space-between; .title { + text-align: center; margin: 0 0 1rem 0; } + .user-item { + list-style: none; + border-bottom: 1px solid @secondary__background-color; + cursor: pointer; + + &:hover { + color: @main__background-hover-color; + } + } + .search { .search-results { display: block; @@ -30,6 +42,7 @@ margin-top: 0; padding-inline-start: 15px; padding-inline-end: 15px; + padding-bottom: 0.75rem; .user-item { display: flex; diff --git a/src/components/add-user-modal/user-modal.ts b/src/components/add-user-modal/user-modal.ts index 50018db..35e73ae 100644 --- a/src/components/add-user-modal/user-modal.ts +++ b/src/components/add-user-modal/user-modal.ts @@ -12,8 +12,7 @@ export class UserModalBase extends Block { super({ ...props, onClose: () => { - Store.set('isAddUserOpen', false); - Store.set('selectedUser', null); + Store.set({isAddUserOpen: false}); }, events: { click: (event: Event) => { @@ -27,34 +26,6 @@ export class UserModalBase extends Block { this.children.Search = new Search({} as any); } - protected componentDidUpdate(oldProps: any, newProps: any) { - if (oldProps.selectedUser !== newProps.selectedUser && newProps.selectedUser) { - if (this.props.isUserSearchEnabled) { - this.addUserToChat(newProps.selectedUser); - } else { - this.removeUserFromChat(newProps.selectedUser); - } - Store.set('selectedUser', null); - } - - return true; - } - - private async addUserToChat(user: any) { - const chatId = Store.getState().selectedChat?.id; - if (chatId) { - try { - await ChatController.addUsers({ id: chatId, user: user.id }); - await ChatController.getChatUsers(chatId); - Store.set('isAddUserOpen', false); - } catch (error) { - console.error('Error adding user to chat:', error); - } - } else { - console.error('No selected chat found'); - } - } - private async handleUserClick(e: Event) { const target = e.target as HTMLElement; const userItem = target.closest('.user-item'); @@ -64,28 +35,14 @@ export class UserModalBase extends Block { let user; if (this.props.isUserSearchEnabled) { user = this.props.users.find((u: User) => u.id.toString() === userId); + await ChatController.addUsers(user); + Store.set({isAddUserOpen: false}); } else { user = this.props.currentChatUsers.find((u: User) => u.id.toString() === userId); + await ChatController.removeUsers(user); + Store.set({isAddUserOpen: false}); } - if (user) { - Store.setSelectedUser(user); - } - } - } - } - - private async removeUserFromChat(user: any) { - const chatId = Store.getState().selectedChat?.id; - if (chatId) { - try { - await ChatController.removeUsers({ id: chatId, users: [user.id] }); - await ChatController.getChatUsers(chatId); - Store.set('isAddUserOpen', false); - } catch (error) { - console.error('Error removing user from chat:', error); } - } else { - console.error('No selected chat found'); } } @@ -98,9 +55,10 @@ export const UserModal = connect((state) => { return { isAddUserOpen: state?.isAddUserOpen || false, isUserSearchEnabled: state?.isUserSearchEnabled || false, - usersList: state?.usersList || false, selectedUser: state.selectedUser, selectedChatId: state.selectedChat?.id, - currentChatUsers: state?.currentChatUsers || [] + currentChatUsers: state?.currentChatUsers || [], + users: state?.users || [], + usersList: state.usersList } })(UserModalBase); diff --git a/src/components/avatar/avatar.hbs b/src/components/avatar/avatar.hbs index df606d1..c1f714c 100644 --- a/src/components/avatar/avatar.hbs +++ b/src/components/avatar/avatar.hbs @@ -1,4 +1,4 @@ -
+
{{ label }} + diff --git a/src/components/chat-body/chat-body.hbs b/src/components/chat-body/chat-body.hbs index 047aabb..0faecbc 100644 --- a/src/components/chat-body/chat-body.hbs +++ b/src/components/chat-body/chat-body.hbs @@ -1,10 +1,22 @@ {{#if selectedChat}}
-

{{selectedChat.title}}

- {{#if websocketError}} -
{{websocketError}}
- {{/if}} +
+
+ {{#if selectedChat.avatar}} + {{{ Avatar avatar=selectedChat.avatar name=selectedChat.login }}} + {{else}} +
+ {{firstLetter selectedChat.title}} +
+ {{/if}} +
+

{{selectedChat.title}}

+ {{#if isSettingsModalOpen }} + {{{ ChatSettingsModal selectedChat=selectedChat currentChatUsers=currentChatUsers }}} +
+ {{/if}} +
{{{ MenuButton isOpenChatMenu=isOpenChatMenu class="chat-menu-button" onClick=toggleMenu }}} {{#if isOpenChatMenu}} @@ -12,6 +24,7 @@ {{/if}} {{#if isAddUserOpen }} {{{ UserModal }}} +
{{/if}}
diff --git a/src/components/chat-body/chat-body.less b/src/components/chat-body/chat-body.less index ab74d29..0cfe2c1 100644 --- a/src/components/chat-body/chat-body.less +++ b/src/components/chat-body/chat-body.less @@ -27,8 +27,80 @@ background-color: @main__background-color; border-bottom: 1px solid @secondary__background-color; - h2 { + .left-section { + display: flex; + flex-direction: row; padding-inline-start: 2rem; + justify-content: space-between; + align-items: center; + width: auto; + border-right: 1px solid @secondary__background-color; + + .avatar { + display: flex; + align-items: center; + justify-content: center; + width: 1.375rem !important; + height: 1.375rem !important; + z-index: 100; + + .default-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 1.375rem !important; + height: 1.375rem !important; + z-index: 100; + + .title { + position: absolute; + } + + &:after { + content: ''; + border: 30px solid #343A4F; + border-radius: 100%; + } + } + + .image { + position: absolute; + + &.uploaded { + width: 3.675rem !important; + height: 3.675rem !important; + + img { + width: 3.675rem !important; + height: 3.675rem !important; + border-radius: 50%; + } + } + } + } + + .chat-title { + padding-right: 0.5rem; + cursor: pointer; + margin-left: 3rem; + + &:hover { + color: @main__button-background-hover; + + &:after { + color: @main__button-background-hover; + } + } + + &:after { + font-family: @icon-font; + content: @icon__angle-right; + color: @main__font-color; + vertical-align: middle; + font-size: 12px; + margin-left: 0.25rem; + } + } } } @@ -78,6 +150,7 @@ border-left: 1px solid @secondary__background-color; display: flex; position: relative; + align-items: center; .sender { .input(@border-radius: 0, @width: 100%, @padding: 0, @color: @main__font-color); @@ -107,20 +180,21 @@ position: absolute; right: 0; - border: 10px solid @main__button-background-color; + border: 20px solid @main__button-background-color; border-radius: 100%; &:before { font-family: @icon-font; content: '\e901'; color: @main__icon-color; - font-size: 20px; + font-size: 30px; position: absolute; transform: translate(-50%, -50%); } &:hover { .custom-button-hover(); + border-color: @main__button-background-hover; } } } diff --git a/src/components/chat-body/chat-body.ts b/src/components/chat-body/chat-body.ts index dfd0bba..e90cf2c 100644 --- a/src/components/chat-body/chat-body.ts +++ b/src/components/chat-body/chat-body.ts @@ -19,23 +19,24 @@ class ChatBodyBase extends Block { super({ ...props, events: { - submit: (e: Event) => this.onSubmit(e), - click: (e: Event) => this.handleClick(e) + submit: (event: Event) => this.onSubmit(event), + click: (event: Event) => this.handleClick(event) }, - toggleMenu: (e: Event) => { - e.stopPropagation(); + toggleMenu: (event: Event) => { + event.stopPropagation(); const isOpen = Store.getState().isOpenChatMenu; - Store.set('isOpenChatMenu', !isOpen); + Store.set({isOpenChatMenu: !isOpen}); + }, + openSettingsModal: (event: Event) => { + if (!event) return; + event.preventDefault(); + Store.set({isSettingsModalOpen: true}); } }); } - protected componentDidUpdate(_oldProps: ChatBodyProps, _newProps: ChatBodyProps): boolean { - return true; - } - private closeMenu() { - Store.set('isOpenChatMenu', false); + Store.set({isOpenChatMenu: false}); } private handleClick(e: Event) { @@ -47,6 +48,10 @@ class ChatBodyBase extends Block { } else if (isMenuOpen && !target.closest('.menu__wrapper') && !target.closest('form')) { this.closeMenu(); } + + if (target.closest('.chat-title')) { + this.props.openSettingsModal(e); + } } private onSubmit(e: Event) { @@ -72,5 +77,7 @@ export const ChatBody = connect((state) => { websocketError: state.websocketError, isOpenChatMenu: state.isOpenChatMenu || false, isAddUserOpen: state.isAddUserOpen || false, + isSettingsModalOpen: state.isSettingsModalOpen || false, + currentChatUsers: state.currentChatUsers || [], }; })(ChatBodyBase); diff --git a/src/components/chat-create/chat-create.hbs b/src/components/chat-create/chat-create.hbs index 6398a17..a680b05 100644 --- a/src/components/chat-create/chat-create.hbs +++ b/src/components/chat-create/chat-create.hbs @@ -2,8 +2,8 @@
diff --git a/src/components/chat-create/chat-create.less b/src/components/chat-create/chat-create.less new file mode 100644 index 0000000..1e3a622 --- /dev/null +++ b/src/components/chat-create/chat-create.less @@ -0,0 +1,54 @@ +@import '../../utils.less'; + +#app { + .chat { + .create-chat-modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + justify-content: center; + align-items: center; + z-index: 1000; + + &.is-open { + display: flex; + } + + .modal-content { + background-color: @main__background-color; + width: 25%; + padding: 2rem 3rem; + border-radius: 14px; + text-align: center; + + .create-chat-form { + display: flex; + flex-direction: column; + margin: 2rem 1rem; + + .chat-title { + .input(@background: @secondary__background-color); + margin-bottom: 2.5rem; + } + + .chat-create-button { + margin: 0 auto; + } + } + + .close-modal { + margin-top: 1rem; + .button(@background: none, @width: auto, @border: none); + + &:hover { + .custom-button-hover(@color: @main__button-background-color); + } + } + } + } + } +} diff --git a/src/components/chat-create/chat-create.ts b/src/components/chat-create/chat-create.ts index d4f498e..520cc99 100644 --- a/src/components/chat-create/chat-create.ts +++ b/src/components/chat-create/chat-create.ts @@ -1,3 +1,4 @@ +import './chat-create.less'; import Block from '../../core/Block'; import CreateChatFormTemplate from './chat-create.hbs?raw'; import ChatController from '../../controllers/ChatController'; @@ -19,7 +20,7 @@ export class ChatCreateBase extends Block { click: (e: Event) => this.onClick(e), }, closeModal: () => { - Store.set('isCreateChatModalOpen', false); + Store.set({isCreateChatModalOpen: false}); } }); } @@ -33,7 +34,7 @@ export class ChatCreateBase extends Block { } private closeModal() { - Store.set('isCreateChatModalOpen', false); + Store.set({isCreateChatModalOpen: false}); } private async onSubmit(e: Event) { @@ -46,20 +47,17 @@ export class ChatCreateBase extends Block { try { await ChatController.createChat(chatTitle); form.reset(); - Store.set('createChatSuccess', 'Chat created successfully'); - Store.set('createChatError', null); + Store.set({createChatSuccess: 'Chat created successfully', createChatError: null }); setTimeout(() => { this.closeModal(); - Store.set('createChatSuccess', null); + Store.set({createChatSuccess: null}); }, 2000); } catch (error) { console.error('Error creating chat:', error); - Store.set('createChatError', 'Failed to create chat'); - Store.set('createChatSuccess', null); + Store.set({createChatError: 'Failed to create chat', createChatSuccess: null}); } } else { - Store.set('createChatError', 'Chat title cannot be empty'); - Store.set('createChatSuccess', null); + Store.set({createChatError: 'Failed to create chat', createChatSuccess: null}); } } diff --git a/src/components/chat-item/chat-item.hbs b/src/components/chat-item/chat-item.hbs index 4e270fb..9aca15d 100644 --- a/src/components/chat-item/chat-item.hbs +++ b/src/components/chat-item/chat-item.hbs @@ -1,9 +1,13 @@ -
+
{{#if chat.avatar}} - {{chat.title}} +
+ {{{ Avatar avatar=chat.avatar name=chat.login }}} +
{{else}} -
{{firstLetter chat.title}}
+
+ {{firstLetter chat.title}} +
{{/if}}
diff --git a/src/components/chat-item/chat-item.ts b/src/components/chat-item/chat-item.ts index 877ccdb..b15ac75 100644 --- a/src/components/chat-item/chat-item.ts +++ b/src/components/chat-item/chat-item.ts @@ -1,28 +1,26 @@ import ChatItemTmpl from './chat-item.hbs?raw' import Block, { Props } from '../../core/Block'; -import ChatController from '../../controllers/ChatController'; +import isEqual from "../../utils/isEqual"; export class ChatItem extends Block { constructor(props: Props) { super({ ...props, - select: () => (props?.id === props?.currentChat), + select: () => (props?.chat.id === props?.currentChat), events: { click: (e: Event) => { + if (!e) return; e.preventDefault(); - const chatId = this.props.chat.id; - if (chatId !== undefined) { - ChatController.setCurrentChat(chatId); - } else { - console.error('Chat id is undefined'); + if (props.onSetCurrentChat) { + props.onSetCurrentChat.call(this, this.props?.chat.id); } }, }, }); } - protected componentDidUpdate(_oldProps: Props, _newProps: Props): boolean { - return true; + protected componentDidUpdate(oldProps: Props, newProps: Props): boolean { + return !isEqual(oldProps, newProps); } render(): string { diff --git a/src/components/chat-list/chat-list.hbs b/src/components/chat-list/chat-list.hbs index fe26277..dca26e8 100644 --- a/src/components/chat-list/chat-list.hbs +++ b/src/components/chat-list/chat-list.hbs @@ -1,7 +1,7 @@
{{#if chats.length}} {{#each chats}} - {{{ ChatItem chat=this currentChat=../currentChat }}} + {{{ ChatItem chat=this onSetCurrentChat=../onSetCurrentChat currentChat= ../currentChat }}} {{else}}

No chats available

{{/each}} diff --git a/src/components/chat-list/chat-list.less b/src/components/chat-list/chat-list.less new file mode 100644 index 0000000..291b6d1 --- /dev/null +++ b/src/components/chat-list/chat-list.less @@ -0,0 +1,183 @@ +@import '../../utils.less'; + +.sidebar { + flex: 1; + + .list { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + .list-header { + display: flex; + flex-direction: column; + height: 5rem; + border-right: 1px solid @secondary__background-color; + border-bottom: 1px solid @secondary__background-color; + + .section { + &.buttons { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + .create-new-chat { + margin: 0; + + .new-chat { + .button(@margin: 0, @background: none, @border: none, @width: auto); + + &:hover { + .custom-button-hover(); + } + } + } + + .profile { + align-self: flex-end; + padding: 0.25rem 0.1rem; + + .profile-button { + .button(@background: none, @width: 100%, @margin: 0); + + &:hover { + background: none !important; + color: @main__button-background-hover; + + &:after { + color: @main__button-background-hover; + } + } + + &:after { + font-family: @icon-font; + content: @icon__angle-right; + color: @main__font-color; + vertical-align: middle; + font-size: 12px; + margin-left: 0.25rem; + } + } + + &:hover, &:active, &:focus-visible { + background: none !important; + color: @main__button-background-hover !important; + } + } + } + } + + } + + .chat-list { + overflow-y: scroll; + } + + .item { + display: flex; + flex-direction: column; + justify-content: center; + padding-top: 0 !important; + padding-bottom: 0 !important; + padding-inline: 1rem .75rem; + border-bottom: 1px solid @secondary__background-color; + min-height: 4.5rem; + padding-inline-start: 4.5rem !important; + text-decoration: none; + color: @main__font-color; + position: relative; + cursor: pointer; + + &.selected { + background: @main__button-background-color; + + .dialog { + &.subtitle { + color: @main__font-color; + } + } + } + + &:hover { + background: @main__background-hover-color; + } + + .avatar { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + inset-inline-start: .5625rem !important; + + .loaded-avatar { + inset-inline-start: .5625rem !important; + width: 3.575rem !important; + height: 3.575rem !important; + + .image { + &.uploaded { + img { + width: 3.575rem !important; + height: 3.575rem !important; + border-radius: 50%; + } + } + } + } + + .default-avatar { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + inset-inline-start: .1rem !important; + width: 3.375rem !important; + height: 3.375rem !important; + + .title { + position: absolute; + font-size: 25px; + } + + &:after { + content: ''; + border: 30px solid @secondary__background-color; + border-radius: 100%; + } + } + } + + .dialog { + display: flex; + justify-content: space-between; + align-items: center; + + &.title { + height: 1.375rem; + font-weight: bold; + } + + &.subtitle { + height: 1.375rem; + margin-top: .125rem; + color: @secondary__font-color; + + .unread-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + background-color: @secondary__font-color; /* Вы можете изменить цвет на любой подходящий вашему дизайну */ + color: white; + border-radius: 10px; /* Это создаст круглую форму */ + font-size: 12px; + font-weight: bold; + } + } + } + } + } +} diff --git a/src/components/chat-list/chat-list.ts b/src/components/chat-list/chat-list.ts index e94db48..ebca84d 100644 --- a/src/components/chat-list/chat-list.ts +++ b/src/components/chat-list/chat-list.ts @@ -1,3 +1,4 @@ +import './chat-list.less'; import Block, {Props} from '../../core/Block'; import ChatListTemplate from './chat-list.hbs?raw'; import ChatController from '../../controllers/ChatController'; @@ -7,26 +8,10 @@ export class ChatListBase extends Block { constructor(props: Props) { super({ ...props, - onSetCurrentChat: (id: number) => { + onSetCurrentChat: (id: number | undefined) => { ChatController.setCurrentChat(id); }, }); - - setInterval(() => { - ChatController.getChats(); - }, 5000); - } - - init() { - this.loadChats(); - } - - private async loadChats() { - try { - await ChatController.getChats(); - } catch (error) { - console.error('Error loading chats:', error); - } } render(): string { @@ -37,6 +22,7 @@ export class ChatListBase extends Block { export const ChatList = connect((state) => { return { chats: state.chats || [], - currentChat: state.currentChat + currentChat: state.currentChat, + selectedChat: state.selectedChat, }; })(ChatListBase); diff --git a/src/components/chat-menu/chat-menu.ts b/src/components/chat-menu/chat-menu.ts index 8f85f2d..c4fae7f 100644 --- a/src/components/chat-menu/chat-menu.ts +++ b/src/components/chat-menu/chat-menu.ts @@ -10,34 +10,18 @@ export class ChatMenuBase extends Block { super({ ...props, addUser: () => { - Store.set('isAddUserOpen', true); - Store.set('isUserSearchEnabled', true); - Store.set('usersList', false); - Store.set('isOpenChatMenu', false); + Store.set({isAddUserOpen: true, isUserSearchEnabled: true, usersList: false, isOpenChatMenu: false}); }, deleteUser: () => { - Store.set('isAddUserOpen', true); - Store.set('isUserSearchEnabled', false); - Store.set('usersList', true); - Store.set('isOpenChatMenu', false); - - if (Store.getState().usersList) { - this.getChatUsers(); - } + Store.set({isAddUserOpen: true, isUserSearchEnabled: false, usersList: true, isOpenChatMenu: false}); }, deleteChat: () => { - Store.set('isOpenChatMenu', false); - Store.set('selectedChat', false); + Store.set({isOpenChatMenu: false, selectedChat: false}); ChatController.deleteChat(); } }); } - private async getChatUsers() { - const chatId = Store.getState().currentChat; - await ChatController.getChatUsers(chatId); - } - render(): string { return ChatMenuTmpl; } diff --git a/src/components/chat-settings-modal/chat-settings-modal.hbs b/src/components/chat-settings-modal/chat-settings-modal.hbs new file mode 100644 index 0000000..81f991e --- /dev/null +++ b/src/components/chat-settings-modal/chat-settings-modal.hbs @@ -0,0 +1,30 @@ +{{#if isSettingsModalOpen}} +
+
+ {{#if selectedChat.avatar}} +
+ {{{ Avatar avatar=selectedChat.avatar name=selectedChat.login }}} + {{{ Input class="avatar-uploader" style="display: none" id="avatar-uploader" type="file" accept="image/*" skipValidation=true onChange=changeChatAvatar }}} +
+ {{else}} +
+ {{firstLetter selectedChat.title}} + {{{ Input class="avatar-uploader" style="display: none" id="avatar-uploader" type="file" accept="image/*" skipValidation=true onChange=changeChatAvatar }}} +
+ {{/if}} +
+
+
    + {{#each currentChatUsers }} +
  • + {{first_name}} {{second_name}} + +
  • + {{/each}} +
+
+
+ {{{ Button class="close" type="button" label="Cancel" onClick=onClose }}} +
+
+{{/if}} diff --git a/src/components/chat-settings-modal/chat-settings-modal.less b/src/components/chat-settings-modal/chat-settings-modal.less new file mode 100644 index 0000000..fd9d3ca --- /dev/null +++ b/src/components/chat-settings-modal/chat-settings-modal.less @@ -0,0 +1,131 @@ +@import '../../utils.less'; + +.chat.messenger { + .messenger-body { + .chat-body { + .chat-header { + .chat-settings { + display: flex; + flex-direction: column; + z-index: 1000; + top: 15%; + position: absolute; + background: white; + width: 25%; + padding: 3rem 2rem; + align-items: center; + border-radius: 14px; + background: @main__background-color; + + .avatar { + margin-bottom: 2rem; + display: flex; + align-items: center; + justify-content: center; + position: relative; + width: 4.995rem !important; + height: 4.995rem !important; + + &:hover { + cursor: pointer; + background: rgba(0, 0, 0, 0.5); + border-radius: 50%; + width: 6.675rem !important; + height: 6.675rem !important; + + &:before { + content: 'Change avatar' !important; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 15px; + z-index: 1001; + } + + &:after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + } + } + + .loaded-avatar-modal { + position: absolute; + width: 6.575rem !important; + height: 6.575rem !important; + + .image { + width: 6.575rem !important; + height: 6.575rem !important; + cursor: pointer; + + &.uploaded { + width: 6.575rem !important; + height: 6.575rem !important; + + img { + width: 6.575rem !important; + height: 6.575rem !important; + } + } + } + } + + .default-avatar { + position: absolute; + font-size: 25px; + + &:after { + content: ''; + border: 53px solid @secondary__background-color; + border-radius: 100%; + } + } + } + + .chat-users { + margin-bottom: 2rem; + width: 100%; + + .users-list { + display: flex; + flex-direction: column; + padding: 0; + text-align: center; + + .user-item { + display: flex; + flex-direction: column; + border-bottom: 1px solid @secondary__background-color; + padding: 5px; + cursor: pointer; + + &:hover { + background: @main__background-hover-color; + } + } + } + } + + .close-action { + .close { + .button(@margin: 0, @width: auto, @background: none, @color: @secondary__button-background-color); + + &:hover { + .custom-button-hover(@color: @secondary__button-background-color); + opacity: 70%; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/components/chat-settings-modal/chat-settings-modal.ts b/src/components/chat-settings-modal/chat-settings-modal.ts new file mode 100644 index 0000000..b8195f5 --- /dev/null +++ b/src/components/chat-settings-modal/chat-settings-modal.ts @@ -0,0 +1,79 @@ +import './chat-settings-modal.less'; +import ChatSettingsModalTmpl from './chat-settings-modal.hbs?raw'; +import Block, {Props} from '../../core/Block'; +import Store from '../../core/Store'; +import {connect} from "../../utils/connect"; +import ChatController from "../../controllers/ChatController"; + +export class ChatSettingsModalBase extends Block { + constructor(props: Props) { + super({ + ...props, + events: { + click: (event: Event) => { + this.handleClick(event); + } + }, + onClose: () => { + Store.set({isSettingsModalOpen: false}); + }, + changeChatAvatar: (event: Event) => { + if (!event) return; + event.preventDefault(); + + const input = event?.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const file = input.files[0]; + this.uploadAvatar(file); + } + } + }); + } + + private handleClick(event: Event) { + const target = event.target as HTMLElement; + const defaultAvatar = target.closest('.avatar'); + const avatarLoaded = target.closest('.image'); + + if (defaultAvatar) { + this.onClickAvatar(); + } + + if (avatarLoaded) { + this.onClickAvatar(); + } + } + + onClickAvatar() { + const avatarUploader: HTMLInputElement | undefined = this._element?.querySelector('#avatar-uploader') as HTMLInputElement | undefined; + if (avatarUploader) { + avatarUploader.click(); + } + } + + async uploadAvatar(file: File) { + try { + const data = new FormData(); + data.set('chatId', this.props.selectedChat.id); + data.set('avatar', file, file.name); + + const update = await ChatController.changeAvatar(data); + + if (update) { + this.setProps({selectedChat: update}); + } + } catch (error) { + console.error('AvatarUploader: Error uploading avatar:', error); + } + } + + render(): string { + return ChatSettingsModalTmpl; + } +} + +export const ChatSettingsModal = connect((state) => { + return { + isSettingsModalOpen: state.isSettingsModalOpen || false, + } +})(ChatSettingsModalBase); diff --git a/src/components/error/error.hbs b/src/components/error/error.hbs index b294b5b..a772db2 100644 --- a/src/components/error/error.hbs +++ b/src/components/error/error.hbs @@ -1,6 +1,6 @@

{{errorTitle}}

{{errorText}} - {{{ Button class="error back-button" label="Chat" page="ChatPage" }}} + {{{ Button class="error back-button" label="Chat" }}}
diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 555deab..3889989 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -31,7 +31,9 @@ export class Input extends Block { if (typeof this.props.onChange === 'function') { this.props.onChange(e); } - } + }, + + ...(props.onInput ? { input: props.onInput} : {}) } }); } diff --git a/src/components/search/search.less b/src/components/search/search.less index 7ffab04..5b3bba6 100644 --- a/src/components/search/search.less +++ b/src/components/search/search.less @@ -22,7 +22,9 @@ z-index: 100; list-style: none; border-radius: 5px; - width: 28.6%; + max-height: 70%; + width: 29.5%; + overflow-y: scroll; margin-top: 0; padding-inline-start: 15px; padding-inline-end: 15px; diff --git a/src/components/search/search.ts b/src/components/search/search.ts index d610151..25e7d2a 100644 --- a/src/components/search/search.ts +++ b/src/components/search/search.ts @@ -1,10 +1,10 @@ import './search.less'; -import Block from '../../core/Block'; +import Block, {Props} from '../../core/Block'; import SearchTemplate from './search.hbs?raw'; import { User, Chat } from '../../utils/types'; import UserController from '../../controllers/UserController'; import ChatController from '../../controllers/ChatController'; -import Store from '../../core/Store'; +import isEqual from "../../utils/isEqual"; interface SearchUsersProps { onUserSelect: (user: User) => void; @@ -37,6 +37,10 @@ export class Search extends Block { }); } + protected componentDidUpdate(oldProps: Props, newProps: Props): boolean { + return isEqual(oldProps, newProps); + } + private handleClick(e: Event) { const target = e.target as HTMLElement; const userItem = target.closest('.user-item'); @@ -47,7 +51,7 @@ export class Search extends Block { if (userId) { const user = this.props.users.find((u: User) => u.id.toString() === userId); if (user) { - Store.setSelectedUser(user); + // To do: add create chat with user } } } diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index e3357f2..9adc278 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -1,6 +1,7 @@ import AuthApi from "../api/AuthApi"; import {TOptionsData} from "../core/HTTPTransport"; import { goToLogin, goToMessenger, goToError500 } from "../utils/router"; +import ChatController from "./ChatController"; import Store from "../core/Store"; class AuthController { @@ -26,18 +27,13 @@ class AuthController { public async login(data: TOptionsData): Promise { try { - const { status, response } = await AuthApi.login(data); - if (status === 200) { - Store.set('auth', true); - goToMessenger(); - this.getUser(); - } else if (status === 500) { - goToError500(); - } else { - alert(JSON.parse(response).reason ?? 'Bad request'); - } - } catch (e) { - console.error(e); + await AuthApi.login(data); + const user = await this.getUser(); + const chats = await ChatController.getChats(); + Store.set({user: user, chats}); + goToMessenger(); + } catch (error) { + console.error(error); } } @@ -45,35 +41,22 @@ class AuthController { try { const response = await AuthApi.getUser(); - if (response.status === 200 && response) { - Store.set('user', response.response); - Store.set('auth', true); + if (response) { + Store.set({user: response}); return true; } else { - Store.set('auth', false); return false; } } catch (e) { - console.error('Error in getUser:', e); - Store.set('auth', false); + console.error('Error in user:', e); return false; } } public async logout(): Promise { - try { - const { status, response } = await AuthApi.logout(); - if (status === 200) { - Store.setResetState(); - goToLogin(); - } else if (status === 500) { - goToError500(); - } else { - alert(JSON.parse(response).reason ?? 'Error response'); - } - } catch (e) { - console.error(e); - } + await AuthApi.logout(); + Store.set({user: null, currentChat: null}); + goToLogin(); } } diff --git a/src/controllers/ChatController.ts b/src/controllers/ChatController.ts index aa55cd7..d371c41 100644 --- a/src/controllers/ChatController.ts +++ b/src/controllers/ChatController.ts @@ -1,6 +1,6 @@ import ChatAPI from '../api/ChatApi'; import Store from '../core/Store'; -import { Chat } from '../utils/types'; +import {Chat, User} from '../utils/types'; import { MessageController } from "./MessageController"; const messageController = new MessageController(); @@ -8,16 +8,7 @@ class ChatController { async getChats(): Promise { try { const chats = await ChatAPI.getChats(); - if (chats instanceof XMLHttpRequest) { - const chatsData = chats.response; - if (Array.isArray(chatsData)) { - Store.set('chats', chatsData); - return chatsData; - } else { - return []; - } - } else if (Array.isArray(chats)) { - Store.set('chats', chats); + if (chats) { return chats; } else { return []; @@ -32,7 +23,7 @@ class ChatController { try { const newChat = await ChatAPI.createChat(title); const currentChats = Store.getState().chats || []; - Store.set('chats', [...currentChats, newChat]); + Store.set({chats: [...currentChats, newChat]}); return newChat; } catch (error) { console.error('Error creating chat:', error); @@ -52,47 +43,42 @@ class ChatController { } } - public async getChatUsers(id: number): Promise { + public async getChatUsers(id: number | undefined): Promise { try { const chatUsers = await ChatAPI.getChatUsers(id); - if (chatUsers instanceof XMLHttpRequest) { - const usersData = chatUsers.response; - if (Array.isArray(usersData)) { - Store.set('currentChatUsers', usersData); - } - return usersData; - } + Store.set({currentChatUsers: chatUsers}); + return chatUsers; } catch (error) { console.error('Error fetching chatUsers:', error); } } - public async addUsers(data: any) { - if (!data.id || !data.user) return false; - - try { - const response= await ChatAPI.addUsers(data.id, [data.user]); - if (response instanceof XMLHttpRequest) { - return true; - } else if (Array.isArray(response)) { - return true; - } else { - return false; + public async addUsers(user: User): Promise { + const { selectedChat, currentChatUsers } = Store.getState(); + if (user && selectedChat) { + try { + await ChatAPI.addUsers({ chatId: selectedChat.id, users: [user.id] }); + } catch (error) { + console.error(error); } - } catch (e) { - console.error(e); - } - return false; + const newCurrentChatUsers = [...currentChatUsers, user]; + Store.set({currentChatUsers: newCurrentChatUsers}); + } } - async removeUsers(data: { id: number, users: number[] }) { - try { - await ChatAPI.removeUsers(data.id, data.users); - await this.getChatUsers(data.id); - } catch (error) { - console.error('Error removing users from chat:', error); - throw error; + async removeUsers(data: { id: number, users: number[] }): Promise { + const { selectedChat, currentChatUsers } = Store.getState(); + + if (data && selectedChat) { + try { + await ChatAPI.removeUsers({ users: [data.id], chatId: selectedChat.id }); + } catch (error) { + console.error(error); + } + + const newCurrentChatUsers = currentChatUsers.filter((user: { id: number; }) => user.id !== data.id); + Store.set({currentChatUsers: newCurrentChatUsers}); } } @@ -100,30 +86,34 @@ class ChatController { const { chats, currentChat } = Store.getState(); if (currentChat) { await ChatAPI.deleteChat({ chatId: currentChat }); - Store.set('chats', chats.filter((chat: { id: any; }) => (chat.id !== currentChat))); - Store.set('currentChat', null); + Store.set({ + chats: chats.filter((chat: { id: any; }) => (chat.id !== currentChat)), + currentChat: null + }) } } - public async setCurrentChat(id: number) { + public async changeAvatar(data: FormData): Promise { + try { + const response = await ChatAPI.uploadAvatar(data); + if (response) { + Store.set({selectedChat: response}); + return response; + } + } catch (error) { + console.error('Error uploading avatar:', error); + } + } + + public async setCurrentChat(id: number | undefined) { const chats = Store.getState().chats; const selectedChat = chats.find((chat: Chat) => chat.id === id); + const chatUsers = await this.getChatUsers(id); - if (selectedChat) { - Store.set('currentChat', id); - Store.set('selectedChat', selectedChat); - Store.set('currentChatMessages', []); - await messageController.disconnect(); + Store.set({ currentChat: id, selectedChat: selectedChat, currentChatUsers: chatUsers }); - try { - await messageController.connect(); - await this.getChats(); - } catch (error) { - Store.set('websocketError', 'Failed to connect to chat'); - } - } else { - console.error('Chat not found:', id); - } + messageController.disconnect(); + messageController.connect(); } } diff --git a/src/controllers/MessageController.ts b/src/controllers/MessageController.ts index fc95b01..199dc22 100644 --- a/src/controllers/MessageController.ts +++ b/src/controllers/MessageController.ts @@ -3,13 +3,27 @@ import Socket, { Message, WebSocketProps } from '../core/Socket'; import ChatAPI from '../api/ChatApi'; import Store from '../core/Store'; -const setLastMessage = (_message: MessageProps) => { - const { chats, currentChat } = Store.getState(); +const setLastMessage = (message: MessageProps) => { + const { chats, currentChat, currentChatUsers } = Store.getState(); if (chats && currentChat) { const chat = chats.find((c: { id: any; }) => c.id === currentChat); if (chat) { + const user = currentChatUsers.find((u: { id: any; }) => u.id === message.user_id); const newChat = { ...chat }; - Store.set('chats', chats.map((c: any) => (c === chat ? newChat : c))); + newChat.last_message = { + user: { + id: user?.id || 0, + login: user?.login || '', + first_name: user?.first_name || '', + second_name: user?.second_name || '', + display_name: user?.display_name || '', + avatar: user?.avatar || '', + role: user?.role || '', + }, + time: message.time, + content: message.content, + }; + Store.set({chats: chats.map((c: any) => (c === chat ? newChat : c))}) } } }; @@ -73,8 +87,8 @@ export class MessageController { newChatMessages = [...message].reverse(); } else { newChatMessages = [...currentChatMessages, message]; - setLastMessage(message); + setLastMessage(message as any); } - Store.set('currentChatMessages', newChatMessages); + Store.set({currentChatMessages: newChatMessages}); } } diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 89f23d9..4c13665 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -10,10 +10,10 @@ class UserController { if (response && typeof response === 'object') { if (response instanceof XMLHttpRequest) { const userData = response.response; - Store.set('user', userData); + Store.set({user: userData}); return userData; } else { - Store.set('user', response); + Store.set({user: response}); return response; } } else { @@ -48,10 +48,10 @@ class UserController { if (response && typeof response === 'object') { if (response instanceof XMLHttpRequest) { const userData = response.response; - Store.set('user', userData); + Store.set({user: userData}); return userData; } else { - Store.set('user', response); + Store.set({user: response}); return response; } } else { @@ -65,14 +65,8 @@ class UserController { public async searchUsers(query: string): Promise { try { const response = await UserApi.searchUsers(query); - if (response instanceof XMLHttpRequest) { - const data = response.response; - if (Array.isArray(data)) { - return data; - } else { - return []; - } - } else if (Array.isArray(response)) { + if (response) { + Store.set({users: response}); return response; } else { return []; diff --git a/src/core/Block.ts b/src/core/Block.ts index c0a22ef..b74264e 100644 --- a/src/core/Block.ts +++ b/src/core/Block.ts @@ -98,7 +98,8 @@ class Block { }); } - public componentDidMount(): void {} + public componentDidMount(): void { + } public dispatchComponentDidMount(): void { this.eventBus().emit(Block.EVENTS.FLOW_CDM); @@ -118,7 +119,7 @@ class Block { } protected componentDidUpdate(oldProps: Props, newProps: Props) { - return isEqual(oldProps, newProps); + return !isEqual(oldProps, newProps); } protected _componentWillUnmount() { @@ -139,49 +140,41 @@ class Block { private _render() { const fragment = this._compile(); - const newElement = fragment.firstElementChild as HTMLElement; - if (this._element) { + if (this._element && this._element.parentNode) { this._removeEvents(); this._element.replaceWith(newElement); } this._element = newElement; - this._addEvents(); - - Object.values(this.children).forEach(child => { - if (child instanceof Block) { - child.forceUpdate(); - } - }); } private _compile(): DocumentFragment { const template = this.render(); const fragment = document.createElement('template'); - const context = { + if (!template) { + return document.createDocumentFragment(); + } + + fragment.innerHTML = Handlebars.compile(template)({ ...this.props, __children: this.children, - }; - - fragment.innerHTML = Handlebars.compile(template)(context); + }); Object.entries(this.children).forEach(([id, child]) => { const stub = fragment.content.querySelector(`[data-id="${id}"]`); if (!stub) { return; } - if (child instanceof Block) { const content = child.getContent(); - if (content) { - stub.replaceWith(content); + if (!content) { + return; } - } else if (child instanceof Element) { - stub.replaceWith(child); + stub.replaceWith(content); } }); diff --git a/src/core/HTTPTransport.ts b/src/core/HTTPTransport.ts index b8d6463..1730851 100644 --- a/src/core/HTTPTransport.ts +++ b/src/core/HTTPTransport.ts @@ -41,7 +41,7 @@ class HTTPTransport { }; request = (url: string, options: Options = {}, timeout = 5000) => { - const {headers = {}, method, data} = options; + const {method, data} = options; return new Promise(function(resolve, reject) { if (!method) { @@ -50,23 +50,25 @@ class HTTPTransport { } const xhr = new XMLHttpRequest(); - const isGet = method === METHODS.GET; if (method === METHODS.GET && data) { - url += queryStringify(data as Record); + // eslint-disable-next-line no-param-reassign + url += queryStringify(data as Record); } - xhr.open( - method, - isGet && !!data - ? `${url}${queryStringify(data)}` - : url, - ); - xhr.onload = function() { - if (xhr.status >= 200 && xhr.status < 300) { - resolve(xhr); + xhr.open(method || METHODS.GET, url); + + if (data instanceof FormData) { + xhr.setRequestHeader('Accept', 'application/json'); + } else { + xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); + } + + xhr.onload = () => { + if (xhr.status !== 200) { + reject(new Error(`Error ${xhr.status}: ${xhr?.response?.reason || xhr.statusText}`)); } else { - reject(xhr); + resolve(xhr.response); } }; @@ -77,14 +79,12 @@ class HTTPTransport { xhr.responseType = 'json'; xhr.withCredentials = true; - Object.keys(headers).forEach(key => { - xhr.setRequestHeader(key, headers[key]); - }); - - if (isGet || !data) { + if (method === METHODS.GET || !data) { xhr.send(); + } else if (data instanceof FormData) { + xhr.send(data); } else { - xhr.send(data instanceof FormData ? data : JSON.stringify(data)); + xhr.send(JSON.stringify(data)); } }); }; diff --git a/src/core/RegistrationComponent.ts b/src/core/RegistrationComponent.ts index 5bbbadd..f52be83 100644 --- a/src/core/RegistrationComponent.ts +++ b/src/core/RegistrationComponent.ts @@ -2,16 +2,21 @@ import Handlebars from 'handlebars'; import Block from "./Block"; export function registerComponent(name: string, Component: typeof Block) { - Handlebars.registerHelper(name, function(this: any, { hash, data }) { - const component: Block = new Component(hash); - const id: string = `${name}-${component.id}`; + if (!Handlebars.helpers[name]) { + Handlebars.registerHelper(name, function (this: any, {hash, data, fn}) { + const component: Block = new Component(hash); + const id: string = `${name}-${component.id}`; - if (hash.ref) { - (data.root.__refs = data.root.__refs || {})[hash.ref] = component; - } + if (hash.ref) { + (data.root.__refs = data.root.__refs || {})[hash.ref] = component; + } - (data.root.__children = data.root.__children || {})[id] = component; + (data.root.__children = data.root.__children || {})[id] = component; - return `
`; - }); + const contents = fn ? fn(this) : ''; + + + return `
${contents}
`; + }); + } } diff --git a/src/core/Render.ts b/src/core/Render.ts index 50406f7..0e1ef2b 100644 --- a/src/core/Render.ts +++ b/src/core/Render.ts @@ -1,9 +1,19 @@ import Block from './Block'; export function render(rootQuery: string, block: Block) { - const root = document.querySelector(rootQuery); + const root = document.querySelector(rootQuery) as HTMLElement; + if (root) { root.innerHTML = ''; - root.append(block.getContent()); + const content = block.getContent(); + if (content) { + root.appendChild(content); + } + + block.dispatchComponentDidMount(); + + return root; } + + return null; } diff --git a/src/core/Socket.ts b/src/core/Socket.ts index 81cea87..642395b 100644 --- a/src/core/Socket.ts +++ b/src/core/Socket.ts @@ -69,10 +69,14 @@ class Socket { } public message(event: MessageEvent) { - const data = JSON.parse(event.data); - - if (data.type !== 'user connected' && data.type !== 'pong') { - this.callbackMessages(data); + try { + const data = JSON.parse(event.data); + + if (data.type !== 'user connected' && data.type !== 'pong') { + this.callbackMessages(data); + } + } catch (error) { + console.error('Error receiving message', error); } } diff --git a/src/core/Store.ts b/src/core/Store.ts index e018166..10aa848 100644 --- a/src/core/Store.ts +++ b/src/core/Store.ts @@ -1,5 +1,4 @@ import { EventBus } from './EventBus'; -import { Indexed, set } from '../utils/utils' import isEqual from "../utils/isEqual"; import { User } from '../utils/types'; @@ -7,8 +6,15 @@ export enum StoreEvents { Updated = 'updated', } +interface StoreState { + notificationMessage?: string; + selectedUser?: User; + users?: User[]; + [key: string]: any; +} + class Store extends EventBus { - private state: Indexed = {}; + private state: StoreState = {}; constructor() { super(); @@ -18,24 +24,18 @@ class Store extends EventBus { return this.state; } - public set(path: string, value: unknown) { - const oldValue = this.getState()[path]; - if (!isEqual(oldValue, value)) { - set(this.state, path, value); - this.emit(StoreEvents.Updated, oldValue, value); - } - } - - public setNotificationMessage(message: string | null) { - this.set('notificationMessage', message); - } + public set(nextState: Partial) { + const prevState = { ...this.state }; + const newState = { ...this.state, ...nextState }; - public setSelectedUser(user: User | null) { - this.set('selectedUser', user); + if (!isEqual(prevState, newState)) { + this.state = newState; + this.emit(StoreEvents.Updated, prevState, newState); + } } - public setUsers(users: User[]) { - this.set('users', users); + public setNotificationMessage(message: string) { + this.set({notificationMessage: message}); } public emit(event: string, ...args: unknown[]) { @@ -47,19 +47,6 @@ class Store extends EventBus { listener(...args); }); } - - public setResetState(): void { - try { - this.state = { - auth: false, - user: null, - getPage: '/', - }; - this.emit(StoreEvents.Updated); - } catch (e) { - console.error(e); - } - } } export default new Store(); diff --git a/src/main.ts b/src/main.ts index 6d849fc..2ae3fed 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,8 @@ import { BlockType } from "./core/Block"; import './style.less'; import AuthController from './controllers/AuthController'; import './utils/helpers'; +import Store from "./core/Store"; +import ChatController from "./controllers/ChatController"; type ImportValue = Record; type ImportGlob = Record; @@ -52,8 +54,8 @@ async function initApp() { .start(); try { - const isLoggedIn = await AuthController.getUser(); - if (isLoggedIn) { + const user = await AuthController.getUser(); + if (user) { const currentRoute = router.getCurrentRoute(); if (currentRoute && ( currentRoute.match('/')) @@ -69,6 +71,9 @@ async function initApp() { console.error('Error during app initialization:', e); goToLogin(); } + + const chats = await ChatController.getChats(); + Store.set({chats}); } -initApp(); +document.addEventListener('DOMContentLoaded', () => initApp()); \ No newline at end of file diff --git a/src/pages/chat/chat.hbs b/src/pages/chat/chat.hbs index 4c8caed..10e587d 100644 --- a/src/pages/chat/chat.hbs +++ b/src/pages/chat/chat.hbs @@ -15,7 +15,7 @@
{{error}}
{{/if}}
- {{{ ChatList }}} + {{{ ChatList chats=chats }}}
diff --git a/src/pages/chat/chat.less b/src/pages/chat/chat.less index 778a5f1..c2cc101 100644 --- a/src/pages/chat/chat.less +++ b/src/pages/chat/chat.less @@ -11,155 +11,6 @@ height: 100%; } - .sidebar { - flex: 1; - - .list { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - - .list-header { - display: flex; - flex-direction: column; - height: 5rem; - border-right: 1px solid @secondary__background-color; - border-bottom: 1px solid @secondary__background-color; - - .section { - &.buttons { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - .create-new-chat { - margin: 0; - - .new-chat { - .button(@margin: 0, @background: none, @border: none, @width: auto); - - &:hover { - .custom-button-hover(); - } - } - } - - .profile { - align-self: flex-end; - padding: 0.25rem 0.1rem; - - .profile-button { - .button(@background: none, @width: 100%, @margin: 0); - - &:hover { - background: none !important; - color: @main__button-background-hover; - - &:after { - color: @main__button-background-hover; - } - } - - &:after { - font-family: @icon-font; - content: @icon__angle-right; - color: @main__font-color; - vertical-align: middle; - font-size: 12px; - margin-left: 0.25rem; - } - } - - &:hover, &:active, &:focus-visible { - background: none !important; - color: @main__button-background-hover !important; - } - } - } - } - - } - - .chat-list { - overflow-y: scroll; - } - - .item { - display: flex; - flex-direction: column; - justify-content: center; - padding-top: 0 !important; - padding-bottom: 0 !important; - padding-inline: 1rem .75rem; - border-bottom: 1px solid @secondary__background-color; - min-height: 4.5rem; - padding-inline-start: 4.5rem !important; - text-decoration: none; - color: @main__font-color; - position: relative; - - .avatar { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - inset-inline-start: .5625rem !important; - width: 3.375rem !important; - height: 3.375rem !important; - - .default-avatar { - position: absolute; - font-size: 25px; - } - - &:before { - content: ''; - color: @main__font-color; - font-size: 24px; - } - - &:after { - content: ''; - border: 30px solid @secondary__background-color; - border-radius: 100%; - } - } - - .dialog { - display: flex; - justify-content: space-between; - align-items: center; - - &.title { - height: 1.375rem; - font-weight: bold; - } - - &.subtitle { - height: 1.375rem; - margin-top: .125rem; - color: @secondary__font-color; - - .unread-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - background-color: @secondary__font-color; /* Вы можете изменить цвет на любой подходящий вашему дизайну */ - color: white; - border-radius: 10px; /* Это создаст круглую форму */ - font-size: 12px; - font-weight: bold; - } - } - } - } - } - } - .messenger { flex: 2; display: flex; @@ -167,38 +18,5 @@ background: @secondary__background-color; } - .create-chat-modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - justify-content: center; - align-items: center; - z-index: 1000; - - &.is-open { - display: flex; - } - - .modal-content { - background-color: white; - padding: 20px; - border-radius: 5px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - } - - .create-chat-form { - display: flex; - flex-direction: column; - gap: 10px; - } - - .close-modal { - margin-top: 10px; - } - } } } diff --git a/src/pages/chat/chat.ts b/src/pages/chat/chat.ts index 7bb932a..bd435da 100644 --- a/src/pages/chat/chat.ts +++ b/src/pages/chat/chat.ts @@ -15,7 +15,7 @@ class ChatPageBase extends Block { onChatCreate: (e: Event) => { e.preventDefault(); e.stopPropagation(); - Store.set('isCreateChatModalOpen', true); + Store.set({isCreateChatModalOpen: true}); } }); } diff --git a/src/pages/login-form/login-form.hbs b/src/pages/login-form/login-form.hbs index 78c1527..f826a17 100644 --- a/src/pages/login-form/login-form.hbs +++ b/src/pages/login-form/login-form.hbs @@ -2,10 +2,10 @@

Sign In