atri.dad/islands/Chat.tsx
Atridad Lahiji 871000c333
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m9s
Added a weird little chat for shits and giggles
2025-04-26 01:34:49 -06:00

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>
);
}