amount field
This commit is contained in:
parent
dc08d7d20e
commit
88618df7b8
@ -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();
|
@ -14,51 +14,182 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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 { 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 { 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({
|
export function AmountField({
|
||||||
label,
|
label,
|
||||||
handler,
|
handler,
|
||||||
currency,
|
lowestDenom = 1,
|
||||||
|
highestDenom = 1,
|
||||||
required,
|
required,
|
||||||
}: {
|
}: {
|
||||||
label: VNode;
|
label: VNode;
|
||||||
|
lowestDenom?: number;
|
||||||
|
highestDenom?: number;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
currency: string;
|
handler: AmountFieldHandler;
|
||||||
handler: TextFieldHandler;
|
|
||||||
}): VNode {
|
}): 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 {
|
function positiveAmount(value: string): string {
|
||||||
if (!value) return "";
|
setDotAtTheEnd(value.endsWith("."));
|
||||||
try {
|
if (!value) {
|
||||||
const num = Number.parseFloat(value);
|
|
||||||
if (Number.isNaN(num) || num < 0) return handler.value;
|
|
||||||
if (handler.onInput) {
|
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) {
|
} catch (e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
return handler.value;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normal = denormalize(handler.value, unit) ?? handler.value;
|
||||||
|
|
||||||
|
const textValue = Amounts.stringifyValue(normal) + (dotAtTheEnd ? "." : "");
|
||||||
return (
|
return (
|
||||||
<TextField
|
<Fragment>
|
||||||
label={label}
|
<TextField
|
||||||
type="number"
|
label={label}
|
||||||
min="0"
|
type="text"
|
||||||
step="0.1"
|
min="0"
|
||||||
variant="filled"
|
inputmode="decimal"
|
||||||
error={handler.error}
|
step="0.1"
|
||||||
required={required}
|
variant="filled"
|
||||||
startAdornment={
|
error={handler.error}
|
||||||
<div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
|
required={required}
|
||||||
}
|
startAdornment={
|
||||||
value={handler.value}
|
currencyLabels.length === 1 ? (
|
||||||
disabled={!handler.onInput}
|
<div
|
||||||
onInput={positiveAmount}
|
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;
|
||||||
|
}
|
||||||
|
@ -57,9 +57,9 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
|
|||||||
? !tx.withdrawalDetails.confirmed
|
? !tx.withdrawalDetails.confirmed
|
||||||
? i18n.str`Need approval in the Bank`
|
? i18n.str`Need approval in the Bank`
|
||||||
: i18n.str`Exchange is waiting the wire transfer`
|
: i18n.str`Exchange is waiting the wire transfer`
|
||||||
: undefined
|
: tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
|
||||||
: tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
|
? i18n.str`Exchange is waiting the wire transfer`
|
||||||
? i18n.str`Exchange is waiting the wire transfer`
|
: "" //pending but no message
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -25,5 +25,6 @@ import * as a3 from "./Amount.stories.js";
|
|||||||
import * as a4 from "./ShowFullContractTermPopup.stories.js";
|
import * as a4 from "./ShowFullContractTermPopup.stories.js";
|
||||||
import * as a5 from "./TermsOfService/stories.js";
|
import * as a5 from "./TermsOfService/stories.js";
|
||||||
import * as a6 from "./QR.stories";
|
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];
|
||||||
|
@ -41,6 +41,7 @@ export interface Props {
|
|||||||
multiline?: boolean;
|
multiline?: boolean;
|
||||||
onChange?: (s: string) => void;
|
onChange?: (s: string) => void;
|
||||||
onInput?: (s: string) => string;
|
onInput?: (s: string) => string;
|
||||||
|
inputmode?: string;
|
||||||
min?: string;
|
min?: string;
|
||||||
step?: string;
|
step?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
You should have received a copy of the GNU General Public License along with
|
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/>
|
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";
|
import { TalerError } from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
export interface TextFieldHandler {
|
export interface TextFieldHandler {
|
||||||
@ -21,6 +22,12 @@ export interface TextFieldHandler {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AmountFieldHandler {
|
||||||
|
onInput?: (value: AmountJson) => Promise<void>;
|
||||||
|
value: AmountJson;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ButtonHandler {
|
export interface ButtonHandler {
|
||||||
onClick?: () => Promise<void>;
|
onClick?: () => Promise<void>;
|
||||||
error?: TalerError;
|
error?: TalerError;
|
||||||
|
@ -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,
|
|
||||||
});
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'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>
|
|
||||||
);
|
|
||||||
}
|
|
@ -18,6 +18,7 @@ import { AmountJson, PaytoUri } from "@gnu-taler/taler-util";
|
|||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||||
import {
|
import {
|
||||||
|
AmountFieldHandler,
|
||||||
ButtonHandler,
|
ButtonHandler,
|
||||||
SelectFieldHandler,
|
SelectFieldHandler,
|
||||||
TextFieldHandler,
|
TextFieldHandler,
|
||||||
@ -98,7 +99,7 @@ export namespace State {
|
|||||||
totalFee: AmountJson;
|
totalFee: AmountJson;
|
||||||
totalToDeposit: AmountJson;
|
totalToDeposit: AmountJson;
|
||||||
|
|
||||||
amount: TextFieldHandler;
|
amount: AmountFieldHandler;
|
||||||
account: SelectFieldHandler;
|
account: SelectFieldHandler;
|
||||||
cancelHandler: ButtonHandler;
|
cancelHandler: ButtonHandler;
|
||||||
depositHandler: ButtonHandler;
|
depositHandler: ButtonHandler;
|
||||||
|
@ -52,9 +52,13 @@ export function useComponentState(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const initialValue =
|
const initialValue =
|
||||||
parsed !== undefined ? Amounts.stringifyValue(parsed) : "0";
|
parsed !== undefined
|
||||||
|
? parsed
|
||||||
|
: currency !== undefined
|
||||||
|
? Amounts.zeroOfCurrency(currency)
|
||||||
|
: undefined;
|
||||||
// const [accountIdx, setAccountIdx] = useState<number>(0);
|
// 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 [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
|
||||||
|
|
||||||
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
|
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
|
||||||
@ -81,7 +85,7 @@ export function useComponentState(
|
|||||||
}
|
}
|
||||||
const { accounts, balances } = hook.response;
|
const { accounts, balances } = hook.response;
|
||||||
|
|
||||||
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
|
// const parsedAmount = Amounts.parse(`${currency}:${amount}`);
|
||||||
|
|
||||||
if (addingAccount) {
|
if (addingAccount) {
|
||||||
return {
|
return {
|
||||||
@ -129,8 +133,8 @@ export function useComponentState(
|
|||||||
const firstAccount = accounts[0].uri;
|
const firstAccount = accounts[0].uri;
|
||||||
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
|
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
|
||||||
|
|
||||||
if (fee === undefined && parsedAmount) {
|
if (fee === undefined) {
|
||||||
getFeeForAmount(currentAccount, parsedAmount, api).then((initialFee) => {
|
getFeeForAmount(currentAccount, amount, api).then((initialFee) => {
|
||||||
setFee(initialFee);
|
setFee(initialFee);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -143,9 +147,9 @@ export function useComponentState(
|
|||||||
|
|
||||||
async function updateAccountFromList(accountStr: string): Promise<void> {
|
async function updateAccountFromList(accountStr: string): Promise<void> {
|
||||||
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
|
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
|
||||||
if (uri && parsedAmount) {
|
if (uri) {
|
||||||
try {
|
try {
|
||||||
const result = await getFeeForAmount(uri, parsedAmount, api);
|
const result = await getFeeForAmount(uri, amount, api);
|
||||||
setSelectedAccount(uri);
|
setSelectedAccount(uri);
|
||||||
setFee(result);
|
setFee(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -155,17 +159,15 @@ export function useComponentState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateAmount(numStr: string): Promise<void> {
|
async function updateAmount(newAmount: AmountJson): Promise<void> {
|
||||||
const parsed = Amounts.parse(`${currency}:${numStr}`);
|
// const parsed = Amounts.parse(`${currency}:${numStr}`);
|
||||||
if (parsed) {
|
try {
|
||||||
try {
|
const result = await getFeeForAmount(currentAccount, newAmount, api);
|
||||||
const result = await getFeeForAmount(currentAccount, parsed, api);
|
setAmount(newAmount);
|
||||||
setAmount(numStr);
|
setFee(result);
|
||||||
setFee(result);
|
} catch (e) {
|
||||||
} catch (e) {
|
setAmount(newAmount);
|
||||||
setAmount(numStr);
|
setFee(undefined);
|
||||||
setFee(undefined);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,32 +177,29 @@ export function useComponentState(
|
|||||||
: Amounts.zeroOfCurrency(currency);
|
: Amounts.zeroOfCurrency(currency);
|
||||||
|
|
||||||
const totalToDeposit =
|
const totalToDeposit =
|
||||||
parsedAmount && fee !== undefined
|
fee !== undefined
|
||||||
? Amounts.sub(parsedAmount, totalFee).amount
|
? Amounts.sub(amount, totalFee).amount
|
||||||
: Amounts.zeroOfCurrency(currency);
|
: Amounts.zeroOfCurrency(currency);
|
||||||
|
|
||||||
const isDirty = amount !== initialValue;
|
const isDirty = amount !== initialValue;
|
||||||
const amountError = !isDirty
|
const amountError = !isDirty
|
||||||
? undefined
|
? undefined
|
||||||
: !parsedAmount
|
: Amounts.cmp(balance, amount) === -1
|
||||||
? "Invalid amount"
|
|
||||||
: Amounts.cmp(balance, parsedAmount) === -1
|
|
||||||
? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
|
? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const unableToDeposit =
|
const unableToDeposit =
|
||||||
!parsedAmount || //no amount specified
|
|
||||||
Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
|
Amounts.isZero(totalToDeposit) || //deposit may be zero because of fee
|
||||||
fee === undefined || //no fee calculated yet
|
fee === undefined || //no fee calculated yet
|
||||||
amountError !== undefined; //amount field may be invalid
|
amountError !== undefined; //amount field may be invalid
|
||||||
|
|
||||||
async function doSend(): Promise<void> {
|
async function doSend(): Promise<void> {
|
||||||
if (!parsedAmount || !currency) return;
|
if (!currency) return;
|
||||||
|
|
||||||
const depositPaytoUri = stringifyPaytoUri(currentAccount);
|
const depositPaytoUri = stringifyPaytoUri(currentAccount);
|
||||||
const amount = Amounts.stringify(parsedAmount);
|
const amountStr = Amounts.stringify(amount);
|
||||||
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
|
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
|
||||||
amount,
|
amount: amountStr,
|
||||||
depositPaytoUri,
|
depositPaytoUri,
|
||||||
});
|
});
|
||||||
onSuccess(currency);
|
onSuccess(currency);
|
||||||
@ -211,7 +210,7 @@ export function useComponentState(
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
currency,
|
currency,
|
||||||
amount: {
|
amount: {
|
||||||
value: String(amount),
|
value: amount,
|
||||||
onInput: updateAmount,
|
onInput: updateAmount,
|
||||||
error: amountError,
|
error: amountError,
|
||||||
},
|
},
|
||||||
|
@ -52,7 +52,7 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
|
|||||||
onInput: async () => {
|
onInput: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
value: "10:USD",
|
value: Amounts.parseOrThrow("USD:10"),
|
||||||
},
|
},
|
||||||
onAddAccount: {},
|
onAddAccount: {},
|
||||||
cancelHandler: {},
|
cancelHandler: {},
|
||||||
@ -87,7 +87,7 @@ export const WithIBANAccountTypeSelected = createExample(ReadyView, {
|
|||||||
onInput: async () => {
|
onInput: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
value: "10:USD",
|
value: Amounts.parseOrThrow("USD:10"),
|
||||||
},
|
},
|
||||||
onAddAccount: {},
|
onAddAccount: {},
|
||||||
cancelHandler: {},
|
cancelHandler: {},
|
||||||
@ -123,7 +123,7 @@ export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
|
|||||||
onInput: async () => {
|
onInput: async () => {
|
||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
value: "10:USD",
|
value: Amounts.parseOrThrow("USD:10"),
|
||||||
},
|
},
|
||||||
cancelHandler: {},
|
cancelHandler: {},
|
||||||
depositHandler: {
|
depositHandler: {
|
||||||
|
@ -194,7 +194,7 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri));
|
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;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
|
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.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.account.onChange).not.undefined;
|
expect(r.account.onChange).not.undefined;
|
||||||
@ -285,7 +285,7 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(accountSelected);
|
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.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
@ -423,7 +423,7 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
|
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.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:0`));
|
||||||
expect(r.account.onChange).not.undefined;
|
expect(r.account.onChange).not.undefined;
|
||||||
@ -439,13 +439,13 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(accountSelected);
|
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.depositHandler.onClick).undefined;
|
||||||
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
|
|
||||||
expect(r.amount.onInput).not.undefined;
|
expect(r.amount.onInput).not.undefined;
|
||||||
if (!r.amount.onInput) return;
|
if (!r.amount.onInput) return;
|
||||||
r.amount.onInput("10");
|
r.amount.onInput(Amounts.parseOrThrow("EUR:10"));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await waitForStateUpdate()).true;
|
expect(await waitForStateUpdate()).true;
|
||||||
@ -456,7 +456,7 @@ describe("DepositPage states", () => {
|
|||||||
expect(r.cancelHandler.onClick).not.undefined;
|
expect(r.cancelHandler.onClick).not.undefined;
|
||||||
expect(r.currency).eq(currency);
|
expect(r.currency).eq(currency);
|
||||||
expect(r.account.value).eq(accountSelected);
|
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.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`));
|
||||||
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
|
expect(r.totalToDeposit).deep.eq(Amounts.parseOrThrow(`${currency}:7`));
|
||||||
expect(r.depositHandler.onClick).not.undefined;
|
expect(r.depositHandler.onClick).not.undefined;
|
||||||
|
@ -173,25 +173,22 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
<Grid item xs={1}>
|
<Grid item xs={1}>
|
||||||
<AmountField
|
<AmountField
|
||||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
currency={state.currency}
|
|
||||||
handler={state.amount}
|
handler={state.amount}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={1}>
|
<Grid item xs={1}>
|
||||||
<AmountField
|
<AmountField
|
||||||
label={<i18n.Translate>Deposit fee</i18n.Translate>}
|
label={<i18n.Translate>Deposit fee</i18n.Translate>}
|
||||||
currency={state.currency}
|
|
||||||
handler={{
|
handler={{
|
||||||
value: Amounts.stringifyValue(state.totalFee),
|
value: state.totalFee,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={1}>
|
<Grid item xs={1}>
|
||||||
<AmountField
|
<AmountField
|
||||||
label={<i18n.Translate>Total deposit</i18n.Translate>}
|
label={<i18n.Translate>Total deposit</i18n.Translate>}
|
||||||
currency={state.currency}
|
|
||||||
handler={{
|
handler={{
|
||||||
value: Amounts.stringifyValue(state.totalToDeposit),
|
value: state.totalToDeposit,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -33,9 +33,7 @@ import { useTranslationContext } from "../context/translation.js";
|
|||||||
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
|
||||||
import { Button } from "../mui/Button.js";
|
import { Button } from "../mui/Button.js";
|
||||||
import { Grid } from "../mui/Grid.js";
|
import { Grid } from "../mui/Grid.js";
|
||||||
import { TextFieldHandler } from "../mui/handlers.js";
|
|
||||||
import { Paper } from "../mui/Paper.js";
|
import { Paper } from "../mui/Paper.js";
|
||||||
import { TextField } from "../mui/TextField.js";
|
|
||||||
import { Pages } from "../NavigationBar.js";
|
import { Pages } from "../NavigationBar.js";
|
||||||
import arrowIcon from "../svg/chevron-down.svg";
|
import arrowIcon from "../svg/chevron-down.svg";
|
||||||
import bankIcon from "../svg/ri-bank-line.svg";
|
import bankIcon from "../svg/ri-bank-line.svg";
|
||||||
@ -279,12 +277,13 @@ export function DestinationSelectionGetCash({
|
|||||||
const parsedInitialAmount = !initialAmount
|
const parsedInitialAmount = !initialAmount
|
||||||
? undefined
|
? undefined
|
||||||
: Amounts.parse(initialAmount);
|
: Amounts.parse(initialAmount);
|
||||||
const parsedInitialAmountValue = !parsedInitialAmount
|
|
||||||
? "0"
|
|
||||||
: Amounts.stringifyValue(parsedInitialAmount);
|
|
||||||
const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
|
const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
|
||||||
|
|
||||||
const [amount, setAmount] = useState(parsedInitialAmountValue);
|
const [amount, setAmount] = useState(
|
||||||
|
parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
|
||||||
|
);
|
||||||
|
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const previous1: Contact[] = [];
|
const previous1: Contact[] = [];
|
||||||
const previous2: Contact[] = [
|
const previous2: Contact[] = [
|
||||||
@ -313,10 +312,8 @@ export function DestinationSelectionGetCash({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const currencyAndAmount = `${currency}:${amount}`;
|
const currencyAndAmount = Amounts.stringify(amount);
|
||||||
const parsedAmount = Amounts.parse(currencyAndAmount);
|
const invalid = Amounts.isZero(amount);
|
||||||
// const dirty = parsedInitialAmountValue !== amount;
|
|
||||||
const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<h1>
|
<h1>
|
||||||
@ -325,7 +322,6 @@ export function DestinationSelectionGetCash({
|
|||||||
<Grid container columns={2} justifyContent="space-between">
|
<Grid container columns={2} justifyContent="space-between">
|
||||||
<AmountField
|
<AmountField
|
||||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
currency={currency}
|
|
||||||
required
|
required
|
||||||
handler={{
|
handler={{
|
||||||
onInput: async (s) => setAmount(s),
|
onInput: async (s) => setAmount(s),
|
||||||
@ -416,12 +412,12 @@ export function DestinationSelectionSendCash({
|
|||||||
const parsedInitialAmount = !initialAmount
|
const parsedInitialAmount = !initialAmount
|
||||||
? undefined
|
? undefined
|
||||||
: Amounts.parse(initialAmount);
|
: Amounts.parse(initialAmount);
|
||||||
const parsedInitialAmountValue = !parsedInitialAmount
|
|
||||||
? ""
|
|
||||||
: Amounts.stringifyValue(parsedInitialAmount);
|
|
||||||
const currency = parsedInitialAmount?.currency;
|
const currency = parsedInitialAmount?.currency;
|
||||||
|
|
||||||
const [amount, setAmount] = useState(parsedInitialAmountValue);
|
const [amount, setAmount] = useState(
|
||||||
|
parsedInitialAmount ?? Amounts.zeroOfCurrency(currency ?? "KUDOS"),
|
||||||
|
);
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const previous1: Contact[] = [];
|
const previous1: Contact[] = [];
|
||||||
const previous2: Contact[] = [
|
const previous2: Contact[] = [
|
||||||
@ -450,9 +446,9 @@ export function DestinationSelectionSendCash({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const currencyAndAmount = `${currency}:${amount}`;
|
const currencyAndAmount = Amounts.stringify(amount);
|
||||||
const parsedAmount = Amounts.parse(currencyAndAmount);
|
//const parsedAmount = Amounts.parse(currencyAndAmount);
|
||||||
const invalid = !parsedAmount || Amounts.isZero(parsedAmount);
|
const invalid = Amounts.isZero(amount);
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<h1>
|
<h1>
|
||||||
@ -462,7 +458,6 @@ export function DestinationSelectionSendCash({
|
|||||||
<div>
|
<div>
|
||||||
<AmountField
|
<AmountField
|
||||||
label={<i18n.Translate>Amount</i18n.Translate>}
|
label={<i18n.Translate>Amount</i18n.Translate>}
|
||||||
currency={currency}
|
|
||||||
required
|
required
|
||||||
handler={{
|
handler={{
|
||||||
onInput: async (s) => setAmount(s),
|
onInput: async (s) => setAmount(s),
|
||||||
|
@ -44,21 +44,22 @@ export function useComponentState(
|
|||||||
|
|
||||||
const comparingExchanges = selectedIdx !== initialValue;
|
const comparingExchanges = selectedIdx !== initialValue;
|
||||||
|
|
||||||
const initialExchange =
|
const initialExchange = comparingExchanges
|
||||||
comparingExchanges ? exchanges[initialValue] : undefined;
|
? exchanges[initialValue]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const hook = useAsyncAsHook(async () => {
|
const hook = useAsyncAsHook(async () => {
|
||||||
const selected = !selectedExchange
|
const selected = !selectedExchange
|
||||||
? undefined
|
? undefined
|
||||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
||||||
exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
|
exchangeBaseUrl: selectedExchange.exchangeBaseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
const original = !initialExchange
|
const original = !initialExchange
|
||||||
? undefined
|
? undefined
|
||||||
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
: await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, {
|
||||||
exchangeBaseUrl: initialExchange.exchangeBaseUrl,
|
exchangeBaseUrl: initialExchange.exchangeBaseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
exchanges,
|
exchanges,
|
||||||
|
@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as a1 from "./Backup.stories.js";
|
import * as a1 from "./Backup.stories.js";
|
||||||
import * as a3 from "./CreateManualWithdraw.stories.js";
|
|
||||||
import * as a4 from "./DepositPage/stories.js";
|
import * as a4 from "./DepositPage/stories.js";
|
||||||
import * as a5 from "./ExchangeAddConfirm.stories.js";
|
import * as a5 from "./ExchangeAddConfirm.stories.js";
|
||||||
import * as a6 from "./ExchangeAddSetUrl.stories.js";
|
import * as a6 from "./ExchangeAddSetUrl.stories.js";
|
||||||
@ -40,7 +39,6 @@ import * as a20 from "./ManageAccount/stories.js";
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
a1,
|
a1,
|
||||||
a3,
|
|
||||||
a4,
|
a4,
|
||||||
a5,
|
a5,
|
||||||
a6,
|
a6,
|
||||||
|
Loading…
Reference in New Issue
Block a user