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
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>