Skip to content

Commit 7063440

Browse files
authored
Merge pull request #74 from nitingupta95/feat/add-notification
feat implemented the toast notifications for messages, join/leave eve…
2 parents 00703f0 + 98e28db commit 7063440

5 files changed

Lines changed: 85 additions & 29 deletions

File tree

frontend/src/components/ChatOverlay.tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState, useRef, useEffect } from "react";
22
import { io, Socket } from "socket.io-client";
3+
import { toast } from "sonner";
34

45
const SOCKET_SERVER_URL = "http://localhost:3000";
56

@@ -8,9 +9,9 @@ interface ChatOverlayProps {
89
userName?: string;
910
}
1011

11-
const ChatOverlay: React.FC<ChatOverlayProps> = ({
12-
roomId = "global",
13-
userName = "Anonymous"
12+
const ChatOverlay: React.FC<ChatOverlayProps> = ({
13+
roomId = "global",
14+
userName = "Anonymous"
1415
}) => {
1516
const [open, setOpen] = useState(false);
1617
const [messages, setMessages] = useState<{ user: string; text: string; time?: Date }[]>([]);
@@ -25,7 +26,11 @@ const ChatOverlay: React.FC<ChatOverlayProps> = ({
2526
socketRef.current.on("chat-message", (msg) => {
2627
setMessages((prev) => [...prev, msg]);
2728
if (!open) {
28-
setUnreadCount(prev => prev + 1);
29+
setUnreadCount((prev) => prev + 1);
30+
toast.info(`New message from ${msg.user}`, {
31+
description: msg.text,
32+
duration: 2500,
33+
});
2934
}
3035
});
3136
socketRef.current.on("chat-history", (history) => {
@@ -53,6 +58,9 @@ const ChatOverlay: React.FC<ChatOverlayProps> = ({
5358
if (input.trim() === "" || !socketRef.current) return;
5459
const msg = { roomId, user: userName, text: input };
5560
socketRef.current.emit("chat-message", msg);
61+
toast.success("Message sent!", {
62+
duration: 1200,
63+
});
5664
setInput("");
5765
};
5866

@@ -61,7 +69,10 @@ const ChatOverlay: React.FC<ChatOverlayProps> = ({
6169
{!open && (
6270
<button
6371
className="fixed bottom-6 right-6 bg-green-600 dark:bg-green-500 text-white rounded-full w-14 h-14 flex items-center justify-center shadow-lg hover:bg-green-700 dark:hover:bg-green-600 transition-all z-50"
64-
onClick={() => setOpen(true)}
72+
onClick={() => {
73+
setOpen(true);
74+
toast("Chat opened.");
75+
}}
6576
aria-label="Open chat"
6677
>
6778
💬
@@ -75,18 +86,30 @@ const ChatOverlay: React.FC<ChatOverlayProps> = ({
7586
{open && (
7687
<div className="fixed bottom-6 right-6 w-80 max-w-[90vw] bg-white dark:bg-gray-900 shadow-2xl rounded-xl flex flex-col z-50 animate-fade-in-up border border-gray-200 dark:border-gray-800">
7788
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
78-
<span className="font-semibold text-gray-900 dark:text-gray-100">In-Call Chat - {roomId}</span>
79-
<button onClick={() => setOpen(false)} className="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500">
89+
<span className="font-semibold text-gray-900 dark:text-gray-100">
90+
In-Call Chat - {roomId}
91+
</span>
92+
<button
93+
onClick={() => {
94+
setOpen(false);
95+
toast("Chat closed.");
96+
}}
97+
className="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500"
98+
>
8099
×
81100
</button>
82101
</div>
83102
<div className="flex-1 overflow-y-auto p-4 space-y-2 h-72 bg-gray-50 dark:bg-gray-950">
84103
{messages.map((msg, idx) => (
85104
<div key={idx} className="flex flex-col">
86105
<div className="flex items-center gap-2">
87-
<span className="text-xs text-green-700 dark:text-green-400 font-bold">{msg.user}</span>
106+
<span className="text-xs text-green-700 dark:text-green-400 font-bold">
107+
{msg.user}
108+
</span>
88109
<span className="text-xs text-gray-500 dark:text-gray-400">
89-
{msg.time ? new Date(msg.time).toLocaleTimeString() : ""}
110+
{msg.time
111+
? new Date(msg.time).toLocaleTimeString()
112+
: ""}
90113
</span>
91114
</div>
92115
<span className="bg-green-100 dark:bg-green-900/30 text-gray-900 dark:text-gray-100 rounded px-2 py-1 text-sm w-fit max-w-[85%]">{msg.text}</span>

frontend/src/components/ConnectionQualityIndicator.tsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React, { useState } from "react";
22
import { WifiOff, SignalLow, SignalMedium, SignalHigh, Info } from "lucide-react";
33
import { motion, AnimatePresence } from "framer-motion";
4-
import { ConnectionQuality, ConnectionQualityStats } from "../hooks/useConnectionQuality";
4+
import { toast } from "sonner";
5+
import { ConnectionQuality, ConnectionQualityStats } from "../hooks/useConnectionQuality.js";
56

67
interface ConnectionQualityIndicatorProps {
78
quality: ConnectionQuality;
89
stats?: ConnectionQualityStats;
910
showLabel?: boolean;
1011
className?: string;
11-
compact?: boolean;
12+
compact?: boolean;
1213
}
1314

1415
const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
@@ -29,7 +30,7 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
2930
inactiveColor: "bg-gray-700/40",
3031
textColor: "text-green-500",
3132
label: "Excellent",
32-
bars: [true, true, true, true],
33+
bars: [true, true, true, true],
3334
};
3435
case "good": // 3 bars
3536
return {
@@ -38,7 +39,7 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
3839
inactiveColor: "bg-gray-700/40",
3940
textColor: "text-green-400",
4041
label: "Good",
41-
bars: [true, true, true, false],
42+
bars: [true, true, true, false],
4243
};
4344
case "fair": // 2 bars
4445
return {
@@ -47,7 +48,7 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
4748
inactiveColor: "bg-gray-700/40",
4849
textColor: "text-yellow-500",
4950
label: "Fair",
50-
bars: [true, true, false, false],
51+
bars: [true, true, false, false],
5152
};
5253
case "poor": // 1 bar
5354
return {
@@ -56,7 +57,7 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
5657
inactiveColor: "bg-gray-700/40",
5758
textColor: "text-red-500",
5859
label: "Poor",
59-
bars: [true, false, false, false],
60+
bars: [true, false, false, false],
6061
};
6162
default: // 0 bars
6263
return {
@@ -65,14 +66,40 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
6566
inactiveColor: "bg-gray-700/40",
6667
textColor: "text-gray-400",
6768
label: "Unknown",
68-
bars: [false, false, false, false],
69+
bars: [false, false, false, false],
6970
};
7071
}
7172
};
7273

7374
const config = getQualityConfig();
7475
const Icon = config.icon;
75-
const barHeights = [6, 9, 12, 15];
76+
const barHeights = [6, 9, 12, 15];
77+
78+
const prevQuality = React.useRef<ConnectionQuality | null>(null);
79+
React.useEffect(() => {
80+
if (prevQuality.current === quality) return;
81+
prevQuality.current = quality;
82+
switch (quality) {
83+
case "poor":
84+
toast.error("Your connection is poor. Expect lag or interruptions.", {
85+
duration: 2500,
86+
});
87+
break;
88+
case "fair":
89+
toast.warning("Your connection is fair. Performance may vary.", {
90+
duration: 2500,
91+
});
92+
break;
93+
case "good":
94+
toast("Your connection is good.", { duration: 2000 });
95+
break;
96+
case "excellent":
97+
toast.success("Excellent connection!", { duration: 2000 });
98+
break;
99+
default:
100+
toast("Connection quality unknown.", { duration: 2000 });
101+
}
102+
}, [quality]);
76103

77104
const formatMetric = (value?: number, unit: string = "") => {
78105
if (value === undefined) return "N/A";
@@ -142,7 +169,7 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
142169
className={`w-1 rounded-sm transition-all duration-200 ${
143170
active ? config.barColor : config.inactiveColor
144171
}`}
145-
style={{
172+
style={{
146173
height: `${barHeights[index]}px`,
147174
}}
148175
/>
@@ -186,9 +213,9 @@ const ConnectionQualityIndicator: React.FC<ConnectionQualityIndicatorProps> = ({
186213
className={`w-1.5 rounded-sm transition-all duration-200 ${
187214
active ? config.barColor : config.inactiveColor
188215
}`}
189-
style={{
216+
style={{
190217
height: `${barHeights[index]}px`,
191-
}}
218+
}}
192219
/>
193220
))}
194221
</div>

frontend/src/pages/CreateRoom.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { React, useState } from "react";
33
import { useNavigate } from "react-router-dom";
44
import { Button } from "../components/ui/button.js";
55
import axios from "axios";
6+
import { toast } from "sonner";
67

78
export default function CreateRoom() {
89
const [roomName, setRoomName] = useState("");
@@ -12,7 +13,7 @@ export default function CreateRoom() {
1213

1314
const handleCreate = async (e: React.FormEvent) => {
1415
e.preventDefault();
15-
if (!roomName.trim()) return alert("Please enter a room name!");
16+
if (!roomName.trim()) return toast.message("please enter the room name")
1617

1718
setLoading(true);
1819
try {
@@ -26,12 +27,12 @@ export default function CreateRoom() {
2627
}
2728
);
2829

29-
alert("Room created successfully!");
30+
toast.success("Room created sucessfully");
3031
navigate(`/lobby/${res.data._id}`);
3132

3233
} catch (err: any) {
3334
console.error(err);
34-
alert(err.response?.data?.message || "Failed to create room.");
35+
toast.error(err.response?.data?.message ||"Failed to create room.")
3536
} finally {
3637
setLoading(false);
3738
}

frontend/src/pages/InRoom.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { useParams, useNavigate } from "react-router-dom";
33
import { io, Socket } from "socket.io-client";
44
import { Mic, MicOff, Video, VideoOff, PhoneOff, Users, MessageSquare } from "lucide-react";
55
import { motion } from "framer-motion";
6-
import { HotKeys } from "react-hotkeys";
6+
import { HotKeys } from "react-hotkeys";
7+
import { toast } from "sonner";
78
import { useConnectionQuality } from "../hooks/useConnectionQuality.js";
9+
import API_ENDPOINTS from "../lib/apiConfig.js";
810
import ConnectionQualityIndicator from "../components/ConnectionQualityIndicator.js";
9-
import { API_ENDPOINTS } from "../lib/apiConfig.js";
1011

1112
const keyMap = {
1213
TOGGLE_MIC: "ctrl+m",

frontend/src/pages/JoinRoom.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
33
import { Button } from "../components/ui/button.js";
44
import axios from "axios";
55
import PreJoinPreview from "../components/PreJoinPreview.js";
6+
import { toast } from "sonner"
67

78
export default function JoinRoom() {
89
const [roomName, setRoomName] = useState("");
@@ -32,11 +33,11 @@ export default function JoinRoom() {
3233
console.error("Media access denied:", err);
3334

3435
if (err.name === "NotAllowedError") {
35-
alert(
36+
toast.error(
3637
"You blocked the camera/mic.\n\nPlease enable permissions:\n1. Click lock icon in URL bar\n2. Open Site Settings\n3. Set Camera & Microphone to Allow\n4. Reload the page"
3738
);
3839
} else {
39-
alert("Unable to access camera/mic: " + err.message);
40+
toast.error("Unable to access camera/mic: " + err.message);
4041
}
4142
}
4243
};
@@ -63,15 +64,17 @@ export default function JoinRoom() {
6364
}
6465
);
6566

66-
alert("Joined room successfully!");
67+
toast.success("Joined room successfully!");
68+
// Optional: Stop preview stream before entering actual call
69+
6770

6871
// 🛑 SAFE STOP (prevents getTracks() crash)
6972
mediaStream.getTracks().forEach((track) => track.stop());
7073

7174
navigate(`/room/${roomName}`);
7275
} catch (err: any) {
7376
console.error("Join room error:", err);
74-
alert(err.response?.data?.message || err.message || "Failed to join room.");
77+
toast.error(err.response?.data?.message || err.message || "Failed to join room.");
7578
setShowPreview(false);
7679
} finally {
7780
setLoading(false);
@@ -116,3 +119,4 @@ export default function JoinRoom() {
116119
</div>
117120
);
118121
}
122+

0 commit comments

Comments
 (0)