Skip to content

Commit

Permalink
Merge pull request #64 from L1nkWave/feature/LWF-15_online-and-offlin…
Browse files Browse the repository at this point in the history
…e-handling

feature/LWF-15_online-and-offline-handling
  • Loading branch information
JeriRov committed May 5, 2024
2 parents a35eed0 + e834865 commit bd7036d
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 52 deletions.
5 changes: 2 additions & 3 deletions frontend/src/api/http/chat/chat.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { instance } from "@/api/http";
import { ChatParams, UserParams } from "@/api/http/contacts/contacts.types";
import { ChatParams } from "@/api/http/contacts/contacts.types";

export async function addDuoChat(userId: string) {
const body = {
recipient: userId,
};
const { data } = await instance.post<UserParams>(`chats`, body);
const { data } = await instance.post(`chats`, body);
return data;
}

export async function getChats(offset: number = 0, limit: number = 10) {
const { data } = await instance.get<ChatParams[]>(`chats?limit=${limit}&offset=${offset}`);
console.log(data);
return data;
}
18 changes: 13 additions & 5 deletions frontend/src/api/http/contacts/contacts.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { instance } from "@/api/http";
import { UserParams } from "@/api/http/contacts/contacts.types";
import { Contacts } from "@/components/Chat/InteractiveList/interactiveList.types";
import { ContactParams, UserParams } from "@/api/http/contacts/contacts.types";

