All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m9s
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import { useEffect, useState } from "preact/hooks";
|
|
|
|
interface ChatMessage {
|
|
text: string;
|
|
sender: string;
|
|
timestamp: string;
|
|
}
|
|
|
|
export default function Chat() {
|
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
const [newMessage, setNewMessage] = useState("");
|
|
const [username, setUsername] = useState("");
|
|
const [socket, setSocket] = useState<WebSocket | null>(null);
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
const [userCount, setUserCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (!username) {
|
|
const randomNum = Math.floor(Math.random() * 10000);
|
|
setUsername(`User${randomNum}`);
|
|
}
|
|
|
|
const ws = new WebSocket(`ws://${globalThis.location.host}/api/chat`);
|
|
|
|
ws.onopen = () => {
|
|
console.log("Connected to chat");
|
|
setIsConnected(true);
|
|
setSocket(ws);
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === "user_count") {
|
|
setUserCount(data.count);
|
|
} else {
|
|
setMessages((prev) => [...prev, data]);
|
|
|
|
const chatBox = document.getElementById("chat-messages");
|
|
if (chatBox) {
|
|
setTimeout(() => {
|
|
chatBox.scrollTop = chatBox.scrollHeight;
|
|
}, 50);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("Error processing message:", err);
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log("Disconnected from chat");
|
|
setIsConnected(false);
|
|
};
|
|
|
|
return () => {
|
|
ws.close();
|
|
};
|
|
}, []);
|
|
|
|
const sendMessage = (e: Event) => {
|
|
e.preventDefault();
|
|
if (!newMessage.trim() || !socket) return;
|
|
|
|
const messageData = {
|
|
text: newMessage,
|
|
sender: username,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
socket.send(JSON.stringify(messageData));
|
|
setNewMessage("");
|
|
};
|
|
|
|
return (
|
|
<div class="w-full max-w-4xl mx-auto bg-dark rounded-lg shadow-lg overflow-hidden border border-gray-800">
|
|
<div class="p-4 bg-secondary text-white">
|
|
<h2 class="text-xl font-bold">Live Chat</h2>
|
|
<p class="text-sm opacity-80">
|
|
{isConnected
|
|
? `${userCount} online • Messages are not saved`
|
|
: "Connecting..."}
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
id="chat-messages"
|
|
class="p-4 h-96 overflow-y-auto bg-dark text-gray-300"
|
|
>
|
|
{messages.length === 0
|
|
? (
|
|
<p class="text-center text-gray-500 py-8">
|
|
No messages yet. Be the first to chat!
|
|
</p>
|
|
)
|
|
: (
|
|
messages.map((msg, i) => (
|
|
<div
|
|
key={i}
|
|
class={`mb-3 max-w-md ${
|
|
msg.sender === username ? "ml-auto" : ""
|
|
}`}
|
|
>
|
|
<div
|
|
class={`px-4 py-2 rounded-lg ${
|
|
msg.sender === username
|
|
? "bg-secondary text-white rounded-br-none"
|
|
: "bg-gray-800 text-gray-200 rounded-bl-none"
|
|
}`}
|
|
>
|
|
<div class="flex justify-between items-baseline mb-1">
|
|
<span class="font-bold text-sm">
|
|
{msg.sender === username ? "You" : msg.sender}
|
|
</span>
|
|
<span class="text-xs opacity-70">
|
|
{new Date(msg.timestamp).toLocaleTimeString([], {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})}
|
|
</span>
|
|
</div>
|
|
<p>{msg.text}</p>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
|
|
<form onSubmit={sendMessage} class="p-4 border-t border-gray-800">
|
|
<div class="flex">
|
|
<input
|
|
type="text"
|
|
value={newMessage}
|
|
onChange={(e) => setNewMessage(e.currentTarget.value)}
|
|
placeholder="Type your message..."
|
|
class="flex-1 px-4 py-3 bg-gray-800 text-white rounded-l-lg border-0 focus:outline-none focus:ring-2 focus:ring-secondary placeholder-gray-500"
|
|
disabled={!isConnected}
|
|
maxLength={2000}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
class="bg-secondary text-white px-8 py-3 rounded-r-lg hover:bg-opacity-90 focus:outline-none focus:ring-2 focus:ring-secondary font-medium"
|
|
disabled={!isConnected || !newMessage.trim()}
|
|
>
|
|
Send
|
|
</button>
|
|
</div>
|
|
<p class="mt-2 text-xs text-gray-500">
|
|
You are chatting as{" "}
|
|
<span class="font-medium text-gray-400">{username}</span>
|
|
</p>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|