From 87de71f0f9389355a329c4f5537e70a9516e2aec Mon Sep 17 00:00:00 2001 From: nitingupta95 Date: Fri, 14 Nov 2025 17:11:48 +0530 Subject: [PATCH] feat implemented the toast notifications for messages, join/leave events, or connection --- frontend/src/components/ChatOverlay.tsx | 41 ++++++++++++---- .../components/ConnectionQualityIndicator.tsx | 49 ++++++++++++++----- frontend/src/pages/CreateRoom.tsx | 7 +-- frontend/src/pages/InRoom.tsx | 11 +++-- frontend/src/pages/JoinRoom.tsx | 12 +++-- 5 files changed, 89 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/ChatOverlay.tsx b/frontend/src/components/ChatOverlay.tsx index 74c815b..0119f80 100644 --- a/frontend/src/components/ChatOverlay.tsx +++ b/frontend/src/components/ChatOverlay.tsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from "react"; import { io, Socket } from "socket.io-client"; +import { toast } from "sonner"; const SOCKET_SERVER_URL = "http://localhost:3000"; @@ -8,9 +9,9 @@ interface ChatOverlayProps { userName?: string; } -const ChatOverlay: React.FC = ({ - roomId = "global", - userName = "Anonymous" +const ChatOverlay: React.FC = ({ + roomId = "global", + userName = "Anonymous" }) => { const [open, setOpen] = useState(false); const [messages, setMessages] = useState<{ user: string; text: string; time?: Date }[]>([]); @@ -25,7 +26,11 @@ const ChatOverlay: React.FC = ({ socketRef.current.on("chat-message", (msg) => { setMessages((prev) => [...prev, msg]); if (!open) { - setUnreadCount(prev => prev + 1); + setUnreadCount((prev) => prev + 1); + toast.info(`New message from ${msg.user}`, { + description: msg.text, + duration: 2500, + }); } }); socketRef.current.on("chat-history", (history) => { @@ -53,6 +58,9 @@ const ChatOverlay: React.FC = ({ if (input.trim() === "" || !socketRef.current) return; const msg = { roomId, user: userName, text: input }; socketRef.current.emit("chat-message", msg); + toast.success("Message sent!", { + duration: 1200, + }); setInput(""); }; @@ -61,7 +69,10 @@ const ChatOverlay: React.FC = ({ {!open && ( @@ -84,9 +103,13 @@ const ChatOverlay: React.FC = ({ {messages.map((msg, idx) => (
- {msg.user} + + {msg.user} + - {msg.time ? new Date(msg.time).toLocaleTimeString() : ""} + {msg.time + ? new Date(msg.time).toLocaleTimeString() + : ""}
{msg.text} diff --git a/frontend/src/components/ConnectionQualityIndicator.tsx b/frontend/src/components/ConnectionQualityIndicator.tsx index f61a255..83bbb36 100644 --- a/frontend/src/components/ConnectionQualityIndicator.tsx +++ b/frontend/src/components/ConnectionQualityIndicator.tsx @@ -1,14 +1,15 @@ import React, { useState } from "react"; import { WifiOff, SignalLow, SignalMedium, SignalHigh, Info } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; -import { ConnectionQuality, ConnectionQualityStats } from "../hooks/useConnectionQuality"; +import { toast } from "sonner"; +import { ConnectionQuality, ConnectionQualityStats } from "../hooks/useConnectionQuality.js"; interface ConnectionQualityIndicatorProps { quality: ConnectionQuality; stats?: ConnectionQualityStats; showLabel?: boolean; className?: string; - compact?: boolean; + compact?: boolean; } const ConnectionQualityIndicator: React.FC = ({ @@ -29,7 +30,7 @@ const ConnectionQualityIndicator: React.FC = ({ inactiveColor: "bg-gray-700/40", textColor: "text-green-500", label: "Excellent", - bars: [true, true, true, true], + bars: [true, true, true, true], }; case "good": // 3 bars return { @@ -38,7 +39,7 @@ const ConnectionQualityIndicator: React.FC = ({ inactiveColor: "bg-gray-700/40", textColor: "text-green-400", label: "Good", - bars: [true, true, true, false], + bars: [true, true, true, false], }; case "fair": // 2 bars return { @@ -47,7 +48,7 @@ const ConnectionQualityIndicator: React.FC = ({ inactiveColor: "bg-gray-700/40", textColor: "text-yellow-500", label: "Fair", - bars: [true, true, false, false], + bars: [true, true, false, false], }; case "poor": // 1 bar return { @@ -56,7 +57,7 @@ const ConnectionQualityIndicator: React.FC = ({ inactiveColor: "bg-gray-700/40", textColor: "text-red-500", label: "Poor", - bars: [true, false, false, false], + bars: [true, false, false, false], }; default: // 0 bars return { @@ -65,14 +66,40 @@ const ConnectionQualityIndicator: React.FC = ({ inactiveColor: "bg-gray-700/40", textColor: "text-gray-400", label: "Unknown", - bars: [false, false, false, false], + bars: [false, false, false, false], }; } }; const config = getQualityConfig(); const Icon = config.icon; - const barHeights = [6, 9, 12, 15]; + const barHeights = [6, 9, 12, 15]; + + const prevQuality = React.useRef(null); + React.useEffect(() => { + if (prevQuality.current === quality) return; + prevQuality.current = quality; + switch (quality) { + case "poor": + toast.error("Your connection is poor. Expect lag or interruptions.", { + duration: 2500, + }); + break; + case "fair": + toast.warning("Your connection is fair. Performance may vary.", { + duration: 2500, + }); + break; + case "good": + toast("Your connection is good.", { duration: 2000 }); + break; + case "excellent": + toast.success("Excellent connection!", { duration: 2000 }); + break; + default: + toast("Connection quality unknown.", { duration: 2000 }); + } + }, [quality]); const formatMetric = (value?: number, unit: string = "") => { if (value === undefined) return "N/A"; @@ -142,7 +169,7 @@ const ConnectionQualityIndicator: React.FC = ({ className={`w-1 rounded-sm transition-all duration-200 ${ active ? config.barColor : config.inactiveColor }`} - style={{ + style={{ height: `${barHeights[index]}px`, }} /> @@ -186,9 +213,9 @@ const ConnectionQualityIndicator: React.FC = ({ className={`w-1.5 rounded-sm transition-all duration-200 ${ active ? config.barColor : config.inactiveColor }`} - style={{ + style={{ height: `${barHeights[index]}px`, - }} + }} /> ))}
diff --git a/frontend/src/pages/CreateRoom.tsx b/frontend/src/pages/CreateRoom.tsx index ec2883b..f03eea9 100644 --- a/frontend/src/pages/CreateRoom.tsx +++ b/frontend/src/pages/CreateRoom.tsx @@ -3,6 +3,7 @@ import { React, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "../components/ui/button.js"; import axios from "axios"; +import { toast } from "sonner"; export default function CreateRoom() { const [roomName, setRoomName] = useState(""); @@ -12,7 +13,7 @@ export default function CreateRoom() { const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); - if (!roomName.trim()) return alert("Please enter a room name!"); + if (!roomName.trim()) return toast.message("please enter the room name") setLoading(true); try { @@ -26,12 +27,12 @@ export default function CreateRoom() { } ); - alert("Room created successfully!"); + toast.success("Room created sucessfully"); navigate(`/lobby/${res.data._id}`); } catch (err: any) { console.error(err); - alert(err.response?.data?.message || "Failed to create room."); + toast.error(err.response?.data?.message ||"Failed to create room.") } finally { setLoading(false); } diff --git a/frontend/src/pages/InRoom.tsx b/frontend/src/pages/InRoom.tsx index b33ceb7..d220664 100644 --- a/frontend/src/pages/InRoom.tsx +++ b/frontend/src/pages/InRoom.tsx @@ -3,10 +3,11 @@ import { useParams, useNavigate } from "react-router-dom"; import { io, Socket } from "socket.io-client"; import { Mic, MicOff, Video, VideoOff, PhoneOff, Users, MessageSquare,} from "lucide-react"; import { motion } from "framer-motion"; -import { HotKeys } from "react-hotkeys"; -import { useConnectionQuality } from "../hooks/useConnectionQuality"; -import ConnectionQualityIndicator from "../components/ConnectionQualityIndicator"; -import { API_ENDPOINTS } from "../lib/apiConfig"; +import { HotKeys } from "react-hotkeys"; +import { toast } from "sonner"; +import { useConnectionQuality } from "../hooks/useConnectionQuality.js"; +import API_ENDPOINTS from "../lib/apiConfig.js"; +import ConnectionQualityIndicator from "../components/ConnectionQualityIndicator.js"; const keyMap = { TOGGLE_MIC: "ctrl+m", @@ -134,7 +135,7 @@ const InRoom: React.FC = () => { setVideoOn(stream.getVideoTracks().some((t) => t.enabled)); } catch (err) { console.error("Error accessing camera/mic:", err); - alert("Please allow camera and microphone permissions."); + toast.error("Please allow camera and microphone permissions.") setMicOn(false); setVideoOn(false); } diff --git a/frontend/src/pages/JoinRoom.tsx b/frontend/src/pages/JoinRoom.tsx index c8bffbc..8c9e92e 100644 --- a/frontend/src/pages/JoinRoom.tsx +++ b/frontend/src/pages/JoinRoom.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { Button } from "../components/ui/button.js"; import axios from "axios"; import PreJoinPreview from "../components/PreJoinPreview.js"; +import { toast } from "sonner" export default function JoinRoom() { const [roomName, setRoomName] = useState(""); @@ -16,7 +17,11 @@ export default function JoinRoom() { // 🔹 Step 1: Handle room join initiation const handleJoinClick = (e: React.FormEvent) => { e.preventDefault(); - if (!roomName.trim()) return alert("Please enter a room name!"); + if (!roomName.trim()) { + toast.error("Please enter a room name!"); + return; + } + // Instead of joining immediately, show preview first setShowPreview(true); }; @@ -38,13 +43,13 @@ export default function JoinRoom() { } ); - alert("Joined room successfully!"); + toast.success("Joined room successfully!"); // Optional: Stop preview stream before entering actual call mediaStream.getTracks().forEach(track => track.stop()); navigate(`/room/${roomName}`); } catch (err: any) { console.error("Join room error:", err); - alert(err.response?.data?.message || err.message || "Failed to join room."); + toast.error(err.response?.data?.message || err.message || "Failed to join room."); setShowPreview(false); } finally { setLoading(false); @@ -87,3 +92,4 @@ export default function JoinRoom() { ); } +