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:
@@ -0,0 +1,35 @@
|
||||
import { createServerFn, createServerOnlyFn } from '@tanstack/react-start'
|
||||
import { getRequest } from '@tanstack/react-start/server'
|
||||
|
||||
import { auth } from '#/lib/auth.ts'
|
||||
|
||||
// Wrapped in createServerOnlyFn: this file is exported for reuse by several
|
||||
// server/*.ts modules, so (unlike a private, unexported helper) the
|
||||
// framework's client/server code-split can't tree-shake it away on its own
|
||||
// — without this it drags getRequest (and the rest of this file) into any
|
||||
// client bundle that transitively imports one of this module's exports,
|
||||
// which @tanstack/react-start's import-protection then refuses to load.
|
||||
export const getSessionUser = createServerOnlyFn(async () => {
|
||||
const session = await auth.api.getSession({ headers: getRequest().headers })
|
||||
return session?.user
|
||||
})
|
||||
|
||||
export const getSessionCustomerId = createServerOnlyFn(
|
||||
async () => (await getSessionUser())?.id,
|
||||
)
|
||||
|
||||
// Defense in depth: an admin-only server function must check this itself
|
||||
// rather than trust the caller's route already gated on role, since a
|
||||
// server function is reachable directly over its RPC endpoint.
|
||||
export const requireAdmin = createServerOnlyFn(async () => {
|
||||
const user = await getSessionUser()
|
||||
if (user?.role !== 'admin') throw new Error('Admin access required')
|
||||
})
|
||||
|
||||
// A minimal round trip the admin layout route's loader can call to gate
|
||||
// every /admin/* page behind one check, without fetching any page's data.
|
||||
export const requireAdminSession = createServerFn({ method: 'GET' }).handler(
|
||||
async () => {
|
||||
await requireAdmin()
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user