export async function getContacts() {
const { data } = await instance.get<Contacts>("users/contacts?username=&limit=10&offset=0");
return data;
const { data } = await instance.get<ContactParams[]>("users/contacts?username=&limit=10&offset=0");
const contacts = new Map<number, ContactParams>();
data.forEach(contact => {
contacts.set(contact.user.id, contact);
});

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}`);
return data;
const users = new Map<number, UserParams>();
data.forEach(user => {
users.set(user.id, user);
});
return users;
}

export async function addContact(userId: string, alias: string) {
Expand Down
118 changes: 88 additions & 30 deletions frontend/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
"use client";

import React, { useContext, useState } from "react";
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 { InteractiveList } from "@/components/Chat/InteractiveList/InteractiveList";
import { Contacts } from "@/components/Chat/InteractiveList/interactiveList.types";
import { 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";
import { SidebarButtonName } from "@/components/Chat/SideBar/sidebar.types";
import { CustomButton } from "@/components/CustomButton/CustomButton";
import { SocketContext } from "@/context/SocketContext/SocketContext";
import { MessageAction } from "@/context/SocketContext/socketContext.types";
import { lastSeenDateNow } from "@/helpers/contactHelpers";
import { useAccessTokenEffect } from "@/hooks/useAccessTokenEffect";
import { setCurrentInteractiveListState, setCurrentMainBoxState } from "@/lib/features/chat/chatSlice";
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
Expand All @@ -26,55 +28,76 @@ export function Chat() {

const { message } = useContext(SocketContext);

const [contacts, setContacts] = useState<Contacts>([]);
const [contacts, setContacts] = useState<ContactsMap>(new Map());
const [contact, setContact] = useState<ContactParams | undefined>(undefined);

const [globalUsers, setGlobalUsers] = useState<UserParams[]>([]);
const [globalUsers, setGlobalUsers] = useState<UserMap>(new Map());
const [globalUser, setGlobalUser] = useState<UserParams | undefined>(undefined);

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

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

const fetchGlobalContacts = useCallback(async () => {
try {
const fetchedContacts = await searchContacts();
setGlobalUsers(fetchedContacts);
} catch (error) {
toast.error("Error fetching global users");
}
}, []);

const fetchContacts = useCallback(async () => {
try {
const fetchedContacts = await getContacts();
setContacts(fetchedContacts);
} catch (error) {
toast.error("Error fetching contacts");
}
}, []);

useAccessTokenEffect(() => {
const fetchContacts = async () => {
try {
const fetchedContacts = await getContacts();
setContacts(fetchedContacts);
} catch (error) {
toast.error("Error fetching contacts");
}
};
fetchContacts();
}, [contacts]);
}, [globalUsers]);

useAccessTokenEffect(() => {
const fetchGlobalContacts = async () => {
try {
const fetchedContacts = await searchContacts();
setGlobalUsers(fetchedContacts);
} catch (error) {
toast.error("Error fetching global users");
}
};
fetchGlobalContacts();
}, [globalUsers]);
}, []);

useAccessTokenEffect(() => {
const fetchChats = async () => {
try {
const fetchedChats = await getChats();
setChats(fetchedChats);
console.log("Chats:", fetchedChats);
} catch (error) {
toast.error("Error fetching chats");
}
};
fetchChats();
}, [chats]);
}, []);

useAccessTokenEffect(() => {
console.log(message);
console.log("message", message);
if (message?.action === MessageAction.OFFLINE) {
setContacts(prevContacts => {
const updatedContact = prevContacts.get(message.senderId);
if (updatedContact) {
updatedContact.user.online = false;
updatedContact.user.lastSeen = lastSeenDateNow();
return new Map(prevContacts).set(message.senderId, updatedContact);
}
return prevContacts;
});
} else if (message?.action === MessageAction.ONLINE) {
setContacts(prevContacts => {
const updatedContact = prevContacts.get(message.senderId);
if (updatedContact) {
updatedContact.user.online = true;
return new Map(prevContacts).set(message.senderId, updatedContact);
}
return prevContacts;
});
}
}, [message]);

const handleContactClick = (currentContact: ContactParams) => {
Expand All @@ -90,20 +113,55 @@ export function Chat() {
};

const handleAddContact = async (userId: string, alias: string) => {
const addDuoChatWithTryCatch = async () => {
try {
await addDuoChat(userId);
} catch (error) {
// TODO: catch this somehow!
}
};

try {
await addContact(userId, alias);
await addDuoChat(userId);
setGlobalUsers(prevGlobalUsers =>
prevGlobalUsers.filter(prevGlobalUser => prevGlobalUser.id.toString() !== userId)
);
await addDuoChatWithTryCatch();

setGlobalUsers(prevGlobalUsers => {
const updatedUsers = new Map(prevGlobalUsers);
updatedUsers.delete(parseInt(userId, 10));
return updatedUsers;
});

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

await fetchContacts();
} catch (error) {
toast.error("Error adding contact");
}
};

const handleRemoveContact = async (userId: string) => {
try {
await removeContact(userId);
setContacts(prevContacts => prevContacts.filter(prevContact => prevContact.user.id.toString() !== userId));

setContacts(prevContacts => {
const updatedContacts = new Map(prevContacts);
updatedContacts.delete(parseInt(userId, 10));
return updatedContacts;
});

if (contact) {
setGlobalUser(contact.user);
}
setContact(undefined);

await fetchGlobalContacts();
} catch (error) {
toast.error("Error adding contact");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { ListStateEnum } from "@/components/Chat/chat.types";

export type InteractiveListVariant = ListStateEnum.CONTACTS | ListStateEnum.CHATS | ListStateEnum.FIND_CONTACTS;

export type Contacts = ContactParams[];
export type ContactsMap = Map<number, ContactParams>;
export type UserMap = Map<number, UserParams>;

export type InteractiveContactParams = {
currentContact?: ContactParams;
contacts?: Contacts;
contacts?: ContactsMap;
onContactClick?: (id: ContactParams) => void;
};

Expand All @@ -19,7 +20,7 @@ export type InteractiveChatParams = {

export type InteractiveGlobalContactParams = {
currentGlobalUser?: UserParams;
globalContacts?: UserParams[];
globalContacts?: UserMap;
onGlobalContactClick?: (id: UserParams) => void;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export function ContactList({
onContactClick: handleContactClick,
currentContact,
}: Readonly<InteractiveContactParams>) {
if (!contacts || contacts.length === 0) {
if (!contacts || contacts.size === 0) {
return <ScrollList className="justify-center items-center">No contacts</ScrollList>;
}

return (
<ScrollList>
{contacts?.map(contact => (
{Array.from(contacts.values()).map(contact => (
<ContactItem
contact={contact}
onClick={handleContactClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function GlobalContactList({
}: Readonly<InteractiveGlobalContactParams>) {
let currentGlobalContact: ContactParams | undefined;

if (!globalContacts || globalContacts.length === 0) {
if (!globalContacts || globalContacts.size === 0) {
return <ScrollList className="justify-center items-center">No contacts</ScrollList>;
}

Expand All @@ -32,7 +32,7 @@ export function GlobalContactList({

return (
<ScrollList>
{globalContacts?.map(globalUserParams => {
{Array.from(globalContacts.values()).map(globalUserParams => {
const contact: ContactParams = {
alias: undefined,
addedAt: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PropsWithChildren, useEffect, useMemo, useState } from "react";
import { refreshToken } from "@/api/http/auth/auth";
import { connectToSocket } from "@/api/socket";
import { SocketContext } from "@/context/SocketContext/SocketContext";
import { SocketContextProps } from "@/context/SocketContext/socketContext.types";
import { MessageContextParams, SocketContextProps } from "@/context/SocketContext/socketContext.types";
import { RECONNECT_TIMEOUT } from "@/context/SocketContext/SocketProvider/socketProvider.config";
import { setAccessToken } from "@/lib/features/user/userSlice";
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
Expand All @@ -16,7 +16,7 @@ export function SocketProvider({ children }: Readonly<PropsWithChildren>) {
const { accessToken } = useAppSelector(state => state.auth);
const route = useRouter();
const [webSocket, setWebSocket] = useState<WebSocket | null>(null);
const [message, setMessage] = useState<unknown>();
const [message, setMessage] = useState<MessageContextParams>();

useEffect(() => {
if (!accessToken) {
Expand Down Expand Up @@ -44,15 +44,13 @@ export function SocketProvider({ children }: Readonly<PropsWithChildren>) {

newSocket.onopen = event => {
console.log("Open", event);
setWebSocket(newSocket);
};

newSocket.onmessage = event => {
console.log("Message", event.data);
setMessage(JSON.parse(event.data));
};

newSocket.addEventListener("error", () => {});
setWebSocket(newSocket);

return () => {
if (newSocket.readyState === WebSocket.OPEN) {
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/context/SocketContext/socketContext.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
export enum MessageAction {
ONLINE = "ONLINE",
OFFLINE = "OFFLINE",
}

export type MessageContextParams = {
action: MessageAction;
senderId: number;
timestamp: string;
};

export type SocketContextProps = {
webSocket?: WebSocket | null;
message?: unknown;
message?: MessageContextParams;
};
4 changes: 4 additions & 0 deletions frontend/src/helpers/contactHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ import { ContactParams } from "@/api/http/contacts/contacts.types";
export const getContactName = (contact: ContactParams) => {
return contact.alias ? contact.alias : contact.user.name;
};

export const lastSeenDateNow = () => {
return Math.floor(Date.now() / 1000);
};
2 changes: 1 addition & 1 deletion frontend/src/hooks/useAccessTokenEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export function useAccessTokenEffect(callback: EffectCallback, dependencies: Dep
return;
}
callback();
}, [accessToken, router, callback, dependencies]);
}, [accessToken, router, ...dependencies]);
}

0 comments on commit bd7036d

Please sign in to comment.