Files
sxchnge/docs/ux/exchange-workspace.md

26 KiB

Exchange workspace — UX plan

Task #24. Approved plan; implementation tracked in task #28.

Product direction

Make it easy to fund a currency account, exchange into another currency, and pay out. Accounts answer “what can I use?”, Activity answers “what happened?”, Limits answers “what can I do next?”, and Settings owns account preferences.

A new user should see a clear first step: Top up an account. A funded user should be able to exchange or pay out from that account without navigating through settings or a generic dashboard. Unverified is a valid state with basic limits. KYC is offered when an upgrade is useful, not as a forced onboarding detour.

Use the existing SXCHNGE theme, rounded surfaces, typography and semantic colors. Keep decoration secondary to balances, amounts, recipients and transaction terms.

Navigation and screen hierarchy

Destination What it contains Main next action
Accounts (default) Currency accounts, available balances, recent activity, actionable pending items Top up, exchange, pay out
Currency account Available balance, relevant activity and applicable limits Act on this currency
Activity All operations, filters, search, processing items Inspect an operation or resolve an actionable issue
Limits Current caps, used and available, period/reset information and upgrade benefits Verify identity when useful
Settings Profile, language, security, notifications and help Manage the account

Desktop: a persistent sidebar with Accounts · Activity · Limits · Settings. Mobile: the same four destinations in bottom navigation. Highlight the active section. Identity verification is reached from Limits and Settings; it does not need a separate primary navigation item.

Top up, exchange and pay out are actions, not three competing dashboard tabs. On Accounts and currency-detail screens, put them beside the selected account on wide screens and in a clearly labeled action area beneath its balance on mobile. The transaction flow uses a focused page with Back and Close, a visible source account and a review step. Avoid full-screen wizard chrome on simple settings.

Proposed routes, to be finalized during implementation:

Route Purpose
/accounts Overview
/accounts/:accountId Currency account detail
/top-up Account → method → instructions/payment → operation
/exchange Source/destination/amount → quote review → operation
/payout Account/method/recipient/amount → review → operation
/activity Operation list
/activity/:operationId Canonical operation detail
/limits Usage, constraints and KYC upgrade
/identity Existing identity verification flow
/settings Account settings
/settings/preferences Language and other supported preferences
/settings/security Supported security actions

Use opaque IDs in paths. Never put recipient details, document data, amounts, verification tokens or sensitive filter text into route parameters/history. URLs identify screens; providers hold transient input. Deep links to operation status must reload authorized service data, not depend on a previous screen.

Accounts overview

The first viewport should show account balances and useful actions, rather than charts or promotional blocks.

ACCOUNTS                                         Account menu

Your accounts                 [Add currency account, if supported]
[ EUR · available balance ] [ USD · available balance ] …

Selected account: EUR
Available: …
[Top up] [Exchange] [Pay out]

Action needed / in progress     (only when there is something useful to show)
Recent activity                                       [See all]
  • Each card shows currency code/name, available amount and a text status when restricted. Do not rely on flags to identify currencies.
  • Show only available balances. Held/reserved/booked amounts are internal service details, not customer-facing concepts. Backend balances remain authoritative.
  • Do not sum different currencies directly. A future total in a reporting currency must be labeled estimated, with valuation currency/time and a service-provided conversion. It is not needed for the first interface.
  • Zero balance: explain that the account is ready and make Top up primary. Funded account: make Exchange primary, with Top up and Pay out visible. Never rearrange actions while the user is interacting with them.
  • No accounts: offer account creation only if the backend supports it. Otherwise explain provisioning status and provide Refresh/Help; do not invent accounts.
  • Partial failure: keep successfully loaded accounts usable. Mark stale balances, offer refresh, and obtain fresh eligibility/quote data before execution.
  • Context travels into the flow: starting from EUR preselects EUR. Account switching invalidates any quote and refreshes methods and eligibility.

Top-up journey

Outcome: funds are credited and become available, not merely instructions shown or a payment button pressed.

  1. Choose the receiving account. If there is one eligible account, preselect it.
  2. Choose from service-provided methods. Show fees, minimum/maximum, expected timing when supplied, and whether an external step is required. Do not advertise rails that are not actually available.
  3. Collect only the fields required by the selected method. Amount is required only when that method needs it. Validate currency precision and current bounds.
  4. Review what the user pays, fees, and what is expected to be credited. For bank instructions, show exact destination/reference from the service with labeled copy actions and “Copied” feedback. For redirects, explain the next step before opening the provider. Never collect card details in generic custom fields.
  5. Create/continue the operation and show its canonical status. “Awaiting payment” or “Processing” remains pending until the service confirms credit.
  6. On credit, refresh balances, usage and activity. Offer Exchange this balance with the funded currency preselected, plus View account.

Returned from a provider does not mean paid. Reconcile using the operation ID. Handle abandoned redirects, payment decline, expired instructions, wrong/partial amount, delayed credit and service failure with method-specific service responses. Do not invent a completion SLA. Never expose a “Mark paid” control as proof of funds.

Exchange journey

Outcome: the exchange is executed and both currency accounts reflect the result.

  1. One composition screen: source account, destination currency/account, “You send” and “You receive.” Clearly mark which amount is editable; editing the other side requests a new quote if the backend supports receive-exact quotations.
  2. Show available source balance and eligibility near the input. Currency swap exchanges the selection and invalidates the prior quote. Unsupported pairs are explained before the review step. Offer Add account only if supported.
  3. Quote response owns the rate, fees, total debit, net credit, quote ID and expiry. Show 1 source currency = … destination currency, the actual debit and net receipt; never bury fees behind an unexplained estimate.
  4. Review exchange summarizes both accounts, amounts, fees and rate. The final button says Exchange [amount/currency]. Disable final execution while the quote is missing, stale, expired or being refreshed, with a nearby reason.
  5. If terms change, show the new quote and require confirmation again. Never silently execute a refreshed rate. Prevent double submission and reuse the idempotency key when recovering the same request.
  6. Processing leads to operation detail. Only service-confirmed execution shows Exchange complete. Refresh both accounts, activity and limits. Offer Pay out [destination currency] and View account.

“Max” must use a service-provided spendable amount inclusive of fees and applicable limits; do not assume the displayed balance can all be exchanged. If the amount is above the current permitted limit, show the actual blocker and an upgrade link only when KYC would help. Keep the entered amount when requesting a new quote.

Payout journey

Outcome: the payout reaches a service-confirmed terminal result. Distinguish “submitted,” “sent” and “received” when those are separate states in the contract.

  1. Preselect the source currency account. Choose a supported payout method and an existing recipient, or enter a new destination. Saved recipients are optional future functionality; do not imply they exist without a service.
  2. Method-specific fields and validation come from the service contract. Clearly identify the beneficiary/destination, bank or network when applicable. Do not guess which currencies support which destination types.
  3. Enter amount; show fee, total debit, destination amount/currency, available funds, limits and expected timing supplied by the backend.
  4. Review the recipient and full destination sufficiently to detect a mistake, alongside the amount and costs. The final button says Pay out [amount/currency]. Explain irreversibility where applicable; avoid a redundant confirmation dialog after an already clear review page. Perform step-up authentication when required by the backend, without losing the pending operation context.
  5. Show processing/status with a reference. Cancellation is offered only when the service says the operation remains cancellable. If supported, confirm the cancellation and refresh status rather than optimistically removing the payout.
  6. On confirmed success, show the outcome and account/activity links. A returned or reversed payout remains visible with its own explanation and linked reversal.

Insufficient funds should offer Top up this account or Exchange into this currency, preserving the draft locally. After funding, revalidate recipient, fees, available balance and limits before allowing submission. Never auto-submit. A transfer destination must never be included in analytics, logs or URLs.

Shared transaction states and recovery

State User-facing behavior
Initial/draft Clear primary action, no invented balances/rates
Loading balance/methods/quote Specific progress; keep unrelated navigation usable
Validation issue Inline field message and focus on first problem; preserve input
Not eligible Reason plus relevant next step: email, limits, account funding or support
No supported method/pair Explain unavailability; let user change the selection
Quote expired Keep input; request a new quote and require fresh confirmation
Submission in progress Prevent duplicate execution and show progress
Connection lost after submit “Checking the outcome”; retrieve by operation/idempotency ID, not a fresh submission
Pending external/human processing Status and next useful action; pending is not failure or abandonment
Confirmed failure Safe reason, support/reference where available; retry only when service permits
Confirmed success Authoritative receipt, refreshed balances/limits and useful next action
Session expired Reauthenticate and rehydrate operation status; do not retain sensitive fields in browser storage

Close/back preserves an in-memory draft for a clear session-scoped lifetime. Warn before discarding meaningful recipient/payment input. Quotes always expire independently of the draft. Hard reload can discard draft input; submitted operations must survive because they are reloaded from the backend. Logging out clears all account-scoped providers and drafts to prevent cross-account leakage.

Activity and operation detail

Activity is the common destination for the outcome of every transaction flow. Show type, source/destination currencies, meaningful amount(s), time, status and reference. Exchanges are one parent operation showing both debit and credit, not two unrelated rows that look like duplicate transactions.

  • Filters: all/top-ups/exchanges/payouts, status, currency/account and date range. Preserve filters when returning from a detail page. Provide Clear filters and distinguish “no operations yet” from “no matching results” and a load failure.
  • Account-detail activity is the same view scoped to that account. Paginate with stable service cursors; refresh without duplicating rows.
  • Detail includes status timeline, amount breakdown, fees, relevant destination, timestamps, available receipt/reference and service-permitted actions.
  • Pending items show what is known and whether the user needs to act. Avoid endless spinners or a fake percentage for external settlement.
  • Try again creates a new draft only after confirmed failure and backend permission; never replays a consequential request from a history button.

Limits and identity

Reuse the current KYC and usage work (PR #21, PR #23). Limits must show used, reserved, available and caps for the relevant operation/currency/period. Include window/reset information only when supplied; do not imply a calendar reset for a rolling window. Preserve service ordering and applicability.

The full page explains active tier and what verification would unlock. Transaction forms show the relevant constraint in context and link to that page. A KYC upgrade must not claim to resolve insufficient funds, an unsupported method or an unrelated restriction. Submission/pending review does not increase limits; refetch the actual backend projection after any decision.

Account settings and language

Language belongs to the account. Remove page-level language selectors from the main workspace and identity pages. Settings → Preferences contains English/Russian with one clear selected value. Change language without resetting drafts or filters.

  • Profile service owns preferredLocale; an account provider loads and updates it. Device storage is a cache/guest fallback, not the source of truth for a signed-in account. Use an injectable stub until profile endpoints are available.
  • On sign-in, the server preference takes precedence. Before account creation, use browser/device locale plus a remembered guest preference. If a guest needs a manual override, expose it once through the auth shell's settings/help entry, not on every login/verification page.
  • On save failure, clearly state “Not saved to your account,” retain the choice for the current session and offer Retry. Do not report a successful account update.
  • Server-provided human-readable labels must be fetched for the selected language; preserve stable IDs and in-progress inputs. Avoid mixed-language stale responses.
  • Security settings show only capabilities actually implemented (password change, sessions, MFA when supported). Do not present decorative toggles as protection.
  • Identity status is linked here but managed in the existing verification flow. Notification preferences require a real preference service; support has a genuine destination before production. No placeholder action should pretend to complete.

Service and architecture boundaries

Keep shared DTO → mapper → service → model → provider ownership for future mobile reuse. Widgets consume provider state; they do not compute financial outcomes. Proposed domains, not speculative API endpoints:

Domain service Owns authoritative data
Accounts Account IDs, currencies, booked/available/held balances, status
Funding Eligible methods, instructions, fees, top-up creation/status
Exchange Pair eligibility, quotes/expiry, idempotent execution
Payout Method/recipient validation, quotes, submission and permissible actions
Operations History, canonical status, references, receipts and reconciliation
Limits Applicable caps, committed/reserved usage, remaining and upgrade projection
Profile Account preferences including language

Use currency-aware decimal/minor-unit values with currency precision metadata; never floating-point money or a universal two-decimal assumption. Balance held amounts and limits reserved usage are different concepts, even when values match. Backend authorization and limit reservations remain necessary at execution time. A visible balance, valid quote or frontend KYC state cannot authorize a payment.

For the demo, shared stub state must be coherent across screens: exchange affects both balances; pending payouts hold capacity; failures release simulated holds where appropriate; activity and limits reflect the same simulated operation. Never mix a simulated transaction with a live success claim. Avoid seeded history that contradicts displayed balances or usage without explaining the fixtures.

Responsiveness, accessibility and trust

  • Mobile: one-column forms and review summaries; no horizontal table required to complete a task. Wide screens may place quote summary beside input.
  • Large values and long beneficiary names wrap safely. Use EN/RU, realistic currency precision and text scaling in design/test fixtures.
  • Monetary fields identify currency and precision; keyboard input accepts the active locale's decimal separator without silently altering value. Avoid formatting the value under the user's cursor on every keystroke.
  • Provide labeled 48px targets, visible keyboard focus and predictable focus after validation/navigation. Statuses have text/icons, never color alone.
  • Announce meaningful quote/state changes, not every countdown tick. Honor reduced motion. An expiry countdown accompanies a clear expired state.
  • Mask sensitive destinations in lists, reveal enough in explicit review/detail to verify them. No session replay/autocapture on money, recipient or identity forms.
  • Keep unrelated accounts/history accessible during a pending operation. Do not block the whole product behind an exchange spinner.

Empty currency accounts (task #66)

The shared empty-account message used on Accounts and transfer screens explains that no currency accounts have been set up and directs the customer to support to request setup. English and Russian no longer suggest that refreshing creates accounts or imply that automatic provisioning is underway. This follows the existing support guidance for missing funding and payout setup. The application has no configured support contact channel; the message therefore provides text guidance without a nonfunctional contact button or invented contact details.

UX review: the copy names the missing resource and the next step, retains the existing wrapping text and accessible reading order, and does not alter loading, error or populated-state behavior. Existing manual refresh remains available for accounts provisioned externally. This copy-only change adds no controls or journey outcomes and requires no new analytics events. Existing transaction journey signals and their production measurement limitations below still apply; support resolution and external account provisioning are not measured in-app.

Validation: flutter gen-l10n, flutter analyze in frontend/app, make test-frontend (217 tests), and git diff --check passed. No Dart source changed, so Dart formatting was not applicable. Existing responsive workspace tests cover Russian at 320px with enlarged text; this copy-only change does not add a dedicated empty-state rendering or native screen-reader test. No security scan was requested. Delivery: task #66, branch wallet-66, PR #68. Merge and deployment remain separate.

Journey measurement plan

Reuse the typed analytics abstraction and maintain a separate journey per active transaction draft. Random journey IDs correlate steps; opaque operation IDs link accepted requests to server outcomes. Retries reuse the journey and idempotency lineage. Never capture amount, balance, currency-account IDs, recipient, form input, URLs or raw exceptions in client UX events by default.

Journey Start Useful states Actual success
Top up User begins funding Method ready, instructions/payment ready, accepted/pending, failure Service-confirmed credit
Exchange User begins exchange Valid quote, reviewed, accepted/pending, expired/failure Service-confirmed execution
Payout User begins payout Valid destination/quote, reviewed, accepted/pending, failure Contract-defined confirmed payout outcome

Measure start-to-acceptance and acceptance-to-terminal conversion separately. Use one count per journey for draft conversion and one per operation for financial outcomes. Track review-to-submit drop-off, quote expiry, recoverable failure rate, time to acceptance and server time to terminal outcome. Classify failures with safe enums, such as insufficient funds, limits, expired quote, unavailable method, session and service error; emitted events must have a concrete diagnostic use.

An explicit draft discard or 30 minutes of settled inactivity is draft abandonment. Changing tabs while preserving a draft is not immediate abandonment. Accepted operations leave the draft abandonment window and remain pending until authoritative status; app closure must not fabricate failure. Multi-session reconciliation and server-side outcome events are required before claiming production funnel coverage. Deduplicate by event/operation identity. Analytics errors must not delay execution.

Account/language preference updates reuse profile outcome signals; do not add click events for every setting, card or navigation tab. Simulated events are always flagged and excluded from real transaction/verification conversion dashboards.

Implementation sequence after approval

  1. Shared workspace foundation: accounts/operations/profile contracts and coherent stubs, shell/routes, account overview/detail, Activity/detail, account language provider/settings. Move language selectors into settings. Preserve auth/email and existing identity/limits navigation.
  2. Top-up vertical slice: method/instruction/review/status, balance refresh and an exchange handoff. Include cancellation, pending and failed outcomes.
  3. Exchange vertical slice: input/quote/review, expiry, idempotent execution, coherent balances/history/usage and payout handoff.
  4. Payout vertical slice: method/destination/review, step-up contract hooks, reconciliation and confirmed failure/return paths.
  5. Integrated review: cross-flow drafts, limits/KYC return paths, language changes, responsive/keyboard/screen-reader checks, shared-service tests and semantic event assertions. Complete each slice's tests as it is built, not only at the end.

Actual supported currencies/rails, account provisioning, fee/rate policies, recipient requirements, cancellation and support contracts must come from backend capabilities. Demo fixtures can illustrate them but must be labeled and isolated. Real execution is outside this UX-plan task.

Plan acceptance and validation boundary

The plan connects all requested areas and defines optional KYC, usage, language ownership, consequential review, state recovery, privacy and measurement. Before calling implementation complete, walk: new account → top up → exchange → payout → Activity; pending/unknown outcome recovery; exceeded limits → KYC → resume/requote; and locale change with a preserved draft. Test stale/out-of-order responses, repeated submit, expired quote, zero balances, no methods, denied KYC, and logout.

This document is a design deliverable, not evidence that these screens exist or that accessibility/payment flows have been tested. Existing demo implementation and pending PRs remain unchanged by this task.

Implementation simplification — user direction for #28

Only available balances are shown. Customer limits show used and available, with no held/reserved breakdown or explanatory jargon. Keep four navigation destinations and straightforward input → review → result flows. Demo funding/payout methods are explicitly simulated, not representations of live supported rails.

Implemented demo scope — #28

Four primary destinations and three transaction journeys are implemented. The single demo method needs no method picker; top-up and exchange collect an amount, payout additionally collects sample destination text. Review shows fees, source debit, destination credit and rate. There are no live payment instructions, redirects, saved recipients, security controls or notification preferences. Settings implements account language and links to identity. Unsupported production capabilities remain future work, not decorative controls.

Pending top-up: money has not reached available balance. Pending outgoing: total amount plus fee has left available balance, and processing continues. Completed exchange/top-up: destination balance is credited. Failed outgoing: amount plus fee is available again. These explanations appear on canonical operation detail. Pending snapshots refresh automatically across screens; refresh failures mark data potentially stale. The limits service independently supplies usage and remaining. The customer sees aggregate consumed capacity and available capacity, not internal reservations.

Activity offers kind/status/currency filters and reference search; operation detail is deep-linkable while the demo session exists. Date filtering, pagination, receipts, persistent history, actual reset timestamps and production support await backend contracts. Transient drafts survive pushed settings/limits screens and locale changes; closing a draft requires discard confirmation. Browser reload resets the entire demo and is not durable recovery.

Measurement uses existing random journey IDs and typed quote/submission/settlement steps. Only service-confirmed terminal status closes the transaction outcome. Accepted operations are excluded from draft-abandonment on app disposal. Actual server events, persistent reconciliation and cross-session conversion remain integration gaps. No automatic click tracking, replay or financial data collection is introduced.