Initial local commit

This commit is contained in:
2025-09-03 11:07:29 +02:00
commit e6cf08893e
20 changed files with 11387 additions and 0 deletions

315
public/adminlogin.html Normal file
View File

@@ -0,0 +1,315 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Lizenzgenerator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 400px;
width: 100%;
position: relative;
overflow: hidden;
}
.login-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2, #f093fb, #f5576c);
background-size: 300% 100%;
animation: gradientShift 3s ease infinite;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2em;
font-weight: 300;
letter-spacing: -1px;
}
.form-group {
margin-bottom: 25px;
position: relative;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
font-size: 0.95em;
}
input {
width: 100%;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 12px;
font-size: 1em;
transition: all 0.3s ease;
background: #fafafa;
font-family: inherit;
}
input:focus {
outline: none;
border-color: #667eea;
background: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
input:hover {
border-color: #ccc;
}
.login-btn {
width: 100%;
padding: 18px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
position: relative;
overflow: hidden;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
.login-btn:active {
transform: translateY(0);
}
.login-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.error {
background: #ffebee;
color: #c62828;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
border-left: 4px solid #f44336;
font-size: 0.9em;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
}
.error.show {
opacity: 1;
transform: translateY(0);
}
.success {
background: #e8f5e8;
color: #2e7d32;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
border-left: 4px solid #4caf50;
font-size: 0.9em;
opacity: 0;
transform: translateY(-10px);
transition: all 0.3s ease;
}
.success.show {
opacity: 1;
transform: translateY(0);
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.info-text {
text-align: center;
color: #666;
font-size: 0.85em;
margin-top: 20px;
line-height: 1.5;
}
@media (max-width: 480px) {
.login-container {
padding: 30px 20px;
margin: 10px;
}
h1 {
font-size: 1.6em;
}
}
</style>
</head>
<body>
<div class="login-container">
<h1>🔐 Anmeldung</h1>
<form id="loginForm" onsubmit="handleLogin(event)">
<div class="form-group">
<label for="username">Benutzername</label>
<input type="text" id="username" name="username" placeholder="Ihr Benutzername" required>
</div>
<div class="form-group">
<label for="password">Passwort</label>
<input type="password" id="password" name="password" placeholder="Ihr Passwort" required>
</div>
<button type="submit" class="login-btn" id="loginBtn">
<span id="btn-text">Anmelden</span>
</button>
</form>
<div id="error" class="error"></div>
<div id="success" class="success"></div>
<div class="info-text">
Melden Sie sich an, um auf den Lizenzgenerator zuzugreifen.
</div>
</div>
<script>
function showMessage(elementId, message, isError = false) {
const messageDiv = document.getElementById(elementId);
messageDiv.textContent = message;
messageDiv.classList.add("show");
setTimeout(() => {
messageDiv.classList.remove("show");
}, 4000);
}
function showError(message) {
showMessage("error", message, true);
}
function showSuccess(message) {
showMessage("success", message, false);
}
function setLoading(isLoading) {
const btnText = document.getElementById("btn-text");
const btn = document.getElementById("loginBtn");
if (isLoading) {
btnText.innerHTML = '<span class="loading"></span>Anmelde...';
btn.disabled = true;
} else {
btnText.textContent = 'Anmelden';
btn.disabled = false;
}
}
async function handleLogin(event) {
event.preventDefault();
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
if (!username || !password) {
showError('Bitte füllen Sie alle Felder aus.');
return;
}
setLoading(true);
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
});
const result = await response.json();
if (result.success) {
showSuccess('✅ Anmeldung erfolgreich! Weiterleitung...');
setTimeout(() => {
window.location.href = '/generator';
}, 1000);
} else {
showError(result.message || 'Anmeldung fehlgeschlagen');
}
} catch (error) {
console.error('Fehler bei der Anmeldung:', error);
showError('Verbindungsfehler. Bitte versuchen Sie es erneut.');
} finally {
setLoading(false);
}
}
// Enter-Taste für Login
document.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
handleLogin(e);
}
});
// Fokus auf erstes Eingabefeld
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('username').focus();
});
</script>
</body>
</html>

319
public/dashboard.html Normal file
View File

