From e12bbcfc6ad642bb4cf5e774b9366b01760c010f Mon Sep 17 00:00:00 2001 From: ishikabhoyar Date: Tue, 22 Jul 2025 15:26:54 +0530 Subject: [PATCH] Implement Code Challenge component with styling and WebSocket integration --- Frontend/src/App.jsx | 4 +- Frontend/src/components/CodeChallenge.css | 282 ++++++++++++++ Frontend/src/components/CodeChallenge.jsx | 451 ++++++++++++++++++++++ Frontend/src/index.css | 282 ++++++++++++++ 4 files changed, 1017 insertions(+), 2 deletions(-) create mode 100644 Frontend/src/components/CodeChallenge.css create mode 100644 Frontend/src/components/CodeChallenge.jsx diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 8f8699c..b19fb9e 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -1,10 +1,10 @@ -import VSCodeUI from "./components/VSCodeUI.jsx" +import CodeChallenge from "./components/CodeChallenge.jsx" import "./index.css" function App() { return (
- +
) } diff --git a/Frontend/src/components/CodeChallenge.css b/Frontend/src/components/CodeChallenge.css new file mode 100644 index 0000000..5eaf7e9 --- /dev/null +++ b/Frontend/src/components/CodeChallenge.css @@ -0,0 +1,282 @@ +/* Code Challenge Component Styles */ +.code-challenge-container { + display: flex; + flex-direction: column; + height: 100vh; + background-color: #1e1e1e; + color: #d4d4d4; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +.code-challenge-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: #1e1e1e; + border-bottom: 1px solid #333; +} + +.code-challenge-header h1 { + margin: 0; + font-size: 18px; + font-weight: 500; +} + +.sign-in-btn { + background-color: #333; + color: #fff; + border: none; + padding: 5px 10px; + border-radius: 3px; + cursor: pointer; +} + +.sign-in-btn:hover { + background-color: #444; +} + +.code-challenge-problem-nav { + padding: 10px 20px; + border-bottom: 1px solid #333; +} + +.problem-number { + margin: 0; + font-size: 16px; + font-weight: 500; +} + +.code-challenge-main { + display: flex; + flex-direction: row; + height: calc(70vh - 120px); + overflow: hidden; +} + +.left-panel { + display: flex; + flex-direction: column; + width: 50%; + border-right: 1px solid #333; +} + +.problem-tabs { + display: flex; + width: 100%; + height: 40px; + border-bottom: 1px solid #333; +} + +.problem-tabs button { + min-width: 120px; + padding: 0 20px; + background-color: #252526; + color: #d4d4d4; + border: none; + text-align: center; + cursor: pointer; + border-right: 1px solid #333; + font-size: 14px; +} + +.problem-tabs button.tab-active { + background-color: #1e1e1e; + border-left: 2px solid #0078d4; + color: #ffffff; +} + +.problem-tabs button:hover:not(.tab-active) { + background-color: #252526; +} + +.problem-content { + flex: 1; + overflow-y: auto; + padding: 20px; + border-right: 1px solid #333; +} + +.problem-container h1 { + margin-top: 0; + margin-bottom: 15px; + font-size: 24px; +} + +.problem-description { + margin-bottom: 20px; + line-height: 1.5; +} + +.problem-description code { + background-color: #2d2d2d; + padding: 2px 4px; + border-radius: 3px; + font-family: 'Consolas', 'Courier New', monospace; +} + +/* Removed problem examples styles */ + +.editor-section { + flex: 1; + display: flex; + flex-direction: column; + border-left: 1px solid #333; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: #252526; + border-bottom: 1px solid #333; +} + +.editor-controls { + display: flex; + align-items: center; +} + +.language-selector { + background-color: #2d2d2d; + color: #d4d4d4; + border: 1px solid #3c3c3c; + padding: 5px 10px; + border-radius: 3px; + margin-right: 10px; +} + +.auto-btn { + background-color: #252526; + color: #d4d4d4; + border: 1px solid #3c3c3c; + padding: 5px 10px; + border-radius: 3px; + cursor: pointer; +} + +.auto-selected { + background-color: #0e639c; + border-color: #0e639c; + color: white; +} + +.editor-actions { + display: flex; + align-items: center; +} + +.run-btn { + display: flex; + align-items: center; + background-color: #252526; + color: #d4d4d4; + border: 1px solid #3c3c3c; + padding: 5px 10px; + border-radius: 3px; + margin-right: 10px; + cursor: pointer; +} + +.submit-btn { + display: flex; + align-items: center; + background-color: #0e8a16; + color: white; + border: none; + padding: 5px 10px; + border-radius: 3px; + cursor: pointer; +} + +.run-btn svg, .submit-btn svg { + margin-right: 5px; +} + +.run-btn:hover { + background-color: #2d2d2d; +} + +.submit-btn:hover { + background-color: #0ca01c; +} + +.editor-container { + flex: 1; + overflow: hidden; +} + +.terminal-section { + height: 30vh; + background-color: #1e1e1e; + border-top: 1px solid #333; +} + +.terminal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 5px 10px; + background-color: #252526; + border-bottom: 1px solid #333; +} + +.terminal-controls { + display: flex; + align-items: center; +} + +.terminal-btn { + background: none; + border: none; + color: #d4d4d4; + font-size: 12px; + cursor: pointer; + margin-left: 5px; +} + +.terminal-content { + padding: 10px; + font-family: 'Consolas', 'Courier New', monospace; + height: calc(30vh - 40px); + overflow-y: auto; +} + +.terminal-line { + margin-bottom: 5px; + word-wrap: break-word; + white-space: pre-wrap; +} + +.terminal-line.system { + color: #569cd6; +} + +.terminal-line.output { + color: #d4d4d4; +} + +.terminal-line.error { + color: #f48771; +} + +.terminal-prompt { + display: flex; + align-items: center; + margin-top: 5px; +} + +.prompt-symbol { + margin-right: 5px; + color: #569cd6; +} + +.terminal-input { + flex: 1; + background: none; + border: none; + color: #d4d4d4; + font-family: 'Consolas', 'Courier New', monospace; + outline: none; +} diff --git a/Frontend/src/components/CodeChallenge.jsx b/Frontend/src/components/CodeChallenge.jsx new file mode 100644 index 0000000..9dae839 --- /dev/null +++ b/Frontend/src/components/CodeChallenge.jsx @@ -0,0 +1,451 @@ +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); + + // 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 + +};` + } + }; + + // Set initial code based on active problem + useEffect(() => { + if (problems[activeQuestion]) { + setCode(problems[activeQuestion].starterCode); + } + }, [activeQuestion]); + + // Cleanup WebSocket connection on unmount + useEffect(() => { + return () => { + if (socketRef.current) { + socketRef.current.close(); + } + }; + }, []); + + // Connect to WebSocket + const connectToWebSocket = (id) => { + // Close existing connection if any + if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) { + socketRef.current.close(); + } + + 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); + + switch (message.type) { + case 'output': + setTerminalOutput(prev => [ + ...prev, + { + type: message.content.isError ? 'error' : 'output', + content: message.content.text + } + ]); + break; + + case 'status': + setTerminalOutput(prev => [ + ...prev, + { type: 'system', content: `Status: ${message.content.status}` } + ]); + + if (message.content.status === 'completed' || message.content.status === 'failed') { + setIsRunning(false); + } + break; + + case 'error': + setTerminalOutput(prev => [ + ...prev, + { type: 'error', content: message.content.message } + ]); + setIsRunning(false); + break; + + case 'system': + setTerminalOutput(prev => [ + ...prev, + { type: 'system', content: message.content } + ]); + break; + + default: + console.log('Unknown message type:', 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' } + ]); + setIsRunning(false); + }; + + socket.onclose = () => { + console.log('WebSocket connection closed'); + setActiveSocket(null); + }; + + socketRef.current = socket; + return socket; + }; + + // Handle code execution + const runCode = async () => { + 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: language.toLowerCase(), + input: '', // Add input if needed + }), + }); + + 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}` } + ]); + setIsRunning(false); + } + }; + + // 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: language.toLowerCase(), + 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 */} +
+ ); + }; + + return ( +
+
+

OnScreen Test

+ +
+ +
+

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

+
+ +
+
+ + + +
+ +
+ {renderProblem()} +
+ +
+
+
+ + + +
+ +
+ + + +
+
+ +
+ setCode(value)} + theme="vs-dark" + options={{ + fontSize: 14, + minimap: { enabled: false }, + scrollBeyondLastLine: false, + automaticLayout: true, + }} + /> +
+
+
+ +
+
+ Terminal +
+ + + +
+
+
+ {terminalOutput.map((line, index) => ( +
+ {line.content} +
+ ))} +
+ $ + { + if (e.key === 'Enter' && activeSocket) { + const input = e.target.value; + // Send input to WebSocket + activeSocket.send(JSON.stringify({ + type: 'input', + content: { text: input } + })); + + // Add input to terminal output + setTerminalOutput(prev => [ + ...prev, + { type: 'system', content: `$ ${input}` } + ]); + + // Clear the input field + e.target.value = ''; + } + }} + /> +
+
+
+
+ ); +}; + +export default CodeChallenge; diff --git a/Frontend/src/index.css b/Frontend/src/index.css index c762840..e714630 100644 --- a/Frontend/src/index.css +++ b/Frontend/src/index.css @@ -1025,4 +1025,286 @@ body { .panel-close-btn:hover { opacity: 1; +} + +/* Code Challenge Component Styles */ +.code-challenge-container { + display: flex; + flex-direction: column; + height: 100vh; + background-color: var(--vscode-background); + color: var(--vscode-foreground); +} + +.code-challenge-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; + background-color: var(--vscode-background); + border-bottom: 1px solid rgba(128, 128, 128, 0.35); +} + +.code-challenge-header h1 { + margin: 0; + font-size: 18px; + font-weight: 400; +} + +.sign-in-btn { + background-color: transparent; + color: var(--vscode-foreground); + border: 1px solid rgba(128, 128, 128, 0.5); + padding: 4px 12px; + border-radius: 4px; + font-size: 14px; + cursor: pointer; +} + +.sign-in-btn:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.code-challenge-problem-nav { + padding: 8px 16px; + border-bottom: 1px solid rgba(128, 128, 128, 0.35); +} + +.problem-number { + margin: 0; + font-size: 16px; + font-weight: 400; +} + +.code-challenge-main { + display: flex; + height: 60vh; + border-bottom: 1px solid rgba(128, 128, 128, 0.35); +} + +.problem-tabs { + display: flex; + flex-direction: column; + width: 130px; + border-right: 1px solid rgba(128, 128, 128, 0.35); +} + +.problem-tabs button { + padding: 16px; + background-color: transparent; + color: var(--vscode-foreground); + border: none; + text-align: left; + cursor: pointer; + border-bottom: 1px solid rgba(128, 128, 128, 0.2); +} + +.problem-tabs button.tab-active { + background-color: var(--vscode-background); + font-weight: 500; + border-left: 2px solid #007acc; +} + +.problem-tabs button:hover:not(.tab-active) { + background-color: rgba(255, 255, 255, 0.05); +} + +.problem-content { + flex: 1; + overflow-y: auto; + padding: 16px; + border-right: 1px solid rgba(128, 128, 128, 0.35); + width: 50%; +} + +.problem-container h1 { + margin-top: 0; + font-size: 22px; + margin-bottom: 16px; +} + +.problem-description { + margin-bottom: 20px; + font-size: 14px; + line-height: 1.5; +} + +.problem-examples h2 { + font-size: 16px; + margin-top: 24px; + margin-bottom: 12px; +} + +.example-box { + background-color: rgba(0, 0, 0, 0.3); + padding: 12px; + border-radius: 6px; + margin-bottom: 12px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 14px; + line-height: 1.5; +} + +.editor-section { + width: 50%; + display: flex; + flex-direction: column; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: #252526; + border-bottom: 1px solid rgba(128, 128, 128, 0.35); +} + +.editor-controls { + display: flex; + align-items: center; +} + +.language-selector { + background-color: #3c3c3c; + color: #d4d4d4; + border: 1px solid #3c3c3c; + border-radius: 4px; + padding: 4px 8px; + font-size: 13px; + margin-right: 8px; +} + +.auto-btn { + background-color: #3c3c3c; + color: #d4d4d4; + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 13px; + cursor: pointer; +} + +.auto-selected { + background-color: #4d4d4d; +} + +.editor-actions { + display: flex; + gap: 8px; +} + +.run-btn { + display: flex; + align-items: center; + gap: 4px; + background-color: #3c3c3c; + color: #d4d4d4; + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 13px; + cursor: pointer; +} + +.submit-btn { + display: flex; + align-items: center; + gap: 4px; + background-color: #0e639c; + color: #ffffff; + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 13px; + cursor: pointer; +} + +.run-btn:hover { + background-color: #4d4d4d; +} + +.submit-btn:hover { + background-color: #1177bb; +} + +.editor-container { + flex: 1; +} + +.terminal-section { + flex: 1; + display: flex; + flex-direction: column; + background-color: var(--vscode-panel-background); +} + +.terminal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 4px 12px; + background-color: #252526; + font-size: 13px; +} + +.terminal-controls { + display: flex; + gap: 4px; +} + +.terminal-btn { + background-color: transparent; + color: #d4d4d4; + border: none; + cursor: pointer; + font-size: 12px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.terminal-content { + flex: 1; + padding: 8px 12px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 14px; + overflow-y: auto; + white-space: pre-wrap; +} + +.terminal-line { + margin-bottom: 4px; + line-height: 1.4; +} + +.terminal-line.system { + color: #569cd6; +} + +.terminal-line.error { + color: #f48771; +} + +.terminal-prompt { + display: flex; + align-items: center; + margin-top: 8px; +} + +.prompt-symbol { + color: #569cd6; + margin-right: 8px; +} + +.terminal-input { + background-color: transparent; + color: #d4d4d4; + border: none; + outline: none; + flex: 1; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 14px; } \ No newline at end of file