fix(web): Revenue-by-Route truncation, refund button state, explorer links, modal a11y - #249
Open
mhikel66 wants to merge 10 commits into
Open
fix(web): Revenue-by-Route truncation, refund button state, explorer links, modal a11y#249mhikel66 wants to merge 10 commits into
mhikel66 wants to merge 10 commits into
Conversation
Adds `lib/explorer.ts`. `resolveStellarNetwork` reads `NEXT_PUBLIC_STELLAR_NETWORK` (accepting the `public`/`pubnet` aliases), throws on an unrecognised value, and falls back to testnet with a one-time console warning when unset — a labelled default, not a silent one. `explorerTxUrl` and `explorerContractUrl` build stellar.expert links for the resolved network, mapping mainnet to the explorer's `public` path. vitest gets `NEXT_PUBLIC_STELLAR_NETWORK=testnet` so the suite runs against a defined network rather than the warning path. Refs accensa#188
…the var The landing page and the batch page each built a testnet stellar.expert URL inline. Both now call `explorerContractUrl`, and the app README documents `NEXT_PUBLIC_STELLAR_NETWORK` — its values, the testnet default, and why a mainnet deployment must set it. Refs accensa#188
`RefundPanel` modelled five phases but handled four. `submitting` fell through to the idle branch, which rendered an enabled "Refund this payment" button while a wallet prompt was open and a transaction in flight; clicking it re-entered the preflight and could reach a second signing prompt for a refund the vault would reject. Splits the component into a stateful `RefundPanel` and a pure `RefundPanelView` that renders one phase via an exhaustive `switch`. `submitting` returns an early note that a signing prompt is open and offers no action; the `default` branch calls `assertNever(phase)`, so a new unhandled phase is a type error. Also routes the panel's two explorer links through `explorerTxUrl`. Closes accensa#186
Renders `RefundPanelView` at each phase: idle offers the action, checking disables it, submitting exposes no button at all (only the "signing prompt is open" note), done shows the outcome. Documents the compile-time exhaustiveness guard a runtime test cannot replace. Refs accensa#186
`lib/dialog-focus.ts`: `getFocusable` lists tabbable descendants, `wrapTabTarget` returns where Tab / Shift+Tab should wrap to keep focus in a dialog (or null when the browser already would), and `focusRestorer` captures the active element and returns a function that hands focus back. Plain functions, unit tested without a DOM. Refs accensa#191
The modal was a plain <div> overlay: no role, no accessible name, no focus trap, and on close focus fell to <body> instead of the row that opened it — a screen-reader user could Tab out and read the page behind a dialog they were never told about. The card now carries role="dialog", aria-modal, aria-labelledby pointing at its heading, and a tabindex so it can receive focus. A mount effect moves focus in, traps Tab with `wrapTabTarget`, keeps Escape working, and restores focus with `focusRestorer` on unmount. The dead `closeButtonRef` and the parent's modal effect are gone; `onClose` is memoised so a 15s poll re-render cannot re-trap focus. Kept the custom overlay rather than native <dialog> to avoid restyling, which is out of scope. Also swaps the explorer link to `explorerTxUrl`. Closes accensa#191
SSR-renders PaymentModal and checks role="dialog", aria-modal, the aria-labelledby / id pairing, the focusable container, and the labelled close control. Focus trapping and restoration are covered by `dialog-focus.test.ts`. Refs accensa#191
`fetchAllPayments` follows `/api/payments`' `next_cursor` to the end (it serves at most 1,000 rows per request), reporting progress per page and capping at 1,000 pages so a non-advancing cursor cannot loop. `filterByRange` returns the payments inside a `RangeKey` window, its start aligned to midnight UTC to match `buildRevenueSeries`' bucketing; `'all'` is a passthrough. Refs accensa#185
The page fetched `/api/payments` with no limit, received the newest 100 rows, then offered an "All time" range selector — so every figure (total settled, attributed split, per-route revenue, share bars, chart) was computed from at most 100 payments while presenting itself as complete. On a page whose purpose is proportions this is the worst kind of wrong: a truncated sample distorts the relative share between routes. Now pages the full history with `fetchAllPayments` (showing a running count while it loads), then filters to the selected range with `filterByRange` before building the breakdown and series, so shares are computed against the in-range total. A perf test records the cost: ~8 ms for 600 payments across all three ranges, ~15 ms for 3,000, on the dev machine. Closes accensa#185
`pnpm format:check` fails on `main` (a long line in verify/page.tsx left unwrapped by accensa#248), which turns the `format` job red on every PR. `prettier --write` on the one file, no behavioural change. Refs accensa#188
|
@mhikel66 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@mhikel66 is attempting to deploy a commit to the ACCENSA Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes four
apps/webbugs from the Stellar Wave batch. They share files(
dashboard/page.tsx,refund-panel.tsx) and two small helpers, so they landas one PR of 10 commits rather than four.
Closes #185
Closes #186
Closes #188
Closes #191
#185 — Revenue by Route aggregated only the newest 100 payments
app/dashboard/routes/page.tsxfetched/api/paymentswith no limit (newest100) and still offered an "All time" range selector, so every figure — total
settled, attributed split, per-route revenue, share bars, chart — was computed
from a truncated sample while presenting itself as complete.
lib/payments-fetch.ts→fetchAllPaymentswalks/api/payments'next_cursorto the end (1,000 rows/page), reports a running count while itloads, and caps at 1,000 pages (flags
truncatedrather than looping on abad cursor).
filterByRangeinlib/revenue-analytics.tsscopes the payments to theselected window, its start aligned to midnight UTC to match
buildRevenueSeries' bucketing. The route breakdown and its sharepercentages are now computed against the in-range total.
Loading payment history — N so farwhile paging.Acceptance: each range aggregates every payment in that range ✓ · "All time"
covers a >100 fixture (
payments-fetch.test.tsuses 1,200) ✓ · shares computedagainst the full range total ✓ · render cost:
revenue-analytics.perf.test.tsmeasures the aggregation across all three ranges at ~8 ms for 600 payments
and ~15 ms for 3,000 on the dev machine (best-of-5, warm) ✓
#186 — Refund button stayed live during
submittingRefundPanelmodelled five phases but handled four;submittingfell throughto the idle branch and rendered an enabled "Refund this payment" button while a
wallet prompt was open.
RefundPaneland a pureRefundPanelViewthat rendersone phase via an exhaustive
switch.submittingreturns early with "asigning prompt is open" and no action.
default:callsassertNever(phase), so adding aPhasevariant without acaseis a type error (pnpm typecheck).submitRefund/ the preflight flow are untouched.Acceptance: no refund-starting control while submitting ✓ · panel says a wallet
prompt is open ✓ · unhandled new phase is a type error ✓ · test drives the
panel into
submittingand asserts no actionable control ✓#188 — Explorer links hardcoded to testnet in five places
lib/explorer.ts— the only module inapps/web/srcthat containsthe string
stellar.expert(grep -rnconfirms).resolveStellarNetworkreads
NEXT_PUBLIC_STELLAR_NETWORK(+public/pubnetaliases), throwson an unrecognised value, and falls back to testnet with a one-time
console.warnwhen unset — the "clearly-labelled default" the issue allows(a hard throw would take down statically-rendered pages at build time).
dashboard/page.tsx,refund-panel.tsx×2,batches/[id]/page.tsx,page.tsx) now useexplorerTxUrl/explorerContractUrl.apps/web/README.md.Acceptance: exactly one module contains
stellar.expert✓ · links resolve tothe configured network ✓ · unset fails loudly (warns, not silent) ✓ ·
documented ✓ ·
explorer.test.tsasserts construction for testnet andmainnet ✓
#191 — Payment details modal was not a real dialog
lib/dialog-focus.ts—getFocusable,wrapTabTarget(Tab wrap logic),focusRestorer(capture + restore). Plain functions, unit-tested without aDOM.
role="dialog",aria-modal,aria-labelledby→ its heading
id, andtabindex="-1". A mount effect moves focus in, trapsTab, keeps
Escapeand backdrop-click, and restores focus to the opener onunmount. Dead
closeButtonRefand the parent's modal effect removed;onCloseis memoised so the 15 s poll can't re-trap focus.<dialog>: matching the existingfull-screen blur +
zoom-in+max-h-[90vh]styling on<dialog>/::backdropwould be a visible restyle, which the issue puts out of scope.
Acceptance: announced as a dialog with an accessible name ✓ · Tab/Shift+Tab
trapped ✓ · focus returns to opener ✓ · Escape + backdrop both dismiss ✓ ·
tests cover trapping (
wrapTabTarget) and restoration (focusRestorer) ✓Verification
Run in
apps/webagainstmainat 27c0932:pnpm typecheckpnpm lintpnpm testdb.integration.test.tsfails locally with no Postgres — pre-existing, identical on cleanmain, green in CIpnpm buildpnpm format:checkCommit 10 (
chore(web): reformat verify/page.tsx…) is aprettier --writeonone file this PR otherwise doesn't touch:
format:checkis currently red onmain(an unwrapped line left by #248), which turns theformatjob red onevery PR. Happy to drop it into a separate PR if you'd rather.
Per CONTRIBUTING I'd normally wait for assignment on each issue — flagging that
here; glad to split this back into four PRs if the Wave process needs it.