Skip to content

Commit

Permalink
Add socket, messagner, chat & chat api
Browse files Browse the repository at this point in the history
  • Loading branch information
VladToby committed Aug 21, 2024
1 parent d6343d6 commit d7b2323
Show file tree
Hide file tree
Showing 39 changed files with 1,513 additions and 152 deletions.
61 changes: 61 additions & 0 deletions src/api/ChatApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import HTTPTransport from '../core/HTTPTransport';
import { Chat, User } from '../utils/types';

const chats = new HTTPTransport('/chats');

class ChatAPI {
public async getChats(): Promise<Chat[]> {
return chats.get('/');
}

public async createChat(title: string): Promise<Chat> {
return chats.post('/', {
data: { title },
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
}

public async getUserToken(chatId: number): Promise<{ token: string }> {
const response = await chats.post(`/token/${chatId}`);
if (response instanceof XMLHttpRequest) {
return response.response;
}

return response;
}

public async getChatUsers(chatId: number): Promise<User[]> {
return chats.get(`/${chatId}/users`);
}

public async addUsers(chatId: number, users: any): Promise<Chat> {
return chats.put('/users', {
data: { chatId, users },
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
}

public async removeUsers(chatId: number, users: any): Promise<void> {
return chats.delete('/users', {
data: { chatId, users },
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
});
}

public async deleteChat(chatId: any): Promise<void> {
return chats.delete('/', {
data: chatId,
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
});
}
}

export default new ChatAPI();
16 changes: 16 additions & 0 deletions src/components/add-user-modal/user-modal.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{#if isAddUserOpen }}
<form class="user-modal" method="dialog">
<h3 class="title">Добавить пользователя в чат</h3>
{{#if isUserSearchEnabled}}
{{{ Search users=users class="search" placeholder="Search users" }}}
{{/if}}
{{#if usersList }}
{{#each currentChatUsers }}
<ul>
<li class="user-item" data-user-id="{{id}}">{{login}}</li>
</ul>
{{/each}}
{{/if}}
{{{ Button class="cancel" type="button" label="Cancel" onClick=onClose }}}
</form>
{{/if}}
56 changes: 56 additions & 0 deletions src/components/add-user-modal/user-modal.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@import '../../utils.less';

.user-modal {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 14px;
z-index: 1000;
background: @main__background-color;
padding: 3rem 2rem 1rem 2rem;
display: flex;
flex-direction: column;
justify-content: space-between;

.title {
margin: 0 0 1rem 0;
}

.search {
.search-results {
display: block;
background: @secondary__background-color;
position: relative;
list-style: none;
width: auto;
max-height: 15rem;
overflow-y: scroll;
border-radius: 5px;
margin-top: 0;
padding-inline-start: 15px;
padding-inline-end: 15px;

.user-item {
display: flex;
flex-direction: column;
border-bottom: 1px solid @main__background-color;
padding: 5px;
}
}
}

.cancel {
.button(
@margin: 3rem 0 0 0,
@width: auto,
@background: none,
@border: none,
@color: @secondary__button-background-color
);

&:hover {
.custom-button-hover(@color: @main__button-background-hover);
}
}
}
106 changes: 106 additions & 0 deletions src/components/add-user-modal/user-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import './user-modal.less';
import UserModalTmpl from './user-modal.hbs?raw';
import Block, {Props} from '../../core/Block';
import { Search } from '../search/search';
import ChatController from "../../controllers/ChatController";
import Store from '../../core/Store';
import { connect } from '../../utils/connect';
import {User} from "../../utils/types";

export class UserModalBase extends Block {
constructor(props: Props) {
super({
...props,
onClose: () => {
Store.set('isAddUserOpen', false);
Store.set('selectedUser', null);
},
events: {
click: (event: Event) => {
this.handleUserClick(event);
}
}
});
}

init() {
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');
if (userItem) {
const userId = userItem.getAttribute('data-user-id') || userItem.id;
if (userId) {
let user;
if (this.props.isUserSearchEnabled) {
user = this.props.users.find((u: User) => u.id.toString() === userId);
} else {
user = this.props.currentChatUsers.find((u: User) => u.id.toString() === userId);
}
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');
}
}

render(): string {
return UserModalTmpl;
}
}

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 || []
}
})(UserModalBase);
1 change: 0 additions & 1 deletion src/components/avatar/avatar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// import './avatar.less';
import AvatarTmpl from './avatar.hbs?raw';
import Block, {Props} from '../../core/Block';

Expand Down
39 changes: 39 additions & 0 deletions src/components/chat-body/chat-body.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{{#if selectedChat}}
<div class="chat-body">
<div class="chat-header">
<h2>{{selectedChat.title}}</h2>
{{#if websocketError}}
<div class="error-message">{{websocketError}}</div>
{{/if}}
<div class="chat-menu">
{{{ MenuButton isOpenChatMenu=isOpenChatMenu class="chat-menu-button" onClick=toggleMenu }}}
{{#if isOpenChatMenu}}
{{{ ChatMenu }}}
{{/if}}
{{#if isAddUserOpen }}
{{{ UserModal }}}
{{/if}}
</div>
</div>
<div class="chat-messages">
{{#if messages.length}}
{{#each messages}}
<div class="message {{#if (eq user_id ../currentUserId)}}out{{else}}in{{/if}}">
<p class="content">{{content}}</p>
<span class="time">{{formatDate time}}</span>
</div>
{{/each}}
{{else}}
<p>No messages yet</p>
{{/if}}
</div>
<form class="chat-input">
<input class="sender" type="text" placeholder="Message..." />
<button type="submit" class="send-message"></button>
</form>
</div>
{{else}}
<div class="no-chat-selected">
<p>Select a chat to start messaging</p>
</div>
{{/if}}
Loading

0 comments on commit d7b2323

Please sign in to comment.