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:
@@ -10,12 +10,20 @@
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as AdminRouteImport } from './routes/admin'
|
||||
import { Route as BasketRouteImport } from './routes/basket'
|
||||
import { Route as LoginRouteImport } from './routes/login'
|
||||
import { Route as SignupRouteImport } from './routes/signup'
|
||||
import { Route as AdminIndexRouteImport } from './routes/admin/index'
|
||||
import { Route as AdminKitchenRouteImport } from './routes/admin/kitchen'
|
||||
import { Route as ItemsMenuItemIdRouteImport } from './routes/items/$menuItemId'
|
||||
import { Route as OrdersIndexRouteImport } from './routes/orders/index'
|
||||
import { Route as OrdersOrderIdRouteImport } from './routes/orders/$orderId'
|
||||
import { Route as AdminMenuIndexRouteImport } from './routes/admin/menu/index'
|
||||
import { Route as AdminMenuMenuItemIdRouteImport } from './routes/admin/menu/$menuItemId'
|
||||
import { Route as AdminMenuNewRouteImport } from './routes/admin/menu/new'
|
||||
import { Route as AdminOrdersIndexRouteImport } from './routes/admin/orders/index'
|
||||
import { Route as AdminOrdersOrderIdRouteImport } from './routes/admin/orders/$orderId'
|
||||
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
||||
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
@@ -23,6 +31,11 @@ const IndexRoute = IndexRouteImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AdminRoute = AdminRouteImport.update({
|
||||
id: '/admin',
|
||||
path: '/admin',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const BasketRoute = BasketRouteImport.update({
|
||||
id: '/basket',
|
||||
path: '/basket',
|
||||
@@ -38,6 +51,16 @@ const SignupRoute = SignupRouteImport.update({
|
||||
path: '/signup',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AdminIndexRoute = AdminIndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const AdminKitchenRoute = AdminKitchenRouteImport.update({
|
||||
id: '/kitchen',
|
||||
path: '/kitchen',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const ItemsMenuItemIdRoute = ItemsMenuItemIdRouteImport.update({
|
||||
id: '/items/$menuItemId',
|
||||
path: '/items/$menuItemId',
|
||||
@@ -53,6 +76,31 @@ const OrdersOrderIdRoute = OrdersOrderIdRouteImport.update({
|
||||
path: '/orders/$orderId',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AdminMenuIndexRoute = AdminMenuIndexRouteImport.update({
|
||||
id: '/menu/',
|
||||
path: '/menu/',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const AdminMenuMenuItemIdRoute = AdminMenuMenuItemIdRouteImport.update({
|
||||
id: '/menu/$menuItemId',
|
||||
path: '/menu/$menuItemId',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const AdminMenuNewRoute = AdminMenuNewRouteImport.update({
|
||||
id: '/menu/new',
|
||||
path: '/menu/new',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const AdminOrdersIndexRoute = AdminOrdersIndexRouteImport.update({
|
||||
id: '/orders/',
|
||||
path: '/orders/',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const AdminOrdersOrderIdRoute = AdminOrdersOrderIdRouteImport.update({
|
||||
id: '/orders/$orderId',
|
||||
path: '/orders/$orderId',
|
||||
getParentRoute: () => AdminRoute,
|
||||
} as any)
|
||||
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
||||
id: '/api/auth/$',
|
||||
path: '/api/auth/$',
|
||||
@@ -61,70 +109,117 @@ const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/admin': typeof AdminRouteWithChildren
|
||||
'/basket': typeof BasketRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/signup': typeof SignupRoute
|
||||
'/admin/kitchen': typeof AdminKitchenRoute
|
||||
'/items/$menuItemId': typeof ItemsMenuItemIdRoute
|
||||
'/orders/$orderId': typeof OrdersOrderIdRoute
|
||||
'/admin/': typeof AdminIndexRoute
|
||||
'/orders/': typeof OrdersIndexRoute
|
||||
'/admin/menu/$menuItemId': typeof AdminMenuMenuItemIdRoute
|
||||
'/admin/menu/new': typeof AdminMenuNewRoute
|
||||
'/admin/orders/$orderId': typeof AdminOrdersOrderIdRoute
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/admin/menu/': typeof AdminMenuIndexRoute
|
||||
'/admin/orders/': typeof AdminOrdersIndexRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/basket': typeof BasketRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/signup': typeof SignupRoute
|
||||
'/admin/kitchen': typeof AdminKitchenRoute
|
||||
'/items/$menuItemId': typeof ItemsMenuItemIdRoute
|
||||
'/orders/$orderId': typeof OrdersOrderIdRoute
|
||||
'/admin': typeof AdminIndexRoute
|
||||
'/orders': typeof OrdersIndexRoute
|
||||
'/admin/menu/$menuItemId': typeof AdminMenuMenuItemIdRoute
|
||||
'/admin/menu/new': typeof AdminMenuNewRoute
|
||||
'/admin/orders/$orderId': typeof AdminOrdersOrderIdRoute
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/admin/menu': typeof AdminMenuIndexRoute
|
||||
'/admin/orders': typeof AdminOrdersIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/admin': typeof AdminRouteWithChildren
|
||||
'/basket': typeof BasketRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/signup': typeof SignupRoute
|
||||
'/admin/kitchen': typeof AdminKitchenRoute
|
||||
'/items/$menuItemId': typeof ItemsMenuItemIdRoute
|
||||
'/orders/$orderId': typeof OrdersOrderIdRoute
|
||||
'/admin/': typeof AdminIndexRoute
|
||||
'/orders/': typeof OrdersIndexRoute
|
||||
'/admin/menu/$menuItemId': typeof AdminMenuMenuItemIdRoute
|
||||
'/admin/menu/new': typeof AdminMenuNewRoute
|
||||
'/admin/orders/$orderId': typeof AdminOrdersOrderIdRoute
|
||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
||||
'/admin/menu/': typeof AdminMenuIndexRoute
|
||||
'/admin/orders/': typeof AdminOrdersIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/admin'
|
||||
| '/basket'
|
||||
| '/login'
|
||||
| '/signup'
|
||||
| '/admin/kitchen'
|
||||
| '/items/$menuItemId'
|
||||
| '/orders/$orderId'
|
||||
| '/admin/'
|
||||
| '/orders/'
|
||||
| '/admin/menu/$menuItemId'
|
||||
| '/admin/menu/new'
|
||||
| '/admin/orders/$orderId'
|
||||
| '/api/auth/$'
|
||||
| '/admin/menu/'
|
||||
| '/admin/orders/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/'
|
||||
| '/basket'
|
||||
| '/login'
|
||||
| '/signup'
|
||||
| '/admin/kitchen'
|
||||
| '/items/$menuItemId'
|
||||
| '/orders/$orderId'
|
||||
| '/admin'
|
||||
| '/orders'
|
||||
| '/admin/menu/$menuItemId'
|
||||
| '/admin/menu/new'
|
||||
| '/admin/orders/$orderId'
|
||||
| '/api/auth/$'
|
||||
| '/admin/menu'
|
||||
| '/admin/orders'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/admin'
|
||||
| '/basket'
|
||||
| '/login'
|
||||
| '/signup'
|
||||
| '/admin/kitchen'
|
||||
| '/items/$menuItemId'
|
||||
| '/orders/$orderId'
|
||||
| '/admin/'
|
||||
| '/orders/'
|
||||
| '/admin/menu/$menuItemId'
|
||||
| '/admin/menu/new'
|
||||
| '/admin/orders/$orderId'
|
||||
| '/api/auth/$'
|
||||
| '/admin/menu/'
|
||||
| '/admin/orders/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AdminRoute: typeof AdminRouteWithChildren
|
||||
BasketRoute: typeof BasketRoute
|
||||
LoginRoute: typeof LoginRoute
|
||||
SignupRoute: typeof SignupRoute
|
||||
@@ -143,6 +238,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/admin': {
|
||||
id: '/admin'
|
||||
path: '/admin'
|
||||
fullPath: '/admin'
|
||||
preLoaderRoute: typeof AdminRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/basket': {
|
||||
id: '/basket'
|
||||
path: '/basket'
|
||||
@@ -164,6 +266,20 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof SignupRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/admin/': {
|
||||
id: '/admin/'
|
||||
path: '/'
|
||||
fullPath: '/admin/'
|
||||
preLoaderRoute: typeof AdminIndexRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/admin/kitchen': {
|
||||
id: '/admin/kitchen'
|
||||
path: '/kitchen'
|
||||
fullPath: '/admin/kitchen'
|
||||
preLoaderRoute: typeof AdminKitchenRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/items/$menuItemId': {
|
||||
id: '/items/$menuItemId'
|
||||
path: '/items/$menuItemId'
|
||||
@@ -185,6 +301,41 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof OrdersOrderIdRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/admin/menu/': {
|
||||
id: '/admin/menu/'
|
||||
path: '/menu'
|
||||
fullPath: '/admin/menu/'
|
||||
preLoaderRoute: typeof AdminMenuIndexRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/admin/menu/$menuItemId': {
|
||||
id: '/admin/menu/$menuItemId'
|
||||
path: '/menu/$menuItemId'
|
||||
fullPath: '/admin/menu/$menuItemId'
|
||||
preLoaderRoute: typeof AdminMenuMenuItemIdRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/admin/menu/new': {
|
||||
id: '/admin/menu/new'
|
||||
path: '/menu/new'
|
||||
fullPath: '/admin/menu/new'
|
||||
preLoaderRoute: typeof AdminMenuNewRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/admin/orders/': {
|
||||
id: '/admin/orders/'
|
||||
path: '/orders'
|
||||
fullPath: '/admin/orders/'
|
||||
preLoaderRoute: typeof AdminOrdersIndexRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/admin/orders/$orderId': {
|
||||
id: '/admin/orders/$orderId'
|
||||
path: '/orders/$orderId'
|
||||
fullPath: '/admin/orders/$orderId'
|
||||
preLoaderRoute: typeof AdminOrdersOrderIdRouteImport
|
||||
parentRoute: typeof AdminRoute
|
||||
}
|
||||
'/api/auth/$': {
|
||||
id: '/api/auth/$'
|
||||
path: '/api/auth/$'
|
||||
@@ -195,8 +346,31 @@ declare module '@tanstack/react-router' {
|
||||
}
|
||||
}
|
||||
|
||||
interface AdminRouteChildren {
|
||||
AdminKitchenRoute: typeof AdminKitchenRoute
|
||||
AdminIndexRoute: typeof AdminIndexRoute
|
||||
AdminMenuMenuItemIdRoute: typeof AdminMenuMenuItemIdRoute
|
||||
AdminMenuNewRoute: typeof AdminMenuNewRoute
|
||||
AdminOrdersOrderIdRoute: typeof AdminOrdersOrderIdRoute
|
||||
AdminMenuIndexRoute: typeof AdminMenuIndexRoute
|
||||
AdminOrdersIndexRoute: typeof AdminOrdersIndexRoute
|
||||
}
|
||||
|
||||
const AdminRouteChildren: AdminRouteChildren = {
|
||||
AdminKitchenRoute: AdminKitchenRoute,
|
||||
AdminIndexRoute: AdminIndexRoute,
|
||||
AdminMenuMenuItemIdRoute: AdminMenuMenuItemIdRoute,
|
||||
AdminMenuNewRoute: AdminMenuNewRoute,
|
||||
AdminOrdersOrderIdRoute: AdminOrdersOrderIdRoute,
|
||||
AdminMenuIndexRoute: AdminMenuIndexRoute,
|
||||
AdminOrdersIndexRoute: AdminOrdersIndexRoute,
|
||||
}
|
||||
|
||||
const AdminRouteWithChildren = AdminRoute._addFileChildren(AdminRouteChildren)
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AdminRoute: AdminRouteWithChildren,
|
||||
BasketRoute: BasketRoute,
|
||||
LoginRoute: LoginRoute,
|
||||
SignupRoute: SignupRoute,
|
||||
|
||||
Reference in New Issue
Block a user