Skip to content

Commit

Permalink
feat: Chat last message
Browse files Browse the repository at this point in the history
  • Loading branch information
Harrylever committed Apr 10, 2024
1 parent 9a506d6 commit ce9ddda
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 46 deletions.
7 changes: 7 additions & 0 deletions src/app/features/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export class MessageRequests {
});
return fetch.data;
}

async useGetLastChatMessage(chatId: string) {
const fetch = await this.axiosInstance.get(
`/messages/chat/${chatId}/last-message`
);
return fetch.data;
}
}

export class NotificationRequests {
Expand Down
2 changes: 1 addition & 1 deletion src/app/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './hooks/hooks';
export * from './store/store';
export * from './constants/const';
export * from './slices/authSlice';
export * from './store/store';
export { default as useAxiosPrivate } from './hooks/useAxiosPrivate';
164 changes: 147 additions & 17 deletions src/components/molecules/UserChat/UserChat.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { IUser, IUserChatProps } from '../../../../typings';
import { useAppSelector, useAxiosPrivate } from '../../../app';
import { UserRequests } from '../../../app/features/requests';
import clsx from 'clsx';
import moment from 'moment';
import { FetchLatestMessage } from '../../tools';
import { UserRequests } from '../../../app/features/requests';
import { updateCurrentChat } from '../../../app/slices/chatSlice';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { setSideBarChatDisplay } from '../../../app/slices/appUIStateSlice';
import { setReduxNotifications } from '../../../app/slices/notificationSlice';
import { useAppDispatch, useAppSelector, useAxiosPrivate } from '../../../app';
import { UnreadNotificationsFunc } from '../../../util/manipulate-notification';
import {
IChat,
INotification,
IUser,
IUserChatProps,
} from '../../../../typings';

const UserChat: React.FC<{ props: IUserChatProps }> = ({
props: { user, chat },
}) => {
const dispatch = useAppDispatch();
const axiosInstance = useAxiosPrivate();
const userRequests = useMemo(
() => new UserRequests(axiosInstance),
[axiosInstance]
);

const sideBarChatListIsOpen = useAppSelector(
(state) => state.appUIStateReduce.sideBarChatOpen
);

const [recipientUser, setRecipientUser] = useState<IUser | undefined>(
undefined
);
const [unReadNotifications, setUnReadNotifications] = useState<
INotification[]
>([]);
const [thisUserNotifications, setThisUserNotifications] = useState<
INotification[]
>([]);

const [, setIsLoading] = useState(false);

Expand Down Expand Up @@ -50,18 +72,112 @@ const UserChat: React.FC<{ props: IUserChatProps }> = ({
onlineUsers &&
onlineUsers.some((user) => user.userId === recipientUser?._id);

const notifications = useAppSelector(
(state) => state.notificationReduce.notifications
);

// Update all unread messages
useEffect(() => {
const newUnReadNotifications = UnreadNotificationsFunc(notifications);
setUnReadNotifications(newUnReadNotifications);
}, [notifications]);

// Update User Notification for Specific Sender
useEffect(() => {
const newThisUserNotifications = unReadNotifications.filter(
(notif) => notif.senderId._id === recipientUser?._id
);
setThisUserNotifications(newThisUserNotifications);
}, [recipientUser?._id, unReadNotifications]);

// Mark All Notifications for Specific sender
const markThisUserNotfications = useCallback(
(
notificationsParam: INotification[],
thisUserNotificationsParam: INotification[]
) => {
const mNotifications = notificationsParam.map((el) => {
let notification: INotification | undefined = undefined;

thisUserNotificationsParam.forEach((notif) => {
if (notif.senderId._id === el.senderId._id) {
notification = {
...notif,
isRead: true,
};
} else {
notification = el;
}
});

return notification as unknown as INotification;
});

dispatch(
setReduxNotifications({
notifications: mNotifications,
})
);
},
[dispatch]
);

// Update the current chat reducer
const updateCurrentChatHandler = (chat: IChat) => {
dispatch(
updateCurrentChat({
chat,
})
);

if (thisUserNotifications.length > 0) {
markThisUserNotfications(notifications, thisUserNotifications);
}

if (sideBarChatListIsOpen) {
setTimeout(() => {
dispatch(
setSideBarChatDisplay({
sideBarChatOpen: false,
})
);
}, 500);
}
};

const latestMessage = FetchLatestMessage(chat as IChat);

const truncateText = (text: string) => {
let shortText = text.substring(0, 20);

if (text.length > 20) {
shortText = shortText + '...';
}
return shortText;
};

return (
<button
type="button"
onClick={() => updateCurrentChatHandler(chat as IChat)}
className="flex flex-row items-center justify-between w-full lg:w-[300px] border-b border-[#ffffff2d] py-2 px-2 rounded-sm"
>
<div className="flex flex-row items-center justify-center gap-x-2.5">
<div className="">
<div className="relative">
<img
src={recipientUser?.imgUri}
alt={recipientUser?.username}
className="rounded-full h-[45px] w-[45px] shadow-lg border border-[#cccccc36]"
/>
<div
className={clsx([
'rounded-full h-[10px] w-[10px] absolute bottom-[8%] left-[75%]',
{
'bg-green-500': isOnline,
' bg-transparent': !isOnline,
},
])}
></div>
</div>
<div className="flex flex-col gap-2.5 items-start justify-center mt-1">
<div className="">
Expand All @@ -71,26 +187,40 @@ const UserChat: React.FC<{ props: IUserChatProps }> = ({
</div>
<div className="font-normal text-gray-400">
<p className="text-[0.7rem]/[0.5rem] sm:text-xs/[0.7rem]">
Text message
{latestMessage
? truncateText(latestMessage.text)
: 'Send a message'}
</p>
</div>
</div>
</div>

<div className="flex flex-col items-end justify-between gap-2 mt-2">
<div
className={clsx([
'flex flex-col items-end justify-between mt-2',
{
'gap-0.5': thisUserNotifications.length > 0,
'gap-2': thisUserNotifications.length === 0,
},
])}
>
<div>
<p className="text-[0.7rem] text-slate-300">12/12/2023</p>
<p className="text-[0.7rem] text-slate-300">
{latestMessage
? moment(latestMessage.createdAt as string).calendar()
: ''}
</p>
</div>

<div
className={clsx([
'rounded-full h-[10px] w-[10px] ',
{
'bg-green-500': isOnline,
' bg-transparent': !isOnline,
},
])}
></div>
<div className="">
{thisUserNotifications.length > 0 && (
<div className="bg-white h-[16px] w-[16px] rounded-full flex items-center justify-center">
<span className="text-primary-purple text-xs font-bold">
{thisUserNotifications.length}
</span>
</div>
)}
</div>
</div>
</button>
);
Expand Down
29 changes: 2 additions & 27 deletions src/components/molecules/UserChat/UserChatWrap.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import React from 'react';
import UserChat from './UserChat';
import { useAppDispatch, useAppSelector } from '../../../app';
import { IChat, IUserChatWrapProps } from '../../../../typings';
import { updateCurrentChat } from '../../../app/slices/chatSlice';
import { setSideBarChatDisplay } from '../../../app/slices/appUIStateSlice';
import { IUserChatWrapProps } from '../../../../typings';

const UserChatWrap: React.FC<{
props: IUserChatWrapProps;
}> = ({ props: { chats, user } }) => {
const dispatch = useAppDispatch();

const sideBarChatListIsOpen = useAppSelector((state) => state.appUIStateReduce.sideBarChatOpen);

// Update the current chat reducer
const updateCurrentChatHandler = (chat: IChat) => {
dispatch(
updateCurrentChat({
chat,
})
);

if (sideBarChatListIsOpen) {
setTimeout(() => {
dispatch(
setSideBarChatDisplay({
sideBarChatOpen: false,
})
);
}, 500);
}
};

return (
<div className="w-full">
{chats.map((chat, _) => (
<div key={_} onClick={() => updateCurrentChatHandler(chat)}>
<div key={_}>
<UserChat props={{ chat: chat, user: user }} />
</div>
))}
Expand Down
3 changes: 2 additions & 1 deletion src/components/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as SocketClient } from './SocketClient';
export { default as AuthRouteController } from './AuthRouteController';
export { default as SocketClient } from './SocketClient';
export { default as FetchLatestMessage } from './useFetchLatestMessage';
35 changes: 35 additions & 0 deletions src/components/tools/useFetchLatestMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { IChat, IMessage } from '../../../typings';
import { useAppSelector, useAxiosPrivate } from '../../app';
import { MessageRequests } from '../../app/features/requests';
import { useCallback, useEffect, useMemo, useState } from 'react';

const FetchLatestMessage = (chat: IChat): IMessage => {
const axiosInstance = useAxiosPrivate();
const messageRequests = useMemo(
() => new MessageRequests(axiosInstance),
[axiosInstance]
);
const newMessage = useAppSelector((state) => state.socketReduce.newMessage);
const notifications = useAppSelector(
(state) => state.notificationReduce.notifications
);
const [latestMessage, setLatestMessage] = useState<IMessage | undefined>(
undefined
);

const getMessages = useCallback(async () => {
if (!chat._id) return;
const response = (await messageRequests.useGetLastChatMessage(
chat._id as string
)) as { success: boolean; data: IMessage };
setLatestMessage(response.data);
}, [chat._id, messageRequests]);

useEffect(() => {
getMessages();
}, [getMessages, newMessage, notifications]);

return latestMessage as IMessage;
};

export default FetchLatestMessage;

0 comments on commit ce9ddda

Please sign in to comment.