Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/LWF-16_chat-box #68

Merged
merged 25 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
Expand Down
3,107 changes: 1,621 additions & 1,486 deletions frontend/package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions frontend/public/icons/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/public/icons/cut-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/public/icons/folder-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/public/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
export { default as AddChatOutlineIcon } from "./add-chat-outline.svg";
export { default as AddCircleOutlineIcon } from "./add-circle-outline.svg";
export { default as ChatPlusOutlineIcon } from "./chat-plus-outline.svg";
export { default as CheckIcon } from "./check.svg";
export { default as CutCheckIcon } from "./cut-check.svg";
export { default as FindContactsOutlineIcon } from "./find-people-outline.svg";
export { default as FolderOutlineIcon } from "./folder-outline.svg";
export { default as FormOutlineIcon } from "./form-outline.svg";
export { default as GroupOutlineIcon } from "./group-outline.svg";
export { default as LeftAngleIcon } from "./left-angle.svg";
Expand All @@ -11,6 +14,7 @@ export { default as LockOutlineIcon } from "./lock-outline.svg";
export { default as PenWithMessageIcon } from "./pen-with-message.svg";
export { default as PinOutlineIcon } from "./pin-outline.svg";
export { default as RemoveCircleOutlineIcon } from "./remove-circle-outline.svg";
export { default as SendOutlineIcon } from "./send-outline.svg";
export { default as SettingOutlineIcon } from "./setting-outline.svg";
export { default as SignOutCircleIcon } from "./sign-out-circle.svg";
export { default as TimeOutlineIcon } from "./time-outline.svg";
Expand Down
6 changes: 6 additions & 0 deletions frontend/public/icons/send-outline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion frontend/src/api/http/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ export async function addDuoChat(userId: string) {
return data;
}

export async function getChatByUserId(userId: string) {
const chatId = await instance.get<string>(`chats/${userId}`);
return chatId.data;
}

export async function getChats(offset: number = 0, limit: number = 10) {
const { data } = await instance.get<ChatParams[]>(`chats?limit=${limit}&offset=${offset}`);
return data;
const chats = new Map<string, ChatParams>();
data.forEach(chat => {
chats.set(chat.id, chat);
});
return chats;
}
10 changes: 3 additions & 7 deletions frontend/src/api/http/contacts/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,9 @@ export async function getContacts() {
return contacts;
}

