Points: 200
Labels: frontend, typescript, hooks, state-management
Background
useTransaction tracks status with a plain "idle" | "pending" | "success" | "failed" string and separate hash / error fields.
Consumers must manually cross-reference these fields to determine the full state, making it easy to render stale error messages alongside a new pending status. On the Dashboard, both cancelTx and ppuTx use this hook independently with no shared reset logic.
Task
Refactor useTransaction to use a discriminated-union state type:
type TxState =
| { status: "idle" }
| { status: "pending" }
| { status: "success"; hash: string }
| { status: "failed"; error: string };
Expose state: TxState alongside the existing spread fields for backward compatibility during migration.
Add a reset() function that returns the machine to:
Update Dashboard.tsx to call reset() when a modal is dismissed so stale error/hash state does not bleed into the next action.
Keep POLL_INTERVAL_MS and POLL_TIMEOUT_MS configurable via optional constructor arguments.
Key Files
frontend/src/hooks/useTransaction.ts
frontend/src/components/Dashboard.tsx
frontend/src/__tests__/useTransaction.test.ts (new)
Acceptance Criteria
state.status === "success" is the only state where state.hash is accessible without a type-cast; TypeScript must enforce this at compile time (npm run typecheck passes).
reset() transitions from any state back to idle.
- Dashboard calls
reset() on ConfirmModal cancel and on DailyLimitModal close.
- Existing
status, hash, error fields remain on the return type (derived from state) so no other consumer breaks.
- Tests cover all four state transitions and the reset() path.
Points: 200
Labels: frontend, typescript, hooks, state-management
Background
useTransactiontracks status with a plain"idle" | "pending" | "success" | "failed"string and separatehash/errorfields.Consumers must manually cross-reference these fields to determine the full state, making it easy to render stale error messages alongside a new pending status. On the Dashboard, both
cancelTxandppuTxuse this hook independently with no shared reset logic.Task
Refactor
useTransactionto use a discriminated-union state type:Expose
state: TxStatealongside the existing spread fields for backward compatibility during migration.Add a
reset()function that returns the machine to:Update
Dashboard.tsxto callreset()when a modal is dismissed so stale error/hash state does not bleed into the next action.Keep
POLL_INTERVAL_MSandPOLL_TIMEOUT_MSconfigurable via optional constructor arguments.Key Files
frontend/src/hooks/useTransaction.tsfrontend/src/components/Dashboard.tsxfrontend/src/__tests__/useTransaction.test.ts(new)Acceptance Criteria
state.status === "success"is the only state wherestate.hashis accessible without a type-cast; TypeScript must enforce this at compile time (npm run typecheckpasses).reset()transitions from any state back to idle.reset()on ConfirmModal cancel and on DailyLimitModal close.status,hash,errorfields remain on the return type (derived fromstate) so no other consumer breaks.