diff --git a/react-ystemandchess/src/App.tsx b/react-ystemandchess/src/App.tsx index e90ebae8..d6194791 100644 --- a/react-ystemandchess/src/App.tsx +++ b/react-ystemandchess/src/App.tsx @@ -1,6 +1,6 @@ /** * Main Application Component - * + * * This is the root component of the React application that sets up the overall * structure, routing, and global functionality like user time tracking. * It manages the application layout and handles user session monitoring. @@ -15,35 +15,36 @@ import { SetPermissionLevel } from "./globals"; import NavBar from "./components/navbar/NavBar"; import Footer from "./components/footer/Footer"; import AppRoutes from "./AppRoutes"; +import Chatbot from "./components/chatbot/ChatbotTest"; /** * Main App component that serves as the root of the application - * + * * This component handles: * - Application routing setup * - User time tracking for analytics * - Global layout structure (NavBar, Content, Footer) * - User session management - * + * * @returns JSX element representing the entire application */ function App() { // Hook to manage browser cookies, specifically the login token const [cookies] = useCookies(["login"]); - + // Variables to track user session and time spent on website // These are used for analytics and user engagement tracking - let username = null; // Stores the authenticated username - let eventID = null; // Unique identifier for this browsing session - let startTime = null; // Timestamp when user started browsing + let username = null; // Stores the authenticated username + let eventID = null; // Unique identifier for this browsing session + let startTime = null; // Timestamp when user started browsing /** * Initiates time tracking for authenticated users - * + * * This function checks if a user is logged in and starts recording * their browsing session for analytics purposes. It creates a new * time tracking event in the backend and stores the session details. - * + * * @returns Promise - Resolves when recording setup is complete */ async function startRecording() { @@ -52,7 +53,7 @@ function App() { // Exit early if user is not authenticated - no tracking for anonymous users if (uInfo.error) return; - + // Store the authenticated username for tracking purposes username = uInfo.username; @@ -62,25 +63,25 @@ function App() { { method: "POST", headers: { Authorization: `Bearer ${cookies.login}` }, - } + }, ); - + // Log any HTTP errors for debugging purposes if (response.status !== 200) console.log(response); // Extract session data from successful response const data = await response.json(); - eventID = data.eventId; // Store event ID for later updates - startTime = data.startTime; // Store start time for duration calculation + eventID = data.eventId; // Store event ID for later updates + startTime = data.startTime; // Store start time for duration calculation } /** * Handles cleanup when user exits the website - * + * * This function is called when the user navigates away from the site * or closes the browser tab. It calculates the total time spent and * updates the backend with the final session duration. - * + * * @returns Promise - Resolves when cleanup is complete */ const handleUnload = async () => { @@ -88,10 +89,10 @@ function App() { // Calculate the total time spent on the website const startDate = new Date(startTime); const endDate = new Date(); - + // Calculate time difference in milliseconds const diffInMs = endDate.getTime() - startDate.getTime(); - + // Convert to seconds for backend storage const diffInSeconds = Math.floor(diffInMs / 1000); @@ -101,9 +102,9 @@ function App() { { method: "PUT", headers: { Authorization: `Bearer ${cookies.login}` }, - } + }, ); - + // Log any errors that occur during the update if (response.status !== 200) console.log(response); } catch (err) { @@ -114,7 +115,7 @@ function App() { /** * Effect hook for component initialization and cleanup - * + * * This effect runs once when the component mounts and sets up: * - Initial time tracking for authenticated users * - Event listener for browser unload to handle cleanup @@ -131,17 +132,17 @@ function App() { // Add event listener to handle user leaving the website window.addEventListener("beforeunload", handleUnload); - + // Cleanup function to remove event listeners when component unmounts return () => { window.removeEventListener("beforeunload", handleUnload); }; - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Empty dependency array ensures this runs only once on mount /** * Render the main application structure - * + * * The application consists of: * - Router: Enables client-side routing * - NavBar: Top navigation component @@ -153,6 +154,7 @@ function App() {
+
diff --git a/react-ystemandchess/src/components/chatbot/Chatbot.css b/react-ystemandchess/src/components/chatbot/Chatbot.css new file mode 100644 index 00000000..db7a2e1b --- /dev/null +++ b/react-ystemandchess/src/components/chatbot/Chatbot.css @@ -0,0 +1,244 @@ +/* Chatbot Toggle Button - Bottom Right */ +.chatbot-toggle { + position: fixed; + bottom: 20px; + right: 20px; + width: 60px; + height: 60px; + border-radius: 50%; + background: #ead74c; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; + z-index: 1000; +} + +.chatbot-toggle:hover { + transform: scale(1.1); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); + background: #ffd233; +} + +.chatbot-toggle svg { + width: 28px; + height: 28px; + color: #221f20; +} + +/* Chatbot Window */ +.chatbot-window { + position: fixed; + bottom: 90px; + right: 20px; + width: 380px; + height: 500px; + background: white; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + opacity: 0; + transform: scale(0.8) translateY(20px); + pointer-events: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 999; + overflow: hidden; +} + +.chatbot-window.open { + opacity: 1; + transform: scale(1) translateY(0); + pointer-events: all; +} + +/* Header */ +.chatbot-header { + background: #c0df9e; + color: #221f20; + padding: 16px 20px; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 12px 12px 0 0; +} + +.chatbot-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.close-button { + background: none; + border: none; + color: #221f20; + font-size: 24px; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background 0.2s ease; +} + +.close-button:hover { + background: rgba(0, 0, 0, 0.1); +} + +/* Messages Container */ +.chatbot-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + background: #f7f9fc; + display: flex; + flex-direction: column; + gap: 12px; +} + +.chatbot-messages::-webkit-scrollbar { + width: 6px; +} + +.chatbot-messages::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.chatbot-messages::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 3px; +} + +.chatbot-messages::-webkit-scrollbar-thumb:hover { + background: #a1a1a1; +} + +/* Message Bubbles */ +.message { + display: flex; + flex-direction: column; + max-width: 75%; + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.user-message { + align-self: flex-end; +} + +.bot-message { + align-self: flex-start; +} + +.message-content { + padding: 12px 16px; + border-radius: 18px; + word-wrap: break-word; + line-height: 1.4; +} + +.user-message .message-content { + background: #ead74c; + color: #221f20; + border-bottom-right-radius: 4px; +} + +.bot-message .message-content { + background: white; + color: #333; + border: 1px solid #e1e4e8; + border-bottom-left-radius: 4px; +} + +.message-timestamp { + font-size: 11px; + color: #999; + margin-top: 4px; + padding: 0 8px; +} + +.user-message .message-timestamp { + text-align: right; +} + +/* Input Form */ +.chatbot-input-form { + display: flex; + gap: 8px; + padding: 16px; + background: white; + border-top: 1px solid #e1e4e8; +} + +.chatbot-input { + flex: 1; + padding: 12px 16px; + border: 1px solid #e1e4e8; + border-radius: 24px; + font-size: 14px; + outline: none; + transition: border-color 0.2s ease; +} + +.chatbot-input:focus { + border-color: #ead74c; +} + +.send-button { + padding: 12px 24px; + background: #ead74c; + color: #221f20; + border: none; + border-radius: 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.send-button:hover { + transform: scale(1.05); + background: #ffd233; +} + +.send-button:active { + transform: scale(0.95); +} + +/* Responsive Design */ +@media (max-width: 480px) { + .chatbot-window { + width: calc(100vw - 40px); + height: calc(100vh - 140px); + bottom: 90px; + right: 20px; + left: 20px; + } + + .chatbot-toggle { + width: 56px; + height: 56px; + } + + .chatbot-toggle svg { + width: 24px; + height: 24px; + } +} diff --git a/react-ystemandchess/src/components/chatbot/ChatbotTest.tsx b/react-ystemandchess/src/components/chatbot/ChatbotTest.tsx new file mode 100644 index 00000000..2c1138c1 --- /dev/null +++ b/react-ystemandchess/src/components/chatbot/ChatbotTest.tsx @@ -0,0 +1,125 @@ +import React, { useState, useRef, useEffect } from "react"; +import "./Chatbot.css"; + +interface Message { + id: number; + text: string; + sender: "user" | "bot"; + timestamp: Date; +} + +const Chatbot: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [messages, setMessages] = useState([ + { + id: 1, + text: "Hi! How can I help you today?", + sender: "bot", + timestamp: new Date(), + }, + ]); + const [inputValue, setInputValue] = useState(""); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleToggle = () => { + setIsOpen(!isOpen); + }; + + const handleSendMessage = (e: React.FormEvent) => { + e.preventDefault(); + + if (inputValue.trim() === "") return; + + // Add user message + const userMessage: Message = { + id: messages.length + 1, + text: inputValue, + sender: "user", + timestamp: new Date(), + }; + + setMessages([...messages, userMessage]); + setInputValue(""); + + // Simulate bot response + setTimeout(() => { + const botMessage: Message = { + id: messages.length + 2, + text: "Thanks for your message! This is a demo response.", + sender: "bot", + timestamp: new Date(), + }; + setMessages((prev) => [...prev, botMessage]); + }, 1000); + }; + + return ( + <> + {/* Chat Window */} +
+
+

Chat Support

+ +
+ +
+ {messages.map((message) => ( +
+
{message.text}
+
+ {message.timestamp.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} +
+
+ ))} +
+
+ +
+ setInputValue(e.target.value)} + /> + +
+
+ + {/* Toggle Button */} + + + ); +}; + +export default Chatbot;