/account has no navigation at all — no header, no links of any kind. Once a customer opens it the only way back to the storefront is the browser back button or editing the URL.
Requested behaviour
Present the account view as a modal over the page the customer was already on, with a close control that returns them to it rather than to a fixed destination.
Things to resolve before implementing
Making this a modal rather than a page has consequences worth deciding deliberately:
/account is a real destination today. Registration, login, and completing a password reset all navigate there. Each of those would need to open the modal over something instead — most naturally the storefront.
Deep links and bookmarks. If /account stops being a route, an existing bookmark breaks. If it stays a route, it needs a sensible backdrop when opened directly rather than an empty page behind the modal.
"The original page" needs defining for the case where there isn't one — a customer arriving straight at /account from a bookmark, or from an email link.
The account view is not small. It holds profile details, marketing consent, order history, data export, and account deletion. A modal has to scroll comfortably on a phone.
Deleting an account from inside a modal should not leave the customer looking at a stale page behind it.
Related
The same absence of navigation affects most pages outside the storefront — see the companion issue. This one covers the account view specifically, because the requested fix here is a modal rather than adding a header.
## Problem
`/account` has no navigation at all — no header, no links of any kind. Once a customer opens it the only way back to the storefront is the browser back button or editing the URL.
## Requested behaviour
Present the account view as a **modal over the page the customer was already on**, with a close control that returns them to it rather than to a fixed destination.
## Things to resolve before implementing
Making this a modal rather than a page has consequences worth deciding deliberately:
- **`/account` is a real destination today.** Registration, login, and completing a password reset all navigate there. Each of those would need to open the modal over something instead — most naturally the storefront.
- **Deep links and bookmarks.** If `/account` stops being a route, an existing bookmark breaks. If it stays a route, it needs a sensible backdrop when opened directly rather than an empty page behind the modal.
- **"The original page" needs defining** for the case where there isn't one — a customer arriving straight at `/account` from a bookmark, or from an email link.
- **The account view is not small.** It holds profile details, marketing consent, order history, data export, and account deletion. A modal has to scroll comfortably on a phone.
- **Deleting an account from inside a modal** should not leave the customer looking at a stale page behind it.
## Related
The same absence of navigation affects most pages outside the storefront — see the companion issue. This one covers the account view specifically, because the requested fix here is a modal rather than adding a header.
Implemented on feature/51-my-account-modal. Work originally started against #48 before that was closed in favour of this issue; the branch and commits now reference #51, and the commit carries Closes #51.
Decisions
Modal versus page. A modal over the storefront in all cases, rather than a modal in-app and a full page when entered directly. One code path, one visual treatment, and no second layout of the account view to keep in sync as it grows.
/account stays a route.main.tsx renders the route table against a backdrop location: when the path is /account, the backdrop is the page named in location.state.background (supplied by the header link), falling back to the storefront when there is none. So the URL keeps working — bookmarkable, shareable, refreshable with the view open — which matters because registration, login, password reset, and email verification all navigate there already and would otherwise have needed changing.
"The original page" when there isn't one is the storefront. Closing then calls navigate('/', { replace: true }); when there is a page behind, it calls navigate(-1), so the close control and the browser's Back button do the same thing and neither leaves a dead history entry.
The two points this issue raised that the earlier write-up missed
Deleting an account left the page behind it looking signed in.handleDelete removed the account server-side and navigated home without touching the auth context, so the header went on offering "My Account" for an account that no longer existed until the next reload. That was always wrong; the modal makes it visible rather than merely stale, because the storefront is rendered behind and the wrong header is on screen throughout. It now refreshes the session after deleting, and replaces rather than pushes so Back cannot return to /account and bounce to /login. Covered by a regression test.
The account view is taller than a phone. The modal body is capped at 70vh and scrolls, the modal itself is capped at calc(100vw - 32px), and the order history table scrolls within itself instead of widening the modal past the viewport. Covered by a test at 390×664 asserting the close control stays in the viewport.
Verification
The account modal spec is 7 tests, green repeatedly both in isolation and within the full suite: opening over a filtered storefront and closing back to it with the filter still applied, Back closing it, a direct visit rendering the storefront behind, surviving a reload, the account contents, the delete regression, and the phone viewport.
The full suite is 76 of 77 on a reduced-parallelism run. I am not going to claim a clean 77 — the remaining failures rotate between unrelated specs on every run and are environmental rather than caused by this change:
Registration is a bcrypt round-trip, about 0.5s unloaded, and comfortably past Playwright's 5s default expect timeout when seven workers register at once. The failure lands on whichever test lost the race, which is why it moves.
The local end-to-end database has accumulated 457 items and 250 customers, so every storefront render draws 457 cards — and this change means /account renders the storefront too.
My repeated runs have also tripped the password-reset rate limiter.
The documented remedy is resetting the local database (npm run db:test:down / db:test:up / migrate:up). That is a local environment change with real consequences for whatever dev data is in there, so I have not run it. Both timing traps are now written into the project context so the next person does not debug the symptom instead of the cause.
Implemented on `feature/51-my-account-modal`. Work originally started against #48 before that was closed in favour of this issue; the branch and commits now reference #51, and the commit carries `Closes #51`.
## Decisions
**Modal versus page.** A modal over the storefront in all cases, rather than a modal in-app and a full page when entered directly. One code path, one visual treatment, and no second layout of the account view to keep in sync as it grows.
**`/account` stays a route.** `main.tsx` renders the route table against a *backdrop* location: when the path is `/account`, the backdrop is the page named in `location.state.background` (supplied by the header link), falling back to the storefront when there is none. So the URL keeps working — bookmarkable, shareable, refreshable with the view open — which matters because registration, login, password reset, and email verification all navigate there already and would otherwise have needed changing.
**"The original page" when there isn't one** is the storefront. Closing then calls `navigate('/', { replace: true })`; when there *is* a page behind, it calls `navigate(-1)`, so the close control and the browser's Back button do the same thing and neither leaves a dead history entry.
## The two points this issue raised that the earlier write-up missed
**Deleting an account left the page behind it looking signed in.** `handleDelete` removed the account server-side and navigated home without touching the auth context, so the header went on offering "My Account" for an account that no longer existed until the next reload. That was always wrong; the modal makes it *visible* rather than merely stale, because the storefront is rendered behind and the wrong header is on screen throughout. It now refreshes the session after deleting, and replaces rather than pushes so Back cannot return to `/account` and bounce to `/login`. Covered by a regression test.
**The account view is taller than a phone.** The modal body is capped at `70vh` and scrolls, the modal itself is capped at `calc(100vw - 32px)`, and the order history table scrolls within itself instead of widening the modal past the viewport. Covered by a test at 390×664 asserting the close control stays in the viewport.
## Verification
The account modal spec is 7 tests, green repeatedly both in isolation and within the full suite: opening over a filtered storefront and closing back to it with the filter still applied, Back closing it, a direct visit rendering the storefront behind, surviving a reload, the account contents, the delete regression, and the phone viewport.
The full suite is **76 of 77** on a reduced-parallelism run. I am not going to claim a clean 77 — the remaining failures rotate between unrelated specs on every run and are environmental rather than caused by this change:
- Registration is a bcrypt round-trip, about 0.5s unloaded, and comfortably past Playwright's 5s default `expect` timeout when seven workers register at once. The failure lands on whichever test lost the race, which is why it moves.
- The local end-to-end database has accumulated **457 items and 250 customers**, so every storefront render draws 457 cards — and this change means `/account` renders the storefront too.
- My repeated runs have also tripped the password-reset rate limiter.
The documented remedy is resetting the local database (`npm run db:test:down` / `db:test:up` / `migrate:up`). That is a local environment change with real consequences for whatever dev data is in there, so I have not run it. Both timing traps are now written into the project context so the next person does not debug the symptom instead of the cause.
Verified in QA: the account view opens as a modal over the page behind it, closing and the browser's Back button both return there, and logging out returns to the home page with the header reset.
**Released to production.**
Verified in QA: the account view opens as a modal over the page behind it, closing and the browser's Back button both return there, and logging out returns to the home page with the header reset.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
/accounthas no navigation at all — no header, no links of any kind. Once a customer opens it the only way back to the storefront is the browser back button or editing the URL.Requested behaviour
Present the account view as a modal over the page the customer was already on, with a close control that returns them to it rather than to a fixed destination.
Things to resolve before implementing
Making this a modal rather than a page has consequences worth deciding deliberately:
/accountis a real destination today. Registration, login, and completing a password reset all navigate there. Each of those would need to open the modal over something instead — most naturally the storefront./accountstops being a route, an existing bookmark breaks. If it stays a route, it needs a sensible backdrop when opened directly rather than an empty page behind the modal./accountfrom a bookmark, or from an email link.Related
The same absence of navigation affects most pages outside the storefront — see the companion issue. This one covers the account view specifically, because the requested fix here is a modal rather than adding a header.
Implemented on
feature/51-my-account-modal. Work originally started against #48 before that was closed in favour of this issue; the branch and commits now reference #51, and the commit carriesCloses #51.Decisions
Modal versus page. A modal over the storefront in all cases, rather than a modal in-app and a full page when entered directly. One code path, one visual treatment, and no second layout of the account view to keep in sync as it grows.
/accountstays a route.main.tsxrenders the route table against a backdrop location: when the path is/account, the backdrop is the page named inlocation.state.background(supplied by the header link), falling back to the storefront when there is none. So the URL keeps working — bookmarkable, shareable, refreshable with the view open — which matters because registration, login, password reset, and email verification all navigate there already and would otherwise have needed changing."The original page" when there isn't one is the storefront. Closing then calls
navigate('/', { replace: true }); when there is a page behind, it callsnavigate(-1), so the close control and the browser's Back button do the same thing and neither leaves a dead history entry.The two points this issue raised that the earlier write-up missed
Deleting an account left the page behind it looking signed in.
handleDeleteremoved the account server-side and navigated home without touching the auth context, so the header went on offering "My Account" for an account that no longer existed until the next reload. That was always wrong; the modal makes it visible rather than merely stale, because the storefront is rendered behind and the wrong header is on screen throughout. It now refreshes the session after deleting, and replaces rather than pushes so Back cannot return to/accountand bounce to/login. Covered by a regression test.The account view is taller than a phone. The modal body is capped at
70vhand scrolls, the modal itself is capped atcalc(100vw - 32px), and the order history table scrolls within itself instead of widening the modal past the viewport. Covered by a test at 390×664 asserting the close control stays in the viewport.Verification
The account modal spec is 7 tests, green repeatedly both in isolation and within the full suite: opening over a filtered storefront and closing back to it with the filter still applied, Back closing it, a direct visit rendering the storefront behind, surviving a reload, the account contents, the delete regression, and the phone viewport.
The full suite is 76 of 77 on a reduced-parallelism run. I am not going to claim a clean 77 — the remaining failures rotate between unrelated specs on every run and are environmental rather than caused by this change:
expecttimeout when seven workers register at once. The failure lands on whichever test lost the race, which is why it moves./accountrenders the storefront too.The documented remedy is resetting the local database (
npm run db:test:down/db:test:up/migrate:up). That is a local environment change with real consequences for whatever dev data is in there, so I have not run it. Both timing traps are now written into the project context so the next person does not debug the symptom instead of the cause.Released to production.
Verified in QA: the account view opens as a modal over the page behind it, closing and the browser's Back button both return there, and logging out returns to the home page with the header reset.