@@ -0,0 +1,319 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ninja Server - Admin Dashboard</title>
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
}
.logo h1 {
color: #333;
font-size: 1.5rem;
font-weight: 700;
}
.nav-buttons {
display: flex;
gap: 1rem;
align-items: center;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
transform: translateY(-1px);
}
.btn-logout {
background: #dc3545;
color: white;
}
.btn-logout:hover {
background: #c82333;
transform: translateY(-1px);
}
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 2rem;
}
.welcome-card {
background: white;
border-radius: 20px;
padding: 2rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
margin-bottom: 2rem;
text-align: center;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.card {
background: white;
border-radius: 15px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
}
.card h3 {
color: #333;
font-size: 1.3rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card p {
color: #666;
line-height: 1.6;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
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: #666;
font-size: 0.9rem;
}
.user-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
@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;
}
}
</style>
</head>
<body>
<div class="header">
<div class="logo">
<h1>🥷 Ninja Server - Admin</h1>
</div>
<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="index.html" class="btn btn-primary">Back to Times</a>
<button class="btn btn-logout" onclick="logout()">Logout</button>
</div>
</div>
<div class="container">
<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">
<h3><EFBFBD><EFBFBD> Settings</h3>
<p>Configure your account preferences, security settings, and application options. This will be fully functional in the next phase.</p>
</div>
<div class="card">
<h3>📝 Recent Activity</h3>
<p>View your recent actions and system events. This will show a timeline of your activities once we connect it to the database.</p>
</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 {
const { data: { session }, error } = await supabase.auth.getSession();
if (error) {
console.error('Error checking authentication:', error);
window.location.href = 'login.html';
return;
}
if (!session) {
// No session, redirect to login
window.location.href = 'login.html';
return;
}
// User is authenticated, show dashboard
displayUserInfo(session.user);
showDashboard();
} catch (error) {
console.error('An unexpected error occurred:', error);
window.location.href = 'login.html';
}
}
// 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 = 'index.html';
}
} 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.html';
}
});
// Initialize dashboard when page loads
initDashboard();
</script>
</body>
</html>

1052
public/generator.html Normal file

File diff suppressed because it is too large Load Diff

426
public/index.html Normal file
View File

