diff --git a/.gitignore b/.gitignore index 0c70516..92d0917 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ !client/.env __pycache__ .DS_Store + +flask_session/ diff --git a/client/package-lock.json b/client/package-lock.json index 894212a..45726e8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -33,6 +33,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0", + "socket.io-client": "^4.8.1", "zustand": "^4.4.1" }, "devDependencies": { @@ -843,6 +844,12 @@ "node": ">=14.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@tabler/icons": { "version": "2.47.0", "license": "MIT", @@ -1924,7 +1931,6 @@ }, "node_modules/debug": { "version": "4.3.4", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -2024,6 +2030,28 @@ "dev": true, "license": "MIT" }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/entities": { "version": "4.5.0", "license": "BSD-2-Clause", @@ -3095,7 +3123,6 @@ }, "node_modules/ms": { "version": "2.1.2", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -4118,6 +4145,34 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "dev": true, @@ -4857,6 +4912,35 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "dev": true, diff --git a/client/package.json b/client/package.json index fbead0d..ede357a 100644 --- a/client/package.json +++ b/client/package.json @@ -35,6 +35,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0", + "socket.io-client": "^4.8.1", "zustand": "^4.4.1" }, "devDependencies": { diff --git a/client/src/App.tsx b/client/src/App.tsx index b2548b6..83aa77c 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -18,10 +18,13 @@ import ProfilePage from "./routes/profile"; import TicketPage from "./routes/ticket"; import QueuePage from "./routes/queue"; import HomePage from "./routes/home"; +import Chat from "./routes/chat"; +import ChatRoom from "./routes/chatRoom"; import Leaderboard from "./routes/leaderboard"; import AdminPanel from "./routes/admin"; import HeaderNav from "./components/header"; +// TODO: FIX CHATROOM THING I DONT KNOW IF WE EVEN SUPPOSED TO HAVE ONE const router = createBrowserRouter( createRoutesFromElements( } /> } /> } /> + } /> + } /> } /> ) diff --git a/client/src/routes/chat.module.css b/client/src/routes/chat.module.css new file mode 100644 index 0000000..954c39e --- /dev/null +++ b/client/src/routes/chat.module.css @@ -0,0 +1,162 @@ +/* General Styles */ + body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + } + + .content { + background-color: #fff; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 20px; + width: 100%; + max-width: 400px; + text-align: center; + } + + /* Form Styles */ + .buttons { + display: flex; + flex-direction: column; + gap: 15px; + } + + .buttons h3 { + margin-bottom: 20px; + font-size: 1.5rem; + color: #333; + } + + .buttons label { + font-weight: bold; + color: #555; + } + + .buttons input[type="text"] { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 1rem; + margin-top: 5px; + } + + .buttons .join { + display: flex; + gap: 10px; + } + + .buttons .join input[type="text"] { + flex: 1; + } + + .buttons button { + padding: 10px 15px; + border: none; + border-radius: 4px; + background-color: #007bff; + color: #fff; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.3s ease; + } + + .buttons button:hover { + background-color: #0056b3; + } + + .buttons .create-btn { + background-color: #28a745; + } + + .buttons .create-btn:hover { + background-color: #218838; + } + + .buttons ul { + list-style: none; + padding: 0; + margin: 10px 0; + color: #dc3545; + } + + /* Chat Room Styles */ + .messageBox { + display: flex; + flex-direction: column; + gap: 15px; + margin-top: 20px; + color: #333; + } + + .messageBox h2 { + font-size: 1.25rem; + color: #333; + margin-bottom: 10px; + } + + .messages { + height: 300px; + overflow-y: auto; + border: 1px solid #ccc; + border-radius: 4px; + padding: 10px; + background-color: #f9f9f9; + /* + background-color: #f9f9f9; + color: #007bff; + background-color: #666; + */ + } + + .messages .text { + margin-bottom: 10px; + padding: 8px; + background-color: #e9ecef; + /* background-color: #007bff; Change to blue */ + color: #333; /* Ensure text is white for better contrast */ + border-radius: 4px; + } + + .messages .text span { + display: block; + } + + .messages .text strong { + color: #007bff; + } + + .messages .text .muted { + font-size: 0.875rem; + color: #666; + } + + .inputs { + display: flex; + gap: 10px; + } + + .inputs input[type="text"] { + flex: 1; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 1rem; + } + + .inputs button { + padding: 10px 15px; + border: none; + border-radius: 4px; + background-color: #007bff; + color: #fff; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.3s ease; + } + + .inputs button:hover { + background-color: #0056b3; + } \ No newline at end of file diff --git a/client/src/routes/chat.tsx b/client/src/routes/chat.tsx new file mode 100644 index 0000000..503e7d0 --- /dev/null +++ b/client/src/routes/chat.tsx @@ -0,0 +1,80 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import styles from "./chat.module.css"; + +export default function Chat() { + const [name, setName] = useState(""); + const [code, setCode] = useState(""); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + const handleJoinRoom = (e: React.FormEvent) => { + e.preventDefault(); + if (!name || !code) { + setError("Please enter a name and room code."); + return; + } + // TODO: use ChatRoom model, you will need to call an API endpoint here + // TODO: create an API endpoint in chat.py + // check that the code is existing in the database (if not error) + // if code is existing, check that the room is active (if not error) + // if room is active, check that the room is not full (if full error) + // if user is added to room, navigate to the room + // navigate to "/room/${code}" also change in App.tsx + navigate(`/room?name=${encodeURIComponent(name)}&code=${encodeURIComponent(code)}`); + }; + + const handleCreateRoom = (e: React.FormEvent) => { + e.preventDefault(); + if (!name) { + setError("Please enter a name."); + return; + } + // TODO: call api endpoint / create it + // need to check that randomly generated code does not exist in datbase + // if it does, generate a new one until it does not exist in the database + // if it does not exist, create the room in the database + // if room is created, navigate to the room + // navigate to "/room/${code}" also change in App.tsx + const newCode = Math.random().toString(36).substring(2, 6).toUpperCase(); + navigate(`/room?name=${encodeURIComponent(name)}&code=${encodeURIComponent(newCode)}`); + }; + + return ( +
+
+

Enter The Chat Room

+
+ + setName(e.target.value)} + /> +
+
+ setCode(e.target.value)} + /> + +
+ + {error && ( +
    +
  • {error}
  • +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/client/src/routes/chatRoom.tsx b/client/src/routes/chatRoom.tsx new file mode 100644 index 0000000..cb6d853 --- /dev/null +++ b/client/src/routes/chatRoom.tsx @@ -0,0 +1,85 @@ +import React, { useState, useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import io from "socket.io-client"; +import styles from "./chat.module.css"; + + +const socket = io("http://127.0.0.1:3001", { transports: ["polling", "websocket"] }); + +export default function ChatRoom() { + const location = useLocation(); + const navigate = useNavigate(); + const queryParams = new URLSearchParams(location.search); + const name = queryParams.get("name") || ""; + // TODO: get code from the url getParams + const code = queryParams.get("code") || ""; + + const [messages, setMessages] = useState<{ name: string; message: string }[]>([]); + const [message, setMessage] = useState(""); + + useEffect(() => { + if (!name || !code) { + navigate("/"); + return; + } + + socket.emit("join", { name, code }); + + socket.on("message", (data: { name: string; message: string }) => { + console.log("new message:", data) + setMessages((prevMessages) => [...prevMessages, data]); + }); + + return () => { + socket.off("message"); + socket.emit("leave", { name, code }); + }; + }, [name, code, navigate]); + + const sendMessage = () => { + if (message.trim() === "") return; + socket.emit("message", { data: message }); + setMessage(""); + }; + + return ( +
+
+

Chat Room: {code}

+ {/* add a message that says chat not saved, if they refresh ask are you sure you will lose your chat msg */} +
+ {/* Temporary hardcoded message for debugging */} +
+ + Test User: This is a test message + + {/* TODO: check out date problem */} + {new Date().toLocaleString()} +
+ + {messages.map((msg, index) => ( +
+ + {msg.name}: {msg.message} + + {new Date().toLocaleString()} +
+ ))} +
+
+