'use client' import React, { useState, useEffect, useCallback, useRef } from 'react' import { useRouter, useParams } from 'next/navigation' import { Trophy, Clock, Users, Send, RefreshCw, Play, Code, Shield, TestTube } from 'lucide-react' interface Participant { name: string score: number rank: number completed: boolean language?: string submission_time?: string wallet_address?: string wallet_short?: string blockchain_verified?: boolean } interface Problem { title: string description: string function_name: string languages: string[] examples: Array<{input: string, expected_output: string, description: string}> constraints: string[] starter_code: {[key: string]: string} } interface ExamSession { exam_code: string student_name: string wallet_address?: string blockchain_verified?: boolean exam_info: any } export default function EnhancedExamInterface() { const router = useRouter() const params = useParams() // ✅ FIXED: Get exam code from URL params const examCode = params.examCode as string const [examSession, setExamSession] = useState(null) const [problem, setProblem] = useState(null) const [selectedLanguage, setSelectedLanguage] = useState('python') const [code, setCode] = useState('') const [output, setOutput] = useState('') const [testResults, setTestResults] = useState([]) const [leaderboard, setLeaderboard] = useState([]) const [waitingParticipants, setWaitingParticipants] = useState([]) const [timeRemaining, setTimeRemaining] = useState(0) const [isRunning, setIsRunning] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) const [hasSubmitted, setHasSubmitted] = useState(false) const [examStats, setExamStats] = useState({}) const [timerInitialized, setTimerInitialized] = useState(false) const [leftTab, setLeftTab] = useState<'description' | 'examples' | 'constraints'>('description') const [rightTab, setRightTab] = useState<'result' | 'leaderboard'>('result') // ✅ CRITICAL FIX: Use refs to prevent infinite loops const intervalRef = useRef(null) const timerRef = useRef(null) const refreshTimeoutRefs = useRef([]) const isInitializedRef = useRef(false) const languageIcons: {[key: string]: string} = { python: 'Py', java: 'Java', javascript: 'JS', c: 'C', bash: 'Sh' } // ✅ FIXED: Memoized functions to prevent recreation const fetchProblem = useCallback(async (examCode: string) => { try { const response = await fetch(`http://127.0.0.1:5000/api/exam/get-problem/${examCode}`) const data = await response.json() if (data.success) { setProblem(data.problem) const defaultLang = data.problem.languages[0] || 'python' setSelectedLanguage(defaultLang) setCode(data.problem.starter_code[defaultLang] || '') } } catch (error) { console.error('Failed to fetch problem:', error) } }, []) const fetchLeaderboard = useCallback(async (examCode: string) => { try { console.log('🏆 Fetching leaderboard for:', examCode) const response = await fetch(`http://127.0.0.1:5000/api/exam/leaderboard/${examCode}?t=${Date.now()}`) const data = await response.json() if (data.success) { setLeaderboard(data.leaderboard || []) setWaitingParticipants(data.waiting_participants || []) setExamStats(data.stats || {}) // Timer calculation if (data.exam_info && data.exam_info.status === 'active') { if (data.exam_info.end_time) { const now = Date.now() const endTime = new Date(data.exam_info.end_time).getTime() const remaining = Math.max(0, Math.floor((endTime - now) / 1000)) setTimeRemaining(remaining) if (!timerInitialized) { setTimerInitialized(true) } } } // Check user status - only once to prevent loops if (!hasSubmitted && examSession?.student_name) { const userInCompleted = data.leaderboard.find((p: Participant) => p.name === examSession.student_name) if (userInCompleted) { console.log('✅ User found in completed leaderboard') setHasSubmitted(true) } } } } catch (error) { console.error('❌ Failed to fetch leaderboard:', error) } }, [hasSubmitted, examSession?.student_name, timerInitialized]) // ✅ FIXED: Initialization effect - runs only once useEffect(() => { if (!examCode || isInitializedRef.current) return console.log('🚀 Initializing exam interface...') isInitializedRef.current = true // Initialize session const sessionData = localStorage.getItem('exam_session') if (!sessionData) { const newSession = { exam_code: examCode, student_name: localStorage.getItem('student_name') || 'Anonymous', exam_info: {} } setExamSession(newSession) } else { const session = JSON.parse(sessionData) if (session.exam_code !== examCode) { session.exam_code = examCode } setExamSession(session) } // Fetch initial data fetchProblem(examCode) fetchLeaderboard(examCode) return () => { console.log('🛑 Cleaning up initialization effect') } }, [examCode, fetchProblem, fetchLeaderboard]) // ✅ FIXED: Separate effect for polling - controlled interval useEffect(() => { if (!examCode || !examSession) return console.log('📡 Starting leaderboard polling...') // Clear any existing interval if (intervalRef.current) { clearInterval(intervalRef.current) } // Set up polling interval - less aggressive intervalRef.current = setInterval(() => { fetchLeaderboard(examCode) }, 5000) // ✅ REDUCED: Changed from 2000ms to 5000ms return () => { console.log('🛑 Cleaning up polling interval') if (intervalRef.current) { clearInterval(intervalRef.current) intervalRef.current = null } } }, [examCode, examSession, fetchLeaderboard]) // ✅ FIXED: Timer effect - separate and controlled useEffect(() => { if (!timerInitialized || timeRemaining <= 0) return console.log('⏱️ Starting timer...') // Clear any existing timer if (timerRef.current) { clearInterval(timerRef.current) } timerRef.current = setInterval(() => { setTimeRemaining(prev => { const newTime = Math.max(0, prev - 1) if (newTime === 0) { alert('Time is up. Exam has ended.') } return newTime }) }, 1000) return () => { console.log('🛑 Cleaning up timer') if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = null } } }, [timerInitialized, timeRemaining > 0]) // ✅ FIXED: Better dependency // ✅ FIXED: Cleanup all timeouts on unmount useEffect(() => { return () => { console.log('🛑 Component unmounting - cleaning up all timeouts') refreshTimeoutRefs.current.forEach(timeout => clearTimeout(timeout)) refreshTimeoutRefs.current = [] } }, []) const handleLanguageChange = (language: string) => { setSelectedLanguage(language) if (problem?.starter_code[language]) { setCode(problem.starter_code[language]) } setOutput('') setTestResults([]) } const runCode = async () => { if (!code.trim()) { alert('Please write some code first!') return } setIsRunning(true) setOutput('') setTestResults([]) try { const response = await fetch('http://127.0.0.1:5000/api/compiler/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, language: selectedLanguage }) }) const result = await response.json() if (result.success) { setOutput(`Output:\n${result.output}`) if (result.execution_time) { setOutput(prev => prev + `\nExecution time: ${result.execution_time}s`) } } else { setOutput(`Error:\n${result.error}`) } } catch (error) { setOutput(`Execution failed: ${(error as Error).message}`) } finally { setIsRunning(false) } } // ✅ FIXED: Submit solution with controlled refresh const submitSolution = async () => { if (!code.trim()) { alert('Please write some code before submitting!') return } if (!examSession?.student_name) { alert('Student name is missing. Please refresh and try again.') return } if (!confirm('Submit your solution? This cannot be undone.')) return setIsSubmitting(true) try { console.log('📤 Submitting solution...') const submissionData = { exam_code: examCode, username: examSession.student_name, code: code, language: selectedLanguage, problem_id: "problem_1" } console.log('🔍 Submitting data:', submissionData) const response = await fetch('http://127.0.0.1:5000/api/exam/submit-solution', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(submissionData) }) const data = await response.json() console.log('📦 Submit result:', data) if (data.success) { setHasSubmitted(true) setTestResults(data.result?.test_results || []) let alertMessage = `Solution submitted successfully.\n\n` alertMessage += `Overall Score: ${data.result?.score || 0}%\n` alertMessage += `Tests Passed: ${data.result?.passed_tests || 0}/${data.result?.total_tests || 1}\n` if (data.result?.execution_time) { alertMessage += `Execution Time: ${data.result.execution_time}s\n` } alertMessage += `\nCheck the leaderboard for your ranking.` alert(alertMessage) // ✅ FIXED: Controlled refresh sequence - clear previous timeouts console.log('🔄 Starting controlled leaderboard refresh...') // Clear any existing refresh timeouts refreshTimeoutRefs.current.forEach(timeout => clearTimeout(timeout)) refreshTimeoutRefs.current = [] // Single immediate refresh fetchLeaderboard(examCode) // One follow-up refresh after 3 seconds const refreshTimeout = setTimeout(() => { fetchLeaderboard(examCode) }, 3000) refreshTimeoutRefs.current.push(refreshTimeout) } else { alert(`Submission failed: ${data.error}`) } } catch (error) { console.error('Submit network error:', error) alert('Network error: Could not submit solution. Please try again.') } finally { setIsSubmitting(false) } } // ✅ Manual refresh function const manualRefresh = useCallback(() => { console.log('🔄 Manual refresh triggered') fetchLeaderboard(examCode) }, [examCode, fetchLeaderboard]) // Test Results Display Component const TestResultsDisplay = ({ results }: { results: any[] }) => { if (!results || results.length === 0) return null return (

Test Results

{results.map((result, index) => (
Test {index + 1}: {result.passed ? 'PASSED' : 'FAILED'} +{result.points_earned || 0} points
{result.description && result.description !== `Test case ${index+1}` && (

{result.description}

)}
{result.input && (
Input: "{result.input}"
)} {result.expected_output && (
Expected: "{result.expected_output}"
)} {result.actual_output && (
Your Output: "{result.actual_output}"
)}
{!result.passed && result.error && (
Error: {result.error}
)}
))}
) } const formatTime = (seconds: number) => { if (seconds < 0) return "00:00" const mins = Math.floor(seconds / 60) const secs = seconds % 60 return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` } const getRankColor = (rank: number) => { switch (rank) { case 1: return 'bg-gradient-to-r from-yellow-400 to-yellow-600 text-white' case 2: return 'bg-gradient-to-r from-gray-300 to-gray-500 text-white' case 3: return 'bg-gradient-to-r from-orange-400 to-orange-600 text-white' default: return 'bg-gray-100 text-gray-700' } } if (!examSession || !problem) { return (

Loading exam interface...

) } return (

{problem.title}

Code: {examCode} | Participant: {examSession.student_name}

{timeRemaining > 0 && (
{formatTime(timeRemaining)}
)}
{examStats.total_participants || 0}
{hasSubmitted && (
Submitted
)}
{leftTab === 'description' &&

{problem.description}

} {leftTab === 'examples' && (
{problem.examples.map((example, index) => (

Example {index + 1}

Input: {example.input}

Output: {example.expected_output}

{example.description ?

{example.description}

: null}
))}
)} {leftTab === 'constraints' && (
    {problem.constraints.map((constraint, index) => (
  • {constraint}
  • ))}
)}
Function: {problem.function_name}