docs: design for moving Order History onto its own page (#121) #122
@@ -0,0 +1,88 @@
|
|||||||
|
# Order History as its own page
|
||||||
|
|
||||||
|
Design for #121. Written 2026-08-22.
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
The My Account modal accumulated. It now holds a profile line, a name form, two collapsed panels for changing email and password, two consent switches, an order table, and four account controls. Each addition was reasonable on its own; together they are more than a 700px modal with its body capped at 70vh can show without scrolling.
|
||||||
|
|
||||||
|
The order table is the piece that fits worst, because it is the only tabular data in there. Tables want horizontal room and get none: `scroll={{ x: 'max-content' }}` is already on it to stop it widening the modal past a phone viewport, which is a workaround for being in the wrong container rather than a layout choice.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
A real page at `/orders`.
|
||||||
|
|
||||||
|
The app has two precedents and this picks the one that fits. `/account`, `/login` and `/register` are modal routes, listed in `MODAL_ROUTES` and rendered over a backdrop so Back closes them. `/cart` and `/privacy` are ordinary pages in the same `Routes` block. Order history is closer to the cart: a list you read, not a dialog you dismiss.
|
||||||
|
|
||||||
|
Another modal route at `/account/orders` was considered and rejected. It would be the smallest change, but it inherits the same 700px width and the same 70vh scroll cap, so it moves the table without giving it anything. Tabs inside the modal were also considered and rejected for the same reason: they solve the scrolling and leave the cramping.
|
||||||
|
|
||||||
|
## What moves
|
||||||
|
|
||||||
|
Out of `Account.tsx`: the divider, the `Order History` heading, the `<Table>`, the `orders` state, the `fetchMyOrders` effect, and the `fetchMyOrders` and `OrderHistoryItem` imports.
|
||||||
|
|
||||||
|
Into `Account.tsx`: a `View order history` button in the existing control row beside `Download my data` and `Log out`.
|
||||||
|
|
||||||
|
Into a new `frontend/src/customer/Orders.tsx`: the same `fetchMyOrders()` call and the same four columns.
|
||||||
|
|
||||||
|
## Page shell
|
||||||
|
|
||||||
|
`Orders.tsx` follows `Cart.tsx`, which is the established shape for a customer-facing page in this app:
|
||||||
|
|
||||||
|
- `<Layout style={{ minHeight: '100vh' }}>` wrapping a `<Header>` that carries a `Back to Shop` button with `ArrowLeftOutlined` and a `<Title level={3}>Order History</Title>`.
|
||||||
|
- The same auth guard: `if (!authLoading && !customer) navigate('/login')`.
|
||||||
|
- Registered as `<Route path="/orders" element={<Orders />} />` in the backdrop `<Routes>` in `main.tsx`. Deliberately **not** added to `MODAL_ROUTES` — that array is what makes a path render as a modal over a backdrop, and adding it there would undo the whole point of this change.
|
||||||
|
|
||||||
|
## The part that is a fix rather than a move
|
||||||
|
|
||||||
|
The current effect is:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
if (customer) void fetchMyOrders().then(setOrders).catch(() => message.error('Could not load your orders'));
|
||||||
|
```
|
||||||
|
|
||||||
|
A failed load shows a toast and leaves `orders` as `[]`. The toast disappears after a few seconds; the empty table does not. From then on a customer whose request failed sees exactly what a customer with no orders sees, and the interface is asserting something false — that they have never ordered anything.
|
||||||
|
|
||||||
|
That was tolerable as one section among several in a modal. On a page whose entire purpose is this table it is the main thing the page says, so the three states become distinct:
|
||||||
|
|
||||||
|
| State | What is shown |
|
||||||
|
|---|---|
|
||||||
|
| Loading | A `Spin`, so an in-flight request is not mistaken for an empty result either |
|
||||||
|
| Failed | An `Alert` naming the failure, with a Retry button that re-issues the request |
|
||||||
|
| Empty | `<Empty description="No orders yet">` with a Continue Shopping link, mirroring the empty cart |
|
||||||
|
|
||||||
|
The retry matters more than the alert. A transient failure otherwise strands the customer on a page that needs a full reload to recover.
|
||||||
|
|
||||||
|
## Presentation
|
||||||
|
|
||||||
|
- A container capped at 960px and centred, so rows are not stretched across the full width of a wide monitor. 960 rather than the modal's 700: four columns of which one is a free-text item name, with room to add a fifth without another rethink.
|
||||||
|
- Amount right-aligned, which is what makes a column of currency readable.
|
||||||
|
- Status rendered as a `Tag` rather than the raw string the modal prints today.
|
||||||
|
- `scroll={{ x: 'max-content' }}` is kept. It is still correct on a phone; it is simply no longer compensating for the container.
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
No backend change. `GET /api/customers/me/orders` already returns `id`, `processor`, `amount_cents`, `status`, `created_at` and `item_name`, which is every column.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
Two things were considered and deliberately left out.
|
||||||
|
|
||||||
|
**Per-order detail** — clicking a row to see line items, address and payment reference. It needs a new endpoint and a second view, and it is a feature rather than a layout fix. Its own issue when there is a reason for it.
|
||||||
|
|
||||||
|
**Server-side paging and filtering** — worth doing once somebody has enough orders to scroll, which is a guess today. Adding it now would mean designing pagination against a table nobody has filled.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
New `frontend/tests/e2e/orders.spec.ts`:
|
||||||
|
|
||||||
|
- A signed-out visit to `/orders` redirects to `/login`.
|
||||||
|
- The page renders for a signed-in customer, with its heading and Back to Shop control.
|
||||||
|
- The empty state appears when the customer has no orders.
|
||||||
|
- Back to Shop returns to the storefront.
|
||||||
|
- The `View order history` control in My Account navigates to `/orders`.
|
||||||
|
|
||||||
|
Two existing tests in `account-modal.spec.ts` assert that the modal contains the text `Order History` — the "shows the signed-in account and its settings" test and the phone test. Both must be **updated to assert the link**, not deleted. Deleting them would remove the only coverage that the account view still offers a route to the orders at all, which is precisely the thing this change could silently break.
|
||||||
|
|
||||||
|
## Sequencing
|
||||||
|
|
||||||
|
This edits `Account.tsx`, which the #111 account-details work also edits and which is committed on `feature/111-account-ui`. #111 lands first; this branch rebases onto it. Doing them in the other order means resolving a conflict that need not exist.
|
||||||
Reference in New Issue
Block a user