@@ -0,0 +1,426 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ninja Server - Times Display</title>
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.logo h1 {
color: #333;
font-size: 1.8rem;
font-weight: 700;
}
.nav-buttons {
display: flex;
gap: 1rem;
align-items: center;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-admin {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-admin:hover {
transform: translateY(-1px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.btn-dashboard {
background: #28a745;
color: white;
}
.btn-dashboard:hover {
background: #218838;
transform: translateY(-1px);
}
.btn-logout {
background: #dc3545;
color: white;
}
.btn-logout:hover {
background: #c82333;
transform: translateY(-1px);
}
.container {
max-width: 1200px;
margin: 2rem auto;
padding: 0 2rem;
}
.welcome-card {
background: white;
border-radius: 20px;
padding: 2rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
margin-bottom: 2rem;
text-align: center;
}
.welcome-card h2 {
color: #333;
font-size: 2rem;
margin-bottom: 1rem;
}
.welcome-card p {
color: #666;
font-size: 1.1rem;
line-height: 1.6;
}
.times-section {
background: white;
border-radius: 20px;
padding: 2rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
margin-bottom: 2rem;
}
.times-section h3 {
color: #333;
font-size: 1.5rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.times-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.time-card {
background: #f8f9fa;
border-radius: 15px;
padding: 1.5rem;
border-left: 4px solid #667eea;
transition: transform 0.3s ease;
}
.time-card:hover {
transform: translateY(-2px);
}
.time-card h4 {
color: #333;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
.time-card p {
color: #666;
margin-bottom: 0.25rem;
font-size: 0.9rem;
}
.time-value {
font-size: 1.2rem;
font-weight: bold;
color: #667eea;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
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); }
}
.no-data {
text-align: center;
padding: 2rem;
color: #666;
font-style: italic;
}
.user-info {
display: flex;
align-items: center;
gap: 0.5rem;
color: #666;
font-size: 0.9rem;
}
.user-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
@media (max-width: 768px) {
.header {
padding: 1rem;
flex-direction: column;
gap: 1rem;
}
.nav-buttons {
flex-wrap: wrap;
justify-content: center;
}
.container {
padding: 0 1rem;
}
.times-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="header">
<div class="logo">
<h1>🥷 Ninja Server</h1>
</div>
<div class="nav-buttons">
<div id="userInfo" class="user-info" style="display: none;">
<div class="user-avatar" id="userAvatar">U</div>
<span id="userEmail">user@example.com</span>
</div>
<a href="login.html" class="btn btn-admin" id="adminLoginBtn">Admin Login</a>
<a href="dashboard.html" class="btn btn-dashboard" id="dashboardBtn" style="display: none;">Dashboard</a>
<button class="btn btn-logout" id="logoutBtn" onclick="logout()" style="display: none;">Logout</button>
</div>
</div>
<div class="container">
<div class="welcome-card">
<h2>Welcome to Ninja Server! 🥷</h2>
<p>View the latest times and performance data from our ninja training sessions.</p>
</div>
<div class="times-section">
<h3>⏱️ Latest Times</h3>
<div id="loading" class="loading">
<div class="spinner"></div>
<p>Loading times...</p>
</div>
<div id="timesContent" style="display: none;">
<div class="times-grid" id="timesGrid">
<!-- Times will be loaded here -->
</div>
</div>
<div id="noData" class="no-data" style="display: none;">
No times available yet. Check back later!
</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 status
async function checkAuth() {
try {
const { data: { session } } = await supabase.auth.getSession();
if (session) {
// User is logged in, show user info and dashboard button
displayUserInfo(session.user);
showAuthenticatedUI();
} else {
// User is not logged in, show admin login button
showPublicUI();
}
} catch (error) {
console.error('Error checking auth:', error);
showPublicUI();
}
}
// 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 UI for authenticated users
function showAuthenticatedUI() {
document.getElementById('userInfo').style.display = 'flex';
document.getElementById('adminLoginBtn').style.display = 'none';
document.getElementById('dashboardBtn').style.display = 'inline-block';
document.getElementById('logoutBtn').style.display = 'inline-block';
}
// Show UI for public users
function showPublicUI() {
document.getElementById('userInfo').style.display = 'none';
document.getElementById('adminLoginBtn').style.display = 'inline-block';
document.getElementById('dashboardBtn').style.display = 'none';
document.getElementById('logoutBtn').style.display = 'none';
}
// Load times from local database
async function loadTimes() {
try {
// Fetch times from local database
const response = await fetch('/api/times');
if (!response.ok) {
throw new Error('Failed to fetch times');
}
const times = await response.json();
if (times && times.length > 0) {
displayTimes(times);
} else {
showNoData();
}
} catch (error) {
console.error('Error loading times:', error);
showNoData();
}
}
// Display times in the grid
function displayTimes(times) {
const timesGrid = document.getElementById('timesGrid');
timesGrid.innerHTML = '';
times.forEach(time => {
const timeCard = document.createElement('div');
timeCard.className = 'time-card';
// Format time from recorded_time object
let timeString = 'N/A';
if (time.recorded_time) {
const { minutes, seconds, milliseconds } = time.recorded_time;
timeString = `${minutes}:${seconds.toString().padStart(2, '0')}.${milliseconds}`;
}
const userName = time.player ?
`${time.player.firstname || ''} ${time.player.lastname || ''}`.trim() || 'Unknown Player' :
'Unknown Player';
const locationName = time.location ? time.location.name : 'Unknown Location';
const createdAt = new Date(time.created_at).toLocaleString();
timeCard.innerHTML = `
<h4>${userName}</h4>
<p><strong>Time:</strong> <span class="time-value">${timeString}</span></p>
<p><strong>Location:</strong> ${locationName}</p>
<p><strong>Date:</strong> ${createdAt}</p>
`;
timesGrid.appendChild(timeCard);
});
document.getElementById('loading').style.display = 'none';
document.getElementById('timesContent').style.display = 'block';
}
// Show no data message
function showNoData() {
document.getElementById('loading').style.display = 'none';
document.getElementById('noData').style.display = 'block';
}
// Logout function
async function logout() {
try {
const { error } = await supabase.auth.signOut();
if (error) {
console.error('Error logging out:', error);
} else {
// Refresh the page to update UI
window.location.reload();
}
} catch (error) {
console.error('Error during logout:', error);
}
}
// Listen for auth state changes
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_OUT') {
showPublicUI();
} else if (event === 'SIGNED_IN' && session) {
displayUserInfo(session.user);
showAuthenticatedUI();
}
});
// Initialize page
async function init() {
await checkAuth();
await loadTimes();
}
// Start the application
init();
</script>
</body>
</html>

371
public/login.html Normal file
View File

@@ -0,0 +1,371 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ninja Server - Admin Login</title>
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: white;
padding: 2rem;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.logo {
text-align: center;
margin-bottom: 2rem;
}
.logo h1 {
color: #333;
font-size: 2rem;
font-weight: 700;
}
.logo p {
color: #666;
margin-top: 0.5rem;
}
.form-container {
display: none;
}
.form-container.active {
display: block;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
color: #333;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 0.75rem;
border: 2px solid #e1e5e9;
border-radius: 10px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.btn {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 1rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: transparent;
color: #667eea;
border: 2px solid #667eea;
}
.btn-secondary:hover {
background: #667eea;
color: white;
}
.toggle-form {
text-align: center;
margin-top: 1rem;
}
.toggle-form button {
background: none;
border: none;
color: #667eea;
cursor: pointer;
text-decoration: underline;
font-size: 0.9rem;
}
.toggle-form button:hover {
color: #764ba2;
}
.message {
padding: 0.75rem;
border-radius: 10px;
margin-bottom: 1rem;
text-align: center;
font-weight: 500;
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.loading {
display: none;
text-align: center;
color: #666;
}
.loading.active {
display: block;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="logo">
<h1>🥷 Ninja Server</h1>
<p>Secure Authentication Portal</p>
</div>
<div id="message"></div>
<div id="loading" class="loading">
<div class="spinner"></div>
<p>Processing...</p>
</div>
<!-- Login Form -->
<div id="loginForm" class="form-container active">
<h2 style="text-align: center; margin-bottom: 1.5rem; color: #333;">Welcome Back</h2>
<form id="loginFormElement">
<div class="form-group">
<label for="loginEmail">Email</label>
<input type="email" id="loginEmail" required>
</div>
<div class="form-group">
<label for="loginPassword">Password</label>
<input type="password" id="loginPassword" required>
</div>
<button type="submit" class="btn btn-primary">Sign In</button>
</form>
<div class="toggle-form">
<p>Don't have an account? <button type="button" onclick="toggleForm()">Sign Up</button></p>
</div>
</div>
<!-- Register Form -->
<div id="registerForm" class="form-container">
<h2 style="text-align: center; margin-bottom: 1.5rem; color: #333;">Create Account</h2>
<form id="registerFormElement">
<div class="form-group">
<label for="registerEmail">Email</label>
<input type="email" id="registerEmail" required>
</div>
<div class="form-group">
<label for="registerPassword">Password</label>
<input type="password" id="registerPassword" required minlength="6">
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input type="password" id="confirmPassword" required>
</div>
<button type="submit" class="btn btn-primary">Create Account</button>
</form>
<div class="toggle-form">
<p>Already have an account? <button type="button" onclick="toggleForm()">Sign In</button></p>
</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 if user is already logged in
async function checkAuth() {
const { data: { session } } = await supabase.auth.getSession();
if (session) {
window.location.href = 'dashboard.html';
}
}
// Toggle between login and register forms
function toggleForm() {
const loginForm = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
if (loginForm.classList.contains('active')) {
loginForm.classList.remove('active');
registerForm.classList.add('active');
} else {
registerForm.classList.remove('active');
loginForm.classList.add('active');
}
clearMessage();
}
// Show message
function showMessage(message, type = 'success') {
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = `<div class="message ${type}">${message}</div>`;
setTimeout(() => {
messageDiv.innerHTML = '';
}, 5000);
}
// Clear message
function clearMessage() {
document.getElementById('message').innerHTML = '';
}
// Show/hide loading
function setLoading(show) {
const loading = document.getElementById('loading');
if (show) {
loading.classList.add('active');
} else {
loading.classList.remove('active');
}
}
// Handle login
document.getElementById('loginFormElement').addEventListener('submit', async (e) => {
e.preventDefault();
setLoading(true);
clearMessage();
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;
try {
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password
});
if (error) {
showMessage(error.message, 'error');
} else {
showMessage('Login successful! Redirecting...', 'success');
setTimeout(() => {
window.location.href = 'dashboard.html';
}, 1000);
}
} catch (error) {
showMessage('An unexpected error occurred', 'error');
} finally {
setLoading(false);
}
});
// Handle registration
document.getElementById('registerFormElement').addEventListener('submit', async (e) => {
e.preventDefault();
setLoading(true);
clearMessage();
const email = document.getElementById('registerEmail').value;
const password = document.getElementById('registerPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (password !== confirmPassword) {
showMessage('Passwords do not match', 'error');
setLoading(false);
return;
}
if (password.length < 6) {
showMessage('Password must be at least 6 characters', 'error');
setLoading(false);
return;
}
try {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password
});
if (error) {
showMessage(error.message, 'error');
} else {
if (data.user && !data.user.email_confirmed_at) {
showMessage('Registration successful! Please check your email to confirm your account.', 'success');
} else {
showMessage('Registration successful! Redirecting...', 'success');
setTimeout(() => {
window.location.href = 'dashboard.html';
}, 1000);
}
}
} catch (error) {
showMessage('An unexpected error occurred', 'error');
} finally {
setLoading(false);
}
});
// Check authentication on page load
checkAuth();
</script>
</body>
</html>