What
Rebuilds the admin Bank withdrawals screen.
-
List: the withdrawer column shows the phone number (name or email underneath) instead of the user id; bank name +
•••• last4(with a "removed" tag); gross amount with fee · net; status with the failure reason; created time with relative age. Status filter is a pill group (Needs review first, still the default). The whole row links to the detail page. -
Detail page
/fiat-payouts/:payoutId: amount header withphone → bank •••• last4and status, an alert strip when the payout needs attention, gross/fee/net, withdrawer (link to the user), destination bank, Airwallex provider data (copyable ids), a timeline with the next pending step, and the resolution panel. LINK / RELEASE / RETURN are choice cards; RELEASE and RETURN need an inline confirm. A recorded resolution shows the admin's name instead of an id. -
Backend:
FiatPayoutAdminServicelist / getOne / resolve return an enriched view withuser(id, phone, email, name),bank(beneficiary incl. soft-deleted, no Airwallex beneficiary id) andresolvedBy(id, email, name). Batch-loaded (one query per relation per page), no migration, permissions unchanged.
Verification
- admin:
pnpm test270 pass,pnpm lint:check0 errors,pnpm type-checkclean. - be:
yarn test src/modules/fiat-payoutpass;fiat-payout-admin.postgres.spec.ts17/17 against a throwaway DB (new case: getOne returns phone + bank last4). -
resolvedBywas checked by hand end to end (worktree BE on a cloned local DB: the API returned the resolving admin and the detail page rendered "Resolved by …"); no automated spec covers that query against Postgres. - Pages checked visually in a browser with 8 payouts across every status, light and dark.
- Opus review gate: PASS after 2 rounds.