grubflip/admin/index.html
Sarah 05dd55e0b6 Add GrubFlip consumer app — feed, trades, chat, post, landing, admin login
Full vanilla HTML/CSS/JS consumer app with mobile-first responsive design.
Pages: feed (index), post meal, trades, messages inbox, chat, landing page,
and admin login skeleton. Uses Ava's design tokens throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 05:44:30 +00:00

194 lines
5.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Grubflip Admin — Login</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/tokens.css">
<link rel="stylesheet" href="css/admin.css">
<style>
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--gf-bg-dark) 0%, #2a2a4a 100%);
padding: var(--gf-space-4);
}
.login-card {
background: var(--gf-bg);
border-radius: var(--gf-radius-xl);
box-shadow: var(--gf-shadow-xl);
width: 100%;
max-width: 26rem;
padding: var(--gf-space-8);
}
.login-brand {
text-align: center;
margin-bottom: var(--gf-space-8);
}
.login-brand .logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 3.5rem;
height: 3.5rem;
background: var(--gf-primary);
border-radius: var(--gf-radius-lg);
color: #fff;
font-size: 1.5rem;
font-weight: var(--gf-weight-bold);
margin-bottom: var(--gf-space-4);
}
.login-brand h1 {
font-family: var(--gf-font-heading);
font-size: var(--gf-text-2xl);
color: var(--gf-neutral-900);
margin-bottom: var(--gf-space-1);
}
.login-brand p {
color: var(--gf-neutral-500);
font-size: var(--gf-text-sm);
}
.login-footer {
text-align: center;
margin-top: var(--gf-space-6);
font-size: var(--gf-text-sm);
color: var(--gf-neutral-400);
}
.login-footer a {
color: var(--gf-primary);
font-weight: var(--gf-weight-medium);
}
.login-error {
background: var(--gf-error-bg);
color: var(--gf-error);
padding: var(--gf-space-3) var(--gf-space-4);
border-radius: var(--gf-radius-md);
font-size: var(--gf-text-sm);
margin-bottom: var(--gf-space-4);
display: none;
}
.login-error.visible { display: block; }
.btn-login {
width: 100%;
padding: 0.875rem;
font-size: var(--gf-text-base);
}
</style>
</head>
<body>
<div class="login-page">
<div class="login-card">
<div class="login-brand">
<div class="logo">🍔</div>
<h1>Grubflip Admin</h1>
<p>Restaurant management dashboard</p>
</div>
<div id="loginError" class="login-error" role="alert"></div>
<form id="loginForm" novalidate>
<div class="form-group">
<label for="email" class="form-label">Email address</label>
<input type="email" id="email" class="form-input" placeholder="owner@restaurant.com" autocomplete="email" required>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" class="form-input" placeholder="Enter your password" autocomplete="current-password" required>
</div>
<div class="form-group flex items-center justify-between">
<label class="flex items-center gap-2 text-sm" style="cursor:pointer">
<input type="checkbox" id="remember" style="accent-color:var(--gf-primary)">
Remember me
</label>
<a href="#" class="text-sm">Forgot password?</a>
</div>
<button type="submit" class="btn btn-primary btn-login" id="loginBtn">
Sign In
</button>
</form>
<div class="login-footer">
Don't have an account? <a href="#">Contact Grubflip</a>
</div>
</div>
</div>
<script>
const loginForm = document.getElementById('loginForm');
const loginError = document.getElementById('loginError');
const loginBtn = document.getElementById('loginBtn');
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
loginError.classList.remove('visible');
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
if (!email || !password) {
loginError.textContent = 'Please enter your email and password.';
loginError.classList.add('visible');
return;
}
loginBtn.disabled = true;
loginBtn.textContent = 'Signing in…';
try {
// Wire to Mike's API when ready
const resp = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!resp.ok) {
const data = await resp.json().catch(() => ({}));
throw new Error(data.message || 'Invalid email or password.');
}
const data = await resp.json();
localStorage.setItem('gf_token', data.token);
localStorage.setItem('gf_user', JSON.stringify(data.user));
window.location.href = 'dashboard.html';
} catch (err) {
// For now, allow demo login
if (email === 'demo@grubflip.com' && password === 'demo') {
localStorage.setItem('gf_token', 'demo-token');
localStorage.setItem('gf_user', JSON.stringify({
id: 1,
name: 'Demo Restaurant',
email: 'demo@grubflip.com',
role: 'owner'
}));
window.location.href = 'dashboard.html';
return;
}
loginError.textContent = err.message;
loginError.classList.add('visible');
} finally {
loginBtn.disabled = false;
loginBtn.textContent = 'Sign In';
}
});
</script>
</body>
</html>