1483 lines
49 KiB
HTML
1483 lines
49 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>SPEEDRUN ARENA - Admin Dashboard</title>
|
|
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
|
<!-- QR Code Scanner Library -->
|
|
<script src="https://unpkg.com/jsqr@1.4.0/dist/jsQR.js"></script>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: #0a0a0f;
|
|
color: #ffffff;
|
|
min-height: 100vh;
|
|
background-image:
|
|
radial-gradient(circle at 20% 80%, #1a1a2e 0%, transparent 50%),
|
|
radial-gradient(circle at 80% 20%, #16213e 0%, transparent 50%),
|
|
radial-gradient(circle at 40% 40%, #0f3460 0%, transparent 50%);
|
|
}
|
|
|
|
.main-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.header-section {
|
|
text-align: center;
|
|
margin-bottom: 3rem;
|
|
}
|
|
|
|
.main-title {
|
|
font-size: 4rem;
|
|
font-weight: 700;
|
|
background: linear-gradient(135deg, #00d4ff, #ff6b35, #ffd700);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
margin-bottom: 0.5rem;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.tagline {
|
|
font-size: 1.2rem;
|
|
color: #8892b0;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.nav-buttons {
|
|
position: fixed;
|
|
top: 2rem;
|
|
right: 2rem;
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.btn {
|
|
padding: 0.75rem 1.5rem;
|
|
border: none;
|
|
border-radius: 0.75rem;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #00d4ff, #0891b2);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
.btn-logout {
|
|
background: linear-gradient(135deg, #dc3545, #c82333);
|
|
color: white;
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px rgba(220, 53, 69, 0.3);
|
|
}
|
|
|
|
.dashboard-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.welcome-card {
|
|
background: rgba(15, 23, 42, 0.8);
|
|
border: 1px solid #1e293b;
|
|
border-radius: 1rem;
|
|
padding: 2rem;
|
|
backdrop-filter: blur(20px);
|
|
margin-bottom: 2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.card {
|
|
background: rgba(15, 23, 42, 0.8);
|
|
border: 1px solid #1e293b;
|
|
border-radius: 1rem;
|
|
padding: 2rem;
|
|
backdrop-filter: blur(20px);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: #00d4ff;
|
|
box-shadow: 0 10px 25px rgba(0, 212, 255, 0.1);
|
|
}
|
|
|
|
.card h3 {
|
|
color: #ffffff;
|
|
font-size: 1.3rem;
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.card p {
|
|
color: #8892b0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: #8892b0;
|
|
}
|
|
|
|
.spinner {
|
|
border: 3px solid #1e293b;
|
|
border-top: 3px solid #00d4ff;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 1rem;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: #8892b0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, #00d4ff, #0891b2);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* Modal Styles */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 2000;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.8);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.modal-content {
|
|
background: rgba(15, 23, 42, 0.95);
|
|
margin: 5% auto;
|
|
padding: 2rem;
|
|
border: 1px solid #1e293b;
|
|
border-radius: 1rem;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
backdrop-filter: blur(20px);
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.modal-title {
|
|
color: #ffffff;
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.close {
|
|
color: #8892b0;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.close:hover {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
color: #e2e8f0;
|
|
font-weight: 500;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
background: #1e293b;
|
|
border: 2px solid #334155;
|
|
border-radius: 0.5rem;
|
|
color: #ffffff;
|
|
font-size: 1rem;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #00d4ff;
|
|
}
|
|
|
|
.form-input::placeholder {
|
|
color: #64748b;
|
|
}
|
|
|
|
.message {
|
|
padding: 1rem;
|
|
border-radius: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.message.success {
|
|
background: rgba(34, 197, 94, 0.1);
|
|
color: #22c55e;
|
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
}
|
|
|
|
.message.error {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
color: #ef4444;
|
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
}
|
|
|
|
.message.info {
|
|
background: rgba(59, 130, 246, 0.1);
|
|
color: #3b82f6;
|
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
}
|
|
|
|
.player-selection {
|
|
display: grid;
|
|
gap: 0.5rem;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
background: #0f172a;
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.player-option {
|
|
padding: 0.75rem;
|
|
background: #1e293b;
|
|
border: 2px solid #334155;
|
|
border-radius: 0.5rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.player-option:hover {
|
|
border-color: #00d4ff;
|
|
background: #334155;
|
|
}
|
|
|
|
.player-option.selected {
|
|
border-color: #00d4ff;
|
|
background: rgba(0, 212, 255, 0.1);
|
|
}
|
|
|
|
.player-info {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.player-rfid {
|
|
color: #8892b0;
|
|
font-size: 0.9rem;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.times-grid {
|
|
display: grid;
|
|
gap: 1rem;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.time-card {
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.time-location {
|
|
color: #00d4ff;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.time-value {
|
|
color: #ffffff;
|
|
font-size: 1.2rem;
|
|
font-weight: bold;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.time-date {
|
|
color: #8892b0;
|
|
font-size: 0.9rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* Times Section Styles */
|
|
.times-section {
|
|
background: rgba(15, 23, 42, 0.8);
|
|
border: 1px solid #1e293b;
|
|
border-radius: 1rem;
|
|
padding: 2rem;
|
|
backdrop-filter: blur(20px);
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.times-header {
|
|
text-align: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.times-header h2 {
|
|
color: #ffffff;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.times-header p {
|
|
color: #8892b0;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.times-loading {
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: #8892b0;
|
|
}
|
|
|
|
.times-not-linked {
|
|
text-align: center;
|
|
padding: 3rem 2rem;
|
|
}
|
|
|
|
.not-linked-content {
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.not-linked-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.times-not-linked h3 {
|
|
color: #ffffff;
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.times-not-linked p {
|
|
color: #8892b0;
|
|
font-size: 1.1rem;
|
|
margin-bottom: 2rem;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.link-info {
|
|
background: rgba(0, 212, 255, 0.1);
|
|
border: 1px solid rgba(0, 212, 255, 0.3);
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
margin-top: 2rem;
|
|
text-align: left;
|
|
}
|
|
|
|
.link-info h4 {
|
|
color: #00d4ff;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.link-info ol {
|
|
color: #cbd5e1;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.link-info li {
|
|
margin-bottom: 0.5rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.times-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: rgba(0, 212, 255, 0.1);
|
|
border: 1px solid rgba(0, 212, 255, 0.3);
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: #00d4ff;
|
|
box-shadow: 0 10px 25px rgba(0, 212, 255, 0.1);
|
|
}
|
|
|
|
.stat-number {
|
|
color: #00d4ff;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
font-family: monospace;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #8892b0;
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.times-content {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.times-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.user-time-card {
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
border-radius: 0.75rem;
|
|
padding: 1.5rem;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.user-time-card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: #00d4ff;
|
|
box-shadow: 0 10px 25px rgba(0, 212, 255, 0.1);
|
|
}
|
|
|
|
.user-time-card.expanded {
|
|
transform: none;
|
|
border-color: #00d4ff;
|
|
box-shadow: 0 10px 25px rgba(0, 212, 255, 0.2);
|
|
}
|
|
|
|
.user-time-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, #00d4ff, #0891b2);
|
|
}
|
|
|
|
.time-location-name {
|
|
color: #00d4ff;
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.time-value-large {
|
|
color: #ffffff;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
font-family: monospace;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.time-date-info {
|
|
color: #8892b0;
|
|
font-size: 0.9rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.time-rank {
|
|
background: rgba(255, 107, 53, 0.1);
|
|
color: #ff6b35;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 0.375rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.expand-indicator {
|
|
color: #8892b0;
|
|
font-size: 1.2rem;
|
|
transition: transform 0.3s ease;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.user-time-card.expanded .expand-indicator {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.card-main-content {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.expanded-content {
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
transition: max-height 0.3s ease, opacity 0.3s ease;
|
|
opacity: 0;
|
|
}
|
|
|
|
.user-time-card.expanded .expanded-content {
|
|
max-height: 1000px;
|
|
opacity: 1;
|
|
}
|
|
|
|
.all-runs-title {
|
|
color: #00d4ff;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
margin-bottom: 1rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid #334155;
|
|
}
|
|
|
|
.run-item {
|
|
background: rgba(0, 212, 255, 0.05);
|
|
border: 1px solid rgba(0, 212, 255, 0.1);
|
|
border-radius: 0.5rem;
|
|
padding: 1rem;
|
|
margin-bottom: 0.75rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.run-item:hover {
|
|
background: rgba(0, 212, 255, 0.1);
|
|
border-color: rgba(0, 212, 255, 0.3);
|
|
}
|
|
|
|
.run-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.run-time {
|
|
color: #ffffff;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.run-details {
|
|
text-align: right;
|
|
color: #8892b0;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.run-rank-badge {
|
|
background: #00d4ff;
|
|
color: #0a0a0f;
|
|
padding: 0.2rem 0.5rem;
|
|
border-radius: 0.3rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.run-rank-badge.best {
|
|
background: #22c55e;
|
|
}
|
|
|
|
.run-rank-badge.second {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.run-rank-badge.third {
|
|
background: #ef4444;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.header {
|
|
padding: 1rem;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.nav-buttons {
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.container {
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.dashboard-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.modal-content {
|
|
margin: 2% auto;
|
|
width: 95%;
|
|
padding: 1.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="main-container">
|
|
<div class="nav-buttons">
|
|
<div class="user-info">
|
|
<div class="user-avatar" id="userAvatar">U</div>
|
|
<span id="userEmail">user@example.com</span>
|
|
</div>
|
|
<a href="/" class="btn btn-primary">Back to Times</a>
|
|
<button class="btn btn-logout" onclick="logout()">Logout</button>
|
|
</div>
|
|
|
|
<div class="header-section">
|
|
<h1 class="main-title">ADMIN DASHBOARD</h1>
|
|
<p class="tagline">Verwalte deine SPEEDRUN ARENA</p>
|
|
</div>
|
|
<div id="loading" class="loading">
|
|
<div class="spinner"></div>
|
|
<p>Loading dashboard...</p>
|
|
</div>
|
|
|
|
<div id="dashboardContent" style="display: none;">
|
|
<div class="welcome-card">
|
|
<h2>Admin Dashboard 🥷</h2>
|
|
<p>Welcome to the admin panel! Manage your ninja server from here.</p>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<div class="card">
|
|
<h3>📊 Analytics</h3>
|
|
<p>Track your performance and monitor key metrics. This section will show detailed analytics once we implement the functionality.</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>⚡ Quick Actions</h3>
|
|
<p>Common tasks and shortcuts will be available here. We'll add buttons for creating new records, managing settings, and more.</p>
|
|
</div>
|
|
|
|
<div class="card" onclick="showRFIDSettings()" style="cursor: pointer;">
|
|
<h3>🏷️ RFID Verknüpfung</h3>
|
|
<p>Verknüpfe deine RFID-Karte mit deinem Account, um deine Zeiten automatisch zu tracken.</p>
|
|
<button class="btn btn-primary" style="margin-top: 1rem;" onclick="event.stopPropagation(); showRFIDSettings();">RFID verknüpfen</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>📊 Statistiken</h3>
|
|
<p>Hier werden bald detaillierte Statistiken zu deinen Läufen angezeigt - beste Zeiten, Verbesserungen und Vergleiche mit anderen Spielern.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Times Section -->
|
|
<div class="times-section">
|
|
<div class="times-header">
|
|
<h2>🏃♂️ Meine Zeiten</h2>
|
|
<p>Deine persönlichen Bestzeiten an allen Standorten</p>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div id="timesLoading" class="times-loading" style="display: none;">
|
|
<div class="spinner"></div>
|
|
<p>Lade deine Zeiten...</p>
|
|
</div>
|
|
|
|
<!-- Not Linked State -->
|
|
<div id="timesNotLinked" class="times-not-linked">
|
|
<div class="not-linked-content">
|
|
<div class="not-linked-icon">🔗</div>
|
|
<h3>RFID noch nicht verknüpft</h3>
|
|
<p>Um deine persönlichen Zeiten zu sehen, musst du zuerst deine RFID-Karte mit deinem Account verknüpfen.</p>
|
|
<button class="btn btn-primary" onclick="showRFIDSettings()">
|
|
🏷️ RFID jetzt verknüpfen
|
|
</button>
|
|
<div class="link-info">
|
|
<h4>So funktioniert's:</h4>
|
|
<ol>
|
|
<li>Klicke auf "RFID jetzt verknüpfen"</li>
|
|
<li>Scanne den QR-Code auf deiner RFID-Karte</li>
|
|
<li>Fertig! Deine Zeiten werden automatisch hier angezeigt</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Times Display -->
|
|
<div id="timesDisplay" style="display: none;">
|
|
<div class="times-stats">
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="totalRuns">0</div>
|
|
<div class="stat-label">Gesamte Läufe</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="bestTime">--:--</div>
|
|
<div class="stat-label">Beste Zeit</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="locationsCount">0</div>
|
|
<div class="stat-label">Standorte</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="linkedPlayer">--</div>
|
|
<div class="stat-label">Verknüpfter Spieler</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="times-content">
|
|
<div class="times-grid" id="userTimesGrid">
|
|
<!-- Times will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RFID Settings Modal -->
|
|
<div id="rfidModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">📱 RFID QR-Code Scanner</h2>
|
|
<span class="close" onclick="closeModal('rfidModal')">×</span>
|
|
</div>
|
|
<div id="rfidMessage"></div>
|
|
|
|
<!-- QR Scanner Step -->
|
|
<div id="qrScannerStep">
|
|
<p style="color: #8892b0; margin-bottom: 1.5rem; text-align: center;">
|
|
Scanne den QR-Code auf deiner RFID-Karte, um sie mit deinem Account zu verknüpfen.
|
|
</p>
|
|
|
|
<!-- Camera Preview -->
|
|
<div id="cameraContainer" style="display: none;">
|
|
<video id="qrVideo" style="width: 100%; max-width: 400px; border-radius: 0.75rem; margin: 0 auto; display: block;"></video>
|
|
<canvas id="qrCanvas" style="display: none;"></canvas>
|
|
</div>
|
|
|
|
<!-- Scanner Controls -->
|
|
<div style="text-align: center; margin: 1.5rem 0;">
|
|
<button class="btn btn-primary" onclick="startQRScanner()" id="startScanBtn">
|
|
📷 Kamera starten
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="stopQRScanner()" id="stopScanBtn" style="display: none;">
|
|
🛑 Scanner stoppen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Manual Input Fallback -->
|
|
<div style="border-top: 1px solid #334155; padding-top: 1.5rem; margin-top: 1.5rem;">
|
|
<p style="color: #8892b0; text-align: center; margin-bottom: 1rem; font-size: 0.9rem;">
|
|
Kamera funktioniert nicht? RFID UID manuell eingeben:
|
|
</p>
|
|
<div class="form-group">
|
|
<input type="text" id="manualRfidInput" class="form-input" placeholder="z.B. aaaaaa, FFFFFF oder FF:FF:FF:FF" style="text-align: center; font-family: monospace;">
|
|
</div>
|
|
<button class="btn btn-secondary" onclick="linkManualRfid()" style="width: 100%;">
|
|
Manuell verknüpfen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Scanning Status -->
|
|
<div id="scanningStatus" style="display: none; text-align: center; color: #00d4ff; margin-top: 1rem;">
|
|
<div class="spinner" style="width: 20px; height: 20px; margin: 0 auto 0.5rem;"></div>
|
|
Suche nach QR-Code...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
|
// Supabase configuration
|
|
const SUPABASE_URL = 'https://lfxlplnypzvjrhftaoog.supabase.co';
|
|
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxmeGxwbG55cHp2anJoZnRhb29nIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDkyMTQ3NzIsImV4cCI6MjA2NDc5MDc3Mn0.XR4preBqWAQ1rT4PFbpkmRdz57BTwIusBI89fIxDHM8';
|
|
|
|
// Initialize Supabase client
|
|
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
|
|
|
// Check authentication and load dashboard
|
|
async function initDashboard() {
|
|
try {
|
|
// Get current session
|
|
const { data: { session }, error } = await supabase.auth.getSession();
|
|
|
|
if (error) {
|
|
console.error('Error checking authentication:', error);
|
|
// Temporarily show dashboard for testing
|
|
displayUserInfo({ email: 'admin@speedrun-arena.com' });
|
|
showDashboard();
|
|
return;
|
|
}
|
|
|
|
if (!session) {
|
|
// No session, redirect to login
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
// User is authenticated, show dashboard
|
|
if (session.user) {
|
|
console.log('User data:', session.user);
|
|
displayUserInfo(session.user);
|
|
} else {
|
|
// Fallback if no user data
|
|
displayUserInfo({ email: 'admin@speedrun-arena.com' });
|
|
}
|
|
showDashboard();
|
|
|
|
} catch (error) {
|
|
console.error('An unexpected error occurred:', error);
|
|
// window.location.href = '/login';
|
|
}
|
|
}
|
|
|
|
// Display user information
|
|
function displayUserInfo(user) {
|
|
const userEmail = document.getElementById('userEmail');
|
|
const userAvatar = document.getElementById('userAvatar');
|
|
|
|
userEmail.textContent = user.email;
|
|
userAvatar.textContent = user.email.charAt(0).toUpperCase();
|
|
}
|
|
|
|
// Show dashboard content
|
|
function showDashboard() {
|
|
document.getElementById('loading').style.display = 'none';
|
|
document.getElementById('dashboardContent').style.display = 'block';
|
|
}
|
|
|
|
// Logout function
|
|
async function logout() {
|
|
try {
|
|
const { error } = await supabase.auth.signOut();
|
|
if (error) {
|
|
console.error('Error logging out:', error);
|
|
} else {
|
|
window.location.href = '/';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during logout:', error);
|
|
}
|
|
}
|
|
|
|
// Listen for auth state changes
|
|
supabase.auth.onAuthStateChange((event, session) => {
|
|
if (event === 'SIGNED_OUT' || !session) {
|
|
window.location.href = '/login';
|
|
}
|
|
});
|
|
|
|
// Global variables
|
|
let currentUser = null;
|
|
|
|
// Initialize dashboard when page loads
|
|
initDashboard();
|
|
|
|
// Modal functions
|
|
function openModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'block';
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'none';
|
|
// Reset modal state
|
|
if (modalId === 'rfidModal') {
|
|
stopQRScanner();
|
|
document.getElementById('manualRfidInput').value = '';
|
|
}
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.onclick = function(event) {
|
|
if (event.target.classList.contains('modal')) {
|
|
closeModal(event.target.id);
|
|
}
|
|
}
|
|
|
|
// QR Scanner variables
|
|
let qrStream = null;
|
|
let qrScanning = false;
|
|
|
|
// Show RFID Settings
|
|
async function showRFIDSettings() {
|
|
openModal('rfidModal');
|
|
// Reset scanner state
|
|
stopQRScanner();
|
|
}
|
|
|
|
// Check link status and load times
|
|
async function checkLinkStatusAndLoadTimes() {
|
|
if (!currentUser) {
|
|
showTimesNotLinked();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Check if user has a linked player
|
|
const response = await fetch(`/api/user-player/${currentUser.id}`);
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
// User is linked, load times
|
|
await loadUserTimesSection(result.data);
|
|
} else {
|
|
// User is not linked
|
|
showTimesNotLinked();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking link status:', error);
|
|
showTimesNotLinked();
|
|
}
|
|
}
|
|
|
|
// Start QR Scanner
|
|
async function startQRScanner() {
|
|
try {
|
|
// Request camera access
|
|
qrStream = await navigator.mediaDevices.getUserMedia({
|
|
video: {
|
|
facingMode: 'environment', // Use back camera if available
|
|
width: { ideal: 1280 },
|
|
height: { ideal: 720 }
|
|
}
|
|
});
|
|
|
|
const video = document.getElementById('qrVideo');
|
|
const canvas = document.getElementById('qrCanvas');
|
|
const context = canvas.getContext('2d');
|
|
|
|
video.srcObject = qrStream;
|
|
video.play();
|
|
|
|
// Show camera container and update buttons
|
|
document.getElementById('cameraContainer').style.display = 'block';
|
|
document.getElementById('startScanBtn').style.display = 'none';
|
|
document.getElementById('stopScanBtn').style.display = 'inline-block';
|
|
document.getElementById('scanningStatus').style.display = 'block';
|
|
|
|
qrScanning = true;
|
|
|
|
// Start scanning loop
|
|
video.addEventListener('loadedmetadata', () => {
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
scanQRCode();
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error accessing camera:', error);
|
|
showMessage('rfidMessage', 'Kamera-Zugriff fehlgeschlagen. Bitte verwende die manuelle Eingabe.', 'error');
|
|
}
|
|
}
|
|
|
|
// Stop QR Scanner
|
|
function stopQRScanner() {
|
|
qrScanning = false;
|
|
|
|
if (qrStream) {
|
|
qrStream.getTracks().forEach(track => track.stop());
|
|
qrStream = null;
|
|
}
|
|
|
|
// Reset UI
|
|
document.getElementById('cameraContainer').style.display = 'none';
|
|
document.getElementById('startScanBtn').style.display = 'inline-block';
|
|
document.getElementById('stopScanBtn').style.display = 'none';
|
|
document.getElementById('scanningStatus').style.display = 'none';
|
|
}
|
|
|
|
// Scan QR Code from video stream
|
|
function scanQRCode() {
|
|
if (!qrScanning) return;
|
|
|
|
const video = document.getElementById('qrVideo');
|
|
const canvas = document.getElementById('qrCanvas');
|
|
const context = canvas.getContext('2d');
|
|
|
|
if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
const code = jsQR(imageData.data, imageData.width, imageData.height);
|
|
|
|
if (code) {
|
|
console.log('QR Code detected:', code.data);
|
|
handleQRCodeDetected(code.data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Continue scanning
|
|
if (qrScanning) {
|
|
requestAnimationFrame(scanQRCode);
|
|
}
|
|
}
|
|
|
|
// Format RFID UID to match database format
|
|
function formatRfidUid(rawUid) {
|
|
// Remove any existing formatting (spaces, colons, etc.)
|
|
let cleanUid = rawUid.replace(/[^a-fA-F0-9]/g, '').toUpperCase();
|
|
|
|
// Handle different UID lengths
|
|
if (cleanUid.length === 6) {
|
|
// Pad 6-digit UID to 8 digits by adding leading zeros
|
|
cleanUid = '00' + cleanUid;
|
|
} else if (cleanUid.length === 8) {
|
|
// Already correct length
|
|
} else if (cleanUid.length < 6) {
|
|
// Pad shorter UIDs to 8 digits
|
|
cleanUid = cleanUid.padStart(8, '0');
|
|
} else {
|
|
throw new Error(`Ungültige RFID UID Länge: ${cleanUid.length} Zeichen (unterstützt: 6-8)`);
|
|
}
|
|
|
|
// Format as XX:XX:XX:XX
|
|
return cleanUid.match(/.{2}/g).join(':');
|
|
}
|
|
|
|
// Handle detected QR code
|
|
async function handleQRCodeDetected(qrData) {
|
|
stopQRScanner();
|
|
|
|
try {
|
|
// Extract and format RFID UID from QR code
|
|
const rawUid = qrData.trim();
|
|
|
|
if (!rawUid) {
|
|
showMessage('rfidMessage', 'QR-Code enthält keine gültige RFID UID', 'error');
|
|
return;
|
|
}
|
|
|
|
// Format the UID to match database format (XX:XX:XX:XX)
|
|
const formattedUid = formatRfidUid(rawUid);
|
|
|
|
showMessage('rfidMessage', `QR-Code erkannt: ${rawUid} → ${formattedUid}`, 'info');
|
|
|
|
// Link the user using the formatted RFID UID
|
|
await linkUserByRfidUid(formattedUid);
|
|
|
|
} catch (error) {
|
|
console.error('Error formatting RFID UID:', error);
|
|
showMessage('rfidMessage', `Fehler beim Formatieren der RFID UID: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Manual RFID linking
|
|
async function linkManualRfid() {
|
|
const rawUid = document.getElementById('manualRfidInput').value.trim();
|
|
|
|
if (!rawUid) {
|
|
showMessage('rfidMessage', 'Bitte gib eine RFID UID ein', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Format the UID to match database format
|
|
const formattedUid = formatRfidUid(rawUid);
|
|
|
|
showMessage('rfidMessage', `Formatiert: ${rawUid} → ${formattedUid}`, 'info');
|
|
|
|
await linkUserByRfidUid(formattedUid);
|
|
|
|
} catch (error) {
|
|
console.error('Error formatting manual RFID UID:', error);
|
|
showMessage('rfidMessage', `Fehler beim Formatieren: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Link user by RFID UID (core function)
|
|
async function linkUserByRfidUid(rfidUid) {
|
|
if (!currentUser) {
|
|
showMessage('rfidMessage', 'Benutzer nicht authentifiziert', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// First, find the player with this RFID UID
|
|
const response = await fetch('/api/link-by-rfid', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
rfiduid: rfidUid,
|
|
supabase_user_id: currentUser.id
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
showMessage('rfidMessage', `✅ RFID erfolgreich verknüpft!\nSpieler: ${result.data.firstname} ${result.data.lastname}`, 'success');
|
|
setTimeout(() => {
|
|
closeModal('rfidModal');
|
|
// Reload times section after successful linking
|
|
checkLinkStatusAndLoadTimes();
|
|
}, 2000);
|
|
} else {
|
|
showMessage('rfidMessage', result.message || 'Fehler beim Verknüpfen', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error linking RFID:', error);
|
|
showMessage('rfidMessage', 'Fehler beim Verknüpfen der RFID', 'error');
|
|
}
|
|
}
|
|
|
|
// Show not linked state
|
|
function showTimesNotLinked() {
|
|
document.getElementById('timesLoading').style.display = 'none';
|
|
document.getElementById('timesNotLinked').style.display = 'block';
|
|
document.getElementById('timesDisplay').style.display = 'none';
|
|
}
|
|
|
|
// Show loading state
|
|
function showTimesLoading() {
|
|
document.getElementById('timesLoading').style.display = 'block';
|
|
document.getElementById('timesNotLinked').style.display = 'none';
|
|
document.getElementById('timesDisplay').style.display = 'none';
|
|
}
|
|
|
|
// Load user times for the section
|
|
async function loadUserTimesSection(playerData) {
|
|
showTimesLoading();
|
|
|
|
try {
|
|
const response = await fetch(`/api/user-times/${currentUser.id}`);
|
|
const times = await response.json();
|
|
|
|
// Update stats
|
|
updateTimesStats(times, playerData);
|
|
|
|
// Display times
|
|
displayUserTimes(times);
|
|
|
|
// Show the times display
|
|
document.getElementById('timesLoading').style.display = 'none';
|
|
document.getElementById('timesNotLinked').style.display = 'none';
|
|
document.getElementById('timesDisplay').style.display = 'block';
|
|
|
|
} catch (error) {
|
|
console.error('Error loading user times:', error);
|
|
showTimesNotLinked();
|
|
}
|
|
}
|
|
|
|
// Update stats cards
|
|
function updateTimesStats(times, playerData) {
|
|
// Total runs
|
|
document.getElementById('totalRuns').textContent = times.length;
|
|
|
|
// Best time
|
|
if (times.length > 0) {
|
|
const bestTimeValue = times.reduce((best, current) => {
|
|
const currentSeconds = convertTimeToSeconds(current.recorded_time);
|
|
const bestSeconds = convertTimeToSeconds(best.recorded_time);
|
|
return currentSeconds < bestSeconds ? current : best;
|
|
});
|
|
document.getElementById('bestTime').textContent = formatTime(bestTimeValue.recorded_time);
|
|
} else {
|
|
document.getElementById('bestTime').textContent = '--:--';
|
|
}
|
|
|
|
// Unique locations count
|
|
const uniqueLocations = [...new Set(times.map(time => time.location_name))];
|
|
document.getElementById('locationsCount').textContent = uniqueLocations.length;
|
|
|
|
// Linked player name
|
|
document.getElementById('linkedPlayer').textContent = `${playerData.firstname} ${playerData.lastname}`;
|
|
}
|
|
|
|
// Display user times in grid
|
|
function displayUserTimes(times) {
|
|
const timesGrid = document.getElementById('userTimesGrid');
|
|
|
|
if (times.length === 0) {
|
|
timesGrid.innerHTML = `
|
|
<div style="grid-column: 1 / -1; text-align: center; padding: 3rem; color: #8892b0;">
|
|
<h3>Noch keine Zeiten aufgezeichnet</h3>
|
|
<p>Deine ersten Läufe werden hier angezeigt, sobald du sie abgeschlossen hast!</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Group times by location
|
|
const timesByLocation = times.reduce((acc, time) => {
|
|
if (!acc[time.location_name]) {
|
|
acc[time.location_name] = [];
|
|
}
|
|
acc[time.location_name].push(time);
|
|
return acc;
|
|
}, {});
|
|
|
|
// Generate cards for each location
|
|
const cards = Object.entries(timesByLocation).map(([locationName, locationTimes], index) => {
|
|
// Sort times by performance (best first)
|
|
const sortedTimes = locationTimes.sort((a, b) => {
|
|
return convertTimeToSeconds(a.recorded_time) - convertTimeToSeconds(b.recorded_time);
|
|
});
|
|
|
|
// Get best time for this location
|
|
const bestTime = sortedTimes[0];
|
|
|
|
// Generate all runs for expanded view
|
|
const allRunsHtml = sortedTimes.map((run, runIndex) => {
|
|
let rankBadge = '';
|
|
let rankClass = '';
|
|
|
|
if (runIndex === 0) {
|
|
rankBadge = '🥇 Beste';
|
|
rankClass = 'best';
|
|
} else if (runIndex === 1) {
|
|
rankBadge = '🥈 2.';
|
|
rankClass = 'second';
|
|
} else if (runIndex === 2) {
|
|
rankBadge = '🥉 3.';
|
|
rankClass = 'third';
|
|
} else {
|
|
rankBadge = `${runIndex + 1}.`;
|
|
rankClass = '';
|
|
}
|
|
|
|
return `
|
|
<div class="run-item">
|
|
<div>
|
|
<div class="run-time">${formatTime(run.recorded_time)}</div>
|
|
</div>
|
|
<div class="run-details">
|
|
<div>${new Date(run.created_at).toLocaleDateString('de-DE')}</div>
|
|
<div>${new Date(run.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</div>
|
|
<span class="run-rank-badge ${rankClass}">${rankBadge}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
return `
|
|
<div class="user-time-card" onclick="toggleTimeCard(this)" data-location="${locationName}">
|
|
<div class="card-header">
|
|
<div class="time-location-name">${locationName}</div>
|
|
<div class="expand-indicator">▼</div>
|
|
</div>
|
|
|
|
<div class="card-main-content">
|
|
<div class="time-value-large">${formatTime(bestTime.recorded_time)}</div>
|
|
<div class="time-date-info">
|
|
<span>${new Date(bestTime.created_at).toLocaleDateString('de-DE')}</span>
|
|
<span class="time-rank">${locationTimes.length} Läufe</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="expanded-content">
|
|
<div class="all-runs-title">Alle Läufe an diesem Standort:</div>
|
|
${allRunsHtml}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
timesGrid.innerHTML = cards;
|
|
}
|
|
|
|
// Toggle time card expansion
|
|
function toggleTimeCard(cardElement) {
|
|
const isExpanded = cardElement.classList.contains('expanded');
|
|
|
|
// Close all other cards first
|
|
document.querySelectorAll('.user-time-card.expanded').forEach(card => {
|
|
if (card !== cardElement) {
|
|
card.classList.remove('expanded');
|
|
}
|
|
});
|
|
|
|
// Toggle current card
|
|
if (isExpanded) {
|
|
cardElement.classList.remove('expanded');
|
|
} else {
|
|
cardElement.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
// Helper function to convert time to seconds for comparison
|
|
function convertTimeToSeconds(timeValue) {
|
|
if (typeof timeValue === 'string') {
|
|
// Handle HH:MM:SS format
|
|
const parts = timeValue.split(':');
|
|
if (parts.length === 3) {
|
|
return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseFloat(parts[2]);
|
|
}
|
|
// Handle MM:SS format
|
|
if (parts.length === 2) {
|
|
return parseInt(parts[0]) * 60 + parseFloat(parts[1]);
|
|
}
|
|
}
|
|
return parseFloat(timeValue) || 0;
|
|
}
|
|
|
|
// Format time interval to readable format
|
|
function formatTime(interval) {
|
|
// Postgres interval format: {"hours":0,"minutes":1,"seconds":23.45}
|
|
if (typeof interval === 'object') {
|
|
const { hours = 0, minutes = 0, seconds = 0 } = interval;
|
|
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
|
return formatSeconds(totalSeconds);
|
|
}
|
|
|
|
// Fallback for string format
|
|
if (typeof interval === 'string') {
|
|
// Parse format like "00:01:23.45"
|
|
const parts = interval.split(':');
|
|
if (parts.length === 3) {
|
|
const hours = parseInt(parts[0]);
|
|
const minutes = parseInt(parts[1]);
|
|
const seconds = parseFloat(parts[2]);
|
|
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
|
return formatSeconds(totalSeconds);
|
|
}
|
|
}
|
|
|
|
return interval;
|
|
}
|
|
|
|
function formatSeconds(totalSeconds) {
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = (totalSeconds % 60).toFixed(2);
|
|
|
|
if (minutes > 0) {
|
|
return `${minutes}:${seconds.padStart(5, '0')}`;
|
|
} else {
|
|
return `${seconds}s`;
|
|
}
|
|
}
|
|
|
|
// Show message in modal
|
|
function showMessage(containerId, message, type) {
|
|
const container = document.getElementById(containerId);
|
|
container.innerHTML = `<div class="message ${type}">${message}</div>`;
|
|
}
|
|
|
|
// Update initDashboard to store current user and load times
|
|
async function initDashboard() {
|
|
try {
|
|
// Get current session
|
|
const { data: { session }, error } = await supabase.auth.getSession();
|
|
|
|
if (error) {
|
|
console.error('Error checking authentication:', error);
|
|
// Temporarily show dashboard for testing
|
|
currentUser = { id: 'test-user', email: 'admin@speedrun-arena.com' };
|
|
displayUserInfo({ email: 'admin@speedrun-arena.com' });
|
|
showDashboard();
|
|
// Check times section
|
|
checkLinkStatusAndLoadTimes();
|
|
return;
|
|
}
|
|
|
|
if (!session) {
|
|
// No session, redirect to login
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
// User is authenticated, show dashboard
|
|
if (session.user) {
|
|
console.log('User data:', session.user);
|
|
currentUser = session.user;
|
|
displayUserInfo(session.user);
|
|
} else {
|
|
// Fallback if no user data
|
|
currentUser = { id: 'test-user', email: 'admin@speedrun-arena.com' };
|
|
displayUserInfo({ email: 'admin@speedrun-arena.com' });
|
|
}
|
|
showDashboard();
|
|
|
|
// Load times section
|
|
checkLinkStatusAndLoadTimes();
|
|
|
|
} catch (error) {
|
|
console.error('An unexpected error occurred:', error);
|
|
// window.location.href = '/login';
|
|
}
|
|
}
|
|
</script>
|
|
</div>
|
|
</body>
|
|
</html>
|