export async function searchContacts(username: string = "", limit: number = 10, offset: number = 0) {
const { data } = await instance.get<UserParams[]>(`users?username=${username}&limit=${limit}&offset=${offset}`);
const users = new Map<number, UserParams>();
data.forEach(user => {
users.set(user.id, user);
});
return users;
export async function getContactById(contactId: string) {
const { data } = await instance.get<ContactParams>(`users/contacts/${contactId}`);
return data;
}

export async function addContact(userId: string, alias: string) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/http/contacts/contacts.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type ContactParams = {

export type MessageParams = {
action: string;
author: Omit<UserParams, "">;
author: UserParams;
createdAt: string;
edited: boolean;
id: string;
Expand All @@ -28,7 +28,7 @@ export type MessageParams = {

export type ChatParams = {
createdAt: string;
id: number;
id: string;
lastMessage: MessageParams;
type: number;
};
2 changes: 1 addition & 1 deletion frontend/src/api/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ instance.interceptors.request.use(
config => {
const newConfig = config;

newConfig.headers.Authorization = `Bearer ${store.getState().auth.accessToken}`;
newConfig.headers.Authorization = `Bearer ${store.getState().user.accessToken}`;
return newConfig;
},
() => {
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/api/http/user/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { instance } from "@/api/http";
import { UserParams } from "@/api/http/contacts/contacts.types";

export async function searchUser(username: string = "", limit: number = 10, offset: number = 0) {
const { data } = await instance.get<UserParams[]>(`users?username=${username}&limit=${limit}&offset=${offset}`);
const users = new Map<number, UserParams>();
data.forEach(user => {
users.set(user.id, user);
});
return users;
}

export async function getUserById(userId: string) {
const { data } = await instance.get<UserParams>(`users/${userId}`);
return data;
}
22 changes: 17 additions & 5 deletions frontend/src/components/Auth/SignInForm/SignInForm.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
"use client";

import { useFormik } from "formik";
import { jwtDecode } from "jwt-decode";
import { useRouter } from "next/navigation";
import React from "react";
import { toast } from "react-toastify";

import { signIn } from "@/api/http/auth/auth";
import { UserParams } from "@/api/http/contacts/contacts.types";
import { getUserById } from "@/api/http/user/user";
import { passwordInput, signInForm, signInValidationSchema, usernameInput } from "@/components/Auth/auth.config";
import { axiosErrorHandler, handleUsernameBlur } from "@/components/Auth/auth.utils";
import { CustomInput } from "@/components/CustomInput/CustomInput";
import { Form } from "@/components/Form/Form";
import { setAccessToken } from "@/lib/features/user/userSlice";
import { setAccessToken, setCurrentUser } from "@/lib/features/user/userSlice";
import { TokenParams } from "@/lib/features/user/userSlice.types";
import { useAppDispatch } from "@/lib/hooks";

export function SignInForm() {
Expand All @@ -24,8 +28,14 @@ export function SignInForm() {
validationSchema: signInValidationSchema,
onSubmit: async values => {
try {
const data = await signIn(values.username, values.password);
dispatch(setAccessToken(data.accessToken));
const { accessToken } = await signIn(values.username, values.password);
dispatch(setAccessToken(accessToken));
const decodedToken = jwtDecode<TokenParams>(accessToken);

const userFromToken: UserParams = await getUserById(decodedToken["user-id"]);
userFromToken.online = true;
dispatch(setCurrentUser(userFromToken));

toast.dismiss();
router.push("/chat");
} catch (error) {
Expand All @@ -47,7 +57,8 @@ export function SignInForm() {
placeholder={usernameInput.placeholder}
label={usernameInput.label}
className="text-base"
containerClassName="w-3/5"
containerClassName="w-3/5 mb-6"
innerContainerClassName="rounded-lg"
icon={usernameInput.icon}
value={formik.values.username}
onChange={formik.handleChange}
Expand All @@ -59,7 +70,8 @@ export function SignInForm() {
placeholder={passwordInput.placeholder}
type={passwordInput.type}
className="text-base"
containerClassName="w-3/5"
containerClassName="w-3/5 mb-6"
innerContainerClassName="rounded-lg"
label={passwordInput.label}
icon={passwordInput.icon}
value={formik.values.password}
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/components/Auth/SignUpForm/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export function SignUpForm() {
placeholder={fullNameInput.placeholder}
label={fullNameInput.label}
className="text-base"
containerClassName="w-3/5"
containerClassName="w-3/5 mb-6"
innerContainerClassName="rounded-lg"
icon={fullNameInput.icon}
value={formik.values.fullName}
onChange={formik.handleChange}
Expand All @@ -63,7 +64,8 @@ export function SignUpForm() {
placeholder={usernameInput.placeholder}
label={usernameInput.label}
className="text-base"
containerClassName="w-3/5"
containerClassName="w-3/5 mb-6"
innerContainerClassName="rounded-lg"
icon={usernameInput.icon}
value={formik.values.username}
onChange={formik.handleChange}
Expand All @@ -75,7 +77,8 @@ export function SignUpForm() {
placeholder={passwordInput.placeholder}
type={passwordInput.type}
className="text-base"
containerClassName="w-3/5"
containerClassName="w-3/5 mb-6"
innerContainerClassName="rounded-lg"
label={passwordInput.label}
icon={passwordInput.icon}
value={formik.values.password}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function Avatar({ item, className, online, status = true, ...props }: Ava
/>
{status && online && (
<div className="flex justify-end items-end">
<Status className="absolute" />
<Status className="absolute" online={online} />
</div>
)}
</span>
Expand Down
37 changes: 20 additions & 17 deletions frontend/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import React, { useCallback, useContext, useState } from "react";
import { toast } from "react-toastify";

import { addDuoChat, getChats } from "@/api/http/chat/chat";
import { addContact, getContacts, removeContact, searchContacts } from "@/api/http/contacts/contacts";
import { ChatParams, ContactParams, UserParams } from "@/api/http/contacts/contacts.types";
import { ListStateEnum } from "@/components/Chat/chat.types";
import { addDuoChat, getChatByUserId, getChats } from "@/api/http/chat/chat";
import { addContact, getContacts, removeContact } from "@/api/http/contacts/contacts";
import { ContactParams, UserParams } from "@/api/http/contacts/contacts.types";
import { searchUser } from "@/api/http/user/user";
import { ListStateEnum, MainBoxStateEnum } from "@/components/Chat/chat.types";
import { InteractiveList } from "@/components/Chat/InteractiveList/InteractiveList";
import { ContactsMap, UserMap } from "@/components/Chat/InteractiveList/interactiveList.types";
import { ChatMap, ContactsMap, UserMap } from "@/components/Chat/InteractiveList/interactiveList.types";
import { MainBox } from "@/components/Chat/MainBox/MainBox";
import { SideBar } from "@/components/Chat/SideBar/SideBar";
import { SIDEBAR_ITEM } from "@/components/Chat/SideBar/sidebar.config";
Expand All @@ -34,13 +35,13 @@ export function Chat() {
const [globalUsers, setGlobalUsers] = useState<UserMap>(new Map());
const [globalUser, setGlobalUser] = useState<UserParams | undefined>(undefined);

const [chats, setChats] = useState<ChatParams[]>([]);
const [chats, setChats] = useState<ChatMap>(new Map());

const [currentSidebarItem, setCurrentSidebarItem] = useState<string>("chat" as SidebarButtonName);

const fetchGlobalContacts = useCallback(async () => {
try {
const fetchedContacts = await searchContacts();
const fetchedContacts = await searchUser();
setGlobalUsers(fetchedContacts);
} catch (error) {
toast.error("Error fetching global users");
Expand All @@ -58,7 +59,7 @@ export function Chat() {

useAccessTokenEffect(() => {
fetchContacts();
}, [globalUsers]);
}, []);

useAccessTokenEffect(() => {
fetchGlobalContacts();
Expand Down Expand Up @@ -103,13 +104,13 @@ export function Chat() {
const handleContactClick = (currentContact: ContactParams) => {
setGlobalUser(undefined);
setContact(currentContact);
dispatch(setCurrentMainBoxState("user-info"));
dispatch(setCurrentMainBoxState(MainBoxStateEnum.USER_INFO));
};

const handleGlobalContactClick = (currentGlobalUser: UserParams) => {
setContact(undefined);
setGlobalUser(currentGlobalUser);
dispatch(setCurrentMainBoxState("user-info"));
dispatch(setCurrentMainBoxState(MainBoxStateEnum.USER_INFO));
};

const handleAddContact = async (userId: string, alias: string) => {
Expand All @@ -130,17 +131,12 @@ export function Chat() {
updatedUsers.delete(parseInt(userId, 10));
return updatedUsers;
});
await fetchContacts();

if (globalUser) {
setContact({
alias: globalUser.name,
addedAt: new Date().toISOString(),
user: globalUser,
});
setContact(contacts.get(globalUser.id));
}
setGlobalUser(undefined);

await fetchContacts();
} catch (error) {
toast.error("Error adding contact");
}
Expand All @@ -167,6 +163,12 @@ export function Chat() {
}
};

const handleMessageButtonClick = async (userId: string) => {
const chatId = await getChatByUserId(userId);
console.log(chatId);
dispatch(setCurrentMainBoxState(MainBoxStateEnum.CHAT));
};

SIDEBAR_ITEM.buttons[ListStateEnum.CONTACTS].onClick = () => {
dispatch(setCurrentInteractiveListState(ListStateEnum.CONTACTS));
};
Expand Down Expand Up @@ -228,6 +230,7 @@ export function Chat() {
globalUser={globalUser}
onAddContactClick={handleAddContact}
onRemoveContactClick={handleRemoveContact}
onMessageButtonClick={handleMessageButtonClick}
/>
</div>
);
Expand Down
22 changes: 8 additions & 14 deletions frontend/src/components/Chat/InteractiveList/InteractiveList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ export function InteractiveList({
}: Readonly<InteractiveListProps>) {
let interactiveList;
const { webSocket } = useContext(SocketContext);
const { accessToken } = useAppSelector(state => state.auth);
const { accessToken, currentUser } = useAppSelector(state => state.user);
const router = useRouter();
if (!currentUser) {
router.replace("/sign-in");
return null;
}

const handleLogout = async () => {
if (!webSocket || !accessToken) {
Expand Down Expand Up @@ -73,20 +77,10 @@ export function InteractiveList({
{interactiveList}
<div className="bg-dark-400 px-8 py-4 left-0 bottom-0 w-full flex justify-between items-center h-auto">
<div className="flex items-center gap-2">
<Avatar
item={{
id: 1,
avatarPath: undefined,
}}
alt="Avatar"
status={false}
/>
<Avatar item={currentUser} alt="Avatar" status={false} />
<div className="flex flex-col gap-0">
<p className="text-lg">Name</p>
<div className="flex items-center text-gray-300">
<Status />
Active
</div>
<p className="text-lg">{currentUser.name}</p>
<Status online={currentUser.online} textStatus />
</div>
</div>
<CustomButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type InteractiveListVariant = ListStateEnum.CONTACTS | ListStateEnum.CHAT

export type ContactsMap = Map<number, ContactParams>;
export type UserMap = Map<number, UserParams>;
export type ChatMap = Map<string, ChatParams>;

export type InteractiveContactParams = {
currentContact?: ContactParams;
Expand All @@ -14,7 +15,7 @@ export type InteractiveContactParams = {

export type InteractiveChatParams = {
currentChat?: ChatParams;
chats?: ChatParams[];
chats?: ChatMap;
onChatClick?: (id: ChatParams) => void;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { ScrollList } from "@/components/ScrollList/ScrollList";
export function ChatList({ chats }: Readonly<InteractiveChatParams>) {
return (
<ScrollList>
{!chats || (chats && chats.length === 0) ? (
{!chats || (chats && chats.size === 0) ? (
<div className="h-full bg-dark-500 flex items-center justify-center">No chats</div>
) : (
chats.map(chat => <ChatItem key={chat.id} chat={chat} />)
Array.from(chats.values()).map(chat => <ChatItem key={chat.id} chat={chat} />)
)}
</ScrollList>
);
Expand Down
Loading
Loading