"use client" import { FC, useEffect, useRef, useState } from "react" import { Socket } from "socket.io-client" import { ClientToServerEvents, ServerToClientEvents } from "../../lib/socket" type ChatMessage = { id: string userId: string name: string text: string ts: number } interface Props { socket: Socket className?: string } // Audio notification constants const NOTIFICATION_FREQUENCY = 800 const NOTIFICATION_VOLUME = 0.3 const NOTIFICATION_VOLUME_END = 0.01 const NOTIFICATION_DURATION = 0.1 // Reusable audio context for notifications let audioContext: AudioContext | null = null const getAudioContext = () => { if (!audioContext) { audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() } return audioContext } const ChatPanel: FC = ({ socket, className }) => { const [messages, _setMessages] = useState([]) const [text, setText] = useState("") const messagesRef = useRef(messages) const setMessages = (m: ChatMessage[]) => { messagesRef.current = m _setMessages(m) } useEffect(() => { const onHistory = (history: ChatMessage[]) => { setMessages(history) } const onNew = (msg: ChatMessage) => { setMessages([...messagesRef.current, msg].slice(-200)) // Play notification sound using Web Audio API try { const ctx = getAudioContext() const oscillator = ctx.createOscillator() const gainNode = ctx.createGain() oscillator.connect(gainNode) gainNode.connect(ctx.destination) oscillator.frequency.value = NOTIFICATION_FREQUENCY oscillator.type = 'sine' gainNode.gain.setValueAtTime(NOTIFICATION_VOLUME, ctx.currentTime) gainNode.gain.exponentialRampToValueAtTime(NOTIFICATION_VOLUME_END, ctx.currentTime + NOTIFICATION_DURATION) oscillator.start(ctx.currentTime) oscillator.stop(ctx.currentTime + NOTIFICATION_DURATION) } catch (err) { console.log("Audio notification failed:", err) } } socket.on("chatHistory", onHistory) socket.on("chatNew", onNew) return () => { socket.off("chatHistory", onHistory) socket.off("chatNew", onNew) } }, [socket]) const send = () => { const trimmed = text.trim() if (!trimmed) return socket.emit("chatMessage", trimmed) setText("") } return (
{messages.length === 0 ? (
No messages yet. Be the first to say hello! 👋
) : ( // Render messages in reverse order without creating a new array messages.map((_, idx) => { const reverseIdx = messages.length - 1 - idx const msg = messages[reverseIdx] return (
{msg.name} • {new Date(msg.ts).toLocaleTimeString()}
{msg.text}
) }) )}
setText(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") send() }} />
) } export default ChatPanel