Fix crashes on unknown menu item / order routes
Both loaders threw a plain Error that the router doesn't treat as a route-level not-found, so visiting /items/<bad-id> or /orders/<bad-id> (or a valid order with a missing/wrong ?token) crashed instead of showing the NotFound page. Menu item lookups now throw notFound() directly; the order loader catches the server function's error and maps both "order not found" and "wrong access token" to notFound(), so a guessed id can't be used to probe which orders exist. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Link, createFileRoute } from '@tanstack/react-router'
|
import { Link, createFileRoute, notFound } from '@tanstack/react-router'
|
||||||
|
|
||||||
import { MenuItemDetail } from '#/components/ordering/MenuItemDetail.tsx'
|
import { MenuItemDetail } from '#/components/ordering/MenuItemDetail.tsx'
|
||||||
import { useBasket } from '#/lib/basket.tsx'
|
import { useBasket } from '#/lib/basket.tsx'
|
||||||
@@ -48,7 +48,7 @@ export const Route = createFileRoute('/items/$menuItemId')({
|
|||||||
const brand = await getBrand()
|
const brand = await getBrand()
|
||||||
const menu = await getMenu({ data: { brandId: brand.id } })
|
const menu = await getMenu({ data: { brandId: brand.id } })
|
||||||
const item = menu.find((menuItem) => menuItem.id === Number(params.menuItemId))
|
const item = menu.find((menuItem) => menuItem.id === Number(params.menuItemId))
|
||||||
if (!item) throw new Error('Menu item not found')
|
if (!item) throw notFound()
|
||||||
return { item }
|
return { item }
|
||||||
},
|
},
|
||||||
component: MenuItemRoute,
|
component: MenuItemRoute,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute, notFound } from '@tanstack/react-router'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import { OrderStatusView } from '#/components/orders/OrderStatusView.tsx'
|
import { OrderStatusView } from '#/components/orders/OrderStatusView.tsx'
|
||||||
@@ -6,6 +6,16 @@ import { getOrderStatus } from '#/server/orders.ts'
|
|||||||
|
|
||||||
const searchSchema = z.object({ token: z.string().optional() })
|
const searchSchema = z.object({ token: z.string().optional() })
|
||||||
|
|
||||||
|
// Errors cross the server-function boundary as a plain `Error` carrying only
|
||||||
|
// `.message` (TanStack Start's ShallowErrorPlugin drops the subclass), so an
|
||||||
|
// unknown order and a wrong/missing guest token are told apart by message
|
||||||
|
// rather than `instanceof`. Both collapse to a 404 rather than a real error
|
||||||
|
// page, so a guessed order id can't be used to probe which ones exist.
|
||||||
|
const notFoundMessages = new Set([
|
||||||
|
'Order not found',
|
||||||
|
'You do not have access to this Order',
|
||||||
|
])
|
||||||
|
|
||||||
const OrderStatusRoute = () => {
|
const OrderStatusRoute = () => {
|
||||||
const status = Route.useLoaderData()
|
const status = Route.useLoaderData()
|
||||||
const { token } = Route.useSearch()
|
const { token } = Route.useSearch()
|
||||||
@@ -17,12 +27,20 @@ export const Route = createFileRoute('/orders/$orderId')({
|
|||||||
validateSearch: (search: Record<string, unknown>) =>
|
validateSearch: (search: Record<string, unknown>) =>
|
||||||
searchSchema.parse(search),
|
searchSchema.parse(search),
|
||||||
loaderDeps: ({ search }) => ({ token: search.token }),
|
loaderDeps: ({ search }) => ({ token: search.token }),
|
||||||
loader: ({ params, deps }) =>
|
loader: async ({ params, deps }) => {
|
||||||
getOrderStatus({
|
try {
|
||||||
|
return await getOrderStatus({
|
||||||
data: {
|
data: {
|
||||||
orderId: Number(params.orderId),
|
orderId: Number(params.orderId),
|
||||||
guestAccessToken: deps.token,
|
guestAccessToken: deps.token,
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && notFoundMessages.has(error.message)) {
|
||||||
|
throw notFound()
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
component: OrderStatusRoute,
|
component: OrderStatusRoute,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user