UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

236 lines (212 loc) • 10.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dashboard - MCP Quiz Server</title> <meta name="description" content="User dashboard for quiz management and analytics"> <!-- Progressive Enhancement: Critical CSS inline --> <style> /* Critical CSS for initial render */ .loading { display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; } .loading-spinner { width: 40px; height: 40px; border: 4px solid #f3f4f6; border-top: 4px solid #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } </style> <!-- External CSS (non-blocking) --> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <!-- Favicon --> <link rel="icon" type="image/x-icon" href="/favicon.ico"> <!-- Open Graph / Social Media --> <meta property="og:title" content="Dashboard - MCP Quiz Server"> <meta property="og:description" content="Manage your quizzes and track performance"> <meta property="og:type" content="website"> <!-- Security headers --> <meta http-equiv="Content-Security-Policy" content="default-src 'self' https://cdn.jsdelivr.net https://unpkg.com; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net;"> </head> <body class="bg-gray-50 font-sans"> <!-- Skip to main content link for accessibility --> <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-blue-600 text-white px-4 py-2 rounded-lg z-50"> Skip to main content </a> <!-- NoScript fallback for users without JavaScript --> <noscript> <div class="min-h-screen flex items-center justify-center bg-gray-50"> <div class="max-w-md mx-auto text-center p-6 bg-white rounded-lg shadow"> <h1 class="text-2xl font-bold text-gray-900 mb-4">Dashboard</h1> <p class="text-gray-600 mb-6">This application requires JavaScript to function properly. Please enable JavaScript in your browser.</p> <div class="space-y-4"> <p class="text-sm text-gray-500">Alternative options:</p> <a href="/auth/login.html" class="block bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"> Go to Login </a> <a href="/quiz" class="block bg-gray-100 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-200"> Browse Quizzes </a> </div> </div> </div> </noscript> <!-- Loading state (shown while JavaScript loads) --> <div id="initial-loading" class="loading"> <div class="text-center"> <div class="loading-spinner mx-auto mb-4"></div> <h1 class="text-xl font-semibold text-gray-900 mb-2">Loading Dashboard</h1> <p class="text-gray-600">Please wait while we prepare your dashboard...</p> <p class="text-sm text-gray-500 mt-4"> <span id="loading-timeout" style="display: none;"> Taking longer than expected? <a href="/auth/login.html" class="text-blue-600 hover:underline">Try logging in again</a> </span> </p> </div> </div> <!-- Application container (will be populated by TypeScript) --> <div id="dashboard-app" style="display: none;"> <!-- Dashboard component will render here --> </div> <!-- Error fallback (shown if JavaScript fails to load) --> <div id="error-fallback" style="display: none;" class="min-h-screen flex items-center justify-center bg-gray-50"> <div class="max-w-md mx-auto text-center p-6 bg-white rounded-lg shadow border-l-4 border-red-500"> <div class="text-red-500 mb-4"> <svg class="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> </div> <h1 class="text-xl font-semibold text-gray-900 mb-2">Dashboard Error</h1> <p class="text-gray-600 mb-6">Unable to load the dashboard. This may be due to a network issue or JavaScript being disabled.</p> <div class="space-y-3"> <button onclick="window.location.reload()" class="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"> Reload Page </button> <a href="/auth/login.html" class="block w-full bg-gray-100 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-200 text-center"> Go to Login </a> </div> </div> </div> <!-- Modern browser detection and progressive enhancement --> <script> // Check for modern browser features const isModernBrowser = 'fetch' in window && 'Promise' in window && 'Map' in window; if (!isModernBrowser) { document.getElementById('initial-loading').style.display = 'none'; document.getElementById('error-fallback').style.display = 'block'; // Update error message for legacy browsers const errorContainer = document.getElementById('error-fallback'); const errorText = errorContainer.querySelector('p'); errorText.textContent = 'This application requires a modern browser. Please update your browser or use a supported browser.'; } </script> <!-- Application JavaScript (ES modules) --> <script type="module"> // Set up error handling let hasLoadError = false; // Show timeout message after 10 seconds setTimeout(() => { if (document.getElementById('initial-loading').style.display !== 'none') { document.getElementById('loading-timeout').style.display = 'inline'; } }, 10000); // Global error handler window.addEventListener('error', (event) => { console.error('Application error:', event.error); if (!hasLoadError) { hasLoadError = true; showErrorFallback('Application failed to load due to an error.'); } }); window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); if (!hasLoadError) { hasLoadError = true; showErrorFallback('Application failed to load due to a promise rejection.'); } }); function showErrorFallback(message) { document.getElementById('initial-loading').style.display = 'none'; const errorContainer = document.getElementById('error-fallback'); errorContainer.style.display = 'block'; const errorText = errorContainer.querySelector('p'); if (errorText && message) { errorText.textContent = message; } } // Load application try { // Dynamic import for better error handling import('/js/main.js').then(async (module) => { try { // Initialize the application await module.initializeDashboard(); // Hide loading, show app document.getElementById('initial-loading').style.display = 'none'; document.getElementById('dashboard-app').style.display = 'block'; } catch (error) { console.error('Dashboard initialization failed:', error); showErrorFallback('Dashboard failed to initialize. Please try refreshing the page.'); } }).catch((error) => { console.error('Failed to load application module:', error); showErrorFallback('Failed to load dashboard. Please check your connection and try again.'); }); } catch (error) { console.error('Failed to import application:', error); showErrorFallback('Browser not supported. Please use a modern browser.'); } </script> <!-- Performance monitoring (optional) --> <script> // Simple performance monitoring window.addEventListener('load', () => { if ('performance' in window) { const timing = performance.timing; const loadTime = timing.loadEventEnd - timing.navigationStart; console.log(`Page load time: ${loadTime}ms`); // Report slow loads (> 3 seconds) if (loadTime > 3000) { console.warn('Slow page load detected:', loadTime + 'ms'); } } }); </script> <!-- Service Worker registration (for future PWA features) --> <script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').then((registration) => { console.log('ServiceWorker registration successful'); }).catch((error) => { console.log('ServiceWorker registration failed'); }); }); } </script> </body> </html>