Add multi-user auth, admin panel, and timezone support; rename to Yolkbook

- Rename app from Eggtracker to Yolkbook throughout
- Add JWT-based authentication (python-jose, passlib/bcrypt)
- Add users table; all data tables gain user_id FK for full data isolation
- Super admin credentials sourced from ADMIN_USERNAME/ADMIN_PASSWORD env vars,
  synced on every startup; orphaned rows auto-assigned to admin post-migration
- Login page with self-registration; JWT stored in localStorage (30-day expiry)
- Admin panel (/admin): list users, reset passwords, disable/enable, delete,
  and impersonate (Login As) with Return to Admin banner
- Settings modal (gear icon in nav): timezone selector and change password
- Timezone stored per-user; stats date windows computed in user's timezone;
  date input setToday() respects user timezone via Intl API
- migrate_v2.sql for existing single-user installs
- Auto-migration adds timezone column to users on startup
- Updated README with full setup, auth, admin, and migration docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 23:19:29 -07:00
parent 7d50af0054
commit aa12648228
31 changed files with 1572 additions and 140 deletions

View File

@@ -173,6 +173,7 @@ label { font-size: 0.875rem; font-weight: 500; }
input[type="text"],
input[type="number"],
input[type="date"],
input[type="password"],
textarea,
select {
padding: 0.5rem 0.75rem;
@@ -259,4 +260,120 @@ td input[type="date"] {
.nav-links { overflow-x: auto; scrollbar-width: none; }
.nav-links::-webkit-scrollbar { display: none; }
.nav-links a { padding: 0.4rem 0.55rem; font-size: 0.82rem; white-space: nowrap; }
.nav-username { display: none; }
}
/* ── Nav user section ─────────────────────────────────────────────────────── */
.nav-user {
margin-left: auto;
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
}
.nav-username {
color: rgba(255,255,255,0.85);
font-size: 0.88rem;
white-space: nowrap;
}
.nav-impersonating {
color: #ffe08a;
font-size: 0.85rem;
white-space: nowrap;
}
.nav-admin-btn {
color: rgba(255,255,255,0.85) !important;
border-color: rgba(255,255,255,0.35) !important;
}
.nav-admin-btn:hover {
background: rgba(255,255,255,0.15) !important;
color: #fff !important;
}
.btn-amber { background: var(--amber); color: #fff; }
.btn-amber:hover { background: #b8720a; }
/* ── Login page ───────────────────────────────────────────────────────────── */
.login-body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--bg);
}
.login-container {
width: 100%;
max-width: 380px;
padding: 0 1rem;
}
.login-brand {
text-align: center;
font-size: 1.6rem;
font-weight: 700;
color: var(--green);
margin-bottom: 1.5rem;
}
.login-card { padding: 2rem; }
.login-title {
text-align: center;
font-size: 1.3rem;
margin-bottom: 1.25rem;
}
/* ── Modals ───────────────────────────────────────────────────────────────── */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 500;
padding: 1rem;
}
.modal-box {
background: var(--card-bg);
border-radius: var(--radius);
padding: 1.75rem;
width: 100%;
max-width: 420px;
box-shadow: 0 8px 32px rgba(0,0,0,0.18);
}
.modal-box h2 { margin-bottom: 1rem; }
/* ── Badges ───────────────────────────────────────────────────────────────── */
.badge {
display: inline-block;
padding: 0.2rem 0.55rem;
border-radius: 99px;
font-size: 0.75rem;
font-weight: 600;
}
.badge-admin { background: #d4edff; color: #0055aa; }
.badge-user { background: #e8f5e9; color: #2e6b3e; }
.badge-active { background: #d4edda; color: #155724; }
.badge-disabled { background: #f8d7da; color: #721c24; }
/* ── Settings modal extras ────────────────────────────────────────────────── */
.settings-section-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--green-dark);
margin-bottom: 0.75rem;
}
.settings-divider {
border: none;
border-top: 1px solid var(--border);
margin: 1.25rem 0;
}