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>
194 lines
5.8 KiB
HTML
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>
|