diff --git a/app/src/features/authentication/login/services/apiLogin.ts b/app/src/features/authentication/login/services/apiLogin.ts index bef7de74..93975f0d 100644 --- a/app/src/features/authentication/login/services/apiLogin.ts +++ b/app/src/features/authentication/login/services/apiLogin.ts @@ -1,5 +1,6 @@ import { API_URL } from "@constants"; import { User } from "../LoginForm/LoginForm"; +import toast from "react-hot-toast"; export async function login(user: User) { const res = await fetch(`${API_URL}/auth/login`, { @@ -15,6 +16,7 @@ export async function login(user: User) { const data = await res.json(); if (data.status !== "success") { + toast.error(data.message); throw new Error(data.message); } diff --git a/app/src/features/chats/ChatBody.tsx b/app/src/features/chats/ChatBody.tsx index 5fd1969f..9a09fb76 100644 --- a/app/src/features/chats/ChatBody.tsx +++ b/app/src/features/chats/ChatBody.tsx @@ -81,6 +81,7 @@ function ChatBody() { chatType={chat?.type as "private" | "group" | "channel"} sender={members.find((member) => member._id === data.senderId)} numberOfMembers={chat?.numberOfMembers} + isAppropriate={data.isAppropriate} > @@ -96,6 +97,7 @@ function ChatBody() { chatType="channel" sender={members.find((member) => member._id === data.senderId)} numberOfMembers={chat?.numberOfMembers} + isAppropriate={data.isAppropriate} > diff --git a/app/src/features/chats/ChatInputIcons.tsx b/app/src/features/chats/ChatInputIcons.tsx index 4d234f58..85c5ca6e 100644 --- a/app/src/features/chats/ChatInputIcons.tsx +++ b/app/src/features/chats/ChatInputIcons.tsx @@ -20,7 +20,7 @@ function ChatInputIcons() { setInput, file, setFile, - setIsFilePreviewOpen, + setIsFilePreviewOpen } = useContext(ChatInputContext); const toggleShowEmojies = () => { @@ -28,11 +28,17 @@ function ChatInputIcons() { }; const handleKeyDown = (e: React.KeyboardEvent) => { + setIsEmojiSelectorOpen(false); if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(e as unknown as Event); } }; + const handleSetFile = (file: File) => { + setIsEmojiSelectorOpen(false); + setFile(file); + setIsFilePreviewOpen(true); + }; return ( <> @@ -50,7 +56,7 @@ function ChatInputIcons() { diff --git a/app/src/features/chats/ChatItem.tsx b/app/src/features/chats/ChatItem.tsx index 960b6ebf..246a515e 100644 --- a/app/src/features/chats/ChatItem.tsx +++ b/app/src/features/chats/ChatItem.tsx @@ -59,7 +59,7 @@ type ChatItemProps = { const ChatItem = ({ chat: { _id, lastMessage, name, photo }, - onClick, + onClick }: ChatItemProps) => { const navigate = useNavigate(); @@ -86,7 +86,7 @@ const ChatItem = ({ {new Date(timestamp).toLocaleTimeString("en-US", { hour: "2-digit", - minute: "2-digit", + minute: "2-digit" }) || "No messages"} diff --git a/app/src/features/chats/MessageContent.tsx b/app/src/features/chats/MessageContent.tsx index ce977bc8..873a0d1d 100644 --- a/app/src/features/chats/MessageContent.tsx +++ b/app/src/features/chats/MessageContent.tsx @@ -36,6 +36,7 @@ function MessageContent() { media, contentType, chatType, + isAppropriate, } = useMessageContext(); const { searchTerm, searchResults } = useAppSelector((state) => state.search); @@ -49,6 +50,10 @@ function MessageContent() { media && (contentType === "GIF" || contentType === "sticker"); const isFile = media && !(contentType === "GIF" || contentType === "sticker"); + const filteredContent = + (chatType === "group" || chatType === "channel") && !isAppropriate + ? content + : "🚫️ This mesaage has unappropriate content"; return ( diff --git a/app/src/features/chats/Topbar.tsx b/app/src/features/chats/Topbar.tsx index 768adcb5..fe6c4599 100644 --- a/app/src/features/chats/Topbar.tsx +++ b/app/src/features/chats/Topbar.tsx @@ -123,6 +123,7 @@ function Topbar() { const { chatId } = useParams<{ chatId: string }>(); const userId = useAppSelector((state) => state.user.userInfo.id); const chats = useAppSelector((state) => state.chats.chats); + const dispatch = useAppDispatch(); const { activeThread } = useAppSelector((state) => state.channelsThreads); const [isSearching, setIsSearching] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false); @@ -153,28 +154,31 @@ function Topbar() { const { removeFromBlockList } = useBlock(); const { setIsRightSideBarOpen } = useRightSideBarContext(); - const dispatch = useAppDispatch(); - const handleOpenRightSideBar = useCallback(() => { - if (!chat) return; - if (chat?.type === "private") setIsRightSideBarOpen(false); - else { - dispatch( - updateSideBarView({ - redirect: - chat?.type === "group" - ? sideBarPages.GROUP_INFO - : sideBarPages.CHANNEL_INFO, - data: { type: "right" } - }) - ); - } - }, [chat, dispatch, setIsRightSideBarOpen]); + const cachedOpenRightSideBar = useCallback( + function handleOpenRightSideBar() { + if (!chat) return; + if (chat?.type === "private") setIsRightSideBarOpen(false); + else { + dispatch( + updateSideBarView({ + redirect: + chat?.type === "group" + ? sideBarPages.GROUP_INFO + : sideBarPages.CHANNEL_INFO, + data: { type: "right" } + }) + ); + } + }, + [chat, dispatch, setIsRightSideBarOpen] + ); useEffect(() => { if (!chatId) return; - handleOpenRightSideBar(); - }, [chatId, handleOpenRightSideBar]); + cachedOpenRightSideBar(); + }, [chatId, chat, cachedOpenRightSideBar]); + let image; let lastSeen; diff --git a/app/src/features/chats/audio/VoiceRecorder.tsx b/app/src/features/chats/audio/VoiceRecorder.tsx index 1106f081..fe34f6eb 100644 --- a/app/src/features/chats/audio/VoiceRecorder.tsx +++ b/app/src/features/chats/audio/VoiceRecorder.tsx @@ -82,9 +82,29 @@ const VoiceRecorder: React.FC = ({ lastModified: Date.now() }); audioChunks.current = []; - uploadVoiceNote(file); + try { + uploadVoiceNote(file, { + onSuccess: (url) => { + console.log("File uploaded successfully:", url); + handleSendMessage("", chatId, url); + }, + onError: (error) => { + console.error("Error uploading file:", error); + } + }); + } catch (error) { + console.error("Unexpected error while sending file:", error); + } + setIsRecording("idle"); } - }, [isRecording, recordingMimeType, uploadVoiceNote]); + }, [ + isRecording, + recordingMimeType, + setIsRecording, + uploadVoiceNote, + handleSendMessage, + chatId + ]); useEffect(() => { if (voiceNoteURL && isRecording === "pause") { diff --git a/app/src/features/chats/contexts/MessageProvider.tsx b/app/src/features/chats/contexts/MessageProvider.tsx index af1983f4..65894c02 100644 --- a/app/src/features/chats/contexts/MessageProvider.tsx +++ b/app/src/features/chats/contexts/MessageProvider.tsx @@ -18,6 +18,7 @@ type ProviderProps = { chatType: string; sender?: Member; numberOfMembers?: number; + isAppropriate: boolean; }; function MessageProvider({ @@ -26,13 +27,21 @@ function MessageProvider({ chatType, sender, numberOfMembers, + isAppropriate, }: ProviderProps) { const userId = useAppSelector((state) => state.user.userInfo.id); const isMine = userId === data.senderId; return ( {children} diff --git a/app/src/features/chats/hooks/useChatInput.ts b/app/src/features/chats/hooks/useChatInput.ts index aade6945..e85de81a 100644 --- a/app/src/features/chats/hooks/useChatInput.ts +++ b/app/src/features/chats/hooks/useChatInput.ts @@ -32,7 +32,7 @@ function useChatInput() { e.preventDefault(); setIsEmojiSelectorOpen(false); if (isRecording !== "idle") return; - handleSendMessage(input, chatId, voiceNoteName); + handleSendMessage(input, chatId, voiceNoteName, "audio"); dispatch(clearActiveMessage()); setInput(""); }; diff --git a/app/src/features/chats/media/MediaUploadComponent.tsx b/app/src/features/chats/media/MediaUploadComponent.tsx index 62693c8c..be0e1e3e 100644 --- a/app/src/features/chats/media/MediaUploadComponent.tsx +++ b/app/src/features/chats/media/MediaUploadComponent.tsx @@ -10,7 +10,7 @@ const InvisibleButton = styled.div` `; interface ChildProps { file: File | null; - setFile: React.Dispatch>; + setFile: (file: File) => void; setIsFilePreviewOpen: React.Dispatch>; } diff --git a/app/src/features/chats/media/hooks/useUploadMedia.tsx b/app/src/features/chats/media/hooks/useUploadMedia.tsx index 59b44545..c33afd6a 100644 --- a/app/src/features/chats/media/hooks/useUploadMedia.tsx +++ b/app/src/features/chats/media/hooks/useUploadMedia.tsx @@ -1,5 +1,6 @@ import { useMutation } from "@tanstack/react-query"; import { uploadFile } from "../services/uploadfileHandler"; +import toast from "react-hot-toast"; export function useUploadMedia() { const { mutate, isPending, error, data } = useMutation({ @@ -7,9 +8,11 @@ export function useUploadMedia() { onError: (error) => { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; + toast.error("Error uploading file: " + errorMessage); console.error("Error uploading file:", errorMessage); }, onSuccess: (data) => { + toast.success("File uploaded successfully"); console.log("File uploaded successfully:", data); }, }); diff --git a/app/src/features/chats/services/apiFetchNextPage.ts b/app/src/features/chats/services/apiFetchNextPage.ts index eeb00466..a2c2fba1 100644 --- a/app/src/features/chats/services/apiFetchNextPage.ts +++ b/app/src/features/chats/services/apiFetchNextPage.ts @@ -25,10 +25,6 @@ async function apiFetchNextPage({ throw new Error(data.message); } - if (data.status !== "success") { - throw new Error(data.message); - } - return { messages: data.data.messages, nextPage: data.data.nextPage }; } diff --git a/app/src/types/messages.ts b/app/src/types/messages.ts index 767a1edd..8a4329a2 100644 --- a/app/src/types/messages.ts +++ b/app/src/types/messages.ts @@ -30,6 +30,7 @@ export interface MessageInterface { parentMessageId: string | null; status: MessageStatus; + isAppropriate: boolean; media?: string; diff --git a/app/src/utils/helpers.ts b/app/src/utils/helpers.ts index 652cdc2f..91150cad 100644 --- a/app/src/utils/helpers.ts +++ b/app/src/utils/helpers.ts @@ -24,7 +24,7 @@ function getElapsedTime(timestamp: string): string { } else if (minutes > 0) { return `${minutes}m`; } else { - return `${seconds}s`; + return `${Math.max(seconds, 1)}s`; } }