diff --git a/Frontend/src/components/Footer.jsx b/Frontend/src/components/Footer.jsx
new file mode 100644
index 0000000..bd488a3
--- /dev/null
+++ b/Frontend/src/components/Footer.jsx
@@ -0,0 +1,14 @@
+const Footer = () => {
+ return (
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
diff --git a/Frontend/src/components/Login.jsx b/Frontend/src/components/Login.jsx
index 3efe677..9ba86a8 100644
--- a/Frontend/src/components/Login.jsx
+++ b/Frontend/src/components/Login.jsx
@@ -14,7 +14,7 @@ const Login = () => {
useEffect(() => {
const token = localStorage.getItem('monaco_token');
if (token) {
- navigate('/editor');
+ navigate('/tests');
}
}, [navigate]);
@@ -39,7 +39,7 @@ const Login = () => {
const success = await login(userInfo.email, credentialResponse.credential, userInfo);
if (success) {
- navigate('/editor');
+ navigate('/tests');
} else {
throw new Error('Authentication failed');
}
diff --git a/Frontend/src/components/TestList.css b/Frontend/src/components/TestList.css
new file mode 100644
index 0000000..de9735d
--- /dev/null
+++ b/Frontend/src/components/TestList.css
@@ -0,0 +1,688 @@
+/* TestList.css */
+.test-list-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+}
+
+.dark .test-list-container {
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d3748 100%);
+}
+
+.test-list-header {
+ background: white;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ border-bottom: 1px solid #e5e7eb;
+ padding: 1.5rem 0;
+}
+
+.dark .test-list-header {
+ background: #1f2937;
+ border-bottom-color: #374151;
+}
+
+.header-content {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 0 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+@media (min-width: 768px) {
+ .header-content {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
+
+.header-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #111827;
+ margin: 0 0 0.5rem 0;
+}
+
+.dark .header-title {
+ color: white;
+}
+
+.header-subtitle {
+ color: #6b7280;
+ margin: 0;
+}
+
+.dark .header-subtitle {
+ color: #9ca3af;
+}
+
+.filter-tabs {
+ display: flex;
+ gap: 0.5rem;
+ background: #f3f4f6;
+ padding: 0.25rem;
+ border-radius: 0.5rem;
+}
+
+.dark .filter-tabs {
+ background: #374151;
+}
+
+.filter-tab {
+ padding: 0.5rem 1rem;
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ border: none;
+ background: transparent;
+ color: #6b7280;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.filter-tab:hover {
+ color: #111827;
+}
+
+.dark .filter-tab {
+ color: #9ca3af;
+}
+
+.dark .filter-tab:hover {
+ color: white;
+}
+
+.filter-tab.active {
+ background: white;
+ color: #2563eb;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.dark .filter-tab.active {
+ background: #4b5563;
+ color: #60a5fa;
+}
+
+.test-list-content {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem 1rem;
+}
+
+.tests-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+}
+
+@media (min-width: 768px) {
+ .tests-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 1024px) {
+ .tests-grid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+.test-card {
+ background: white;
+ border-radius: 0.75rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ border: 1px solid #e5e7eb;
+ transition: all 0.3s;
+}
+
+.test-card:hover {
+ box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
+ border-color: #93c5fd;
+ transform: translateY(-2px);
+}
+
+.dark .test-card {
+ background: #1f2937;
+ border-color: #374151;
+}
+
+.dark .test-card:hover {
+ border-color: #3b82f6;
+}
+
+.test-card-stripe {
+ height: 0.5rem;
+ background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%);
+}
+
+.test-card-content {
+ padding: 1.5rem;
+}
+
+.test-card-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ margin-bottom: 1rem;
+}
+
+.test-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #111827;
+ margin: 0;
+ transition: color 0.2s;
+}
+
+.test-card:hover .test-title {
+ color: #2563eb;
+}
+
+.dark .test-title {
+ color: white;
+}
+
+.dark .test-card:hover .test-title {
+ color: #60a5fa;
+}
+
+.status-badge {
+ padding: 0.25rem 0.75rem;
+ border-radius: 9999px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ white-space: nowrap;
+}
+
+.status-dot {
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 9999px;
+}
+
+.status-active .status-dot {
+ background: #10b981;
+ animation: pulse 2s infinite;
+}
+
+.status-active {
+ background: #d1fae5;
+ color: #065f46;
+}
+
+.dark .status-active {
+ background: rgba(16, 185, 129, 0.2);
+ color: #6ee7b7;
+}
+
+.status-upcoming {
+ background: #dbeafe;
+ color: #1e40af;
+}
+
+.status-upcoming .status-dot {
+ background: #3b82f6;
+}
+
+.dark .status-upcoming {
+ background: rgba(59, 130, 246, 0.2);
+ color: #93c5fd;
+}
+
+.status-closed {
+ background: #f3f4f6;
+ color: #6b7280;
+}
+
+.status-closed .status-dot {
+ background: #9ca3af;
+}
+
+.dark .status-closed {
+ background: #374151;
+ color: #9ca3af;
+}
+
+.test-description {
+ color: #6b7280;
+ margin-bottom: 1.5rem;
+ min-height: 3rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.dark .test-description {
+ color: #9ca3af;
+}
+
+.test-details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-bottom: 1.5rem;
+}
+
+.test-detail {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.875rem;
+ color: #374151;
+}
+
+.dark .test-detail {
+ color: #d1d5db;
+}
+
+.test-detail-icon {
+ width: 1.25rem;
+ height: 1.25rem;
+ flex-shrink: 0;
+}
+
+.icon-blue {
+ color: #3b82f6;
+}
+
+.icon-purple {
+ color: #8b5cf6;
+}
+
+.icon-amber {
+ color: #f59e0b;
+}
+
+.test-button {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ border-radius: 0.5rem;
+ font-weight: 600;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ transition: all 0.2s;
+}
+
+.test-button-active {
+ background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%);
+ color: white;
+ box-shadow: 0 4px 6px rgba(59, 130, 246, 0.3);
+}
+
+.test-button-active:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 8px rgba(59, 130, 246, 0.4);
+}
+
+.test-button-disabled {
+ background: #e5e7eb;
+ color: #9ca3af;
+ cursor: not-allowed;
+}
+
+.dark .test-button-disabled {
+ background: #374151;
+ color: #6b7280;
+}
+
+.loading-container,
+.error-container {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+}
+
+.dark .loading-container,
+.dark .error-container {
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d3748 100%);
+}
+
+.loading-content {
+ text-align: center;
+}
+
+.spinner {
+ display: inline-block;
+ width: 3rem;
+ height: 3rem;
+ border: 0.25rem solid #e5e7eb;
+ border-top-color: #3b82f6;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 1rem;
+}
+
+.loading-text {
+ color: #6b7280;
+ font-size: 1.125rem;
+}
+
+.dark .loading-text {
+ color: #9ca3af;
+}
+
+.error-box {
+ background: #fef2f2;
+ border: 1px solid #fecaca;
+ border-radius: 0.5rem;
+ padding: 1.5rem;
+ max-width: 28rem;
+}
+
+.dark .error-box {
+ background: rgba(220, 38, 38, 0.1);
+ border-color: #991b1b;
+}
+
+.error-content {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.error-icon {
+ width: 1.5rem;
+ height: 1.5rem;
+ color: #dc2626;
+ flex-shrink: 0;
+}
+
+.error-title {
+ font-weight: 600;
+ color: #991b1b;
+ margin: 0 0 0.25rem 0;
+}
+
+.dark .error-title {
+ color: #fca5a5;
+}
+
+.error-message {
+ color: #dc2626;
+ margin: 0;
+}
+
+.dark .error-message {
+ color: #fca5a5;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 4rem 1rem;
+}
+
+.empty-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 4rem;
+ height: 4rem;
+ border-radius: 50%;
+ background: #e5e7eb;
+ margin-bottom: 1rem;
+}
+
+.dark .empty-icon {
+ background: #374151;
+}
+
+.empty-icon svg {
+ width: 2rem;
+ height: 2rem;
+ color: #9ca3af;
+}
+
+.empty-title {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: #111827;
+ margin: 0 0 0.5rem 0;
+}
+
+.dark .empty-title {
+ color: white;
+}
+
+.empty-message {
+ color: #6b7280;
+ margin: 0;
+}
+
+.dark .empty-message {
+ color: #9ca3af;
+}
+
+/* Modal */
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 1000;
+ animation: fadeIn 0.3s;
+}
+
+.modal-content {
+ background: white;
+ border-radius: 1rem;
+ box-shadow: 0 20px 25px rgba(0, 0, 0, 0.3);
+ width: 100%;
+ max-width: 28rem;
+ animation: slideUp 0.3s;
+}
+
+.dark .modal-content {
+ background: #1f2937;
+}
+
+.modal-header {
+ border-bottom: 1px solid #e5e7eb;
+ padding: 1.5rem;
+}
+
+.dark .modal-header {
+ border-bottom-color: #374151;
+}
+
+.modal-header-content {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.modal-icon {
+ width: 3rem;
+ height: 3rem;
+ border-radius: 50%;
+ background: #dbeafe;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.dark .modal-icon {
+ background: rgba(59, 130, 246, 0.2);
+}
+
+.modal-icon svg {
+ width: 1.5rem;
+ height: 1.5rem;
+ color: #3b82f6;
+}
+
+.dark .modal-icon svg {
+ color: #60a5fa;
+}
+
+.modal-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #111827;
+ margin: 0;
+}
+
+.dark .modal-title {
+ color: white;
+}
+
+.modal-subtitle {
+ font-size: 0.875rem;
+ color: #6b7280;
+ margin: 0;
+}
+
+.dark .modal-subtitle {
+ color: #9ca3af;
+}
+
+.modal-body {
+ padding: 1.5rem;
+}
+
+.modal-label {
+ display: block;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #374151;
+ margin-bottom: 0.5rem;
+}
+
+.dark .modal-label {
+ color: #d1d5db;
+}
+
+.modal-input {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ border: 1px solid #d1d5db;
+ border-radius: 0.5rem;
+ font-size: 1rem;
+ transition: all 0.2s;
+ box-sizing: border-box;
+}
+
+.modal-input:focus {
+ outline: none;
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+}
+
+.dark .modal-input {
+ background: #374151;
+ border-color: #4b5563;
+ color: white;
+}
+
+.dark .modal-input:focus {
+ border-color: #3b82f6;
+}
+
+.modal-footer {
+ border-top: 1px solid #e5e7eb;
+ padding: 1.5rem;
+ display: flex;
+ gap: 0.75rem;
+}
+
+.dark .modal-footer {
+ border-top-color: #374151;
+}
+
+.modal-button {
+ flex: 1;
+ padding: 0.625rem 1rem;
+ border-radius: 0.5rem;
+ font-weight: 500;
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.modal-button-cancel {
+ background: #f3f4f6;
+ color: #374151;
+}
+
+.modal-button-cancel:hover {
+ background: #e5e7eb;
+}
+
+.dark .modal-button-cancel {
+ background: #374151;
+ color: #d1d5db;
+}
+
+.dark .modal-button-cancel:hover {
+ background: #4b5563;
+}
+
+.modal-button-submit {
+ background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%);
+ color: white;
+}
+
+.modal-button-submit:hover:not(:disabled) {
+ opacity: 0.9;
+}
+
+.modal-button-submit:disabled {
+ background: #9ca3af;
+ cursor: not-allowed;
+}
+
+.dark .modal-button-submit:disabled {
+ background: #4b5563;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes slideUp {
+ from {
+ transform: translateY(1.25rem);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
diff --git a/Frontend/src/components/TestList.jsx b/Frontend/src/components/TestList.jsx
new file mode 100644
index 0000000..458ef7d
--- /dev/null
+++ b/Frontend/src/components/TestList.jsx
@@ -0,0 +1,346 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../contexts/AuthContext';
+import './TestList.css';
+
+const TestList = () => {
+ const [tests, setTests] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [showPasswordModal, setShowPasswordModal] = useState(false);
+ const [selectedTest, setSelectedTest] = useState(null);
+ const [password, setPassword] = useState('');
+ const [filterStatus, setFilterStatus] = useState('all');
+ const navigate = useNavigate();
+ const { token } = useAuth();
+
+ useEffect(() => {
+ fetchTests();
+ }, []);
+
+ const fetchTests = async () => {
+ try {
+ console.log('Fetching tests with token:', token?.substring(0, 50) + '...');
+ const response = await fetch('http://localhost:5000/api/students/tests', {
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ localStorage.removeItem('monaco_user');
+ localStorage.removeItem('monaco_token');
+ window.location.href = '/login';
+ return;
+ }
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ if (data.success) {
+ console.log('Tests received:', data.tests);
+ data.tests.forEach(test => {
+ console.log(`Test: ${test.title}, Status: ${test.status}, Start: ${test.start_time}, End: ${test.end_time}`);
+ });
+ setTests(data.tests);
+ } else {
+ setError(data.message || 'Failed to fetch tests');
+ }
+ } catch (error) {
+ setError('Failed to fetch tests');
+ console.error('Error:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleStartTest = async (test) => {
+ try {
+ const response = await fetch(`http://localhost:5000/api/students/tests/${test.id}/questions`, {
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }
+ });
+ const data = await response.json();
+ if (data.success) {
+ localStorage.setItem('currentTest', JSON.stringify({
+ id: test.id,
+ questions: data.questions,
+ currentQuestionIndex: 0
+ }));
+ navigate('/editor');
+ } else {
+ setError(data.message);
+ }
+ } catch (error) {
+ setError('Failed to start test');
+ console.error('Error:', error);
+ }
+ };
+
+ const handlePasswordSubmit = async () => {
+ if (!selectedTest || !password) return;
+
+ try {
+ const response = await fetch(`http://localhost:5000/api/students/tests/${selectedTest.id}/verify-password`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ password })
+ });
+ const data = await response.json();
+ if (data.success) {
+ setShowPasswordModal(false);
+ setPassword('');
+ handleStartTest(selectedTest);
+ } else {
+ setError('Invalid password');
+ }
+ } catch (error) {
+ setError('Failed to verify password');
+ console.error('Error:', error);
+ }
+ };
+
+ const handleTestClick = (test) => {
+ if (test.password_required) {
+ setSelectedTest(test);
+ setShowPasswordModal(true);
+ } else {
+ handleStartTest(test);
+ }
+ };
+
+ const filteredTests = tests.filter(test => {
+ if (filterStatus === 'all') return true;
+ return test.status.toLowerCase() === filterStatus.toLowerCase();
+ });
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {/* Header Section */}
+
+
+
+
+ 📝 Available Tests
+
+
+ Select a test to start your coding challenge
+
+
+
+ {/* Filter Tabs */}
+
+
+
+
+
+
+
+
+ {/* Tests Grid */}
+
+ {filteredTests.length === 0 ? (
+
+
+
No tests available
+
Check back later for new tests
+
+ ) : (
+
+ {filteredTests.map(test => (
+
+ {/* Status Badge */}
+
+
+
+ {/* Header */}
+
+
+ {test.title}
+
+
+
+ {test.status}
+
+
+
+ {/* Description */}
+
+ {test.description || 'No description available'}
+
+
+ {/* Test Details */}
+
+
+
+
{test.duration_minutes} minutes
+
+
+ {test.total_questions && (
+
+
+
{test.total_questions} questions
+
+ )}
+
+ {test.password_required && (
+
+
+
Password required
+
+ )}
+
+
+ {/* Action Button */}
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Password Modal */}
+ {showPasswordModal && (
+
+
+ {/* Modal Header */}
+
+
+
+
+
Protected Test
+
Enter password to continue
+
+
+
+
+ {/* Modal Body */}
+
+
+ setPassword(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && handlePasswordSubmit()}
+ className="modal-input"
+ placeholder="Enter password"
+ autoFocus
+ />
+
+
+ {/* Modal Footer */}
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default TestList;
\ No newline at end of file
diff --git a/Frontend/src/contexts/AuthContext.jsx b/Frontend/src/contexts/AuthContext.jsx
index ddde4c8..8538a22 100644
--- a/Frontend/src/contexts/AuthContext.jsx
+++ b/Frontend/src/contexts/AuthContext.jsx
@@ -12,6 +12,7 @@ export const useAuth = () => {
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
+ const [token, setToken] = useState(null);
const [isLoading, setIsLoading] = useState(true);
// Check for existing session on app load
@@ -22,6 +23,7 @@ export const AuthProvider = ({ children }) => {
const savedToken = localStorage.getItem('monaco_token');
if (savedUser && savedToken) {
setUser(JSON.parse(savedUser));
+ setToken(savedToken);
}
} catch (error) {
console.error('Error checking auth status:', error);
@@ -36,54 +38,74 @@ export const AuthProvider = ({ children }) => {
}, []);
const login = async (email, googleToken, userInfo = null) => {
- try {
- // For Google OAuth login
- if (googleToken && userInfo) {
- const userData = {
- id: userInfo.sub || Date.now(),
+ // For Google OAuth login
+ if (googleToken && userInfo) {
+ // Exchange Google token for our JWT
+ const response = await fetch('http://localhost:5000/api/students/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${googleToken}`
+ },
+ body: JSON.stringify({
email: email,
- name: userInfo.name || email.split('@')[0],
- picture: userInfo.picture || null,
- loginTime: new Date().toISOString(),
- googleToken: googleToken
- };
-
- setUser(userData);
- localStorage.setItem('monaco_user', JSON.stringify(userData));
- localStorage.setItem('monaco_token', googleToken);
- return true;
+ name: userInfo.name || email.split('@')[0]
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error(`Server error: ${response.status} ${response.statusText}`);
}
- // Fallback for demo purposes (though we're moving to Google-only)
- if (email && email.includes('@')) {
- const userData = {
- id: Date.now(),
- email: email,
- name: email.split('@')[0],
- loginTime: new Date().toISOString()
- };
-
- setUser(userData);
- localStorage.setItem('monaco_user', JSON.stringify(userData));
- localStorage.setItem('monaco_token', 'demo_token');
- return true;
+ const data = await response.json();
+ if (!data.success) {
+ throw new Error(data.message || 'Login failed');
}
-
- return false;
- } catch (error) {
- console.error('Login error:', error);
- return false;
+
+ const userData = {
+ id: userInfo.sub || Date.now(),
+ email: email,
+ name: userInfo.name || email.split('@')[0],
+ picture: userInfo.picture || null,
+ loginTime: new Date().toISOString(),
+ token: data.token // Store the JWT instead of Google token
+ };
+
+ setUser(userData);
+ localStorage.setItem('monaco_user', JSON.stringify(userData));
+ localStorage.setItem('monaco_token', userData.token);
+ setToken(userData.token);
+ return true;
}
+
+ // Fallback for demo purposes (though we're moving to Google-only)
+ if (email && email.includes('@')) {
+ const userData = {
+ id: Date.now(),
+ email: email,
+ name: email.split('@')[0],
+ loginTime: new Date().toISOString()
+ };
+
+ setUser(userData);
+ localStorage.setItem('monaco_user', JSON.stringify(userData));
+ localStorage.setItem('monaco_token', 'demo_token');
+ return true;
+ }
+
+ return false;
};
const logout = () => {
setUser(null);
+ setToken(null);
localStorage.removeItem('monaco_user');
localStorage.removeItem('monaco_token');
};
const value = {
user,
+ token,
login,
logout,
isAuthenticated: !!user,
diff --git a/Frontend/src/index.css b/Frontend/src/index.css
index 4d1145a..1e2387f 100644
--- a/Frontend/src/index.css
+++ b/Frontend/src/index.css
@@ -1225,6 +1225,8 @@ body {
padding: 8px 12px;
background-color: #252526;
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
+ position: relative;
+ z-index: 200;
}
.editor-controls {
@@ -1259,6 +1261,8 @@ body {
.editor-actions {
display: flex;
gap: 8px;
+ position: relative;
+ z-index: 150;
}
.run-btn {
@@ -1672,4 +1676,19 @@ body {
/* Make sure the footer appears on top of other elements */
footer {
z-index: 1000;
-}
\ No newline at end of file
+}
+
+/* Test List Animations */
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes slideUp {
+ from { transform: translateY(20px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
+
+.animate-fadeIn { animation: fadeIn 0.3s ease-out; }
+.animate-slideUp { animation: slideUp 0.3s ease-out; }
+.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
diff --git a/Frontend/src/utils/studentApi.js b/Frontend/src/utils/studentApi.js
new file mode 100644
index 0000000..8e59267
--- /dev/null
+++ b/Frontend/src/utils/studentApi.js
@@ -0,0 +1,51 @@
+const API_URL = import.meta.env.VITE_FACULTY_API_URL || 'http://localhost:5000/api';
+
+export const studentApi = {
+ async getTests() {
+ const response = await fetch(`${API_URL}/students/tests`, {
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('monaco_token')}`
+ }
+ });
+ return await response.json();
+ },
+
+ async getTestQuestions(testId) {
+ const response = await fetch(`${API_URL}/students/tests/${testId}/questions`, {
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('monaco_token')}`
+ }
+ });
+ return await response.json();
+ },
+
+ async verifyTestPassword(testId, password) {
+ const response = await fetch(`${API_URL}/students/tests/${testId}/verify-password`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('monaco_token')}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ password })
+ });
+ return await response.json();
+ },
+
+ async submitAnswer(testId, questionId, code) {
+ const response = await fetch(`${API_URL}/students/submissions`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${localStorage.getItem('monaco_token')}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ testId,
+ answers: [{
+ questionId,
+ submittedAnswer: code
+ }]
+ })
+ });
+ return await response.json();
+ }
+};
\ No newline at end of file