aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-08-05 13:19:35 -0300
committerSebastian <sebasjm@gmail.com>2024-08-05 13:19:35 -0300
commit784ec2f7695a1a0dc4eb75145309bde53d87eab9 (patch)
treef5eaa8a2845b2b545f6c1bf8e4d14277235bfd7c /packages/taler-wallet-webextension
parentbdb151b8c73e526f72274c5a2d09a06c7f881175 (diff)
use scope info for balance and tx
Diffstat (limited to 'packages/taler-wallet-webextension')
-rw-r--r--packages/taler-wallet-webextension/src/NavigationBar.tsx70
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts17
-rw-r--r--packages/taler-wallet-webextension/src/popup/Application.tsx23
-rw-r--r--packages/taler-wallet-webextension/src/popup/BalancePage.tsx10
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts5
-rw-r--r--packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts28
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Application.tsx241
-rw-r--r--packages/taler-wallet-webextension/src/wallet/BackupPage.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts20
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts123
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts55
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/wallet/History.tsx4
14 files changed, 380 insertions, 226 deletions
diff --git a/packages/taler-wallet-webextension/src/NavigationBar.tsx b/packages/taler-wallet-webextension/src/NavigationBar.tsx
index d3a0ab37c..56c08f906 100644
--- a/packages/taler-wallet-webextension/src/NavigationBar.tsx
+++ b/packages/taler-wallet-webextension/src/NavigationBar.tsx
@@ -39,7 +39,10 @@ import qrIcon from "./svg/qr_code_24px.inline.svg";
import settingsIcon from "./svg/settings_black_24dp.inline.svg";
import warningIcon from "./svg/warning_24px.inline.svg";
import { parseTalerUri, TalerUriAction } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import {
+ encodeCrockForURI,
+ useTranslationContext,
+} from "@gnu-taler/web-util/browser";
/**
* List of pages used by the wallet
@@ -60,10 +63,7 @@ function replaceAll(
): string {
let result = pattern;
for (const v in vars) {
- result = result.replace(
- vars[v],
- !values[v] ? "" : encodeURIComponent(values[v]),
- );
+ result = result.replace(vars[v], !values[v] ? "" : values[v]);
}
return result;
}
@@ -94,23 +94,41 @@ function pageDefinition<T extends object>(pattern: string): PageLocation<T> {
return f;
}
+/**
+ * taler action and scope info may contain
+ * character not suitable for URL
+ */
+type CrockEncodedString = string;
+
export const Pages = {
welcome: "/welcome",
balance: "/balance",
- balanceHistory: pageDefinition<{ scope?: string }>(
+ balanceHistory: pageDefinition<{ scope?: CrockEncodedString }>(
"/balance/history/:scope?",
),
- searchHistory: pageDefinition<{ scope?: string }>(
+ searchHistory: pageDefinition<{ scope?: CrockEncodedString }>(
"/search/history/:scope?",
),
- balanceDeposit: pageDefinition<{ amount: string }>(
- "/balance/deposit/:amount",
- ),
balanceTransaction: pageDefinition<{ tid: string }>(
"/balance/transaction/:tid",
),
- sendCash: pageDefinition<{ scope?: string }>("/destination/send/:scope"),
- receiveCash: pageDefinition<{ scope?: string }>("/destination/get/:scope?"),
+ balanceDeposit: pageDefinition<{
+ scope: CrockEncodedString;
+ amount?: string;
+ }>("/balance/deposit/:scope/:amount?"),
+ sendCash: pageDefinition<{ scope: CrockEncodedString; amount?: string }>(
+ "/destination/send/:scope/:amount?",
+ ),
+ // if no scope is specified, then exchange selection page will be shown
+ receiveCash: pageDefinition<{ scope?: CrockEncodedString; amount?: string }>(
+ "/destination/get/:scope?/:amount?",
+ ),
+ receiveCashForPurchase: pageDefinition<{ id?: string }>(
+ "/add-for-payment/purchase/:id",
+ ),
+ receiveCashForInvoice: pageDefinition<{ id?: string }>(
+ "/add-for-payment/purchase/:id",
+ ),
dev: "/dev",
exchanges: "/exchanges",
@@ -127,8 +145,8 @@ export const Pages = {
"/settings/exchange/add/:currency?",
),
- defaultCta: pageDefinition<{ uri: string }>("/taler-uri/:uri"),
- cta: pageDefinition<{ action: string }>("/cta/:action"),
+ defaultCta: pageDefinition<{ uri: CrockEncodedString }>("/taler-uri/:uri"),
+ cta: pageDefinition<{ action: CrockEncodedString }>("/cta/:action"),
ctaPay: "/cta/pay",
ctaPayTemplate: "/cta/pay/template",
ctaRecovery: "/cta/recovery",
@@ -137,17 +155,21 @@ export const Pages = {
ctaDeposit: "/cta/deposit",
ctaExperiment: "/cta/experiment",
ctaAddExchange: "/cta/add/exchange",
- ctaInvoiceCreate: pageDefinition<{ amount?: string }>(
- "/cta/invoice/create/:amount?",
- ),
- ctaTransferCreate: pageDefinition<{ amount?: string }>(
- "/cta/transfer/create/:amount?",
- ),
+ ctaInvoiceCreate: pageDefinition<{
+ scope: CrockEncodedString;
+ amount?: string;
+ }>("/cta/invoice/create/:scope/:amount?"),
+ ctaTransferCreate: pageDefinition<{
+ scope: CrockEncodedString;
+ amount?: string;
+ }>("/cta/transfer/create/:scope/:amount?"),
ctaInvoicePay: "/cta/invoice/pay",
ctaTransferPickup: "/cta/transfer/pickup",
- ctaWithdrawManual: pageDefinition<{ amount?: string }>(
- "/cta/manual-withdraw/:amount?",
- ),
+
+ ctaWithdrawManual: pageDefinition<{
+ scope: CrockEncodedString;
+ amount?: string;
+ }>("/cta/manual-withdraw/:scope/:amount?"),
};
const talerUriActionToPageName: {
@@ -178,7 +200,7 @@ export function getPathnameForTalerURI(talerUri: string): string | undefined {
typeof Pages[pageName] === "function"
? (Pages[pageName] as any)()
: Pages[pageName];
- return `${pageString}?talerUri=${encodeURIComponent(talerUri)}`;
+ return `${pageString}?talerUri=${encodeCrockForURI(talerUri)}`;
}
export type PopupNavBarOptions = "balance" | "backup" | "dev";
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts
index 276d464a0..3f6708fc0 100644
--- a/packages/taler-wallet-webextension/src/platform/chrome.ts
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -35,6 +35,7 @@ import {
Settings,
defaultSettings,
} from "./api.js";
+import { encodeCrockForURI } from "@gnu-taler/web-util/browser";
const api: BackgroundPlatformAPI & ForegroundPlatformAPI = {
isFirefox,
@@ -178,54 +179,54 @@ function openWalletURIFromPopup(uri: TalerUri): void {
case TalerUriAction.WithdrawExchange:
case TalerUriAction.Withdraw:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/withdraw?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/withdraw?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.Restore:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/recovery?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/recovery?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.Pay:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/pay?talerUri=${encodeURIComponent(talerUri)}`,
+ `static/wallet.html#/cta/pay?talerUri=${encodeCrockForURI(talerUri)}`,
);
break;
case TalerUriAction.Refund:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/refund?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/refund?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.PayPull:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/invoice/pay?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/invoice/pay?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.PayPush:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/transfer/pickup?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/transfer/pickup?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.PayTemplate:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/pay/template?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/pay/template?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
break;
case TalerUriAction.AddExchange:
url = chrome.runtime.getURL(
- `static/wallet.html#/cta/add/exchange?talerUri=${encodeURIComponent(
+ `static/wallet.html#/cta/add/exchange?talerUri=${encodeCrockForURI(
talerUri,
)}`,
);
diff --git a/packages/taler-wallet-webextension/src/popup/Application.tsx b/packages/taler-wallet-webextension/src/popup/Application.tsx
index 8c732728e..0a88b6537 100644
--- a/packages/taler-wallet-webextension/src/popup/Application.tsx
+++ b/packages/taler-wallet-webextension/src/popup/Application.tsx
@@ -20,13 +20,12 @@
* @author sebasjm
*/
-import {
- ScopeInfo,
- stringifyScopeInfoShort
-} from "@gnu-taler/taler-util";
+import { ScopeInfo, stringifyScopeInfoShort } from "@gnu-taler/taler-util";
import {
TranslationProvider,
useTranslationContext,
+ encodeCrockForURI,
+ decodeCrockFromURI,
} from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
import { ComponentChildren, Fragment, VNode, h } from "preact";
@@ -63,7 +62,7 @@ function ApplicationView(): VNode {
useEffect(() => {
if (actionUri) {
- route(Pages.cta({ action: encodeURIComponent(actionUri) }));
+ route(Pages.cta({ action: encodeCrockForURI(actionUri) }));
}
}, [actionUri]);
@@ -86,18 +85,24 @@ function ApplicationView(): VNode {
goToURL={redirectToURL}
>
<BalancePage
- goToWalletManualWithdraw={() => redirectTo(Pages.receiveCash({}))}
+ goToWalletManualWithdraw={(scope: ScopeInfo) =>
+ redirectTo(
+ Pages.receiveCash({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
+ }),
+ )
+ }
goToWalletDeposit={(scope: ScopeInfo) =>
redirectTo(
Pages.sendCash({
- scope: encodeURIComponent(stringifyScopeInfoShort(scope)),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
goToWalletHistory={(scope: ScopeInfo) =>
redirectTo(
Pages.balanceHistory({
- scope: encodeURIComponent(stringifyScopeInfoShort(scope)),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
@@ -114,7 +119,7 @@ function ApplicationView(): VNode {
return (
<PopupTemplate goToURL={redirectToURL}>
<TalerActionFound
- url={decodeURIComponent(action)}
+ url={decodeCrockFromURI(action)}
onDismiss={() => {
setDismissed(true);
return redirectTo(Pages.balance);
diff --git a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
index a819d50e7..ca64ba5a3 100644
--- a/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/BalancePage.tsx
@@ -42,9 +42,9 @@ import { AddNewActionView } from "../wallet/AddNewActionView.js";
import { NoBalanceHelp } from "./NoBalanceHelp.js";
export interface Props {
- goToWalletDeposit: (currency: ScopeInfo) => Promise<void>;
- goToWalletHistory: (currency: ScopeInfo) => Promise<void>;
- goToWalletManualWithdraw: () => Promise<void>;
+ goToWalletDeposit: (scope: ScopeInfo) => Promise<void>;
+ goToWalletHistory: (scope: ScopeInfo) => Promise<void>;
+ goToWalletManualWithdraw: (scope: ScopeInfo) => Promise<void>;
}
export type State = State.Loading | State.Error | State.Action | State.Balances;
@@ -127,7 +127,9 @@ function useComponentState({
onClick: pushAlertOnError(async () => setAddingAction(true)),
},
goToWalletManualWithdraw: {
- onClick: pushAlertOnError(goToWalletManualWithdraw),
+ onClick: pushAlertOnError(async () => {
+ goToWalletManualWithdraw(state.response.balances[0].scopeInfo)
+ }),
},
goToWalletDeposit,
goToWalletHistory,
diff --git a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts
index 5e781121b..1d0ac314a 100644
--- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts
+++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts
@@ -19,7 +19,10 @@ import {
TalerError,
TalerErrorCode,
} from "@gnu-taler/taler-util";
+
import type { MessageFromBackend } from "./platform/api.js";
+import { encodeCrockForURI } from "@gnu-taler/web-util/browser";
+
/**
* This will modify all the pages that the user load when navigating with Web Extension enabled
@@ -64,7 +67,7 @@ function validateTalerUri(uri: string): boolean {
function convertURIToWebExtensionPath(uri: string) {
const url = new URL(
chrome.runtime.getURL(
- `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`,
+ `static/wallet.html#/taler-uri/${encodeCrockForURI(uri)}`,
),
);
return url.href;
diff --git a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts
index 8b15380f9..0c6074c42 100644
--- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts
+++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-support.ts
@@ -14,6 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { encodeCrockForURI } from "@gnu-taler/web-util/browser";
+
/**
* WARNING
*
@@ -22,10 +24,10 @@
*/
(() => {
const logger = {
- debug: (...msg: any[]) => { },
- info: (...msg: any[]) =>
+ debug: (..._msg: unknown[]) => {},
+ info: (...msg: unknown[]) =>
console.log(`${new Date().toISOString()} TALER`, ...msg),
- error: (...msg: any[]) =>
+ error: (...msg: unknown[]) =>
console.error(`${new Date().toISOString()} TALER`, ...msg),
};
@@ -76,13 +78,15 @@
return undefined;
}
const host = `${config.protocol}//${config.hostname}`;
- const path = `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`;
+ const path = `static/wallet.html#/taler-uri/${encodeCrockForURI(uri)}`;
return `${host}/${path}`;
}
function anchorOnClick(ev: MouseEvent) {
if (!(ev.currentTarget instanceof Element)) {
- logger.debug(`onclick: registered in a link that is not an HTML element`);
+ logger.debug(
+ `onclick: registered in a link that is not an HTML element`,
+ );
return;
}
const hrefAttr = ev.currentTarget.attributes.getNamedItem("href");
@@ -95,7 +99,9 @@
targetAttr && targetAttr.value ? targetAttr.value : "_self";
const page = convertURIToWebExtensionPath(hrefAttr.value);
if (!page) {
- logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`);
+ logger.debug(
+ `onclick: could not convert "${hrefAttr.value}" into path`,
+ );
return;
}
// we can use window.open, but maybe some browser will block it?
@@ -118,7 +124,7 @@
function checkForNewAnchors(
mutations: MutationRecord[],
- observer: MutationObserver,
+ _observer: MutationObserver,
) {
mutations.forEach((mut) => {
if (mut.type === "childList") {
@@ -137,7 +143,7 @@
* Register the anchor handler when found
*/
function registerProtocolHandler() {
- if (document.body) overrideAllAnchor(document.body)
+ if (document.body) overrideAllAnchor(document.body);
new MutationObserver(checkForNewAnchors).observe(document, {
childList: true,
subtree: true,
@@ -179,7 +185,8 @@
};
if (apiEnabled) {
- //@ts-ignore
+ // @ts-expect-error we now that `taler` doesn't exist.
+ // we are creating the property
window.taler = taler;
}
@@ -196,5 +203,4 @@
}
start();
-})()
-
+})();
diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx
index 4947eb7a9..60630f747 100644
--- a/packages/taler-wallet-webextension/src/wallet/Application.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx
@@ -33,6 +33,8 @@ import {
} from "@gnu-taler/taler-util";
import {
TranslationProvider,
+ decodeCrockFromURI,
+ encodeCrockForURI,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
@@ -178,23 +180,19 @@ export function Application(): VNode {
scope={
!scope
? undefined
- : parseScopeInfoShort((scope))
+ : parseScopeInfoShort(decodeCrockFromURI(scope))
}
goToWalletDeposit={(scope: ScopeInfo) =>
redirectTo(
Pages.sendCash({
- scope: encodeURIComponent(
- stringifyScopeInfoShort(scope),
- ),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
- goToWalletManualWithdraw={(scope?: ScopeInfo) =>
+ goToWalletManualWithdraw={(scope: ScopeInfo) =>
redirectTo(
Pages.receiveCash({
- scope: !scope
- ? undefined
- : encodeURIComponent(stringifyScopeInfoShort(scope)),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
@@ -214,24 +212,20 @@ export function Application(): VNode {
scope={
!scope
? undefined
- : parseScopeInfoShort((scope))
+ : parseScopeInfoShort(decodeCrockFromURI(scope))
}
search
goToWalletDeposit={(scope: ScopeInfo) =>
redirectTo(
Pages.sendCash({
- scope: encodeURIComponent(
- stringifyScopeInfoShort(scope),
- ),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
- goToWalletManualWithdraw={(scope?: ScopeInfo) =>
+ goToWalletManualWithdraw={(scope: ScopeInfo) =>
redirectTo(
Pages.receiveCash({
- scope: !scope
- ? undefined
- : encodeURIComponent(stringifyScopeInfoShort(scope)),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
@@ -241,37 +235,103 @@ export function Application(): VNode {
/>
<Route
path={Pages.sendCash.pattern}
- component={({ amount }: { amount?: string }) => (
- <WalletTemplate path="balance" goToURL={redirectToURL}>
- <DestinationSelectionPage
- type="send"
- amount={amount}
- goToWalletBankDeposit={(amount: string) =>
- redirectTo(Pages.balanceDeposit({ amount }))
- }
- goToWalletWalletSend={(amount: string) =>
- redirectTo(Pages.ctaTransferCreate({ amount }))
- }
- />
- </WalletTemplate>
- )}
+ component={({
+ scope,
+ amount,
+ }: {
+ scope?: string;
+ amount: string;
+ }) => {
+ if (!scope) return <Redirect to={Pages.balanceHistory({})} />;
+ const s = parseScopeInfoShort(decodeCrockFromURI(scope));
+ if (!s) return <Redirect to={Pages.balanceHistory({})} />;
+
+ return (
+ <WalletTemplate path="balance" goToURL={redirectToURL}>
+ <DestinationSelectionPage
+ type="send"
+ scope={s}
+ amount={!amount ? undefined : Amounts.parse(amount)}
+ goToWalletBankDeposit={(s, amount: string) =>
+ redirectTo(
+ Pages.balanceDeposit({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(s)),
+ amount,
+ }),
+ )
+ }
+ goToWalletWalletSend={(s, amount: string) =>
+ redirectTo(
+ Pages.ctaTransferCreate({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(s)),
+ amount,
+ }),
+ )
+ }
+ />
+ </WalletTemplate>
+ );
+ }}
+ />
+ <Route
+ path={Pages.receiveCashForPurchase.pattern}
+ component={({ id: _purchaseId }: { id?: string }) => {
+ return (
+ <WalletTemplate path="balance" goToURL={redirectToURL}>
+ not yet implemented
+ </WalletTemplate>
+ );
+ }}
+ />
+ <Route
+ path={Pages.receiveCashForInvoice.pattern}
+ component={({ id: _invoiceId }: { id?: string }) => {
+ return (
+ <WalletTemplate path="balance" goToURL={redirectToURL}>
+ not yet implemented
+ </WalletTemplate>
+ );
+ }}
/>
<Route
path={Pages.receiveCash.pattern}
- component={({ amount }: { amount?: string }) => (
- <WalletTemplate path="balance" goToURL={redirectToURL}>
- <DestinationSelectionPage
- type="get"
- amount={amount}
- goToWalletManualWithdraw={(amount?: string) =>
- redirectTo(Pages.ctaWithdrawManual({ amount }))
- }
- goToWalletWalletInvoice={(amount?: string) =>
- redirectTo(Pages.ctaInvoiceCreate({ amount }))
- }
- />
- </WalletTemplate>
- )}
+ component={({
+ scope,
+ amount,
+ }: {
+ scope?: string;
+ amount?: string;
+ }) => {
+ const s = !scope
+ ? undefined
+ : parseScopeInfoShort(decodeCrockFromURI(scope));
+
+ return (
+ <WalletTemplate path="balance" goToURL={redirectToURL}>
+ <DestinationSelectionPage
+ type="get"
+ scope={s}
+ amount={!amount ? undefined : Amounts.parse(amount)}
+ goToWalletManualWithdraw={(s, amount?: string) =>
+ redirectTo(
+ Pages.ctaWithdrawManual({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(s)),
+ amount,
+ }),
+ )
+ }
+ goToWalletWalletInvoice={(s, amount?: string) =>
+ redirectTo(
+ Pages.ctaInvoiceCreate({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(s)),
+ amount,
+ }),
+ )
+ }
+ />
+ </WalletTemplate>
+ );
+ }}
/>
<Route
@@ -283,9 +343,7 @@ export function Application(): VNode {
goToWalletHistory={(scope: ScopeInfo) =>
redirectTo(
Pages.balanceHistory({
- scope: encodeURIComponent(
- stringifyScopeInfoShort(scope),
- ),
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
}),
)
}
@@ -305,13 +363,21 @@ export function Application(): VNode {
}) => (
<WalletTemplate path="balance" goToURL={redirectToURL}>
<DepositPage
- scope={parseScopeInfoShort((scope))}
+ scope={parseScopeInfoShort(decodeCrockFromURI(scope))}
amount={!amount ? undefined : Amounts.parse(amount)}
onCancel={(scope: ScopeInfo) => {
- redirectTo(Pages.balanceHistory({ scope: encodeURIComponent(stringifyScopeInfoShort(scope)) }));
+ redirectTo(
+ Pages.balanceHistory({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
+ }),
+ );
}}
onSuccess={(scope: ScopeInfo) => {
- redirectTo(Pages.balanceHistory({ scope: encodeURIComponent(stringifyScopeInfoShort(scope)) }));
+ redirectTo(
+ Pages.balanceHistory({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(scope)),
+ }),
+ );
}}
/>
</WalletTemplate>
@@ -342,6 +408,7 @@ export function Application(): VNode {
redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`)
}
onWithdraw={(_amount: string) =>
+ // FIXME: use receiveCashForPurchase
redirectTo(Pages.receiveCash({ scope: "FIXME missing" }))
}
onBack={() => redirectTo(Pages.backup)}
@@ -410,9 +477,10 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash payment`}>
<PaymentPage
- talerPayUri={decodeURIComponent(talerUri)}
+ talerPayUri={decodeCrockFromURI(talerUri)}
goToWalletManualWithdraw={(_amount?: string) =>
- redirectTo(Pages.receiveCash({ scope: "FIXME missing" }))
+ // FIXME: use receiveCashForPruchase
+ redirectTo(Pages.receiveCash({}))
}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
@@ -427,9 +495,10 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash payment`}>
<PaymentTemplatePage
- talerTemplateUri={decodeURIComponent(talerUri)}
+ talerTemplateUri={decodeCrockFromURI(talerUri)}
goToWalletManualWithdraw={(_amount?: string) =>
- redirectTo(Pages.receiveCash({ scope: "FIXME missing" }))
+ // FIXME: use receiveCashForPruchase
+ redirectTo(Pages.receiveCash({}))
}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
@@ -444,7 +513,7 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash refund`}>
<RefundPage
- talerRefundUri={decodeURIComponent(talerUri)}
+ talerRefundUri={decodeCrockFromURI(talerUri)}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid }))
@@ -458,7 +527,7 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
<WithdrawPageFromURI
- talerWithdrawUri={decodeURIComponent(talerUri)}
+ talerWithdrawUri={decodeCrockFromURI(talerUri)}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid }))
@@ -470,29 +539,38 @@ export function Application(): VNode {
<Route
path={Pages.ctaWithdrawManual.pattern}
component={({
+ scope,
amount,
talerUri,
}: {
+ scope: string;
amount: string;
talerUri: string;
- }) => (
- <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
- <WithdrawPageFromParams
- onAmountChanged={async (newamount) => {
- const page = `${Pages.ctaWithdrawManual({
- amount: newamount,
- })}?talerUri=${encodeURIComponent(talerUri)}`;
- redirectTo(page);
- }}
- talerExchangeWithdrawUri={talerUri}
- amount={amount}
- cancel={() => redirectTo(Pages.balance)}
- onSuccess={(tid: string) =>
- redirectTo(Pages.balanceTransaction({ tid }))
- }
- />
- </CallToActionTemplate>
- )}
+ }) => {
+ if (!scope) return <Redirect to={Pages.balanceHistory({})} />;
+ const s = parseScopeInfoShort(decodeCrockFromURI(scope));
+ if (!s) return <Redirect to={Pages.balanceHistory({})} />;
+
+ return (
+ <CallToActionTemplate title={i18n.str`Digital cash withdrawal`}>
+ <WithdrawPageFromParams
+ onAmountChanged={async (newamount) => {
+ const page = `${Pages.ctaWithdrawManual({
+ scope: encodeCrockForURI(stringifyScopeInfoShort(s)),
+ amount: newamount,
+ })}?talerUri=${encodeCrockForURI(talerUri)}`;
+ redirectTo(page);
+ }}
+ talerExchangeWithdrawUri={talerUri}
+ amount={amount}
+ cancel={() => redirectTo(Pages.balance)}
+ onSuccess={(tid: string) =>
+ redirectTo(Pages.balanceTransaction({ tid }))
+ }
+ />
+ </CallToActionTemplate>
+ );
+ }}
/>
<Route
path={Pages.ctaDeposit}
@@ -506,7 +584,7 @@ export function Application(): VNode {
<CallToActionTemplate title={i18n.str`Digital cash deposit`}>
<DepositPageCTA
amountStr={Amounts.stringify(Amounts.parseOrThrow(amount))}
- talerDepositUri={decodeURIComponent(talerUri)}
+ talerDepositUri={decodeCrockFromURI(talerUri)}
cancel={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid }))
@@ -548,9 +626,10 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash invoice`}>
<InvoicePayPage
- talerPayPullUri={decodeURIComponent(talerUri)}
+ talerPayPullUri={decodeCrockFromURI(talerUri)}
goToWalletManualWithdraw={(_amount?: string) =>
- redirectTo(Pages.receiveCash({ scope: "FIXME missing" }))
+ // FIXME: use receiveCashForInvoice
+ redirectTo(Pages.receiveCash({}))
}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
@@ -565,7 +644,7 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash transfer`}>
<TransferPickupPage
- talerPayPushUri={decodeURIComponent(talerUri)}
+ talerPayPushUri={decodeCrockFromURI(talerUri)}
onClose={() => redirectTo(Pages.balance)}
onSuccess={(tid: string) =>
redirectTo(Pages.balanceTransaction({ tid }))
@@ -579,7 +658,7 @@ export function Application(): VNode {
component={({ talerRecoveryUri }: { talerRecoveryUri: string }) => (
<CallToActionTemplate title={i18n.str`Digital cash recovery`}>
<RecoveryPage
- talerRecoveryUri={decodeURIComponent(talerRecoveryUri)}
+ talerRecoveryUri={decodeCrockFromURI(talerRecoveryUri)}
onCancel={() => redirectTo(Pages.balance)}
onSuccess={() => redirectTo(Pages.backup)}
/>
@@ -591,7 +670,7 @@ export function Application(): VNode {
component={({ talerUri }: { talerUri: string }) => (
<CallToActionTemplate title={i18n.str`Development experiment`}>
<DevExperimentPage
- talerExperimentUri={decodeURIComponent(talerUri)}
+ talerExperimentUri={decodeCrockFromURI(talerUri)}
onCancel={() => redirectTo(Pages.balanceHistory({}))}
onSuccess={() => redirectTo(Pages.balanceHistory({}))}
/>
@@ -601,7 +680,7 @@ export function Application(): VNode {
<Route
path={Pages.ctaAddExchange}
component={({ talerUri }: { talerUri: string }) => {
- const tUri = parseTalerUri(decodeURIComponent(talerUri));
+ const tUri = parseTalerUri(decodeCrockFromURI(talerUri));
const baseUrl =
tUri?.type === TalerUriAction.AddExchange
? tUri.exchangeBaseUrl
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index 8a3710f69..5c950bec8 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -262,7 +262,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode {
<div style={{ color: !props.active ? "grey" : undefined }}>
<a
href={Pages.backupProviderDetail({
- pid: encodeURIComponent(props.id),
+ pid: props.id,
})}
>
<span>{props.title}</span>
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts
index b56fe5523..74726fc30 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/index.ts
@@ -14,15 +14,15 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { AmountJson, ScopeInfo } from "@gnu-taler/taler-util";
import { ErrorAlertView } from "../../components/CurrentAlerts.js";
import { Loading } from "../../components/Loading.js";
import { ErrorAlert } from "../../context/alert.js";
import {
AmountFieldHandler,
- ButtonHandler,
- ToggleHandler,
+ ButtonHandler
} from "../../mui/handlers.js";
-import { compose, StateViewMap } from "../../utils/index.js";
+import { StateViewMap, compose } from "../../utils/index.js";
import { useComponentState } from "./state.js";
import { ReadyView, SelectCurrencyView } from "./views.js";
@@ -30,15 +30,17 @@ export type Props = PropsGet | PropsSend;
interface PropsGet {
type: "get";
- amount?: string;
- goToWalletManualWithdraw: (amount: string) => void;
- goToWalletWalletInvoice: (amount: string) => void;
+ scope?: ScopeInfo;
+ amount?: AmountJson;
+ goToWalletManualWithdraw: (s:ScopeInfo, amount: string) => void;
+ goToWalletWalletInvoice: (s:ScopeInfo, amount: string) => void;
}
interface PropsSend {
type: "send";
- amount?: string;
- goToWalletBankDeposit: (amount: string) => void;
- goToWalletWalletSend: (amount: string) => void;
+ scope: ScopeInfo;
+ amount?: AmountJson;
+ goToWalletBankDeposit: (s:ScopeInfo, amount: string) => void;
+ goToWalletWalletSend: (s:ScopeInfo, amount: string) => void;
}
export type State =
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts
index d4e270a6c..1ae9960d1 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/state.ts
@@ -14,10 +14,16 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { Amounts } from "@gnu-taler/taler-util";
+import {
+ AmountJson,
+ Amounts,
+ ScopeType,
+ parseScopeInfoShort,
+ stringifyScopeInfoShort,
+} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
@@ -28,54 +34,47 @@ export function useComponentState(props: Props): RecursiveState<State> {
const api = useBackendContext();
const { pushAlertOnError } = useAlertContext();
- const parsedInitialAmount = !props.amount
- ? undefined
- : Amounts.parse(props.amount);
+ const [scope, setScope] = useState(props.scope);
- const hook = useAsyncAsHook(async () => {
- if (!parsedInitialAmount) return undefined;
- const balance = await api.wallet.call(WalletApiOperation.GetBalanceDetail, {
- currency: parsedInitialAmount.currency,
- });
- return { balance };
- });
-
- const info = hook && !hook.hasError ? hook.response : undefined;
+ const [amount, setAmount] = useState<AmountJson | undefined>(
+ props.amount ??
+ (scope ? Amounts.zeroOfCurrency(scope.currency) : undefined),
+ );
- // const initialCurrency = parsedInitialAmount?.currency;
+ useEffect(() => {
+ if (!scope) return;
+ if (!amount) {
+ setAmount(Amounts.zeroOfCurrency(scope.currency));
+ }
+ }, [scope]);
- const [amount, setAmount] = useState(
- !parsedInitialAmount ? undefined : parsedInitialAmount,
- );
//FIXME: get this information from wallet
// eslint-disable-next-line no-constant-condition
const previous: Contact[] = true
? []
: [
- {
- name: "International Bank",
- icon_type: "bank",
- description: "account ending with 3454",
- },
- {
- name: "Max",
- icon_type: "bank",
- description: "account ending with 3454",
- },
- {
- name: "Alex",
- icon_type: "bank",
- description: "account ending with 3454",
- },
- ];
+ {
+ name: "International Bank",
+ icon_type: "bank",
+ description: "account ending with 3454",
+ },
+ {
+ name: "Max",
+ icon_type: "bank",
+ description: "account ending with 3454",
+ },
+ {
+ name: "Alex",
+ icon_type: "bank",
+ description: "account ending with 3454",
+ },
+ ];
- if (!amount) {
+ if (!scope || !amount) {
return () => {
- // eslint-disable-next-line react-hooks/rules-of-hooks
const { i18n } = useTranslationContext();
- // eslint-disable-next-line react-hooks/rules-of-hooks
const hook = useAsyncAsHook(() =>
- api.wallet.call(WalletApiOperation.ListExchanges, {}),
+ api.wallet.call(WalletApiOperation.GetBalances, {}),
);
if (!hook) {
@@ -87,14 +86,30 @@ export function useComponentState(props: Props): RecursiveState<State> {
if (hook.hasError) {
return {
status: "error",
- error: alertFromError(i18n,
- i18n.str`Could not load exchanges`, hook),
+ error: alertFromError(i18n, i18n.str`Could not load exchanges`, hook),
};
}
const currencies: Record<string, string> = {};
- hook.response.exchanges.forEach((e) => {
- if (e.currency) {
- currencies[e.currency] = e.currency;
+ hook.response.balances.forEach((b) => {
+ switch (b.scopeInfo.type) {
+ case ScopeType.Global: {
+ currencies[stringifyScopeInfoShort(b.scopeInfo)] =
+ b.scopeInfo.currency;
+ break;
+ }
+ case ScopeType.Exchange: {
+ currencies[stringifyScopeInfoShort(b.scopeInfo)] =
+ `${b.scopeInfo.currency} ${b.scopeInfo.url}`;
+ break;
+ }
+ case ScopeType.Auditor: {
+ currencies[stringifyScopeInfoShort(b.scopeInfo)] =
+ `${b.scopeInfo.currency} ${b.scopeInfo.url}`;
+ break;
+ }
+ default: {
+ assertUnreachable(b.scopeInfo);
+ }
}
});
currencies[""] = "Select a currency";
@@ -103,7 +118,7 @@ export function useComponentState(props: Props): RecursiveState<State> {
status: "select-currency",
error: undefined,
onCurrencySelected: (c: string) => {
- setAmount(Amounts.zeroOfCurrency(c));
+ setScope(parseScopeInfoShort(c));
},
currencies,
};
@@ -128,8 +143,8 @@ export function useComponentState(props: Props): RecursiveState<State> {
onClick: invalid
? undefined
: pushAlertOnError(async () => {
- props.goToWalletBankDeposit(currencyAndAmount);
- }),
+ props.goToWalletBankDeposit(scope, currencyAndAmount);
+ }),
},
selectMax: {
onClick: pushAlertOnError(async () => {
@@ -146,8 +161,8 @@ export function useComponentState(props: Props): RecursiveState<State> {
onClick: invalid
? undefined
: pushAlertOnError(async () => {
- props.goToWalletWalletSend(currencyAndAmount);
- }),
+ props.goToWalletWalletSend(scope, currencyAndAmount);
+ }),
},
amountHandler: {
onInput: pushAlertOnError(async (s) => setAmount(s)),
@@ -169,22 +184,22 @@ export function useComponentState(props: Props): RecursiveState<State> {
onClick: invalid
? undefined
: pushAlertOnError(async () => {
- props.goToWalletManualWithdraw(currencyAndAmount);
- }),
+ props.goToWalletManualWithdraw(scope, currencyAndAmount);
+ }),
},
goToBank: {
onClick: invalid
? undefined
: pushAlertOnError(async () => {
- props.goToWalletManualWithdraw(currencyAndAmount);
- }),
+ props.goToWalletManualWithdraw(scope, currencyAndAmount);
+ }),
},
goToWallet: {
onClick: invalid
? undefined
: pushAlertOnError(async () => {
- props.goToWalletWalletInvoice(currencyAndAmount);
- }),
+ props.goToWalletWalletInvoice(scope, currencyAndAmount);
+ }),
},
amountHandler: {
onInput: pushAlertOnError(async (s) => setAmount(s)),
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts
index 683378613..fdea7685f 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/test.ts
@@ -20,11 +20,13 @@
*/
import {
+ AmountString,
Amounts,
ExchangeEntryStatus,
ExchangeListItem,
ExchangeTosStatus,
ExchangeUpdateStatus,
+ ScopeInfo,
ScopeType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -34,12 +36,13 @@ import { nullFunction } from "../../mui/handlers.js";
import { createWalletApiMock } from "../../test-utils.js";
import { useComponentState } from "./state.js";
+const currency = "ARS";
const exchangeArs: ExchangeListItem = {
- currency: "ARS",
+ currency,
exchangeBaseUrl: "http://",
masterPub: "123qwe123",
scopeInfo: {
- currency: "ARS",
+ currency,
type: ScopeType.Exchange,
url: "http://",
},
@@ -57,16 +60,31 @@ describe("Destination selection states", () => {
it("should select currency if no amount specified", async () => {
const { handler, TestingContext } = createWalletApiMock();
- handler.addWalletCallResponse(
- WalletApiOperation.ListExchanges,
- {},
- {
- exchanges: [exchangeArs],
- },
- );
+ handler.addWalletCallResponse(WalletApiOperation.GetBalances, undefined, {
+ balances: [
+ {
+ flags: [],
+ available: `${currency}:1` as AmountString,
+ hasPendingTransactions: false,
+ pendingIncoming: `${currency}:0` as AmountString,
+ pendingOutgoing: `${currency}:0` as AmountString,
+ requiresUserInput: false,
+ scopeInfo: {
+ currency,
+ type: ScopeType.Exchange,
+ url: "http://exchange.test.taler.net",
+ },
+ },
+ ],
+ });
const props = {
type: "get" as const,
+ // scope: {
+ // currency: "ARS",
+ // type: ScopeType.Exchange,
+ // url: "http://asd.com",
+ // } as ScopeInfo,
goToWalletManualWithdraw: nullFunction,
goToWalletWalletInvoice: nullFunction,
};
@@ -82,7 +100,7 @@ describe("Destination selection states", () => {
if (state.status !== "select-currency") expect.fail();
if (state.error) expect.fail();
expect(state.currencies).deep.eq({
- ARS: "ARS",
+ "ARS/http%3A%2F%2Fexchange.test.taler.net": "ARS http://exchange.test.taler.net",
"": "Select a currency",
});
@@ -111,9 +129,14 @@ describe("Destination selection states", () => {
const props = {
type: "get" as const,
+ scope: {
+ currency: "ARS",
+ type: ScopeType.Exchange,
+ url: "http://asd.com",
+ } as ScopeInfo,
goToWalletManualWithdraw: nullFunction,
goToWalletWalletInvoice: nullFunction,
- amount: "ARS:2",
+ amount: Amounts.parseOrThrow("ARS:2"),
};
const hookBehavior = await tests.hookBehaveLikeThis(
@@ -133,16 +156,6 @@ describe("Destination selection states", () => {
Amounts.parseOrThrow("ARS:2"),
);
},
- (state) => {
- if (state.status !== "ready") expect.fail();
- if (state.error) expect.fail();
- expect(state.goToBank.onClick).not.eq(undefined);
- expect(state.goToWallet.onClick).not.eq(undefined);
-
- expect(state.amountHandler.value).deep.eq(
- Amounts.parseOrThrow("ARS:2"),
- );
- },
],
TestingContext,
);
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
index 8a74a20f1..a9105a601 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection/views.tsx
@@ -280,7 +280,7 @@ export function ReadyGetView({
<Grid item xs={1}>
<Paper style={{ padding: 8 }}>
<p>
- <i18n.Translate>From a <pre style={{display:"inline"}}>taler://peer-push-credit</pre> URI</i18n.Translate>
+ <i18n.Translate>From a <pre style={{display:"inline"}}>taler://</pre> URI or QR code</i18n.Translate>
</p>
<a href={Pages.qr}>
<i18n.Translate>Enter URI here</i18n.Translate>
diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
index 9feb03714..9aca75531 100644
--- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx
@@ -667,6 +667,12 @@ function ShowAllCoins({
);
}
+/**
+ *
+ * @param str
+ * @deprecated FIXME: use a better base64 function
+ * @returns
+ */
function toBase64(str: string): string {
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 59315d61f..561133887 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -54,7 +54,7 @@ interface Props {
scope?: ScopeInfo;
search?: boolean;
goToWalletDeposit: (scope: ScopeInfo) => Promise<void>;
- goToWalletManualWithdraw: (scope?: ScopeInfo) => Promise<void>;
+ goToWalletManualWithdraw: (scope: ScopeInfo) => Promise<void>;
}
export function HistoryPage({
scope,
@@ -172,7 +172,7 @@ export function HistoryView({
scope: ScopeInfo;
changeScope: (scope: ScopeInfo) => void;
goToWalletDeposit: (scope: ScopeInfo) => Promise<void>;
- goToWalletManualWithdraw: (scope?: ScopeInfo) => Promise<void>;
+ goToWalletManualWithdraw: (scope: ScopeInfo) => Promise<void>;
transactionsByDate: Record<string, Transaction[]>;
balances: WalletBalance[];
}): VNode {