commit 50d183195c94d0995aafd60f0fa3766f5f5ba256 parent d65ff9a6c3c7478838ef1421dd32363495e99cba Author: Sebastian <sebasjm@gmail.com> Date: Mon, 5 Aug 2024 10:41:53 -0300 scope info in tx details Diffstat:
24 files changed, 496 insertions(+), 165 deletions(-)
diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts b/packages/taler-util/src/types-taler-wallet-transactions.ts @@ -191,6 +191,11 @@ export interface TransactionCommon { timestamp: TalerPreciseTimestamp; /** + * Scope of this tx + */ + scopes: ScopeInfo[]; + + /** * Transaction state, as per DD37. */ txState: TransactionState; diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -578,6 +578,47 @@ export type ScopeInfoAuditor = { export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; /** + * Shorter version of stringifyScopeInfo + * + * Format must be stable as it's used in the database. + */ +export function stringifyScopeInfoShort(si: ScopeInfo): string { + switch (si.type) { + case ScopeType.Global: + return `${si.currency}`; + case ScopeType.Exchange: + return `${si.currency}/${encodeURIComponent(si.url)}`; + case ScopeType.Auditor: + return `${si.currency}:${encodeURIComponent(si.url)}`; + } +} +export function parseScopeInfoShort(si: string): ScopeInfo | undefined { + const indexOfColon = si.indexOf(":"); + const indexOfSlash = si.indexOf("/"); + if (indexOfColon === -1 && indexOfColon === -1) { + return { + type: ScopeType.Global, + currency: si, + }; + } + if (indexOfColon > 0) { + return { + type: ScopeType.Auditor, + currency: si.substring(0, indexOfColon), + url: decodeURIComponent(si.substring(indexOfColon + 1)), + }; + } + if (indexOfSlash > 0) { + return { + type: ScopeType.Exchange, + currency: si.substring(0, indexOfSlash), + url: decodeURIComponent(si.substring(indexOfSlash + 1)), + }; + } + return undefined; +} + +/** * Encode scope info as a string. * * Format must be stable as it's used in the database. diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -43,6 +43,7 @@ import { PrepareDepositRequest, PrepareDepositResponse, RefreshReason, + ScopeInfo, SelectedProspectiveCoin, TalerError, TalerErrorCode, @@ -99,7 +100,13 @@ import { timestampProtocolFromDb, timestampProtocolToDb, } from "./db.js"; -import { getExchangeWireDetailsInTx, getExchangeWireFee } from "./exchanges.js"; +import { + getExchangeScopeInfo, + getExchangeScopeInfoOrUndefined, + getExchangeWireDetailsInTx, + getExchangeWireFee, + getScopeForAllExchanges, +} from "./exchanges.js"; import { extractContractData, generateDepositPermissions, @@ -193,6 +200,7 @@ export class DepositTransactionContext implements TransactionContext { return { type: TransactionType.Deposit, txState, + scopes: await getScopeForAllExchanges(tx, !dg.infoPerExchange? []: Object.keys(dg.infoPerExchange)), txActions: computeDepositTransactionActions(dg), amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount), amountEffective: isUnsuccessfulTransaction(txState) diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -248,6 +248,83 @@ async function getExchangeRecordsInternal( return details; } +export async function getScopeForAllCoins( + tx: WalletDbReadOnlyTransaction< + [ + "exchanges", + "exchangeDetails", + "globalCurrencyExchanges", + "globalCurrencyAuditors", + ] + >, + exs: string[], +): Promise<ScopeInfo[]> { + const queries = exs.map((exchange) => { + return getExchangeScopeInfoOrUndefined(tx, exchange); + }); + const rs = await Promise.all(queries); + return rs.filter((d): d is ScopeInfo => d !== undefined); +} + +export async function getScopeForAllExchanges( + tx: WalletDbReadOnlyTransaction< + [ + "exchanges", + "exchangeDetails", + "globalCurrencyExchanges", + "globalCurrencyAuditors", + ] + >, + exs: string[], +): Promise<ScopeInfo[]> { + const queries = exs.map((exchange) => { + return getExchangeScopeInfoOrUndefined(tx, exchange); + }); + const rs = await Promise.all(queries); + return rs.filter((d): d is ScopeInfo => d !== undefined); +} + +export async function getCoinScopeInfoOrUndefined( + tx: WalletDbReadOnlyTransaction< + [ + "coins", + "exchanges", + "exchangeDetails", + "globalCurrencyExchanges", + "globalCurrencyAuditors", + ] + >, + coinPub: string, +): Promise<ScopeInfo | undefined> { + const coin = await tx.coins.get(coinPub); + if (!coin) { + return undefined; + } + const det = await getExchangeRecordsInternal(tx, coin.exchangeBaseUrl); + if (!det) { + return undefined; + } + return internalGetExchangeScopeInfo(tx, det); +} + +export async function getExchangeScopeInfoOrUndefined( + tx: WalletDbReadOnlyTransaction< + [ + "exchanges", + "exchangeDetails", + "globalCurrencyExchanges", + "globalCurrencyAuditors", + ] + >, + exchangeBaseUrl: string, +): Promise<ScopeInfo | undefined> { + const det = await getExchangeRecordsInternal(tx, exchangeBaseUrl); + if (!det) { + return undefined; + } + return internalGetExchangeScopeInfo(tx, det); +} + export async function getExchangeScopeInfo( tx: WalletDbReadOnlyTransaction< [ @@ -2154,6 +2231,7 @@ export class DenomLossTransactionContext implements TransactionContext { return { type: TransactionType.DenomLoss, txState, + scopes: await getScopeForAllExchanges(tx, [rec.exchangeBaseUrl]), txActions: [TransactionAction.Delete], amountRaw: Amounts.stringify(rec.amount), amountEffective: Amounts.stringify(rec.amount), diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -161,6 +161,7 @@ import { getDenomInfo, WalletExecutionContext, } from "./wallet.js"; +import { getScopeForAllExchanges } from "./exchanges.js"; /** * Logger. @@ -276,6 +277,12 @@ export class PayMerchantTransactionContext implements TransactionContext { return { type: TransactionType.Payment, txState, + scopes: await getScopeForAllExchanges( + tx, + !purchaseRec.payInfo.payCoinSelection + ? [] + : purchaseRec.payInfo.payCoinSelection.coinPubs, + ), txActions: computePayMerchantTransactionActions(purchaseRec), amountRaw: Amounts.stringify(contractData.amount), amountEffective: isUnsuccessfulTransaction(txState) @@ -617,10 +624,17 @@ export class RefundTransactionContext implements TransactionContext { summary_i18n: maybeContractData.summaryI18n, }; } + const purchaseRecord = await tx.purchases.get(refundRecord.proposalId); const txState = computeRefundTransactionState(refundRecord); return { type: TransactionType.Refund, + scopes: await getScopeForAllExchanges( + tx, + !purchaseRecord || !purchaseRecord.payInfo?.payCoinSelection + ? [] + : purchaseRecord.payInfo.payCoinSelection.coinPubs, + ), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(refundRecord.amountEffective)) : refundRecord.amountEffective, diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -83,7 +83,7 @@ import { timestampPreciseFromDb, timestampPreciseToDb, } from "./db.js"; -import { fetchFreshExchange } from "./exchanges.js"; +import { fetchFreshExchange, getScopeForAllExchanges } from "./exchanges.js"; import { codecForExchangePurseStatus, getMergeReserveInfo, @@ -194,6 +194,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { type: TransactionType.PeerPullCredit, txState, + scopes: await getScopeForAllExchanges(tx, [pullCredit.exchangeBaseUrl]), txActions: computePeerPullCreditTransactionActions(pullCredit), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount)) @@ -228,6 +229,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { return { type: TransactionType.PeerPullCredit, txState, + scopes: await getScopeForAllExchanges(tx, [pullCredit.exchangeBaseUrl]), txActions: computePeerPullCreditTransactionActions(pullCredit), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount)) diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -103,6 +103,7 @@ import { parseTransactionIdentifier, } from "./transactions.js"; import { WalletExecutionContext } from "./wallet.js"; +import { getScopeForAllExchanges } from "./exchanges.js"; const logger = new Logger("pay-peer-pull-debit.ts"); @@ -166,6 +167,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return { type: TransactionType.PeerPullDebit, txState, + scopes: await getScopeForAllExchanges(tx, [pi.exchangeBaseUrl]), txActions: computePeerPullDebitTransactionActions(pi), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(pi.amount)) diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -79,7 +79,7 @@ import { timestampPreciseFromDb, timestampPreciseToDb, } from "./db.js"; -import { fetchFreshExchange } from "./exchanges.js"; +import { fetchFreshExchange, getScopeForAllExchanges } from "./exchanges.js"; import { codecForExchangePurseStatus, getMergeReserveInfo, @@ -183,6 +183,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { type: TransactionType.PeerPushCredit, txState, + scopes: await getScopeForAllExchanges(tx, [pushInc.exchangeBaseUrl]), txActions: computePeerPushCreditTransactionActions(pushInc), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount)) @@ -207,6 +208,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return { type: TransactionType.PeerPushCredit, txState, + scopes: await getScopeForAllExchanges(tx, [pushInc.exchangeBaseUrl]), txActions: computePeerPushCreditTransactionActions(pushInc), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount)) diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -93,6 +93,7 @@ import { notifyTransition, } from "./transactions.js"; import { WalletExecutionContext } from "./wallet.js"; +import { getScopeForAllExchanges } from "./exchanges.js"; const logger = new Logger("pay-peer-push-debit.ts"); @@ -167,6 +168,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return { type: TransactionType.PeerPushDebit, txState, + scopes: await getScopeForAllExchanges(tx, [pushDebitRec.exchangeBaseUrl]), txActions: computePeerPushDebitTransactionActions(pushDebitRec), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(pushDebitRec.totalCost)) diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -122,6 +122,7 @@ import { WalletExecutionContext, } from "./wallet.js"; import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; +import { getScopeForAllExchanges } from "./exchanges.js"; const logger = new Logger("refresh.ts"); @@ -186,6 +187,7 @@ export class RefreshTransactionContext implements TransactionContext { return { type: TransactionType.Refresh, txState, + scopes: await getScopeForAllExchanges(tx, !refreshGroupRecord.infoPerExchange? []: Object.keys(refreshGroupRecord.infoPerExchange)), txActions: computeRefreshTransactionActions(refreshGroupRecord), refreshReason: refreshGroupRecord.reason, amountEffective: isUnsuccessfulTransaction(txState) diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -160,6 +160,7 @@ import { fetchFreshExchange, getExchangePaytoUri, getExchangeWireDetailsInTx, + getScopeForAllExchanges, listExchanges, lookupExchangeByUri, markExchangeUsed, @@ -182,6 +183,7 @@ const logger = new Logger("withdraw.ts"); function buildTransactionForBankIntegratedWithdraw( wg: WithdrawalGroupRecord, + scopes: ScopeInfo[], ort?: OperationRetryRecord, ): TransactionWithdrawal { if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { @@ -202,6 +204,7 @@ function buildTransactionForBankIntegratedWithdraw( return { type: TransactionType.Withdrawal, txState, + scopes, txActions: computeWithdrawalTransactionActions(wg), exchangeBaseUrl: wg.exchangeBaseUrl, amountEffective: @@ -237,6 +240,7 @@ function buildTransactionForBankIntegratedWithdraw( function buildTransactionForManualWithdraw( wg: WithdrawalGroupRecord, exchangeDetails: ExchangeWireDetails | undefined, + scopes: ScopeInfo[], ort?: OperationRetryRecord, ): TransactionWithdrawal { if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) @@ -259,6 +263,7 @@ function buildTransactionForManualWithdraw( return { type: TransactionType.Withdrawal, txState, + scopes, txActions: computeWithdrawalTransactionActions(wg), amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount)) @@ -318,6 +323,18 @@ export class WithdrawTransactionContext implements TransactionContext { if (!withdrawalGroupRecord) { return undefined; } + const exchangeDetails = + withdrawalGroupRecord.exchangeBaseUrl === undefined + ? undefined + : await getExchangeWireDetailsInTx( + tx, + withdrawalGroupRecord.exchangeBaseUrl, + ); + const scopes = await getScopeForAllExchanges( + tx, + !exchangeDetails ? [] : [exchangeDetails.exchangeBaseUrl], + ); + const ort = await tx.operationRetries.get(this.taskId); if ( withdrawalGroupRecord.wgInfo.withdrawalType === @@ -325,16 +342,10 @@ export class WithdrawTransactionContext implements TransactionContext { ) { return buildTransactionForBankIntegratedWithdraw( withdrawalGroupRecord, + scopes, ort, ); } - const exchangeDetails = - withdrawalGroupRecord.exchangeBaseUrl === undefined - ? undefined - : await getExchangeWireDetailsInTx( - tx, - withdrawalGroupRecord.exchangeBaseUrl, - ); if (!exchangeDetails) { logger.warn( `transaction ${this.transactionId} is a manual withdrawal, but no exchange wire details found`, @@ -343,6 +354,7 @@ export class WithdrawTransactionContext implements TransactionContext { return buildTransactionForManualWithdraw( withdrawalGroupRecord, exchangeDetails, + scopes, ort, ); } diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx @@ -97,11 +97,11 @@ function pageDefinition<T extends object>(pattern: string): PageLocation<T> { export const Pages = { welcome: "/welcome", balance: "/balance", - balanceHistory: pageDefinition<{ currency?: string }>( - "/balance/history/:currency?", + balanceHistory: pageDefinition<{ scope?: string }>( + "/balance/history/:scope?", ), - searchHistory: pageDefinition<{ currency?: string }>( - "/search/history/:currency?", + searchHistory: pageDefinition<{ scope?: string }>( + "/search/history/:scope?", ), balanceDeposit: pageDefinition<{ amount: string }>( "/balance/deposit/:amount", @@ -109,8 +109,8 @@ export const Pages = { balanceTransaction: pageDefinition<{ tid: string }>( "/balance/transaction/:tid", ), - sendCash: pageDefinition<{ amount?: string }>("/destination/send/:amount"), - receiveCash: pageDefinition<{ amount?: string }>("/destination/get/:amount?"), + sendCash: pageDefinition<{ scope?: string }>("/destination/send/:scope"), + receiveCash: pageDefinition<{ scope?: string }>("/destination/get/:scope?"), dev: "/dev", exchanges: "/exchanges", diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, ScopeType, WalletBalance } from "@gnu-taler/taler-util"; +import { Amounts, ScopeInfo, ScopeType, WalletBalance } from "@gnu-taler/taler-util"; import { Fragment, VNode, h } from "preact"; import { TableWithRoundRows as TableWithRoundedRows @@ -25,7 +25,7 @@ export function BalanceTable({ goToWalletHistory, }: { balances: WalletBalance[]; - goToWalletHistory: (currency: string) => void; + goToWalletHistory: (currency: ScopeInfo) => void; }): VNode { return ( <Fragment> @@ -36,7 +36,7 @@ export function BalanceTable({ return ( <tr key={idx} - onClick={() => goToWalletHistory(av.currency)} + onClick={() => goToWalletHistory(entry.scopeInfo)} style={{ cursor: "pointer" }} > <td>{av.currency}</td> diff --git a/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx b/packages/taler-wallet-webextension/src/components/MultiActionButton.tsx @@ -20,10 +20,10 @@ import { Button } from "../mui/Button.js"; import arrowDown from "../svg/chevron-down.inline.svg"; import { ParagraphClickable } from "./styled/index.js"; -export interface Props { - label: (s: string) => TranslatedString; - actions: string[]; - onClick: (s: string) => Promise<void>; +export interface Props<T> { + label: (s: T) => TranslatedString; + actions: T[]; + onClick: (s: T) => Promise<void>; } /** @@ -37,19 +37,19 @@ export interface Props { * * @returns */ -export function MultiActionButton({ +export function MultiActionButton<T>({ label, actions, onClick: doClick, -}: Props): VNode { - const defaultAction = actions.length > 0 ? actions[0] : ""; +}: Props<T>): VNode { + const defaultAction = actions.length > 0 ? actions[0] : "" as T; const [opened, setOpened] = useState(false); - const [selected, setSelected] = useState<string>(defaultAction); + const [selected, setSelected] = useState<T>(defaultAction); const canChange = actions.length > 1; const options = canChange ? actions.filter((a) => a !== selected) : []; - function select(m: string): void { + function select(m: T): void { setSelected(m); setOpened(false); } diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx @@ -21,20 +21,24 @@ */ import { + ScopeInfo, + stringifyScopeInfoShort +} from "@gnu-taler/taler-util"; +import { TranslationProvider, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { createHashHistory } from "history"; -import { ComponentChildren, Fragment, h, VNode } from "preact"; -import { route, Route, Router } from "preact-router"; +import { ComponentChildren, Fragment, VNode, h } from "preact"; +import { Route, Router, route } from "preact-router"; import { useEffect, useState } from "preact/hooks"; +import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js"; import PendingTransactions from "../components/PendingTransactions.js"; import { PopupBox } from "../components/styled/index.js"; import { AlertProvider } from "../context/alert.js"; import { IoCProviderForRuntime } from "../context/iocContext.js"; import { useTalerActionURL } from "../hooks/useTalerActionURL.js"; import { strings } from "../i18n/strings.js"; -import { Pages, PopupNavBar, PopupNavBarOptions } from "../NavigationBar.js"; import { platform } from "../platform/foreground.js"; import { BackupPage } from "../wallet/BackupPage.js"; import { ProviderDetailPage } from "../wallet/ProviderDetailPage.js"; @@ -68,7 +72,7 @@ function ApplicationView(): VNode { } function redirectToURL(str: string): void { - platform.openNewURLFromPopup(new URL(str)) + platform.openNewURLFromPopup(new URL(str)); } return ( @@ -76,14 +80,26 @@ function ApplicationView(): VNode { <Route path={Pages.balance} component={() => ( - <PopupTemplate path="balance" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <PopupTemplate + path="balance" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <BalancePage goToWalletManualWithdraw={() => redirectTo(Pages.receiveCash({}))} - goToWalletDeposit={(currency: string) => - redirectTo(Pages.sendCash({ amount: `${currency}:0` })) + goToWalletDeposit={(scope: ScopeInfo) => + redirectTo( + Pages.sendCash({ + scope: encodeURIComponent(stringifyScopeInfoShort(scope)), + }), + ) } - goToWalletHistory={(currency: string) => - redirectTo(Pages.balanceHistory({ currency })) + goToWalletHistory={(scope: ScopeInfo) => + redirectTo( + Pages.balanceHistory({ + scope: encodeURIComponent(stringifyScopeInfoShort(scope)), + }), + ) } /> </PopupTemplate> @@ -112,7 +128,11 @@ function ApplicationView(): VNode { <Route path={Pages.backup} component={() => ( - <PopupTemplate path="backup" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <PopupTemplate + path="backup" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <BackupPage onAddProvider={() => redirectTo(Pages.backupProviderAdd)} /> @@ -127,9 +147,9 @@ function ApplicationView(): VNode { onPayProvider={(uri: string) => redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) } - onWithdraw={(amount: string) => - redirectTo(Pages.receiveCash({ amount })) - } + onWithdraw={async (_amount: string) => { + // redirectTo(Pages.receiveCash({ amount })) + }} pid={pid} onBack={() => redirectTo(Pages.backup)} /> @@ -219,7 +239,10 @@ function PopupTemplate({ }): VNode { return ( <Fragment> - <PendingTransactions goToTransaction={goToTransaction} goToURL={goToURL} /> + <PendingTransactions + goToTransaction={goToTransaction} + goToURL={goToURL} + /> <PopupNavBar path={path} /> <PopupBox> <AlertProvider>{children}</AlertProvider> diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx @@ -17,6 +17,7 @@ import { Amounts, NotificationType, + ScopeInfo, WalletBalance, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -41,8 +42,8 @@ import { AddNewActionView } from "../wallet/AddNewActionView.js"; import { NoBalanceHelp } from "./NoBalanceHelp.js"; export interface Props { - goToWalletDeposit: (currency: string) => Promise<void>; - goToWalletHistory: (currency: string) => Promise<void>; + goToWalletDeposit: (currency: ScopeInfo) => Promise<void>; + goToWalletHistory: (currency: ScopeInfo) => Promise<void>; goToWalletManualWithdraw: () => Promise<void>; } @@ -70,8 +71,8 @@ export namespace State { error: undefined; balances: WalletBalance[]; addAction: ButtonHandler; - goToWalletDeposit: (currency: string) => Promise<void>; - goToWalletHistory: (currency: string) => Promise<void>; + goToWalletDeposit: (currency: ScopeInfo) => Promise<void>; + goToWalletHistory: (currency: ScopeInfo) => Promise<void>; goToWalletManualWithdraw: ButtonHandler; } } @@ -156,7 +157,7 @@ export function BalanceView(state: State.Balances): VNode { .filter((b) => !Amounts.isZero(b.available)) .map((b) => { b.flags - return b.available.split(":")[0] + return b.scopeInfo }); if (state.balances.length === 0) { @@ -184,7 +185,7 @@ export function BalanceView(state: State.Balances): VNode { </Button> {currencyWithNonZeroAmount.length > 0 && ( <MultiActionButton - label={(s) => i18n.str`Send ${s}`} + label={(s) => i18n.str`Send ${s.currency}`} actions={currencyWithNonZeroAmount} onClick={(c) => state.goToWalletDeposit(c)} /> diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -22,10 +22,13 @@ import { Amounts, + ScopeInfo, TalerUri, TalerUriAction, TranslatedString, + parseScopeInfoShort, parseTalerUri, + stringifyScopeInfoShort, stringifyTalerUri, } from "@gnu-taler/taler-util"; import { @@ -96,7 +99,7 @@ export function Application(): VNode { redirectTo(Pages.balanceTransaction({ tid })); } function redirectToURL(str: string): void { - window.location.href = new URL(str).href + window.location.href = new URL(str).href; } return ( @@ -115,7 +118,10 @@ export function Application(): VNode { <Route path={Pages.qr} component={() => ( - <WalletTemplate goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <WalletTemplate + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <QrReaderPage onDetected={(talerActionUrl: TalerUri) => { redirectTo( @@ -132,7 +138,10 @@ export function Application(): VNode { <Route path={Pages.settings} component={() => ( - <WalletTemplate goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <WalletTemplate + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <SettingsPage /> </WalletTemplate> )} @@ -159,17 +168,33 @@ export function Application(): VNode { <Route path={Pages.balanceHistory.pattern} - component={({ currency }: { currency?: string }) => ( - <WalletTemplate path="balance" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + component={({ scope }: { scope?: string }) => ( + <WalletTemplate + path="balance" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <HistoryPage - currency={currency} - goToWalletDeposit={(currency: string) => - redirectTo(Pages.sendCash({ amount: `${currency}:0` })) + scope={ + !scope + ? undefined + : parseScopeInfoShort((scope)) } - goToWalletManualWithdraw={(currency?: string) => + goToWalletDeposit={(scope: ScopeInfo) => + redirectTo( + Pages.sendCash({ + scope: encodeURIComponent( + stringifyScopeInfoShort(scope), + ), + }), + ) + } + goToWalletManualWithdraw={(scope?: ScopeInfo) => redirectTo( Pages.receiveCash({ - amount: !currency ? undefined : `${currency}:0`, + scope: !scope + ? undefined + : encodeURIComponent(stringifyScopeInfoShort(scope)), }), ) } @@ -179,18 +204,34 @@ export function Application(): VNode { /> <Route path={Pages.searchHistory.pattern} - component={({ currency }: { currency?: string }) => ( - <WalletTemplate path="balance" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + component={({ scope }: { scope?: string }) => ( + <WalletTemplate + path="balance" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <HistoryPage - currency={currency} + scope={ + !scope + ? undefined + : parseScopeInfoShort((scope)) + } search - goToWalletDeposit={(currency: string) => - redirectTo(Pages.sendCash({ amount: `${currency}:0` })) + goToWalletDeposit={(scope: ScopeInfo) => + redirectTo( + Pages.sendCash({ + scope: encodeURIComponent( + stringifyScopeInfoShort(scope), + ), + }), + ) } - goToWalletManualWithdraw={(currency?: string) => + goToWalletManualWithdraw={(scope?: ScopeInfo) => redirectTo( Pages.receiveCash({ - amount: !currency ? undefined : `${currency}:0`, + scope: !scope + ? undefined + : encodeURIComponent(stringifyScopeInfoShort(scope)), }), ) } @@ -239,8 +280,14 @@ export function Application(): VNode { <WalletTemplate path="balance" goToURL={redirectToURL}> <TransactionPage tid={tid} - goToWalletHistory={(currency?: string) => - redirectTo(Pages.balanceHistory({ currency })) + goToWalletHistory={(scope: ScopeInfo) => + redirectTo( + Pages.balanceHistory({ + scope: encodeURIComponent( + stringifyScopeInfoShort(scope), + ), + }), + ) } /> </WalletTemplate> @@ -249,15 +296,22 @@ export function Application(): VNode { <Route path={Pages.balanceDeposit.pattern} - component={({ amount }: { amount: string }) => ( + component={({ + scope, + amount, + }: { + scope: string; + amount?: string; + }) => ( <WalletTemplate path="balance" goToURL={redirectToURL}> <DepositPage - amount={amount} - onCancel={(currency: string) => { - redirectTo(Pages.balanceHistory({ currency })); + scope={parseScopeInfoShort((scope))} + amount={!amount ? undefined : Amounts.parse(amount)} + onCancel={(scope: ScopeInfo) => { + redirectTo(Pages.balanceHistory({ scope: encodeURIComponent(stringifyScopeInfoShort(scope)) })); }} - onSuccess={(currency: string) => { - redirectTo(Pages.balanceHistory({ currency })); + onSuccess={(scope: ScopeInfo) => { + redirectTo(Pages.balanceHistory({ scope: encodeURIComponent(stringifyScopeInfoShort(scope)) })); }} /> </WalletTemplate> @@ -267,7 +321,11 @@ export function Application(): VNode { <Route path={Pages.backup} component={() => ( - <WalletTemplate path="backup" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <WalletTemplate + path="backup" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <BackupPage onAddProvider={() => redirectTo(Pages.backupProviderAdd)} /> @@ -283,8 +341,8 @@ export function Application(): VNode { onPayProvider={(uri: string) => redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) } - onWithdraw={(amount: string) => - redirectTo(Pages.receiveCash({ amount })) + onWithdraw={(_amount: string) => + redirectTo(Pages.receiveCash({ scope: "FIXME missing" })) } onBack={() => redirectTo(Pages.backup)} /> @@ -314,7 +372,11 @@ export function Application(): VNode { <Route path={Pages.dev} component={() => ( - <WalletTemplate path="dev" goToTransaction={redirectToTxInfo} goToURL={redirectToURL}> + <WalletTemplate + path="dev" + goToTransaction={redirectToTxInfo} + goToURL={redirectToURL} + > <DeveloperPage /> </WalletTemplate> )} @@ -349,8 +411,8 @@ export function Application(): VNode { <CallToActionTemplate title={i18n.str`Digital cash payment`}> <PaymentPage talerPayUri={decodeURIComponent(talerUri)} - goToWalletManualWithdraw={(amount?: string) => - redirectTo(Pages.receiveCash({ amount })) + goToWalletManualWithdraw={(_amount?: string) => + redirectTo(Pages.receiveCash({ scope: "FIXME missing" })) } cancel={() => redirectTo(Pages.balance)} onSuccess={(tid: string) => @@ -366,8 +428,8 @@ export function Application(): VNode { <CallToActionTemplate title={i18n.str`Digital cash payment`}> <PaymentTemplatePage talerTemplateUri={decodeURIComponent(talerUri)} - goToWalletManualWithdraw={(amount?: string) => - redirectTo(Pages.receiveCash({ amount })) + goToWalletManualWithdraw={(_amount?: string) => + redirectTo(Pages.receiveCash({ scope: "FIXME missing" })) } cancel={() => redirectTo(Pages.balance)} onSuccess={(tid: string) => @@ -417,7 +479,9 @@ export function Application(): VNode { <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}> <WithdrawPageFromParams onAmountChanged={async (newamount) => { - const page = `${Pages.ctaWithdrawManual({ amount: newamount })}?talerUri=${encodeURIComponent(talerUri)}`; + const page = `${Pages.ctaWithdrawManual({ + amount: newamount, + })}?talerUri=${encodeURIComponent(talerUri)}`; redirectTo(page); }} talerExchangeWithdrawUri={talerUri} @@ -485,8 +549,8 @@ export function Application(): VNode { <CallToActionTemplate title={i18n.str`Digital cash invoice`}> <InvoicePayPage talerPayPullUri={decodeURIComponent(talerUri)} - goToWalletManualWithdraw={(amount?: string) => - redirectTo(Pages.receiveCash({ amount })) + goToWalletManualWithdraw={(_amount?: string) => + redirectTo(Pages.receiveCash({ scope: "FIXME missing" })) } onClose={() => redirectTo(Pages.balance)} onSuccess={(tid: string) => @@ -537,23 +601,26 @@ export function Application(): VNode { <Route path={Pages.ctaAddExchange} component={({ talerUri }: { talerUri: string }) => { - const tUri = parseTalerUri(decodeURIComponent(talerUri)) - const baseUrl = tUri?.type === TalerUriAction.AddExchange ? tUri.exchangeBaseUrl : undefined + const tUri = parseTalerUri(decodeURIComponent(talerUri)); + const baseUrl = + tUri?.type === TalerUriAction.AddExchange + ? tUri.exchangeBaseUrl + : undefined; if (!baseUrl) { - redirectTo(Pages.balanceHistory({})) - return <div> - invalid url {talerUri} - </div> + redirectTo(Pages.balanceHistory({})); + return <div>invalid url {talerUri}</div>; } - return <CallToActionTemplate title={i18n.str`Add exchange`}> - <ConfirmAddExchangeView - url={baseUrl} - status="confirm" - error={undefined} - onCancel={() => redirectTo(Pages.balanceHistory({}))} - onConfirm={() => redirectTo(Pages.balanceHistory({}))} - /> - </CallToActionTemplate> + return ( + <CallToActionTemplate title={i18n.str`Add exchange`}> + <ConfirmAddExchangeView + url={baseUrl} + status="confirm" + error={undefined} + onCancel={() => redirectTo(Pages.balanceHistory({}))} + onConfirm={() => redirectTo(Pages.balanceHistory({}))} + /> + </CallToActionTemplate> + ); }} /> {/** @@ -665,7 +732,8 @@ function WalletTemplate({ <WalletNavBar path={path} /> <PendingTransactions goToTransaction={goToTransaction} - goToURL={goToURL} /> + goToURL={goToURL} + /> <WalletBox> <AlertProvider> <CurrentAlerts /> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -14,7 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, PaytoUri } from "@gnu-taler/taler-util"; +import { AmountJson, PaytoUri, ScopeInfo } from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; @@ -34,9 +34,10 @@ import { } from "./views.js"; export interface Props { - amount?: string; - onCancel: (currency: string) => void; - onSuccess: (currency: string) => void; + scope?:ScopeInfo; + amount?: AmountJson; + onCancel: (scope: ScopeInfo) => void; + onSuccess: (scope: ScopeInfo) => void; } export type State = diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -32,14 +32,15 @@ import { RecursiveState } from "../../utils/index.js"; import { Props, State } from "./index.js"; export function useComponentState({ - amount: amountStr, + amount, + scope, onCancel, onSuccess, }: Props): RecursiveState<State> { const api = useBackendContext(); const { i18n } = useTranslationContext(); const { pushAlertOnError } = useAlertContext(); - const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr); + const parsed = amount; const currency = parsed !== undefined ? parsed.currency : undefined; const hook = useAsyncAsHook(async () => { @@ -214,7 +215,7 @@ export function useComponentState({ amount: amountStr, depositPaytoUri, }); - onSuccess(currency); + onSuccess(scope!); } return { @@ -250,7 +251,7 @@ export function useComponentState({ currentAccount, cancelHandler: { onClick: pushAlertOnError(async () => { - onCancel(currency); + onCancel(scope!); }), }, depositHandler: { diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -24,6 +24,7 @@ import { Amounts, AmountString, parsePaytoUri, + ScopeInfo, ScopeType, stringifyPaytoUri } from "@gnu-taler/taler-util"; @@ -36,21 +37,26 @@ import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; const currency = "EUR"; -const amount = `${currency}:0`; +const amount = Amounts.parseOrThrow(`${currency}:0`); const withoutFee = (value: number): AmountResponse => ({ effectiveAmount: `${currency}:${value}` as AmountString, rawAmount: `${currency}:${value}` as AmountString, }); +const defaultScope: ScopeInfo = { + type: ScopeType.Global, + currency +} + + const withSomeFee = (value: number, fee: number): AmountResponse => ({ effectiveAmount: `${currency}:${value}` as AmountString, rawAmount: `${currency}:${value - fee}` as AmountString, }); - describe("DepositPage states", () => { it("should have status 'no-enough-balance' when balance is empty", async () => { const { handler, TestingContext } = createWalletApiMock(); - const props = { amount, onCancel: nullFunction, onSuccess: nullFunction }; + const props = { scope: defaultScope, amount, onCancel: nullFunction, onSuccess: nullFunction }; handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, { balances: [ diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx @@ -169,7 +169,12 @@ export const SomeBalanceWithNoTransactions = tests.createExample( transactionsByDate: { "11/11/11": [], }, - balances: [ + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, + balances: [ { available: "TESTKUDOS:10" as AmountString, flags: [], @@ -184,7 +189,7 @@ export const SomeBalanceWithNoTransactions = tests.createExample( }, }, ], - balanceIndex: 0, + }, ); @@ -192,6 +197,11 @@ export const OneSimpleTransaction = tests.createExample(TestedComponent, { transactionsByDate: { "11/11/11": [exampleData.withdraw], }, + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, balances: [ { flags: [], @@ -207,7 +217,7 @@ export const OneSimpleTransaction = tests.createExample(TestedComponent, { }, }, ], - balanceIndex: 0, + }); export const TwoTransactionsAndZeroBalance = tests.createExample( @@ -216,7 +226,12 @@ export const TwoTransactionsAndZeroBalance = tests.createExample( transactionsByDate: { "11/11/11": [exampleData.withdraw, exampleData.deposit], }, - balances: [ + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, + balances: [ { flags: [], available: "USD:0" as AmountString, @@ -231,7 +246,7 @@ export const TwoTransactionsAndZeroBalance = tests.createExample( }, }, ], - balanceIndex: 0, + }, ); @@ -246,6 +261,11 @@ export const OneTransactionPending = tests.createExample(TestedComponent, { }, ], }, + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, balances: [ { flags: [], @@ -261,7 +281,7 @@ export const OneTransactionPending = tests.createExample(TestedComponent, { }, }, ], - balanceIndex: 0, + }); export const SomeTransactions = tests.createExample(TestedComponent, { @@ -283,6 +303,11 @@ export const SomeTransactions = tests.createExample(TestedComponent, { exampleData.deposit, ], }, + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, balances: [ { flags: [], @@ -298,7 +323,7 @@ export const SomeTransactions = tests.createExample(TestedComponent, { }, }, ], - balanceIndex: 0, + }); export const SomeTransactionsInDifferentStates = tests.createExample( @@ -379,7 +404,12 @@ export const SomeTransactionsInDifferentStates = tests.createExample( exampleData.deposit, ], }, - balances: [ + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, + balances: [ { flags: [], available: "USD:10" as AmountString, @@ -394,7 +424,7 @@ export const SomeTransactionsInDifferentStates = tests.createExample( }, }, ], - balanceIndex: 0, + }, ); @@ -412,7 +442,12 @@ export const SomeTransactionsWithTwoCurrencies = tests.createExample( exampleData.deposit, ], }, - balances: [ + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, + balances: [ { flags: [], available: "USD:0" as AmountString, @@ -440,7 +475,7 @@ export const SomeTransactionsWithTwoCurrencies = tests.createExample( }, }, ], - balanceIndex: 0, + }, ); @@ -448,6 +483,11 @@ export const FiveOfficialCurrencies = tests.createExample(TestedComponent, { transactionsByDate: { "11/11/11": [exampleData.withdraw], }, + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, balances: [ { flags: [], @@ -515,7 +555,7 @@ export const FiveOfficialCurrencies = tests.createExample(TestedComponent, { }, }, ], - balanceIndex: 0, + }); export const FiveOfficialCurrenciesWithHighValue = tests.createExample( @@ -524,7 +564,12 @@ export const FiveOfficialCurrenciesWithHighValue = tests.createExample( transactionsByDate: { "11/11/11": [exampleData.withdraw], }, - balances: [ + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, + balances: [ { flags: [], available: "USD:881001321230000" as AmountString, @@ -591,7 +636,7 @@ export const FiveOfficialCurrenciesWithHighValue = tests.createExample( }, }, ], - balanceIndex: 0, + }, ); @@ -604,6 +649,11 @@ export const PeerToPeer = tests.createExample(TestedComponent, { exampleData.push_debit, ], }, + scope: { + currency: "Ásd", + type: ScopeType.Auditor, + url: "", + }, balances: [ { flags: [], @@ -619,5 +669,4 @@ export const PeerToPeer = tests.createExample(TestedComponent, { }, }, ], - balanceIndex: 0, }); diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -18,9 +18,11 @@ import { AbsoluteTime, Amounts, NotificationType, + ScopeInfo, ScopeType, Transaction, WalletBalance, + stringifyScopeInfoShort, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -49,35 +51,36 @@ import { TextField } from "../mui/TextField.js"; import { TextFieldHandler } from "../mui/handlers.js"; interface Props { - currency?: string; + scope?: ScopeInfo; search?: boolean; - goToWalletDeposit: (currency: string) => Promise<void>; - goToWalletManualWithdraw: (currency?: string) => Promise<void>; + goToWalletDeposit: (scope: ScopeInfo) => Promise<void>; + goToWalletManualWithdraw: (scope?: ScopeInfo) => Promise<void>; } export function HistoryPage({ - currency: _c, + scope, search: showSearch, goToWalletManualWithdraw, goToWalletDeposit, }: Props): VNode { const { i18n } = useTranslationContext(); const api = useBackendContext(); - const [balanceIndex, setBalanceIndex] = useState<number>(0); + const [selectedScope, setSelectedScope] = useState(scope); const [search, setSearch] = useState<string>(); const [settings] = useSettings(); const state = useAsyncAsHook(async () => { const b = await api.wallet.call(WalletApiOperation.GetBalances, {}); - const balance = - b.balances.length > 0 ? b.balances[balanceIndex] : undefined; + const balances = b.balances; + // const balance = + // b.balances.length > 0 ? b.balances[balanceIndex] : undefined; const tx = await api.wallet.call(WalletApiOperation.GetTransactions, { - scopeInfo: showSearch ? undefined : balance?.scopeInfo, + scopeInfo: showSearch ? undefined : selectedScope, sort: "descending", includeRefreshes: settings.showRefeshTransactions, search, }); - return { b, tx }; - }, [balanceIndex, search]); + return { balances, transactions: tx.transactions }; + }, [selectedScope, search]); useEffect(() => { return api.listener.onUpdateNotification( @@ -103,7 +106,7 @@ export function HistoryPage({ ); } - if (!state.response.b.balances.length) { + if (!state.response.balances.length) { return ( <NoBalanceHelp goToWalletManualWithdraw={{ @@ -113,7 +116,7 @@ export function HistoryPage({ ); } - const byDate = state.response.tx.transactions.reduce( + const txsByDate = state.response.transactions.reduce( (rv, x) => { const startDay = x.timestamp.t_s === "never" @@ -141,41 +144,48 @@ export function HistoryPage({ setSearch(d); }), }} - transactionsByDate={byDate} + transactionsByDate={txsByDate} /> ); } return ( <HistoryView - balanceIndex={balanceIndex} - changeBalanceIndex={(b) => setBalanceIndex(b)} - balances={state.response.b.balances} + scope={selectedScope ?? state.response.balances[0].scopeInfo} + changeScope={(b) => setSelectedScope(b)} + balances={state.response.balances} goToWalletManualWithdraw={goToWalletManualWithdraw} goToWalletDeposit={goToWalletDeposit} - transactionsByDate={byDate} + transactionsByDate={txsByDate} /> ); } export function HistoryView({ balances, - balanceIndex, - changeBalanceIndex, + scope, + changeScope, transactionsByDate, goToWalletManualWithdraw, goToWalletDeposit, }: { - balanceIndex: number; - changeBalanceIndex: (s: number) => void; - goToWalletDeposit: (currency: string) => Promise<void>; - goToWalletManualWithdraw: (currency?: string) => Promise<void>; + scope: ScopeInfo; + changeScope: (scope: ScopeInfo) => void; + goToWalletDeposit: (scope: ScopeInfo) => Promise<void>; + goToWalletManualWithdraw: (scope?: ScopeInfo) => Promise<void>; transactionsByDate: Record<string, Transaction[]>; balances: WalletBalance[]; }): VNode { const { i18n } = useTranslationContext(); + const scopeStr = stringifyScopeInfoShort(scope); + const balanceIndex = balances.findIndex( + (b) => stringifyScopeInfoShort(b.scopeInfo) === scopeStr, + ); const balance = balances[balanceIndex]; + if (!balance) { + return <div>unkown scope</div>; + } const available = balance ? Amounts.jsonifyAmount(balance.available) @@ -200,9 +210,7 @@ export function HistoryView({ tooltip="Transfer money to the wallet" startIcon={DownloadIcon} variant="contained" - onClick={() => - goToWalletManualWithdraw(balance.scopeInfo.currency) - } + onClick={() => goToWalletManualWithdraw(balance.scopeInfo)} > <i18n.Translate>Receive</i18n.Translate> </Button> @@ -212,7 +220,7 @@ export function HistoryView({ startIcon={UploadIcon} variant="outlined" color="primary" - onClick={() => goToWalletDeposit(balance.scopeInfo.currency)} + onClick={() => goToWalletDeposit(balance.scopeInfo)} > <i18n.Translate>Send</i18n.Translate> </Button> @@ -238,9 +246,8 @@ export function HistoryView({ }} value={balanceIndex} onChange={(e) => { - changeBalanceIndex( - Number.parseInt(e.currentTarget.value, 10), - ); + const bIdx = Number.parseInt(e.currentTarget.value, 10); + changeScope(balances[bIdx].scopeInfo); }} > {balances.map((entry, index) => { diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx @@ -61,6 +61,7 @@ export default { const commonTransaction: TransactionCommon = { error: undefined, amountRaw: "KUDOS:11" as AmountString, + scopes: [], amountEffective: "KUDOS:9.2" as AmountString, txState: { major: TransactionMajorState.Done, diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -25,6 +25,8 @@ import { OrderShortInfo, parsePaytoUri, PaytoUri, + ScopeInfo, + ScopeType, stringifyPaytoUri, TalerErrorCode, TalerPreciseTimestamp, @@ -78,7 +80,7 @@ import { assertUnreachable } from "../utils/index.js"; interface Props { tid: string; - goToWalletHistory: (currency?: string) => Promise<void>; + goToWalletHistory: (scope: ScopeInfo) => Promise<void>; } export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { @@ -116,7 +118,11 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { ); } - const currency = Amounts.parse(state.response.amountRaw)?.currency; + const currency = Amounts.parse(state.response.amountEffective)!.currency; + const txScope = !state.response.scopes.length ? { + type: ScopeType.Global as const, + currency, + } : state.response.scopes[0] return ( <TransactionView @@ -125,44 +131,44 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { await api.wallet.call(WalletApiOperation.FailTransaction, { transactionId, }); - goToWalletHistory(currency); + // goToWalletHistory(txScope); }} onSuspend={async () => { await api.wallet.call(WalletApiOperation.SuspendTransaction, { transactionId, }); - goToWalletHistory(currency); + // goToWalletHistory(txScope); }} onResume={async () => { await api.wallet.call(WalletApiOperation.ResumeTransaction, { transactionId, }); - goToWalletHistory(currency); + // goToWalletHistory(txScope); }} onAbort={async () => { await api.wallet.call(WalletApiOperation.AbortTransaction, { transactionId, }); - goToWalletHistory(currency); + // goToWalletHistory(txScope); }} onRetry={async () => { await api.wallet.call(WalletApiOperation.RetryTransaction, { transactionId, }); - goToWalletHistory(currency); + // goToWalletHistory(txScope); }} onDelete={async () => { await api.wallet.call(WalletApiOperation.DeleteTransaction, { transactionId, }); - goToWalletHistory(currency); + goToWalletHistory(txScope); }} onRefund={async (transactionId) => { await api.wallet.call(WalletApiOperation.StartRefundQuery, { transactionId, }); }} - onBack={() => goToWalletHistory(currency)} + onBack={() => goToWalletHistory(txScope)} /> ); }