import React, { useState, useEffect, useRef } from 'react'; import Editor from "@monaco-editor/react"; import { Play, Send } from 'lucide-react'; const CodeChallenge = () => { const [activeQuestion, setActiveQuestion] = useState("Q.1"); const [language, setLanguage] = useState("JavaScript"); const [code, setCode] = useState(""); const [isRunning, setIsRunning] = useState(false); const [terminalOutput, setTerminalOutput] = useState([]); const [autoSelected, setAutoSelected] = useState(true); const [activeSocket, setActiveSocket] = useState(null); const [submissionId, setSubmissionId] = useState(null); const socketRef = useRef(null); // Map frontend language names to backend language identifiers const getLanguageIdentifier = (uiLanguage) => { const languageMap = { 'javascript': 'javascript', 'python': 'python', 'java': 'java', 'c++': 'cpp', 'c': 'c' }; // Important: make sure we convert to lowercase to match the backend's expected format return languageMap[uiLanguage.toLowerCase()] || uiLanguage.toLowerCase(); }; // Reset execution state to allow rerunning const resetExecutionState = () => { setIsRunning(false); // Properly close the socket if it exists and is open if (socketRef.current) { if (socketRef.current.readyState === WebSocket.OPEN) { socketRef.current.close(); } socketRef.current = null; } // Ensure activeSocket is also nullified setActiveSocket(null); console.log('Execution state reset, buttons should be enabled'); }; // Example problem data const problems = { "Q.1": { id: "two-sum", title: "Two Sum", description: "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.", constraints: "You may assume that each input would have exactly one solution, and you may not use the same element twice.", examples: [ { input: "nums = [2,7,11,15], target = 9", output: "[0,1]", explanation: "Because nums[0] + nums[1] == 9, we return [0, 1]." }, { input: "nums = [3,2,4], target = 6", output: "[1,2]" }, { input: "nums = [3,3], target = 6", output: "[0,1]" } ], starterCode: `/** * @param {number[]} nums * @param {number} target * @return {number[]} */ var twoSum = function(nums, target) { // Write your solution here };` }, "Q.2": { id: "palindrome-number", title: "Palindrome Number", description: "Given an integer x, return true if x is a palindrome, and false otherwise.", examples: [ { input: "x = 121", output: "true" }, { input: "x = -121", output: "false", explanation: "From left to right, it reads -121. From right to left, it reads 121-. Therefore it is not a palindrome." } ], starterCode: `/** * @param {number} x * @return {boolean} */ var isPalindrome = function(x) { // Write your solution here };` }, "Q.3": { id: "valid-parentheses", title: "Valid Parentheses", description: "Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.", constraints: "An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order.", examples: [ { input: 's = "()"', output: "true" }, { input: 's = "()[]{}"', output: "true" }, { input: 's = "(]"', output: "false" } ], starterCode: `/** * @param {string} s * @return {boolean} */ var isValid = function(s) { // Write your solution here };` } }; // Get appropriate starter code based on language const getStarterCode = (problem, lang) => { // Language-specific starter code templates const templates = { 'JavaScript': problem.starterCode, 'C': `#include #include #include // ${problem.title} solution int main() { // Write your solution here return 0; }`, 'Python': `# ${problem.title} def solution(): # Write your solution here # Use input() for user input in Python # Example: name = input("Enter your name: ") pass if __name__ == "__main__": solution()`, 'Java': `public class Solution { // ${problem.title} public static void main(String[] args) { // Write your solution here } }`, 'C++': `#include #include using namespace std; // ${problem.title} solution int main() { // Write your solution here return 0; }` }; return templates[lang] || problem.starterCode; }; // Set initial code based on active problem useEffect(() => { if (problems[activeQuestion]) { setCode(getStarterCode(problems[activeQuestion], language)); } }, [activeQuestion, language]); // Cleanup WebSocket connection on unmount useEffect(() => { return () => { if (socketRef.current) { socketRef.current.close(); } }; }, []); // Set a safety timeout to ensure buttons are re-enabled if execution hangs useEffect(() => { let safetyTimer = null; if (isRunning) { // If execution is running for more than 30 seconds, reset state safetyTimer = setTimeout(() => { console.log('Safety timeout reached, re-enabling buttons'); resetExecutionState(); }, 30000); } return () => { if (safetyTimer) clearTimeout(safetyTimer); }; }, [isRunning]); // Connect to WebSocket const connectToWebSocket = (id) => { console.log('Connecting to WebSocket with ID:', id); // Force close any existing connections if (socketRef.current) { console.log('Closing existing socket, state:', socketRef.current.readyState); socketRef.current.onclose = null; // Remove existing handler to avoid conflicts socketRef.current.onerror = null; socketRef.current.onmessage = null; if (socketRef.current.readyState !== WebSocket.CLOSED) { socketRef.current.close(); } socketRef.current = null; } if (activeSocket) { console.log('Clearing active socket reference'); setActiveSocket(null); } console.log('Creating new WebSocket connection'); const wsUrl = `ws://localhost:8080/api/ws/terminal/${id}`; const socket = new WebSocket(wsUrl); socket.onopen = () => { console.log('WebSocket connection established'); setActiveSocket(socket); }; socket.onmessage = (event) => { try { const message = JSON.parse(event.data); console.log('WebSocket message received:', message, 'Current isRunning state:', isRunning); switch (message.type) { case 'output': // Handle output message based on the format seen in the screenshot setTerminalOutput(prev => [ ...prev, { type: message.content.isError ? 'error' : 'output', content: message.content.text } ]); break; case 'input_prompt': // Handle input prompt message (e.g., "Enter your name:") setTerminalOutput(prev => [ ...prev, { type: 'output', content: message.content } ]); break; case 'status': let statusText = ''; let statusValue = ''; if (typeof message.content === 'object') { statusText = `Status: ${message.content.status}`; statusValue = message.content.status; } else { statusText = `Status: ${message.content}`; statusValue = message.content; } setTerminalOutput(prev => [ ...prev, { type: 'system', content: statusText } ]); // If status contains "completed" or "failed", stop running if (statusValue.includes('completed') || statusValue.includes('failed')) { console.log('Execution completed or failed, stopping'); setTimeout(() => { setIsRunning(false); }, 500); // Small delay to ensure UI updates properly } break; case 'error': let errorContent = ''; if (typeof message.content === 'object' && message.content.message) { errorContent = message.content.message; } else { errorContent = String(message.content); } setTerminalOutput(prev => [ ...prev, { type: 'error', content: errorContent } ]); console.log('Error received, enabling buttons'); setTimeout(() => { setIsRunning(false); }, 500); // Small delay to ensure UI updates properly break; case 'system': const systemContent = String(message.content); setTerminalOutput(prev => [ ...prev, { type: 'system', content: systemContent } ]); // Check for connection closing message which indicates execution is complete if (systemContent.includes('Connection will close') || systemContent.includes('completed successfully') || systemContent.includes('Execution completed')) { console.log('System message indicates completion, enabling buttons'); setTimeout(() => { setIsRunning(false); }, 500); } break; default: // Handle any other message types or direct string content console.log('Unknown message type:', message); if (typeof message === 'object') { setTerminalOutput(prev => [ ...prev, { type: 'output', content: JSON.stringify(message) } ]); } } } catch (error) { console.error('Error parsing WebSocket message:', error); } }; socket.onerror = (error) => { console.error('WebSocket error:', error); setTerminalOutput(prev => [ ...prev, { type: 'error', content: 'WebSocket connection error' } ]); console.log('WebSocket error, enabling buttons'); setTimeout(() => { setIsRunning(false); }, 500); // Small delay to ensure UI updates properly }; socket.onclose = () => { console.log('WebSocket connection closed'); setActiveSocket(null); // Ensure buttons are re-enabled when the connection closes setTimeout(() => { resetExecutionState(); }, 100); }; // Set the socket reference early to ensure we can clean it up if needed socketRef.current = socket; return socket; }; // Handle code execution const runCode = async () => { console.log('Run button clicked, current state:', { isRunning, socketState: activeSocket ? activeSocket.readyState : 'no socket', socketRefState: socketRef.current ? socketRef.current.readyState : 'no socket ref' }); // First make sure previous connections are fully closed resetExecutionState(); // Increase the delay to ensure clean state before starting new execution setTimeout(async () => { // Double-check socket state before proceeding if (activeSocket || socketRef.current) { console.warn('Socket still exists after reset, forcing cleanup'); if (activeSocket && activeSocket.readyState !== WebSocket.CLOSED) { activeSocket.close(); } if (socketRef.current && socketRef.current.readyState !== WebSocket.CLOSED) { socketRef.current.close(); } socketRef.current = null; setActiveSocket(null); // Extra delay to ensure socket is fully closed await new Promise(resolve => setTimeout(resolve, 100)); } setIsRunning(true); setTerminalOutput([ { type: 'system', content: `Running ${problems[activeQuestion].id}...` } ]); try { // Submit code to the backend const response = await fetch('http://localhost:8080/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code: code, language: getLanguageIdentifier(language), input: '', }), }); if (!response.ok) { throw new Error(`Error: ${response.statusText}`); } const data = await response.json(); setSubmissionId(data.id); // Connect to WebSocket for real-time updates connectToWebSocket(data.id); } catch (error) { console.error('Error submitting code:', error); setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Error: ${error.message}` } ]); resetExecutionState(); } }, 200); // Increased delay to ensure clean state }; // Handle code submission const submitCode = async () => { setIsRunning(true); setTerminalOutput([ { type: 'system', content: `Submitting solution for ${problems[activeQuestion].id}...` } ]); try { // Submit code to the backend const response = await fetch('http://localhost:8080/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code: code, language: getLanguageIdentifier(language), input: '', problemId: problems[activeQuestion].id }), }); if (!response.ok) { throw new Error(`Error: ${response.statusText}`); } const data = await response.json(); setSubmissionId(data.id); // Connect to WebSocket for real-time updates connectToWebSocket(data.id); } catch (error) { console.error('Error submitting solution:', error); setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Error: ${error.message}` } ]); setIsRunning(false); } }; // Render the current problem const renderProblem = () => { const problem = problems[activeQuestion]; if (!problem) return null; return (

{problem.title}

{problem.description}

{problem.constraints &&

{problem.constraints}

}
{/* Test cases section removed */}
); }; // Add this useEffect to monitor socket state useEffect(() => { // If we have an active socket but aren't running, we should clean up if (activeSocket && !isRunning) { console.log('Cleaning up inactive socket'); if (activeSocket.readyState === WebSocket.OPEN) { activeSocket.close(); } setActiveSocket(null); } }, [activeSocket, isRunning]); return (

