aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension')
-rw-r--r--packages/taler-wallet-webextension/src/components/AmountField.stories.tsx65
-rw-r--r--packages/taler-wallet-webextension/src/components/AmountField.tsx185
-rw-r--r--packages/taler-wallet-webextension/src/components/TransactionItem.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/components/index.stories.tsx3
-rw-r--r--packages/taler-wallet-webextension/src/mui/TextField.tsx1
-rw-r--r--packages/taler-wallet-webextension/src/mui/handlers.ts7
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx58
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts232
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx282
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts3
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts55
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx6
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts14
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx7
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx33
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts13
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx141
-rw-r--r--packages/taler-wallet-webextension/src/wallet/index.stories.tsx2
18 files changed, 298 insertions, 815 deletions
diff --git a/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx b/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx
new file mode 100644
index 000000000..3183364a8
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/AmountField.stories.tsx
@@ -0,0 +1,65 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { AmountJson, Amounts } from "@gnu-taler/taler-util";
+import { styled } from "@linaria/react";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "../context/translation.js";
+import { Grid } from "../mui/Grid.js";
+import { AmountFieldHandler, TextFieldHandler } from "../mui/handlers.js";
+import { AmountField } from "./AmountField.js";
+
+export default {
+ title: "components/amountField",
+};
+
+function RenderAmount(): VNode {
+ const [value, setValue] = useState<AmountJson | undefined>(undefined);
+
+ const error = value === undefined ? undefined : undefined;
+
+ const handler: AmountFieldHandler = {
+ value: value ?? Amounts.zeroOfCurrency("USD"),
+ onInput: async (e) => {
+ setValue(e);
+ },
+ error,
+ };
+ const { i18n } = useTranslationContext();
+ return (
+ <Fragment>
+ <AmountField
+ required
+ label={<i18n.Translate>Amount</i18n.Translate>}
+ currency="USD"
+ highestDenom={2000000}
+ lowestDenom={0.01}
+ handler={handler}
+ />
+ <p>
+ <pre>{JSON.stringify(value, undefined, 2)}</pre>
+ </p>
+ </Fragment>
+ );
+}
+
+export const AmountFieldExample = (): VNode => RenderAmount();
diff --git a/packages/taler-wallet-webextension/src/components/AmountField.tsx b/packages/taler-wallet-webextension/src/components/AmountField.tsx
index 1c57be0df..6081e70ff 100644
--- a/packages/taler-wallet-webextension/src/components/AmountField.tsx
+++ b/packages/taler-wallet-webextension/src/components/AmountField.tsx
@@ -14,51 +14,182 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import {
+ amountFractionalBase,
+ amountFractionalLength,
+ AmountJson,
+ amountMaxValue,
+ Amounts,
+ Result,
+} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
-import { TextFieldHandler } from "../mui/handlers.js";
+import { useState } from "preact/hooks";
+import { AmountFieldHandler } from "../mui/handlers.js";
import { TextField } from "../mui/TextField.js";
-import { ErrorText } from "./styled/index.js";
+
+const HIGH_DENOM_SYMBOL = ["", "K", "M", "G", "T", "P"];
+const LOW_DENOM_SYMBOL = ["", "m", "mm", "n", "p", "f"];
export function AmountField({
label,
handler,
- currency,
+ lowestDenom = 1,
+ highestDenom = 1,
required,
}: {
label: VNode;
+ lowestDenom?: number;
+ highestDenom?: number;
required?: boolean;
- currency: string;
- handler: TextFieldHandler;
+ handler: AmountFieldHandler;
}): VNode {
+ const [unit, setUnit] = useState(1);
+ const [dotAtTheEnd, setDotAtTheEnd] = useState(false);
+ const currency = handler.value.currency;
+
+ let hd = Math.floor(Math.log10(highestDenom || 1) / 3);
+ let ld = Math.ceil((-1 * Math.log10(lowestDenom || 1)) / 3);
+
+ const currencyLabels: Array<{ name: string; unit: number }> = [
+ {
+ name: currency,
+ unit: 1,
+ },
+ ];
+
+ while (hd > 0) {
+ currencyLabels.push({
+ name: `${HIGH_DENOM_SYMBOL[hd]}${currency}`,
+ unit: Math.pow(10, hd * 3),
+ });
+ hd--;
+ }
+ while (ld > 0) {
+ currencyLabels.push({
+ name: `${LOW_DENOM_SYMBOL[ld]}${currency}`,
+ unit: Math.pow(10, -1 * ld * 3),
+ });
+ ld--;
+ }
+
+ const prev = Amounts.stringifyValue(handler.value);
+
function positiveAmount(value: string): string {
- if (!value) return "";
- try {
- const num = Number.parseFloat(value);
- if (Number.isNaN(num) || num < 0) return handler.value;
+ setDotAtTheEnd(value.endsWith("."));
+ if (!value) {
if (handler.onInput) {
- handler.onInput(value);
+ handler.onInput(Amounts.zeroOfCurrency(currency));
}
- return value;
+ return "";
+ }
+ try {
+ //remove all but last dot
+ const parsed = value.replace(/(\.)(?=.*\1)/g, "");
+ const real = parseValue(currency, parsed);
+
+ if (!real || real.value < 0) {
+ return prev;
+ }
+
+ const normal = normalize(real, unit);
+
+ console.log(real, unit, normal);
+ if (normal && handler.onInput) {
+ handler.onInput(normal);
+ }
+ return parsed;
} catch (e) {
// do nothing
}
- return handler.value;
+ return prev;
}
+
+ const normal = denormalize(handler.value, unit) ?? handler.value;
+
+ const textValue = Amounts.stringifyValue(normal) + (dotAtTheEnd ? "." : "");
return (
- <TextField
- label={label}
- type="number"
- min="0"
- step="0.1"
- variant="filled"
- error={handler.error}
- required={required}
- startAdornment={
- <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
- }
- value={handler.value}
- disabled={!handler.onInput}
- onInput={positiveAmount}
- />
+ <Fragment>
+ <TextField
+ label={label}
+ type="text"
+ min="0"
+ inputmode="decimal"
+ step="0.1"
+ variant="filled"
+ error={handler.error}
+ required={required}
+ startAdornment={
+ currencyLabels.length === 1 ? (
+ <div
+ style={{
+ marginTop: 20,
+ padding: "5px 12px 8px 12px",
+ }}
+ >
+ {currency}
+ </div>
+ ) : (
+ <select
+ disabled={!handler.onInput}
+ onChange={(e) => {
+ const unit = Number.parseFloat(e.currentTarget.value);
+ setUnit(unit);
+ }}
+ value={String(unit)}
+ style={{
+ marginTop: 20,
+ padding: "5px 12px 8px 12px",
+ background: "transparent",
+ border: 0,
+ }}
+ >
+ {currencyLabels.map((c) => (
+ <option key={c} value={c.unit}>
+ <div>{c.name}</div>
+ </option>
+ ))}
+ </select>
+ )
+ }
+ value={textValue}
+ disabled={!handler.onInput}
+ onInput={positiveAmount}
+ />
+ </Fragment>
);
}
+
+function parseValue(currency: string, s: string): AmountJson | undefined {
+ const [intPart, fractPart] = s.split(".");
+ const tail = "." + (fractPart || "0");
+ if (tail.length > amountFractionalLength + 1) {
+ return undefined;
+ }
+ const value = Number.parseInt(intPart, 10);
+ if (Number.isNaN(value) || value > amountMaxValue) {
+ return undefined;
+ }
+ return {
+ currency,
+ fraction: Math.round(amountFractionalBase * Number.parseFloat(tail)),
+ value,
+ };
+}
+
+function normalize(amount: AmountJson, unit: number): AmountJson | undefined {
+ if (unit === 1 || Amounts.isZero(amount)) return amount;
+ const result =
+ unit < 1
+ ? Amounts.divide(amount, 1 / unit)
+ : Amounts.mult(amount, unit).amount;
+ return result;
+}
+
+function denormalize(amount: AmountJson, unit: number): AmountJson | undefined {
+ if (unit === 1 || Amounts.isZero(amount)) return amount;
+ const result =
+ unit < 1
+ ? Amounts.mult(amount, 1 / unit).amount
+ : Amounts.divide(amount, unit);
+ return result;
+}
diff --git a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
index e5ce4140f..f8b23081d 100644
--- a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
@@ -57,9 +57,9 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
? !tx.withdrawalDetails.confirmed
? i18n.str`Need approval in the Bank`
: i18n.str`Exchange is waiting the wire transfer`
- : undefined
- : tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
- ? i18n.str`Exchange is waiting the wire transfer`
+ : tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
+ ? i18n.str`Exchange is waiting the wire transfer`
+ : "" //pending but no message
: undefined
}
/>
diff --git a/packages/taler-wallet-webextension/src/components/index.stories.tsx b/packages/taler-wallet-webextension/src/components/index.stories.tsx
index d71adf689..2e4e7fa2e 100644
--- a/packages/taler-wallet-webextension/src/components/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/components/index.stories.tsx
@@ -25,5 +25,6 @@ import * as a3 from "./Amount.stories.js";
import * as a4 from "./ShowFullContractTermPopup.stories.js";
import * as a5 from "./TermsOfService/stories.js";
import * as a6 from "./QR.stories";
+import * as a7 from "./AmountField.stories.js";
-export default [a1, a2, a3, a4, a5, a6];
+export default [a1, a2, a3, a4, a5, a6, a7];
diff --git a/packages/taler-wallet-webextension/src/mui/TextField.tsx b/packages/taler-wallet-webextension/src/mui/TextField.tsx
index ba05158fa..42ac49a00 100644
--- a/packages/taler-wallet-webextension/src/mui/TextField.tsx
+++ b/packages/taler-wallet-webextension/src/mui/TextField.tsx
@@ -41,6 +41,7 @@ export interface Props {
multiline?: boolean;
onChange?: (s: string) => void;
onInput?: (s: string) => string;
+ inputmode?: string;
min?: string;
step?: string;
placeholder?: string;
diff --git a/packages/taler-wallet-webextension/src/mui/handlers.ts b/packages/taler-wallet-webextension/src/mui/handlers.ts
index 9d393e5b7..655fceef9 100644
--- a/packages/taler-wallet-webextension/src/mui/handlers.ts
+++ b/packages/taler-wallet-webextension/src/mui/handlers.ts
@@ -13,6 +13,7 @@
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { AmountJson } from "@gnu-taler/taler-util";
import { TalerError } from "@gnu-taler/taler-wallet-core";
export interface TextFieldHandler {
@@ -21,6 +22,12 @@ export interface TextFieldHandler {
error?: string;
}
+export interface AmountFieldHandler {
+ onInput?: (value: AmountJson) => Promise<void>;
+ value: AmountJson;
+ error?: string;
+}
+
export interface ButtonHandler {
onClick?: () => Promise<void>;
error?: TalerError;
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
deleted file mode 100644
index 2154d35de..000000000
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.stories.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { createExample } from "../test-utils.js";
-import { CreateManualWithdraw as TestedComponent } from "./CreateManualWithdraw.js";
-
-export default {
- title: "wallet/manual withdraw/creation",
- component: TestedComponent,
- argTypes: {},
-};
-
-// ,
-const exchangeUrlWithCurrency = {
- "http://exchange.taler:8081": "COL",
- "http://exchange.tal": "EUR",
-};
-
-export const WithoutAnyExchangeKnown = createExample(TestedComponent, {
- exchangeUrlWithCurrency: {},
-});
-
-export const InitialState = createExample(TestedComponent, {
- exchangeUrlWithCurrency,
-});
-
-export const WithAmountInitialized = createExample(TestedComponent, {
- initialAmount: "10",
- exchangeUrlWithCurrency,
-});
-
-export const WithExchangeError = createExample(TestedComponent, {
- error: "The exchange url seems invalid",
- exchangeUrlWithCurrency,
-});
-
-export const WithAmountError = createExample(TestedComponent, {
- initialAmount: "e",
- exchangeUrlWithCurrency,
-});
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts
deleted file mode 100644
index 37c50285b..000000000
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { expect } from "chai";
-import { SelectFieldHandler, TextFieldHandler } from "../mui/handlers.js";
-import { mountHook } from "../test-utils.js";
-import { useComponentState } from "./CreateManualWithdraw.js";
-
-const exchangeListWithARSandUSD = {
- url1: "USD",
- url2: "ARS",
- url3: "ARS",
-};
-
-const exchangeListEmpty = {};
-
-describe("CreateManualWithdraw states", () => {
- it("should set noExchangeFound when exchange list is empty", () => {
- const { pullLastResultOrThrow } = mountHook(() =>
- useComponentState(exchangeListEmpty, undefined, undefined),
- );
-
- const { noExchangeFound } = pullLastResultOrThrow();
-
- expect(noExchangeFound).equal(true);
- });
-
- it("should set noExchangeFound when exchange list doesn't include selected currency", () => {
- const { pullLastResultOrThrow } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "COL"),
- );
-
- const { noExchangeFound } = pullLastResultOrThrow();
-
- expect(noExchangeFound).equal(true);
- });
-
- it("should select the first exchange from the list", () => {
- const { pullLastResultOrThrow } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, undefined),
- );
-
- const { exchange } = pullLastResultOrThrow();
-
- expect(exchange.value).equal("url1");
- });
-
- it("should select the first exchange with the selected currency", () => {
- const { pullLastResultOrThrow } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- const { exchange } = pullLastResultOrThrow();
-
- expect(exchange.value).equal("url2");
- });
-
- it("should change the exchange when currency change", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- {
- const { exchange, currency } = pullLastResultOrThrow();
-
- expect(exchange.value).equal("url2");
- if (currency.onChange === undefined) expect.fail();
- currency.onChange("USD");
- }
-
- expect(await waitForStateUpdate()).true;
-
- {
- const { exchange } = pullLastResultOrThrow();
- expect(exchange.value).equal("url1");
- }
- });
-
- it("should change the currency when exchange change", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- {
- const { exchange, currency } = pullLastResultOrThrow();
-
- expect(exchange.value).equal("url2");
- expect(currency.value).equal("ARS");
-
- if (exchange.onChange === undefined) expect.fail();
- exchange.onChange("url1");
- }
-
- expect(await waitForStateUpdate()).true;
-
- {
- const { exchange, currency } = pullLastResultOrThrow();
-
- expect(exchange.value).equal("url1");
- expect(currency.value).equal("USD");
- }
- });
-
- it("should update parsed amount when amount change", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- {
- const { amount, parsedAmount } = pullLastResultOrThrow();
-
- expect(parsedAmount).equal(undefined);
-
- expect(amount.onInput).not.undefined;
- if (!amount.onInput) return;
- amount.onInput("12");
- }
-
- expect(await waitForStateUpdate()).true;
-
- {
- const { parsedAmount } = pullLastResultOrThrow();
-
- expect(parsedAmount).deep.equals({
- value: 12,
- fraction: 0,
- currency: "ARS",
- });
- }
- });
-
- it("should have an amount field", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- await defaultTestForInputText(
- waitForStateUpdate,
- () => pullLastResultOrThrow().amount,
- );
- });
-
- it("should have an exchange selector ", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- await defaultTestForInputSelect(
- waitForStateUpdate,
- () => pullLastResultOrThrow().exchange,
- );
- });
-
- it("should have a currency selector ", async () => {
- const { pullLastResultOrThrow, waitForStateUpdate } = mountHook(() =>
- useComponentState(exchangeListWithARSandUSD, undefined, "ARS"),
- );
-
- await defaultTestForInputSelect(
- waitForStateUpdate,
- () => pullLastResultOrThrow().currency,
- );
- });
-});
-
-async function defaultTestForInputText(
- awaiter: () => Promise<boolean>,
- getField: () => TextFieldHandler,
-): Promise<void> {
- let nextValue = "";
- {
- const field = getField();
- const initialValue = field.value;
- nextValue = `${initialValue} something else`;
- expect(field.onInput).not.undefined;
- if (!field.onInput) return;
- field.onInput(nextValue);
- }
-
- expect(await awaiter()).true;
-
- {
- const field = getField();
- expect(field.value).equal(nextValue);
- }
-}
-
-async function defaultTestForInputSelect(
- awaiter: () => Promise<boolean>,
- getField: () => SelectFieldHandler,
-): Promise<void> {
- let nextValue = "";
-
- {
- const field = getField();
- const initialValue = field.value;
- const keys = Object.keys(field.list);
- const nextIdx = keys.indexOf(initialValue) + 1;
- if (keys.length < nextIdx) {
- throw new Error("no enough values");
- }
- nextValue = keys[nextIdx];
- if (field.onChange === undefined) expect.fail();
- field.onChange(nextValue);
- }
-
- expect(await awaiter()).true;
-
- {
- const field = getField();
-
- expect(field.value).equal(nextValue);
- }
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
deleted file mode 100644
index dd80faccd..000000000
--- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { ErrorMessage } from "../components/ErrorMessage.js";
-import { SelectList } from "../components/SelectList.js";
-import {
- BoldLight,
- Centered,
- Input,
- InputWithLabel,
- LightText,
- LinkPrimary,
- SubTitle,
-} from "../components/styled/index.js";
-import { useTranslationContext } from "../context/translation.js";
-import { Button } from "../mui/Button.js";
-import { SelectFieldHandler, TextFieldHandler } from "../mui/handlers.js";
-import { Pages } from "../NavigationBar.js";
-
-export interface Props {
- error: string | undefined;
- initialAmount?: string;
- exchangeUrlWithCurrency: Record<string, string>;
- onCreate: (exchangeBaseUrl: string, amount: AmountJson) => Promise<void>;
- initialCurrency?: string;
-}
-
-export interface State {
- noExchangeFound: boolean;
- parsedAmount: AmountJson | undefined;
- amount: TextFieldHandler;
- currency: SelectFieldHandler;
- exchange: SelectFieldHandler;
-}
-
-export function useComponentState(
- exchangeUrlWithCurrency: Record<string, string>,
- initialAmount: string | undefined,
- initialCurrency: string | undefined,
-): State {
- const exchangeSelectList = Object.keys(exchangeUrlWithCurrency);
- const currencySelectList = Object.values(exchangeUrlWithCurrency);
- const exchangeMap = exchangeSelectList.reduce(
- (p, c) => ({ ...p, [c]: `${c} (${exchangeUrlWithCurrency[c]})` }),
- {} as Record<string, string>,
- );
- const currencyMap = currencySelectList.reduce(
- (p, c) => ({ ...p, [c]: c }),
- {} as Record<string, string>,
- );
-
- const foundExchangeForCurrency = exchangeSelectList.findIndex(
- (e) => exchangeUrlWithCurrency[e] === initialCurrency,
- );
-
- const initialExchange =
- foundExchangeForCurrency !== -1
- ? exchangeSelectList[foundExchangeForCurrency]
- : !initialCurrency && exchangeSelectList.length > 0
- ? exchangeSelectList[0]
- : undefined;
-
- const [exchange, setExchange] = useState(initialExchange || "");
- const [currency, setCurrency] = useState(
- initialExchange ? exchangeUrlWithCurrency[initialExchange] : "",
- );
-
- const [amount, setAmount] = useState(initialAmount || "");
- const parsedAmount = Amounts.parse(`${currency}:${amount}`);
-
- async function changeExchange(exchange: string): Promise<void> {
- setExchange(exchange);
- setCurrency(exchangeUrlWithCurrency[exchange]);
- }
-
- async function changeCurrency(currency: string): Promise<void> {
- setCurrency(currency);
- const found = Object.entries(exchangeUrlWithCurrency).find(
- (e) => e[1] === currency,
- );
-
- if (found) {
- setExchange(found[0]);
- } else {
- setExchange("");
- }
- }
- return {
- noExchangeFound: initialExchange === undefined,
- currency: {
- list: currencyMap,
- value: currency,
- onChange: changeCurrency,
- },
- exchange: {
- list: exchangeMap,
- value: exchange,
- onChange: changeExchange,
- },
- amount: {
- value: amount,
- onInput: async (e: string) => setAmount(e),
- },
- parsedAmount,
- };
-}
-
-export function CreateManualWithdraw({
- initialAmount,
- exchangeUrlWithCurrency,
- error,
- initialCurrency,
- onCreate,
-}: Props): VNode {
- const { i18n } = useTranslationContext();
-
- const state = useComponentState(
- exchangeUrlWithCurrency,
- initialAmount,
- initialCurrency,
- );
-
- if (state.noExchangeFound) {
- if (initialCurrency) {
- return (
- <section>
- <SubTitle>
- <i18n.Translate>
- Manual Withdrawal for {initialCurrency}
- </i18n.Translate>
- </SubTitle>
- <LightText>
- <i18n.Translate>
- Choose a exchange from where the coins will be withdrawn. The
- exchange will send the coins to this wallet after receiving a wire
- transfer with the correct subject.
- </i18n.Translate>
- </LightText>
- <Centered style={{ marginTop: 100 }}>
- <BoldLight>
- <i18n.Translate>
- No exchange found for {initialCurrency}
- </i18n.Translate>
- </BoldLight>
- <LinkPrimary
- href={Pages.settingsExchangeAdd({ currency: initialCurrency })}
- style={{ marginLeft: "auto" }}
- >
- <i18n.Translate>Add Exchange</i18n.Translate>
- </LinkPrimary>
- </Centered>
- </section>
- );
- }
- return (
- <section>
- <SubTitle>
- <i18n.Translate>
- Manual Withdrawal for {state.currency.value}
- </i18n.Translate>
- </SubTitle>
- <LightText>
- <i18n.Translate>
- Choose a exchange from where the coins will be withdrawn. The
- exchange will send the coins to this wallet after receiving a wire
- transfer with the correct subject.
- </i18n.Translate>
- </LightText>
- <Centered style={{ marginTop: 100 }}>
- <BoldLight>
- <i18n.Translate>No exchange configured</i18n.Translate>
- </BoldLight>
- <LinkPrimary
- href={Pages.settingsExchangeAdd({})}
- style={{ marginLeft: "auto" }}
- >
- <i18n.Translate>Add Exchange</i18n.Translate>
- </LinkPrimary>
- </Centered>
- </section>
- );
- }
-
- return (
- <Fragment>
- <section>
- {error && (
- <ErrorMessage
- title={
- <i18n.Translate>Can&apos;t create the reserve</i18n.Translate>
- }
- description={error}
- />
- )}
- <SubTitle>
- <i18n.Translate>
- Manual Withdrawal for {state.currency.value}
- </i18n.Translate>
- </SubTitle>
- <LightText>
- <i18n.Translate>
- Choose a exchange from where the coins will be withdrawn. The
- exchange will send the coins to this wallet after receiving a wire
- transfer with the correct subject.
- </i18n.Translate>
- </LightText>
- <p>
- <Input>
- <SelectList
- label={<i18n.Translate>Currency</i18n.Translate>}
- name="currency"
- {...state.currency}
- />
- </Input>
- <Input>
- <SelectList
- label={<i18n.Translate>Exchange</i18n.Translate>}
- name="exchange"
- {...state.exchange}
- />
- </Input>
- <div style={{ display: "flex", justifyContent: "space-between" }}>
- <LinkPrimary
- href={Pages.settingsExchangeAdd({})}
- style={{ marginLeft: "auto" }}
- >
- <i18n.Translate>Add Exchange</i18n.Translate>
- </LinkPrimary>
- </div>
- {state.currency.value && (
- <InputWithLabel
- invalid={!!state.amount.value && !state.parsedAmount}
- >
- <label>
- <i18n.Translate>Amount</i18n.Translate>
- </label>
- <div>
- <span>{state.currency.value}</span>
- <input
- type="number"
- value={state.amount.value}
- // onInput={(e) => state.amount.onInput(e.currentTarget.value)}
- />
- </div>
- </InputWithLabel>
- )}
- </p>
- </section>
- <footer>
- <div />
- <Button
- variant="contained"
- disabled={!state.parsedAmount || !state.exchange.value}
- onClick={() => onCreate(state.exchange.value, state.parsedAmount!)}
- >
- <i18n.Translate>Start withdrawal</i18n.Translate>
- </Button>
- </footer>
- </Fragment>
- );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
index 373045833..3f23515b2 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts
@@ -18,6 +18,7 @@ import { AmountJson, PaytoUri } from "@gnu-taler/taler-util";
import { Loading } from "../../components/Loading.js";
import { HookError } from "../../hooks/useAsyncAsHook.js";
import {
+ AmountFieldHandler,
ButtonHandler,
SelectFieldHandler,
TextFieldHandler,
@@ -98,7 +99,7 @@ export namespace State {
totalFee: AmountJson;
totalToDeposit: AmountJson;
- amount: TextFieldHandler;
+ amount: AmountFieldHandler;
account: SelectFieldHandler;
cancelHandler: ButtonHandler;
depositHandler: ButtonHandler;
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
index 91883c823..bbf2c2771 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
@@ -52,9 +52,13 @@ export function useComponentState(
});
const initialValue =
- parsed !== undefined ? Amounts.stringifyValue(parsed) : "0";
+ parsed !== undefined
+ ? parsed
+ : currency !== undefined
+ ? Amounts.zeroOfCurrency(currency)
+ : undefined;
// const [accountIdx, setAccountIdx] = useState<number>(0);
- const [amount, setAmount] = useState(initialValue);
+ const [amount, setAmount] = useState<AmountJson>(initialValue ?? ({} as any));
const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
@@ -81,7 +85,7 @@ export function useComponentState(
}
const { accounts, balances } = hook.response;
- const parsedAmount = Amounts.parse(`${currency}:${amount}`);
+ // const parsedAmount = Amounts.parse(`${currency}:${amount}`);
if (addingAccount) {
return {
@@ -129,8 +133,8 @@ export function useComponentState(
const firstAccount = accounts[0].uri;
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
- if (fee === undefined && parsedAmount) {
- getFeeForAmount(currentAccount, parsedAmount, api).then((initialFee) => {
+ if (fee === undefined) {
+ getFeeForAmount(currentAccount, amount, api).then((initialFee) => {
setFee(initialFee);
});
return {
@@ -143,9 +147,9 @@ export function useComponentState(
async function updateAccountFromList(accountStr: string): Promise<void> {
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
- if (uri && parsedAmount) {
+ if (uri) {
try {
- const result = await getFeeForAmount(uri, parsedAmount, api);
+ const result = await getFeeForAmount(uri, amount, api);
setSelectedAccount(uri);
setFee(result);
} catch (e) {
@@ -155,17 +159,15 @@ export function useComponentState(
}
}
- async function updateAmount(numStr: string): Promise<void> {
- const parsed = Amounts.parse(`${currency}:${numStr}`);
- if (parsed) {
- try {
- const result = await getFeeForAmount(currentAccount, parsed, api);
- setAmount(numStr);
- setFee(result);
- } catch (e) {
- setAmount(numStr);
- setFee(undefined);
- }
+ async function updateAmount(newAmount: AmountJson): Promise<void> {
+ // const parsed = Amounts.parse(`${currency}:${numStr}`);
+ try {
+ const result = await getFeeForAmount(currentAccount, newAmount, api);
+ setAmount(newAmount);
+ setFee(result);
+ } catch (e) {
+ setAmount(newAmount);
+ setFee(undefined);
}
}
@@ -175,32 +177,29 @@ export function useComponentState(
: Amounts.zeroOfCurrency(currency);
const totalToDeposit =
- parsedAmount && fee !== undefined
- ? Amounts.sub(parsedAmount, totalFee).amount
+ fee !== undefined
+ ? Amounts.sub(amount, totalFee).amount
: Amounts.zeroOfCurrency(currency);
const isDirty = amount !== initialValue;
const amountError = !isDirty
? undefined
- : !parsedAmount
- ? "Invalid amount"
- : Amounts.cmp(balance, parsedAmount) === -1
+ : Amounts.cmp(balance, amount) === -1
? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
: undefined;
const unableToDeposit =
- !parsedAmount || //no amount specified
Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
fee === undefined || //no fee calculated yet
amountError !== undefined; //amount field may be invalid
async function doSend(): Promise<void> {
- if (!parsedAmount || !currency) return;
+ if (!currency) return;
const depositPaytoUri = stringifyPaytoUri(currentAccount);
- const amount = Amounts.stringify(parsedAmount);
+ const amountStr = Amounts.stringify(amount);
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
- amount,
+ amount: amountStr,
depositPaytoUri,
});
onSuccess(currency);
@@ -211,7 +210,7 @@ export function useComponentState(
error: undefined,
currency,
amount: {
- value: String(amount),
+ value: amount,
onInput: updateAmount,
error: amountError,
},
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
index f03788d4e..75c544c84 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
@@ -52,7 +52,7 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
onInput: async () => {
null;
},
- value: "10:USD",
+ value: Amounts.parseOrThrow("USD:10"),
},
onAddAccount: {},
cancelHandler: {},
@@ -87,7 +87,7 @@ export const WithIBANAccountTypeSelected = createExample(ReadyView, {
onInput: async () => {
null;
},
- value: "10:USD",
+ value: Amounts.parseOrThrow("USD:10"),
},
onAddAccount: {},
cancelHandler: {},
@@ -123,7 +123,7 @@ export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
onInput: async () => {
null;
},
- value: "10:USD",
+ value: Amounts.parseOrThrow("USD:10"),
},
cancelHandler: {},
depositHandler: {
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
index 17e17d185..3f08c678c 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts
@@ -194,7 +194,7 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri));
- expect(r.amount.value).eq("0");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(r.depositHandler.onClick).undefined;
}
@@ -269,7 +269,7 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
- expect(r.amount.value).eq("0");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.account.onChange).not.undefined;
@@ -285,7 +285,7 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(accountSelected);
- expect(r.amount.value).eq("0");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.depositHandler.onClick).undefined;
}
@@ -423,7 +423,7 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
- expect(r.amount.value).eq("0");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
expect(r.account.onChange).not.undefined;
@@ -439,13 +439,13 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(accountSelected);
- expect(r.amount.value).eq("0");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:0"));
expect(r.depositHandler.onClick).undefined;
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.amount.onInput).not.undefined;
if (!r.amount.onInput) return;
- r.amount.onInput("10");
+ r.amount.onInput(Amounts.parseOrThrow("EUR:10"));
}
expect(await waitForStateUpdate()).true;
@@ -456,7 +456,7 @@ describe("DepositPage states", () => {
expect(r.cancelHandler.onClick).not.undefined;
expect(r.currency).eq(currency);
expect(r.account.value).eq(accountSelected);
- expect(r.amount.value).eq("10");
+ expect(r.amount.value).deep.eq(Amounts.parseOrThrow("EUR:10"));
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
expect(r.depositHandler.onClick).not.undefined;
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx
index 771db828d..6a28f31e1 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx
@@ -173,25 +173,22 @@ export function ReadyView(state: State.Ready): VNode {
<Grid item xs={1}>
<AmountField
label={<i18n.Translate>Amount</i18n.Translate>}
- currency={state.currency}
handler={state.amount}
/>
</Grid>
<Grid item xs={1}>
<AmountField
label={<i18n.Translate>Deposit fee</i18n.Translate>}
- currency={state.currency}
handler={{
- value: Amounts.stringifyValue(state.totalFee),
+ value: state.totalFee,
}}
/>
</Grid>
<Grid item xs={1}>
<AmountField
label={<i18n.Translate>Total deposit</i18n.Translate>}
- currency={state.currency}
handler={{
- value: Amounts.stringifyValue(state.totalToDeposit),
+ value: state.totalToDeposit,
}}
/>
</Grid>
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
index ba1a560ef..7e4c775e6 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
@@ -33,9 +33,7 @@ import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js";
-import { TextFieldHandler } from "../mui/handlers.js";
import { Paper } from "../mui/Paper.js";
-import { TextField } from "../mui/TextField.js";
import { Pages } from "../NavigationBar.js";
import arrowIcon from "../svg/chevron-down.svg";
import bankIcon from "../svg/ri-bank-line.svg";
@@ -279,12 +277,13 @@ export function DestinationSelectionGetCash({
const parsedInitialAmount = !initialAmount
? undefined
: Amounts.parse(initialAmount);
- const parsedInitialAmountValue = !parsedInitialAmount
- ? "0"
- : Amounts.stringifyValue(parsedInitialAmount);
+
const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
- const [amount, setAmount] = useState(parsedInitialAmountValue);
+ const [amount, setAmount] = useState(
+ parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
+ );
+
const { i18n } = useTranslationContext();
const previous1: Contact[] = [];
const previous2: Contact[] = [
@@ -313,10 +312,8 @@ export function DestinationSelectionGetCash({
</div>
);
}
- const currencyAndAmount = `${currency}:${amount}`;
- const parsedAmount = Amounts.parse(currencyAndAmount);
- // const dirty = parsedInitialAmountValue !== amount;
- const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
+ const currencyAndAmount = Amounts.stringify(amount);
+ const invalid = Amounts.isZero(amount);
return (
<Container>
<h1>
@@ -325,7 +322,6 @@ export function DestinationSelectionGetCash({
<Grid container columns={2} justifyContent="space-between">
<AmountField
label={<i18n.Translate>Amount</i18n.Translate>}
- currency={currency}
required
handler={{
onInput: async (s) => setAmount(s),
@@ -416,12 +412,12 @@ export function DestinationSelectionSendCash({
const parsedInitialAmount = !initialAmount
? undefined
: Amounts.parse(initialAmount);
- const parsedInitialAmountValue = !parsedInitialAmount
- ? ""
- : Amounts.stringifyValue(parsedInitialAmount);
+
const currency = parsedInitialAmount?.currency;
- const [amount, setAmount] = useState(parsedInitialAmountValue);
+ const [amount, setAmount] = useState(
+ parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
+ );
const { i18n } = useTranslationContext();
const previous1: Contact[] = [];
const previous2: Contact[] = [
@@ -450,9 +446,9 @@ export function DestinationSelectionSendCash({
</div>
);
}
- const currencyAndAmount = `${currency}:${amount}`;
- const parsedAmount = Amounts.parse(currencyAndAmount);
- const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
+ const currencyAndAmount = Amounts.stringify(amount);
+ //const parsedAmount = Amounts.parse(currencyAndAmount);
+ const invalid = Amounts.isZero(amount);
return (
<Container>
<h1>
@@ -462,7 +458,6 @@ export function DestinationSelectionSendCash({
<div>
<AmountField
label={<i18n.Translate>Amount</i18n.Translate>}
- currency={currency}
required
handler={{
onInput: async (s) => setAmount(s),
diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
index 39fbb6ce2..63d545b97 100644
--- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts
@@ -44,21 +44,22 @@ export function useComponentState(
const comparingExchanges = selectedIdx !== initialValue;
- const initialExchange =
- comparingExchanges ? exchanges[initialValue] : undefined;
+ const initialExchange = comparingExchanges
+ ? exchanges[initialValue]
+ : undefined;
const hook = useAsyncAsHook(async () => {
const selected = !selectedExchange
? undefined
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
- exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
- });
+ exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
+ });
const original = !initialExchange
? undefined
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
- exchangeBaseUrl: initialExchange.exchangeBaseUrl,
- });
+ exchangeBaseUrl: initialExchange.exchangeBaseUrl,
+ });
return {
exchanges,
diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
deleted file mode 100644
index e2284a466..000000000
--- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2022 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AcceptManualWithdrawalResult,
- AmountJson,
- Amounts,
- NotificationType,
- parsePaytoUri,
- PaytoUri,
-} from "@gnu-taler/taler-util";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { Loading } from "../components/Loading.js";
-import { LoadingError } from "../components/LoadingError.js";
-import { useTranslationContext } from "../context/translation.js";
-import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
-import { wxApi } from "../wxApi.js";
-import { CreateManualWithdraw } from "./CreateManualWithdraw.js";
-import { ReserveCreated } from "./ReserveCreated.js";
-
-interface Props {
- amount?: string;
- onCancel: () => Promise<void>;
-}
-
-export function ManualWithdrawPage({ amount, onCancel }: Props): VNode {
- const [success, setSuccess] = useState<
- | {
- response: AcceptManualWithdrawalResult;
- exchangeBaseUrl: string;
- amount: AmountJson;
- paytoURI: PaytoUri | undefined;
- payto: string;
- }
- | undefined
- >(undefined);
- const [error, setError] = useState<string | undefined>(undefined);
-
- const state = useAsyncAsHook(() =>
- wxApi.wallet.call(WalletApiOperation.ListExchanges, {}),
- );
- useEffect(() => {
- return wxApi.listener.onUpdateNotification(
- [NotificationType.ExchangeAdded],
- state?.retry,
- );
- });
- const { i18n } = useTranslationContext();
-
- async function doCreate(
- exchangeBaseUrl: string,
- amount: AmountJson,
- ): Promise<void> {
- try {
- const response = await wxApi.wallet.call(
- WalletApiOperation.AcceptManualWithdrawal,
- {
- exchangeBaseUrl: exchangeBaseUrl,
- amount: Amounts.stringify(amount),
- },
- );
- const payto = response.exchangePaytoUris[0];
- const paytoURI = parsePaytoUri(payto);
- setSuccess({ exchangeBaseUrl, response, amount, paytoURI, payto });
- } catch (e) {
- if (e instanceof Error) {
- setError(e.message);
- } else {
- setError("unexpected error");
- }
- setSuccess(undefined);
- }
- }
-
- if (success) {
- return (
- <ReserveCreated
- reservePub={success.response.reservePub}
- paytoURI={success.paytoURI}
- // payto={success.payto}
- exchangeBaseUrl={success.exchangeBaseUrl}
- amount={success.amount}
- onCancel={onCancel}
- />
- );
- }
-
- if (!state) {
- return <Loading />;
- }
- if (state.hasError) {
- return (
- <LoadingError
- title={
- <i18n.Translate>
- Could not load the list of known exchanges
- </i18n.Translate>
- }
- error={state}
- />
- );
- }
-
- const exchangeList = state.response.exchanges.reduce(
- (p, c) => ({
- ...p,
- [c.exchangeBaseUrl]: c.currency || "??",
- }),
- {} as Record<string, string>,
- );
-
- const parsedAmount = !amount ? undefined : Amounts.parse(amount);
- const currency = parsedAmount?.currency;
- const amountValue = !parsedAmount
- ? undefined
- : Amounts.stringifyValue(parsedAmount);
- return (
- <CreateManualWithdraw
- error={error}
- exchangeUrlWithCurrency={exchangeList}
- onCreate={doCreate}
- initialCurrency={currency}
- initialAmount={amountValue}
- />
- );
-}
diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
index 42808b573..ef1295846 100644
--- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx
@@ -20,7 +20,6 @@
*/
import * as a1 from "./Backup.stories.js";
-import * as a3 from "./CreateManualWithdraw.stories.js";
import * as a4 from "./DepositPage/stories.js";
import * as a5 from "./ExchangeAddConfirm.stories.js";
import * as a6 from "./ExchangeAddSetUrl.stories.js";
@@ -40,7 +39,6 @@ import * as a20 from "./ManageAccount/stories.js";
export default [
a1,
- a3,
a4,
a5,
a6,