Add an admin panel: orders dashboard, menu management, kitchen status

Introduces a role-gated /admin section (sidebar layout, requireAdmin
checked both in route loaders and independently in every admin server
function) with three sections:
- Orders: every order across all customers, and an admin-only order
  detail view nested under /admin so the sidebar persists and admin
  access bypasses the guest-token check.
- Menu: create/edit/delete menu items, their options, and their
  preparation steps; deleting an item with past orders is refused
  (protected by the orderItems FK) instead of crashing.
- Kitchen: read-only live view of every machine's busy/idle status and
  per-tag queue depth, with a manual refresh.

Also:
- Adds a `role` field to the user table (Better Auth additionalFields,
  input:false so it can't be self-assigned) and a db:seed-admin script
  to create/promote an admin account.
- Fixes a real bug found while building this: src/server/auth.ts's
  shared session helpers were plain exported functions using a
  server-only import, which the framework couldn't tree-shake out of
  the client bundle once shared across multiple server/*.ts modules —
  this silently broke hydration app-wide. Fixed with createServerOnlyFn.
- Removes the now-fully-unused @tanstack/devtools-vite plugin.
- Disables view transitions for in-admin navigation so the persistent
  sidebar doesn't cross-fade/shift on every click.
- Gives the "your orders" empty state the same visual treatment as the
  basket page's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 23:14:28 +02:00
co-authored by Claude Sonnet 5
parent 18a7d2030e
commit c8d1721358
37 changed files with 2224 additions and 24 deletions
@@ -11,10 +11,46 @@
}
.empty {
color: $color-text-muted;
display: flex;
flex-direction: column;
align-items: center;
gap: $space-1;
padding: $space-5 $space-3;
text-align: center;
border: 1px dashed $color-border;
border-radius: $radius;
background: $color-surface;
a {
color: $color-primary;
svg {
width: 2.5rem;
height: 2.5rem;
margin-bottom: $space-2;
color: $color-text-muted;
}
}
.emptyTitle {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
.emptyText {
margin: 0 0 $space-3;
color: $color-text-muted;
}
.emptyCta {
padding: $space-2 $space-4;
border-radius: $radius;
background: $color-primary;
color: $color-primary-contrast;
font-weight: 600;
text-decoration: none;
transition: opacity 0.15s ease;
&:hover {
opacity: 0.9;
}
}