Skip to content

Payment drawer state uses router.replace: the back button cannot close the drawer #404

Description

@usmanimamu17-create

Problem

PaymentsView (src/components/payments/payments-view.tsx) syncs the drawer with the URL by replacing history:

function openDrawer(id: string) {
  setSelectedPaymentId(id);
  router.replace(`/payments?paymentId=${id}`, { scroll: false });
}
function closeDrawer() {
  setSelectedPaymentId(null);
  router.replace("/payments", { scroll: false });
}

Using replace means opening the drawer does not create a history entry. Consequences:

  • The browser back button cannot close the drawer: an operator who drilled into a payment and reaches for Back (muscle memory, or the browser's gesture) leaves the payments page entirely instead of closing the drawer — then Back again returns to the payments page with the drawer still open.
  • The URL state is not navigable: ?paymentId=X cannot be shared in a way that opens the drawer and allows Back-to-list (replace destroys the list→detail history).
  • The deliberate scroll: false and replace choices show intent, but the interaction model (drawer as a state overlay vs. a navigation) is undocumented and inconsistent with how the app treats other detail states.

Root cause

The drawer was modeled as URL state (replace) rather than a navigation step (push), with no exploration of the back-button semantics.

Why this is architecturally hard

  1. Switching to router.push makes Back close the drawer naturally but adds history entries for open/close cycles (a full drawer session pollutes history) — the design must decide between push-with-popstate-handling and replace-with-a-visible-close-affordance.
  2. The drawer's onClose (via the PaymentDetailDrawer component) must coordinate with the popstate event so the browser Back and the X button stay in sync — a state-sync pattern (listen for popstate when the drawer is open) that must be tested.
  3. The same URL-sync choice will be needed by the outages detail drawer/work and the settings panels; the decision should be a documented convention, not per-view.

Proposed design

Either (a) use router.push for open and handle popstate to close on Back, or (b) keep replace and add an explicit, keyboard-accessible close affordance plus a "open in page" link; document the chosen convention and test the back-button behavior (Playwright or unit with mocked router).

Acceptance criteria

Service

  • The back button either closes the drawer (push model) or the close path is unambiguous and discoverable (replace model).
  • Drawer open state and URL stay in sync across Back/X navigation.

Tests

  • A test covers Back-while-drawer-open behavior.
  • Existing payments-view tests pass.

Out of scope

The payments React Query migration (tracked separately).

Getting started

npm test -- payments-view

Good first files to read: src/components/payments/payments-view.tsx, src/components/payments/payment-detail-drawer.tsx.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardStellar Waveissue-trackingThird CampaignCampaign: Third Campaignarea/paymentsImported campaign issue labelarea/uiImported campaign issue labelpriority/mediumImported campaign issue label

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions