diff --git a/docs/implementation.md b/docs/implementation.md index ed6b82f..31ee0be 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -141,3 +141,15 @@ current packages, app/website commands, verification previews, analytics limits, skill location and contribution workflow. Local links and diff checks pass; commands and prerequisites were checked against repository configuration. Documentation only; no runtime tests were run. + +## Exchange workspace UX plan — task #24 + +- Plan-only scope confirmed by the user; no application code changed. +- [Exchange workspace plan](ux/exchange-workspace.md) defines accounts, top-up, + exchange, payout, Activity, Limits and Settings; account-owned language replaces + repeated page selectors in the proposed interface. +- Includes authoritative quotes/status/limits, idempotent recovery, account-profile + language persistence, accessible/responsive behavior and semantic funnel metrics. +- Standalone documentation branch `exchange-shell-24` from main. Future interface + implementation should incorporate KYC PR #21 and usage PR #23. +- Validation: documentation links and diff checks; no runtime changes or tests. diff --git a/docs/ux/exchange-workspace.md b/docs/ux/exchange-workspace.md new file mode 100644 index 0000000..4d61a5e --- /dev/null +++ b/docs/ux/exchange-workspace.md @@ -0,0 +1,370 @@ +# Exchange workspace — UX plan + +Task [#24](https://git.sendico.io/tech/sxchnge/issues/24). **Plan only; no app changes.** + +## 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, reserved amounts, recent activity, actionable pending items | Top up, exchange, pay out | +| Currency account | Available/booked balance, holds, 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, reserved, 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. + +```text +ACCOUNTS Account menu + +Your accounts [Add currency account, if supported] +[ EUR · available balance ] [ USD · available balance ] … + +Selected account: EUR +Available: … Reserved: … +[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. +- Distinguish available, booked and reserved balances. “Available” is the default + spendable number; disclose the balance breakdown on account detail. Backend + balances are authoritative; a list of recent operations is not a balance ledger. +- 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](https://git.sendico.io/tech/sxchnge/pulls/21), +[PR #23](https://git.sendico.io/tech/sxchnge/pulls/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. + +## 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.