OnScreen Test

{/*

1. {problems["Q.1"].title}

*/}
{renderProblem()}
setCode(value)} theme="hc-black" options={{ fontSize: 14, minimap: { enabled: false }, scrollBeyondLastLine: false, automaticLayout: true, }} />
Terminal {/*
*/}
{terminalOutput.map((line, index) => (
{line.content}
))}
$ { // Auto-focus input when isRunning changes to true if (inputEl && isRunning) { inputEl.focus(); // Clear any previous input inputEl.value = ''; } }} onKeyDown={(e) => { // Change from onKeyPress to onKeyDown for better cross-browser support if (e.key === 'Enter') { e.preventDefault(); // Prevent default to avoid form submissions const input = e.target.value.trim(); if (!input) return; // Skip empty input if (activeSocket && activeSocket.readyState === WebSocket.OPEN) { try { // Send input to server activeSocket.send(JSON.stringify({ "type": "input", "content": input })); // Add input to terminal output setTerminalOutput(prev => [ ...prev, { type: 'system', content: `$ ${input}` } ]); // Clear the input field e.target.value = ''; } catch (error) { console.error("Error sending input:", error); setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Failed to send input: ${error.message}` } ]); } } else { // Better error message with socket state information const socketState = activeSocket ? ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][activeSocket.readyState] : 'NO_SOCKET'; console.log(`Cannot send input: Socket state is ${socketState}`); setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Cannot send input: connection not available (${socketState})` } ]); } } }} onKeyPress={(e) => { if (e.key === 'Enter' && activeSocket && activeSocket.readyState === WebSocket.OPEN) { const input = e.target.value; // Send input to WebSocket with the correct format try { activeSocket.send(JSON.stringify({ "type": "input", "content": input })); // Add input to terminal output setTerminalOutput(prev => [ ...prev, { type: 'system', content: `$ ${input}` } ]); // Clear the input field e.target.value = ''; } catch (error) { console.error("Error sending input:", error); setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Failed to send input: ${error.message}` } ]); } } else if (e.key === 'Enter') { // Inform user if socket isn't available if (!activeSocket || activeSocket.readyState !== WebSocket.OPEN) { setTerminalOutput(prev => [ ...prev, { type: 'error', content: `Cannot send input: connection closed` } ]); } } }} />
); }; export default CodeChallenge;