Enhance UI and UX of Code Challenge Component
This commit is contained in:
@@ -618,6 +618,70 @@ int main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle final test submission
|
||||||
|
const handleSubmitTest = async () => {
|
||||||
|
if (!test) {
|
||||||
|
alert('No test data available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmSubmit = window.confirm(
|
||||||
|
'Are you sure you want to submit the entire test? You will not be able to make any changes after submission.'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmSubmit) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save the current question's answer before submitting
|
||||||
|
const currentQuestion = getCurrentQuestion();
|
||||||
|
if (currentQuestion && code.trim()) {
|
||||||
|
const apiUrl = import.meta.env.VITE_FACULTY_API_URL || 'http://localhost:5000/api';
|
||||||
|
try {
|
||||||
|
const saveResponse = await fetch(`${apiUrl}/students/submissions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
testId: test.id,
|
||||||
|
answers: [{
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
submittedAnswer: code
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (saveResponse.ok) {
|
||||||
|
console.log('Current answer saved successfully');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to save current answer:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, just mark test as completed locally and redirect
|
||||||
|
// TODO: Implement backend endpoint for test submission
|
||||||
|
alert('Test submitted successfully! You will be redirected to the test list.');
|
||||||
|
|
||||||
|
// Mark test as submitted locally
|
||||||
|
localStorage.setItem(`test_${test.id}_submitted`, JSON.stringify({
|
||||||
|
submittedAt: new Date().toISOString(),
|
||||||
|
testId: test.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Clear test data
|
||||||
|
localStorage.removeItem('currentTest');
|
||||||
|
|
||||||
|
// Redirect to tests page
|
||||||
|
navigate('/tests');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting test:', error);
|
||||||
|
alert(`Error submitting test: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Render the current problem
|
// Render the current problem
|
||||||
const renderProblem = () => {
|
const renderProblem = () => {
|
||||||
const currentQuestion = getCurrentQuestion();
|
const currentQuestion = getCurrentQuestion();
|
||||||
@@ -626,8 +690,6 @@ int main() {
|
|||||||
if (currentQuestion) {
|
if (currentQuestion) {
|
||||||
return (
|
return (
|
||||||
<div className="problem-container">
|
<div className="problem-container">
|
||||||
<h1>{currentQuestion.title || `Question ${getQuestionIndex(activeQuestion) + 1}`}</h1>
|
|
||||||
|
|
||||||
<div className="problem-description">
|
<div className="problem-description">
|
||||||
<p>{currentQuestion.question_text || currentQuestion.description}</p>
|
<p>{currentQuestion.question_text || currentQuestion.description}</p>
|
||||||
{currentQuestion.constraints && <p><strong>Constraints:</strong> {currentQuestion.constraints}</p>}
|
{currentQuestion.constraints && <p><strong>Constraints:</strong> {currentQuestion.constraints}</p>}
|
||||||
@@ -664,8 +726,6 @@ int main() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="problem-container">
|
<div className="problem-container">
|
||||||
<h1>{problem.title}</h1>
|
|
||||||
|
|
||||||
<div className="problem-description">
|
<div className="problem-description">
|
||||||
<p>{problem.description}</p>
|
<p>{problem.description}</p>
|
||||||
{problem.constraints && <p>{problem.constraints}</p>}
|
{problem.constraints && <p>{problem.constraints}</p>}
|
||||||
@@ -696,29 +756,8 @@ int main() {
|
|||||||
<h1>{test?.title || 'CS101: Midterm Examination'}</h1>
|
<h1>{test?.title || 'CS101: Midterm Examination'}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="header-right">
|
<div className="header-right">
|
||||||
{timeRemaining && (
|
<span className="saved-indicator">All changes saved ✓</span>
|
||||||
<div className="timer-display">
|
<button className="submit-test-btn" onClick={handleSubmitTest}>Submit Test</button>
|
||||||
<div className="timer-label">Time Remaining</div>
|
|
||||||
<div className="timer-value">
|
|
||||||
{timeRemaining !== 'Time Up!' ? (
|
|
||||||
<>
|
|
||||||
<span className="time-block">{timeRemaining.split(':')[0]}</span>
|
|
||||||
<span className="time-separator">:</span>
|
|
||||||
<span className="time-block">{timeRemaining.split(':')[1]}</span>
|
|
||||||
<span className="time-separator">:</span>
|
|
||||||
<span className="time-block">{timeRemaining.split(':')[2]}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span className="time-up">{timeRemaining}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="timer-labels">
|
|
||||||
<span>Hours</span>
|
|
||||||
<span>Minutes</span>
|
|
||||||
<span>Seconds</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="user-profile">
|
<div className="user-profile">
|
||||||
<div className="user-avatar">
|
<div className="user-avatar">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
@@ -730,297 +769,211 @@ int main() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* <div className="code-challenge-problem-nav">
|
<div className="code-challenge-body">
|
||||||
<h3 className="problem-number">1. {problems["Q.1"].title}</h3>
|
{/* Left Sidebar */}
|
||||||
</div> */}
|
<div className="left-sidebar">
|
||||||
|
{/* Time Remaining */}
|
||||||
|
<div className="time-remaining-widget">
|
||||||
|
<h3>Time Remaining</h3>
|
||||||
|
<div className="timer-value">
|
||||||
|
{timeRemaining && timeRemaining !== 'Time Up!' ? (
|
||||||
|
<>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">{timeRemaining.split(':')[0]}</span>
|
||||||
|
<span className="time-label">Hours</span>
|
||||||
|
</div>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">{timeRemaining.split(':')[1]}</span>
|
||||||
|
<span className="time-label">Minutes</span>
|
||||||
|
</div>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">{timeRemaining.split(':')[2]}</span>
|
||||||
|
<span className="time-label">Seconds</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : timeRemaining === 'Time Up!' ? (
|
||||||
|
<span className="time-up">{timeRemaining}</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">00</span>
|
||||||
|
<span className="time-label">Hours</span>
|
||||||
|
</div>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">00</span>
|
||||||
|
<span className="time-label">Minutes</span>
|
||||||
|
</div>
|
||||||
|
<div className="time-unit">
|
||||||
|
<span className="time-block">00</span>
|
||||||
|
<span className="time-label">Seconds</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="code-challenge-main">
|
{/* Question Palette */}
|
||||||
<div className="problem-tabs">
|
<div className="question-palette-widget">
|
||||||
<div className="question-palette-header">
|
|
||||||
<h3>Question Palette</h3>
|
<h3>Question Palette</h3>
|
||||||
</div>
|
<div className="question-palette-grid">
|
||||||
<div className="question-palette-grid">
|
{(questions.length > 0 ? questions : Array.from({length: 20}, (_, i) => i + 1)).map((q, idx) => {
|
||||||
{(questions.length > 0 ? questions : Array.from({length: 20}, (_, i) => i + 1)).map((q, idx) => {
|
const questionKey = `Q.${idx + 1}`;
|
||||||
const questionKey = `Q.${idx + 1}`;
|
const questionNum = idx + 1;
|
||||||
const questionNum = idx + 1;
|
return (
|
||||||
return (
|
<button
|
||||||
<button
|
key={questionKey}
|
||||||
key={questionKey}
|
className={`question-palette-btn ${activeQuestion === questionKey ? "palette-active" : ""}`}
|
||||||
className={`question-palette-btn ${activeQuestion === questionKey ? "palette-active" : ""}`}
|
onClick={() => setActiveQuestion(questionKey)}
|
||||||
onClick={() => setActiveQuestion(questionKey)}
|
disabled={questions.length > 0 && idx >= questions.length}
|
||||||
disabled={questions.length > 0 && idx >= questions.length}
|
>
|
||||||
>
|
{questionNum}
|
||||||
{questionNum}
|
</button>
|
||||||
</button>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="palette-legend">
|
|
||||||
<div className="legend-item">
|
|
||||||
<div className="legend-dot legend-current"></div>
|
|
||||||
<span>Current</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="legend-item">
|
<div className="palette-legend">
|
||||||
<div className="legend-dot legend-answered"></div>
|
<div className="legend-item">
|
||||||
<span>Answered</span>
|
<div className="legend-dot legend-current"></div>
|
||||||
</div>
|
<span>Current</span>
|
||||||
<div className="legend-item">
|
|
||||||
<div className="legend-dot legend-not-visited"></div>
|
|
||||||
<span>Not Visited</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="problem-content">
|
|
||||||
<div className="problem-header-info">
|
|
||||||
<span className="question-label">Question {activeQuestion.replace('Q.', '')} of {questions.length || 20}</span>
|
|
||||||
<span className="question-points">| 10 Points</span>
|
|
||||||
</div>
|
|
||||||
{renderProblem()}
|
|
||||||
<div className="problem-actions">
|
|
||||||
<button className="action-btn action-btn-secondary">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
|
||||||
<path d="M21 3v5h-5"/>
|
|
||||||
</svg>
|
|
||||||
Clear Response
|
|
||||||
</button>
|
|
||||||
<button className="action-btn action-btn-secondary">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M9 11l3 3L22 4"/>
|
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
||||||
</svg>
|
|
||||||
Mark for Review
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="editor-section">
|
|
||||||
<div className="editor-header">
|
|
||||||
<div className="editor-controls">
|
|
||||||
<select
|
|
||||||
value={language}
|
|
||||||
onChange={(e) => setLanguage(e.target.value)}
|
|
||||||
className="language-selector"
|
|
||||||
>
|
|
||||||
<option value="JavaScript">JavaScript</option>
|
|
||||||
<option value="Python">Python</option>
|
|
||||||
<option value="Java">Java</option>
|
|
||||||
<option value="C++">C++</option>
|
|
||||||
<option value="C">C</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="editor-actions">
|
|
||||||
<button
|
|
||||||
className="run-btn"
|
|
||||||
onClick={runCode}
|
|
||||||
disabled={isRunning}
|
|
||||||
title={isRunning ? "Code execution in progress..." : "Run code"}
|
|
||||||
>
|
|
||||||
{isRunning ? (
|
|
||||||
<>
|
|
||||||
<span className="loading-spinner"></span> Running...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Play size={16} /> Run
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="submit-btn"
|
|
||||||
onClick={submitCode}
|
|
||||||
disabled={isRunning}
|
|
||||||
title={isRunning ? "Code execution in progress..." : "Submit solution"}
|
|
||||||
>
|
|
||||||
{isRunning ? (
|
|
||||||
<>
|
|
||||||
<span className="loading-spinner"></span> Submitting...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Send size={16} /> Submit
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="editor-container">
|
|
||||||
<Editor
|
|
||||||
height="100%"
|
|
||||||
defaultLanguage="python"
|
|
||||||
language={language.toLowerCase() === 'c++' ? 'cpp' : language.toLowerCase()}
|
|
||||||
value={code}
|
|
||||||
onChange={(value) => setCode(value)}
|
|
||||||
theme="hc-black"
|
|
||||||
options={{
|
|
||||||
fontSize: 14,
|
|
||||||
minimap: { enabled: false },
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
automaticLayout: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="terminal-section">
|
|
||||||
<div className="terminal-header">
|
|
||||||
<div className="terminal-tabs">
|
|
||||||
<button className="terminal-tab terminal-tab-active">
|
|
||||||
<span>Console</span>
|
|
||||||
</button>
|
|
||||||
<button className="terminal-tab">
|
|
||||||
<span>Testcases</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="terminal-content-wrapper">
|
|
||||||
<div className="terminal-content">
|
|
||||||
{terminalOutput.length === 0 ? (
|
|
||||||
<div className="terminal-placeholder">
|
|
||||||
Console output will appear here...
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div className="legend-item">
|
||||||
terminalOutput.map((line, index) => (
|
<div className="legend-dot legend-answered"></div>
|
||||||
<div
|
<span>Answered</span>
|
||||||
key={index}
|
</div>
|
||||||
className={`terminal-line ${line.type}`}
|
<div className="legend-item">
|
||||||
>
|
<div className="legend-dot legend-skipped"></div>
|
||||||
{line.content}
|
<span>Skipped</span>
|
||||||
|
</div>
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-dot legend-marked"></div>
|
||||||
|
<span>Marked for Review</span>
|
||||||
|
</div>
|
||||||
|
<div className="legend-item">
|
||||||
|
<div className="legend-dot legend-not-visited"></div>
|
||||||
|
<span>Not Visited</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<div className="main-content-area">
|
||||||
|
{/* Top Section: Question text above editor */}
|
||||||
|
<div className="top-section">
|
||||||
|
<div className="content-wrapper">
|
||||||
|
{/* Question Text Bar */}
|
||||||
|
<div className="question-bar">
|
||||||
|
<div className="question-header">
|
||||||
|
<h2>Question {activeQuestion.replace('Q.', '')} of {questions.length || 20} | 10 Points</h2>
|
||||||
</div>
|
</div>
|
||||||
))
|
<div className="question-text">
|
||||||
)}
|
{renderProblem()}
|
||||||
<div className="terminal-prompt">
|
</div>
|
||||||
<span className="prompt-symbol">$</span>
|
</div>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="terminal-input"
|
|
||||||
placeholder="Type here..."
|
|
||||||
disabled={!isRunning}
|
|
||||||
// Update the ref callback
|
|
||||||
ref={(inputEl) => {
|
|
||||||
// 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
|
{/* Code Editor */}
|
||||||
|
<div className="editor-section-wrapper">
|
||||||
|
<div className="editor-header">
|
||||||
|
<div className="editor-controls">
|
||||||
|
<select
|
||||||
|
value={language}
|
||||||
|
onChange={(e) => setLanguage(e.target.value)}
|
||||||
|
className="language-selector"
|
||||||
|
>
|
||||||
|
<option value="JavaScript">JavaScript</option>
|
||||||
|
<option value="Python">Python</option>
|
||||||
|
<option value="Java">Java</option>
|
||||||
|
<option value="C++">C++</option>
|
||||||
|
<option value="C">C</option>
|
||||||
|
</select>
|
||||||
|
<button className="reset-code-btn">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
||||||
|
<path d="M21 3v5h-5"/>
|
||||||
|
</svg>
|
||||||
|
Reset Code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
|
<div className="editor-container">
|
||||||
try {
|
<Editor
|
||||||
// Send input to server
|
height="100%"
|
||||||
activeSocket.send(JSON.stringify({
|
defaultLanguage="python"
|
||||||
"type": "input",
|
language={language.toLowerCase() === 'c++' ? 'cpp' : language.toLowerCase()}
|
||||||
"content": input
|
value={code}
|
||||||
}));
|
onChange={(value) => setCode(value)}
|
||||||
|
theme="hc-black"
|
||||||
// Add input to terminal output
|
options={{
|
||||||
setTerminalOutput(prev => [
|
fontSize: 14,
|
||||||
...prev,
|
minimap: { enabled: false },
|
||||||
{ type: 'system', content: `$ ${input}` }
|
scrollBeyondLastLine: false,
|
||||||
]);
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
// Clear the input field
|
/>
|
||||||
e.target.value = '';
|
</div>
|
||||||
} catch (error) {
|
</div>
|
||||||
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` }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="terminal-footer">
|
|
||||||
<div className="terminal-footer-actions">
|
|
||||||
<button className="footer-btn footer-btn-outline">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
|
||||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
|
||||||
</svg>
|
|
||||||
Run Code
|
|
||||||
</button>
|
|
||||||
<button className="footer-btn footer-btn-primary">
|
|
||||||
Save & Next
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
||||||
<path d="M5 12h14"/>
|
|
||||||
<path d="m12 5 7 7-7 7"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button className="footer-btn footer-btn-success">
|
|
||||||
Submit Test
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="changes-saved">
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Section: Console/Testcases */}
|
||||||
|
<div className="bottom-section">
|
||||||
|
<div className="console-section">
|
||||||
|
<div className="console-header">
|
||||||
|
<div className="console-tabs">
|
||||||
|
<button className="console-tab console-tab-active">
|
||||||
|
<span>Console</span>
|
||||||
|
</button>
|
||||||
|
<button className="console-tab">
|
||||||
|
<span>Testcases</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="console-content">
|
||||||
|
{terminalOutput.length === 0 ? (
|
||||||
|
<div className="console-placeholder">
|
||||||
|
Console output will appear here...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
terminalOutput.map((line, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`console-line ${line.type}`}
|
||||||
|
>
|
||||||
|
{line.content}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons at Bottom Right */}
|
||||||
|
<div className="action-bar">
|
||||||
|
<button
|
||||||
|
className="action-bar-btn action-run"
|
||||||
|
onClick={runCode}
|
||||||
|
disabled={isRunning}
|
||||||
|
>
|
||||||
|
{isRunning ? (
|
||||||
|
<>
|
||||||
|
<span className="loading-spinner"></span>
|
||||||
|
Running...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Play size={16} />
|
||||||
|
Run Code
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button className="action-bar-btn action-save">
|
||||||
|
Save & Next
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
<path d="M5 12h14"/>
|
||||||
<path d="M22 4 12 14.01l-3-3"/>
|
<path d="m12 5 7 7-7 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>All changes saved</span>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user