manage account instead of add account
This commit is contained in:
parent
7c33040ae3
commit
fe6e9be702
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
After Width: | Height: | Size: 187 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>
|
After Width: | Height: | Size: 188 B |
@ -1,29 +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 { ReadyView } from "./views.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "example",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {});
|
|
@ -1,249 +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 { parsePaytoUri } from "@gnu-taler/taler-util";
|
|
||||||
import { Fragment, h, VNode } from "preact";
|
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
|
||||||
import { SelectList } from "../../components/SelectList.js";
|
|
||||||
import { Input, LightText, SubTitle } from "../../components/styled/index.js";
|
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
|
||||||
import { Button } from "../../mui/Button.js";
|
|
||||||
import { TextFieldHandler } from "../../mui/handlers.js";
|
|
||||||
import { TextField } from "../../mui/TextField.js";
|
|
||||||
import { State } from "./index.js";
|
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoadingError
|
|
||||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ReadyView({
|
|
||||||
currency,
|
|
||||||
error,
|
|
||||||
accountType,
|
|
||||||
alias,
|
|
||||||
onAccountAdded,
|
|
||||||
onCancel,
|
|
||||||
uri,
|
|
||||||
}: State.Ready): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<section>
|
|
||||||
<SubTitle>
|
|
||||||
<i18n.Translate>Add bank account for {currency}</i18n.Translate>
|
|
||||||
</SubTitle>
|
|
||||||
<LightText>
|
|
||||||
<i18n.Translate>
|
|
||||||
Enter the URL of an exchange you trust.
|
|
||||||
</i18n.Translate>
|
|
||||||
</LightText>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<ErrorMessage
|
|
||||||
title={<i18n.Translate>Unable add this account</i18n.Translate>}
|
|
||||||
description={error}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
<Input>
|
|
||||||
<SelectList
|
|
||||||
label={<i18n.Translate>Select account type</i18n.Translate>}
|
|
||||||
list={accountType.list}
|
|
||||||
name="accountType"
|
|
||||||
value={accountType.value}
|
|
||||||
onChange={accountType.onChange}
|
|
||||||
/>
|
|
||||||
</Input>
|
|
||||||
</p>
|
|
||||||
{accountType.value === "" ? undefined : (
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
|
||||||
<CustomFieldByAccountType type={accountType.value} field={uri} />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<TextField
|
|
||||||
label="Account alias"
|
|
||||||
variant="standard"
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
disabled={accountType.value === ""}
|
|
||||||
value={alias.value}
|
|
||||||
onChange={alias.onInput}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="secondary"
|
|
||||||
onClick={onCancel.onClick}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Cancel</i18n.Translate>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={onAccountAdded.onClick}
|
|
||||||
disabled={!onAccountAdded.onClick}
|
|
||||||
>
|
|
||||||
<i18n.Translate>Add</i18n.Translate>
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
const [value, setValue] = useState<string | undefined>(undefined);
|
|
||||||
const errors = undefinedIfEmpty({
|
|
||||||
value: !value ? i18n.str`Can't be empty` : undefined,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<TextField
|
|
||||||
label="Bitcoin address"
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
value={value}
|
|
||||||
error={value !== undefined && !!errors?.value}
|
|
||||||
onChange={(v) => {
|
|
||||||
setValue(v);
|
|
||||||
if (!errors) {
|
|
||||||
field.onInput(`payto://bitcoin/${value}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{value !== undefined && errors?.value && (
|
|
||||||
<ErrorMessage title={<span>{errors?.value}</span>} />
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
|
||||||
return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
|
|
||||||
? obj
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TalerBankAddressAccount({
|
|
||||||
field,
|
|
||||||
}: {
|
|
||||||
field: TextFieldHandler;
|
|
||||||
}): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
const [host, setHost] = useState<string | undefined>(undefined);
|
|
||||||
const [account, setAccount] = useState<string | undefined>(undefined);
|
|
||||||
const errors = undefinedIfEmpty({
|
|
||||||
host: !host ? i18n.str`Can't be empty` : undefined,
|
|
||||||
account: !account ? i18n.str`Can't be empty` : undefined,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<TextField
|
|
||||||
label="Bank host"
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
value={host}
|
|
||||||
error={host !== undefined && !!errors?.host}
|
|
||||||
onChange={(v) => {
|
|
||||||
setHost(v);
|
|
||||||
if (!errors) {
|
|
||||||
field.onInput(`payto://x-taler-bank/${host}/${account}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>{" "}
|
|
||||||
{host !== undefined && errors?.host && (
|
|
||||||
<ErrorMessage title={<span>{errors?.host}</span>} />
|
|
||||||
)}
|
|
||||||
<TextField
|
|
||||||
label="Bank account"
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
value={account}
|
|
||||||
error={account !== undefined && !!errors?.account}
|
|
||||||
onChange={(v) => {
|
|
||||||
setAccount(v || "");
|
|
||||||
if (!errors) {
|
|
||||||
field.onInput(`payto://x-taler-bank/${host}/${account}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>{" "}
|
|
||||||
{account !== undefined && errors?.account && (
|
|
||||||
<ErrorMessage title={<span>{errors?.account}</span>} />
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
const [value, setValue] = useState<string | undefined>(undefined);
|
|
||||||
const errors = undefinedIfEmpty({
|
|
||||||
value: !value ? i18n.str`Can't be empty` : undefined,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<TextField
|
|
||||||
label="IBAN number"
|
|
||||||
variant="standard"
|
|
||||||
fullWidth
|
|
||||||
value={value}
|
|
||||||
error={value !== undefined && !!errors?.value}
|
|
||||||
onChange={(v) => {
|
|
||||||
setValue(v);
|
|
||||||
if (!errors) {
|
|
||||||
field.onInput(`payto://iba/${value}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{value !== undefined && errors?.value && (
|
|
||||||
<ErrorMessage title={<span>{errors?.value}</span>} />
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function CustomFieldByAccountType({
|
|
||||||
type,
|
|
||||||
field,
|
|
||||||
}: {
|
|
||||||
type: string;
|
|
||||||
field: TextFieldHandler;
|
|
||||||
}): VNode {
|
|
||||||
if (type === "bitcoin") {
|
|
||||||
return <BitcoinAddressAccount field={field} />;
|
|
||||||
}
|
|
||||||
if (type === "x-taler-bank") {
|
|
||||||
return <TalerBankAddressAccount field={field} />;
|
|
||||||
}
|
|
||||||
if (type === "iban") {
|
|
||||||
return <IbanAddressAccount field={field} />;
|
|
||||||
}
|
|
||||||
return <Fragment />;
|
|
||||||
}
|
|
@ -24,7 +24,7 @@ import {
|
|||||||
} from "../../mui/handlers.js";
|
} from "../../mui/handlers.js";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import { wxApi } from "../../wxApi.js";
|
import { wxApi } from "../../wxApi.js";
|
||||||
import { AddAccountPage } from "../AddAccount/index.js";
|
import { ManageAccountPage } from "../ManageAccount/index.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import {
|
import {
|
||||||
AmountOrCurrencyErrorView,
|
AmountOrCurrencyErrorView,
|
||||||
@ -62,7 +62,7 @@ export namespace State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AddingAccount {
|
export interface AddingAccount {
|
||||||
status: "adding-account";
|
status: "manage-account";
|
||||||
error: undefined;
|
error: undefined;
|
||||||
currency: string;
|
currency: string;
|
||||||
onAccountAdded: (p: string) => void;
|
onAccountAdded: (p: string) => void;
|
||||||
@ -94,7 +94,7 @@ export namespace State {
|
|||||||
error: undefined;
|
error: undefined;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
selectedAccount: PaytoUri | undefined;
|
currentAccount: PaytoUri;
|
||||||
totalFee: AmountJson;
|
totalFee: AmountJson;
|
||||||
totalToDeposit: AmountJson;
|
totalToDeposit: AmountJson;
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ const viewMapping: StateViewMap<State> = {
|
|||||||
"amount-or-currency-error": AmountOrCurrencyErrorView,
|
"amount-or-currency-error": AmountOrCurrencyErrorView,
|
||||||
"no-enough-balance": NoEnoughBalanceView,
|
"no-enough-balance": NoEnoughBalanceView,
|
||||||
"no-accounts": NoAccountToDepositView,
|
"no-accounts": NoAccountToDepositView,
|
||||||
"adding-account": AddAccountPage,
|
"manage-account": ManageAccountPage,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,9 +50,7 @@ export function useComponentState(
|
|||||||
// const [accountIdx, setAccountIdx] = useState<number>(0);
|
// const [accountIdx, setAccountIdx] = useState<number>(0);
|
||||||
const [amount, setAmount] = useState(initialValue);
|
const [amount, setAmount] = useState(initialValue);
|
||||||
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState<
|
const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
|
||||||
PaytoUri | undefined
|
|
||||||
>();
|
|
||||||
|
|
||||||
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
|
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
|
||||||
const [addingAccount, setAddingAccount] = useState(false);
|
const [addingAccount, setAddingAccount] = useState(false);
|
||||||
@ -82,7 +80,7 @@ export function useComponentState(
|
|||||||
|
|
||||||
if (addingAccount) {
|
if (addingAccount) {
|
||||||
return {
|
return {
|
||||||
status: "adding-account",
|
status: "manage-account",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
currency,
|
currency,
|
||||||
onAccountAdded: (p: string) => {
|
onAccountAdded: (p: string) => {
|
||||||
@ -92,6 +90,7 @@ export function useComponentState(
|
|||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
setAddingAccount(false);
|
setAddingAccount(false);
|
||||||
|
hook.retry();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -122,13 +121,12 @@ export function useComponentState(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const firstAccount = accounts[0].uri
|
||||||
|
const currentAccount = !selectedAccount ? firstAccount : selectedAccount;
|
||||||
|
|
||||||
const accountMap = createLabelsForBankAccount(accounts);
|
const accountMap = createLabelsForBankAccount(accounts);
|
||||||
accountMap[""] = "Select one account...";
|
|
||||||
|
|
||||||
async function updateAccountFromList(accountStr: string): Promise<void> {
|
async function updateAccountFromList(accountStr: string): Promise<void> {
|
||||||
// const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr];
|
|
||||||
// if (!newSelected) return;
|
|
||||||
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
|
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
|
||||||
if (uri && parsedAmount) {
|
if (uri && parsedAmount) {
|
||||||
try {
|
try {
|
||||||
@ -136,7 +134,6 @@ export function useComponentState(
|
|||||||
setSelectedAccount(uri);
|
setSelectedAccount(uri);
|
||||||
setFee(result);
|
setFee(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
setSelectedAccount(uri);
|
setSelectedAccount(uri);
|
||||||
setFee(undefined);
|
setFee(undefined);
|
||||||
}
|
}
|
||||||
@ -145,13 +142,12 @@ export function useComponentState(
|
|||||||
|
|
||||||
async function updateAmount(numStr: string): Promise<void> {
|
async function updateAmount(numStr: string): Promise<void> {
|
||||||
const parsed = Amounts.parse(`${currency}:${numStr}`);
|
const parsed = Amounts.parse(`${currency}:${numStr}`);
|
||||||
if (parsed && selectedAccount) {
|
if (parsed) {
|
||||||
try {
|
try {
|
||||||
const result = await getFeeForAmount(selectedAccount, parsed, api);
|
const result = await getFeeForAmount(currentAccount, parsed, api);
|
||||||
setAmount(numStr);
|
setAmount(numStr);
|
||||||
setFee(result);
|
setFee(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
|
||||||
setAmount(numStr);
|
setAmount(numStr);
|
||||||
setFee(undefined);
|
setFee(undefined);
|
||||||
}
|
}
|
||||||
@ -179,15 +175,14 @@ export function useComponentState(
|
|||||||
|
|
||||||
const unableToDeposit =
|
const unableToDeposit =
|
||||||
!parsedAmount || //no amount specified
|
!parsedAmount || //no amount specified
|
||||||
selectedAccount === undefined || //no account selected
|
|
||||||
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 (!selectedAccount || !parsedAmount || !currency) return;
|
if (!parsedAmount || !currency) return;
|
||||||
|
|
||||||
const depositPaytoUri = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`;
|
const depositPaytoUri = `payto://${currentAccount.targetType}/${currentAccount.targetPath}`;
|
||||||
const amount = Amounts.stringify(parsedAmount);
|
const amount = Amounts.stringify(parsedAmount);
|
||||||
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
|
await api.wallet.call(WalletApiOperation.CreateDepositGroup, {
|
||||||
amount, depositPaytoUri
|
amount, depositPaytoUri
|
||||||
@ -211,10 +206,10 @@ export function useComponentState(
|
|||||||
},
|
},
|
||||||
account: {
|
account: {
|
||||||
list: accountMap,
|
list: accountMap,
|
||||||
value: !selectedAccount ? "" : stringifyPaytoUri(selectedAccount),
|
value: stringifyPaytoUri(currentAccount),
|
||||||
onChange: updateAccountFromList,
|
onChange: updateAccountFromList,
|
||||||
},
|
},
|
||||||
selectedAccount,
|
currentAccount,
|
||||||
cancelHandler: {
|
cancelHandler: {
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
onCancel(currency);
|
onCancel(currency);
|
||||||
|
@ -55,6 +55,13 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
|
|||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentAccount: {
|
||||||
|
isKnown: true,
|
||||||
|
targetType: "iban",
|
||||||
|
iban: "ABCD1234",
|
||||||
|
params: {},
|
||||||
|
targetPath: "/ABCD1234",
|
||||||
|
},
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
amount: {
|
amount: {
|
||||||
onInput: async () => {
|
onInput: async () => {
|
||||||
@ -83,6 +90,13 @@ export const WithIBANAccountTypeSelected = createExample(ReadyView, {
|
|||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentAccount: {
|
||||||
|
isKnown: true,
|
||||||
|
targetType: "iban",
|
||||||
|
iban: "ABCD1234",
|
||||||
|
params: {},
|
||||||
|
targetPath: "/ABCD1234",
|
||||||
|
},
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
amount: {
|
amount: {
|
||||||
onInput: async () => {
|
onInput: async () => {
|
||||||
@ -111,6 +125,13 @@ export const NewBitcoinAccountTypeSelected = createExample(ReadyView, {
|
|||||||
null;
|
null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentAccount: {
|
||||||
|
isKnown: true,
|
||||||
|
targetType: "iban",
|
||||||
|
iban: "ABCD1234",
|
||||||
|
params: {},
|
||||||
|
targetPath: "/ABCD1234",
|
||||||
|
},
|
||||||
onAddAccount: {},
|
onAddAccount: {},
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
amount: {
|
amount: {
|
||||||
|
@ -172,7 +172,7 @@ describe("DepositPage states", () => {
|
|||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
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("");
|
expect(r.account.value).eq(stringifyPaytoUri(ibanPayto.uri));
|
||||||
expect(r.amount.value).eq("0");
|
expect(r.amount.value).eq("0");
|
||||||
expect(r.depositHandler.onClick).undefined;
|
expect(r.depositHandler.onClick).undefined;
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ describe("DepositPage states", () => {
|
|||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
|
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
|
||||||
accounts: [ibanPayto]
|
accounts: [talerBankPayto, ibanPayto]
|
||||||
});
|
});
|
||||||
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
|
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
|
||||||
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
|
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withoutFee())
|
||||||
@ -221,7 +221,7 @@ describe("DepositPage states", () => {
|
|||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
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("");
|
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
|
||||||
expect(r.amount.value).eq("0");
|
expect(r.amount.value).eq("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`));
|
||||||
@ -328,7 +328,7 @@ describe("DepositPage states", () => {
|
|||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
|
handler.addWalletCallResponse(WalletApiOperation.ListKnownBankAccounts, undefined, {
|
||||||
accounts: [ibanPayto]
|
accounts: [talerBankPayto, ibanPayto]
|
||||||
});
|
});
|
||||||
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
|
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
|
||||||
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
|
handler.addWalletCallResponse(WalletApiOperation.GetFeeForDeposit, undefined, withSomeFee())
|
||||||
@ -353,7 +353,7 @@ describe("DepositPage states", () => {
|
|||||||
if (r.status !== "ready") expect.fail();
|
if (r.status !== "ready") expect.fail();
|
||||||
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("");
|
expect(r.account.value).eq(stringifyPaytoUri(talerBankPayto.uri));
|
||||||
expect(r.amount.value).eq("0");
|
expect(r.amount.value).eq("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`));
|
||||||
|
@ -160,61 +160,55 @@ export function ReadyView(state: State.Ready): VNode {
|
|||||||
variant="text"
|
variant="text"
|
||||||
style={{ marginLeft: "auto" }}
|
style={{ marginLeft: "auto" }}
|
||||||
>
|
>
|
||||||
<i18n.Translate>Add another account</i18n.Translate>
|
<i18n.Translate>Manage accounts</i18n.Translate>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{state.selectedAccount && (
|
<p>
|
||||||
<Fragment>
|
<AccountDetails account={state.currentAccount} />
|
||||||
<p>
|
</p>
|
||||||
<AccountDetails account={state.selectedAccount} />
|
<InputWithLabel invalid={!!state.amount.error}>
|
||||||
</p>
|
<label>
|
||||||
<InputWithLabel invalid={!!state.amount.error}>
|
<i18n.Translate>Amount</i18n.Translate>
|
||||||
<label>
|
</label>
|
||||||
<i18n.Translate>Amount</i18n.Translate>
|
<div>
|
||||||
</label>
|
<span>{state.currency}</span>
|
||||||
<div>
|
<input
|
||||||
<span>{state.currency}</span>
|
type="number"
|
||||||
<input
|
value={state.amount.value}
|
||||||
type="number"
|
onInput={(e) => state.amount.onInput(e.currentTarget.value)}
|
||||||
value={state.amount.value}
|
/>
|
||||||
onInput={(e) => state.amount.onInput(e.currentTarget.value)}
|
</div>
|
||||||
/>
|
{state.amount.error && <ErrorText>{state.amount.error}</ErrorText>}
|
||||||
</div>
|
</InputWithLabel>
|
||||||
{state.amount.error && (
|
|
||||||
<ErrorText>{state.amount.error}</ErrorText>
|
|
||||||
)}
|
|
||||||
</InputWithLabel>
|
|
||||||
|
|
||||||
<InputWithLabel>
|
<InputWithLabel>
|
||||||
<label>
|
<label>
|
||||||
<i18n.Translate>Deposit fee</i18n.Translate>
|
<i18n.Translate>Deposit fee</i18n.Translate>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<span>{state.currency}</span>
|
<span>{state.currency}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
disabled
|
disabled
|
||||||
value={Amounts.stringifyValue(state.totalFee)}
|
value={Amounts.stringifyValue(state.totalFee)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputWithLabel>
|
</InputWithLabel>
|
||||||
|
|
||||||
<InputWithLabel>
|
<InputWithLabel>
|
||||||
<label>
|
<label>
|
||||||
<i18n.Translate>Total deposit</i18n.Translate>
|
<i18n.Translate>Total deposit</i18n.Translate>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<span>{state.currency}</span>
|
<span>{state.currency}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
disabled
|
disabled
|
||||||
value={Amounts.stringifyValue(state.totalToDeposit)}
|
value={Amounts.stringifyValue(state.totalToDeposit)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</InputWithLabel>
|
</InputWithLabel>
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<Button
|
<Button
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
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 { KnownBankAccountsInfo } 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 {
|
||||||
@ -57,17 +58,23 @@ export namespace State {
|
|||||||
alias: TextFieldHandler;
|
alias: TextFieldHandler;
|
||||||
onAccountAdded: ButtonHandler;
|
onAccountAdded: ButtonHandler;
|
||||||
onCancel: ButtonHandler;
|
onCancel: ButtonHandler;
|
||||||
|
accountByType: AccountByType,
|
||||||
|
deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AccountByType = {
|
||||||
|
[key: string]: KnownBankAccountsInfo[]
|
||||||
|
};
|
||||||
|
|
||||||
const viewMapping: StateViewMap<State> = {
|
const viewMapping: StateViewMap<State> = {
|
||||||
loading: Loading,
|
loading: Loading,
|
||||||
"loading-error": LoadingUriView,
|
"loading-error": LoadingUriView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddAccountPage = compose(
|
export const ManageAccountPage = compose(
|
||||||
"AddAccount",
|
"ManageAccountPage",
|
||||||
(p: Props) => useComponentState(p, wxApi),
|
(p: Props) => useComponentState(p, wxApi),
|
||||||
viewMapping,
|
viewMapping,
|
||||||
);
|
);
|
@ -14,12 +14,12 @@
|
|||||||
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 { parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
import { KnownBankAccountsInfo, parsePaytoUri, stringifyPaytoUri } from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||||
import { wxApi } from "../../wxApi.js";
|
import { wxApi } from "../../wxApi.js";
|
||||||
import { Props, State } from "./index.js";
|
import { AccountByType, Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState(
|
export function useComponentState(
|
||||||
{ currency, onAccountAdded, onCancel }: Props,
|
{ currency, onAccountAdded, onCancel }: Props,
|
||||||
@ -45,10 +45,10 @@ export function useComponentState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accountType: Record<string, string> = {
|
const accountType: Record<string, string> = {
|
||||||
"": "Choose one account",
|
"": "Choose one account type",
|
||||||
iban: "IBAN",
|
iban: "IBAN",
|
||||||
bitcoin: "Bitcoin",
|
// bitcoin: "Bitcoin",
|
||||||
"x-taler-bank": "Taler Bank",
|
// "x-taler-bank": "Taler Bank",
|
||||||
};
|
};
|
||||||
const uri = parsePaytoUri(payto);
|
const uri = parsePaytoUri(payto);
|
||||||
const found =
|
const found =
|
||||||
@ -73,6 +73,24 @@ export function useComponentState(
|
|||||||
|
|
||||||
const unableToAdd = !type || !alias || !!paytoUriError || !uri;
|
const unableToAdd = !type || !alias || !!paytoUriError || !uri;
|
||||||
|
|
||||||
|
const accountByType: AccountByType = {
|
||||||
|
iban: [],
|
||||||
|
bitcoin: [],
|
||||||
|
"x-taler-bank": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
hook.response.accounts.forEach(acc => {
|
||||||
|
accountByType[acc.uri.targetType].push(acc)
|
||||||
|
});
|
||||||
|
|
||||||
|
async function deleteAccount(account: KnownBankAccountsInfo): Promise<void> {
|
||||||
|
const payto = stringifyPaytoUri(account.uri);
|
||||||
|
await api.wallet.call(WalletApiOperation.ForgetKnownBankAccounts, {
|
||||||
|
payto
|
||||||
|
})
|
||||||
|
hook?.retry()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
@ -97,6 +115,8 @@ export function useComponentState(
|
|||||||
setPayto(v);
|
setPayto(v);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
accountByType,
|
||||||
|
deleteAccount,
|
||||||
onAccountAdded: {
|
onAccountAdded: {
|
||||||
onClick: unableToAdd ? undefined : addAccount,
|
onClick: unableToAdd ? undefined : addAccount,
|
||||||
},
|
},
|
@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
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 { ReadyView } from "./views.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "wallet/manage account",
|
||||||
|
};
|
||||||
|
|
||||||
|
const nullFunction = async () => {
|
||||||
|
null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const JustTwoBitcoinAccounts = createExample(ReadyView, {
|
||||||
|
status: "ready",
|
||||||
|
currency: "ARS",
|
||||||
|
accountType: {
|
||||||
|
list: {
|
||||||
|
"": "Choose one account type",
|
||||||
|
iban: "IBAN",
|
||||||
|
// bitcoin: "Bitcoin",
|
||||||
|
// "x-taler-bank": "Taler Bank",
|
||||||
|
},
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
accountByType: {
|
||||||
|
iban: [],
|
||||||
|
"x-taler-bank": [],
|
||||||
|
bitcoin: [
|
||||||
|
{
|
||||||
|
alias: "my bitcoin addr",
|
||||||
|
currency: "BTC",
|
||||||
|
kyc_completed: false,
|
||||||
|
uri: {
|
||||||
|
targetType: "bitcoin",
|
||||||
|
segwitAddrs: [],
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alias: "my other addr",
|
||||||
|
currency: "BTC",
|
||||||
|
kyc_completed: true,
|
||||||
|
uri: {
|
||||||
|
targetType: "bitcoin",
|
||||||
|
segwitAddrs: [],
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
onAccountAdded: {},
|
||||||
|
onCancel: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WithAllTypeOfAccounts = createExample(ReadyView, {
|
||||||
|
status: "ready",
|
||||||
|
currency: "ARS",
|
||||||
|
accountType: {
|
||||||
|
list: {
|
||||||
|
"": "Choose one account type",
|
||||||
|
iban: "IBAN",
|
||||||
|
// bitcoin: "Bitcoin",
|
||||||
|
// "x-taler-bank": "Taler Bank",
|
||||||
|
},
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
accountByType: {
|
||||||
|
iban: [
|
||||||
|
{
|
||||||
|
alias: "my bank",
|
||||||
|
currency: "ARS",
|
||||||
|
kyc_completed: true,
|
||||||
|
uri: {
|
||||||
|
targetType: "iban",
|
||||||
|
iban: "ASDQWEQWE",
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "/ASDQWEQWE",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"x-taler-bank": [
|
||||||
|
{
|
||||||
|
alias: "my xtaler bank",
|
||||||
|
currency: "ARS",
|
||||||
|
kyc_completed: true,
|
||||||
|
uri: {
|
||||||
|
targetType: "x-taler-bank",
|
||||||
|
host: "localhost",
|
||||||
|
account: "123",
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "localhost/123",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bitcoin: [
|
||||||
|
{
|
||||||
|
alias: "my bitcoin addr",
|
||||||
|
currency: "BTC",
|
||||||
|
kyc_completed: false,
|
||||||
|
uri: {
|
||||||
|
targetType: "bitcoin",
|
||||||
|
segwitAddrs: [],
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alias: "my other addr",
|
||||||
|
currency: "BTC",
|
||||||
|
kyc_completed: true,
|
||||||
|
uri: {
|
||||||
|
targetType: "bitcoin",
|
||||||
|
segwitAddrs: [],
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
onAccountAdded: {},
|
||||||
|
onCancel: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AddingIbanAccount = createExample(ReadyView, {
|
||||||
|
status: "ready",
|
||||||
|
currency: "ARS",
|
||||||
|
accountType: {
|
||||||
|
list: {
|
||||||
|
"": "Choose one account type",
|
||||||
|
iban: "IBAN",
|
||||||
|
// bitcoin: "Bitcoin",
|
||||||
|
// "x-taler-bank": "Taler Bank",
|
||||||
|
},
|
||||||
|
value: "iban",
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
uri: {
|
||||||
|
value: "",
|
||||||
|
onInput: nullFunction,
|
||||||
|
},
|
||||||
|
accountByType: {
|
||||||
|
iban: [
|
||||||
|
{
|
||||||
|
alias: "my bank",
|
||||||
|
currency: "ARS",
|
||||||
|
kyc_completed: true,
|
||||||
|
uri: {
|
||||||
|
targetType: "iban",
|
||||||
|
iban: "ASDQWEQWE",
|
||||||
|
isKnown: true,
|
||||||
|
targetPath: "/ASDQWEQWE",
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"x-taler-bank": [],
|
||||||
|
bitcoin: [],
|
||||||
|
},
|
||||||
|
onAccountAdded: {},
|
||||||
|
onCancel: {},
|
||||||
|
});
|
@ -0,0 +1,534 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
KnownBankAccountsInfo,
|
||||||
|
PaytoUriBitcoin,
|
||||||
|
PaytoUriIBAN,
|
||||||
|
PaytoUriTalerBank,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { styled } from "@linaria/react";
|
||||||
|
import { Fragment, h, VNode } from "preact";
|
||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { ErrorMessage } from "../../components/ErrorMessage.js";
|
||||||
|
import { LoadingError } from "../../components/LoadingError.js";
|
||||||
|
import { SelectList } from "../../components/SelectList.js";
|
||||||
|
import {
|
||||||
|
Input,
|
||||||
|
LightText,
|
||||||
|
SubTitle,
|
||||||
|
SvgIcon,
|
||||||
|
WarningText,
|
||||||
|
} from "../../components/styled/index.js";
|
||||||
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
|
import { Button } from "../../mui/Button.js";
|
||||||
|
import { TextFieldHandler } from "../../mui/handlers.js";
|
||||||
|
import { TextField } from "../../mui/TextField.js";
|
||||||
|
import checkIcon from "../../svg/check_24px.svg";
|
||||||
|
import warningIcon from "../../svg/warning_24px.svg";
|
||||||
|
import deleteIcon from "../../svg/delete_24px.svg";
|
||||||
|
import { State } from "./index.js";
|
||||||
|
|
||||||
|
type AccountType = "bitcoin" | "x-taler-bank" | "iban";
|
||||||
|
type ComponentFormByAccountType = {
|
||||||
|
[type in AccountType]: (props: { field: TextFieldHandler }) => VNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ComponentListByAccountType = {
|
||||||
|
[type in AccountType]: (props: {
|
||||||
|
list: KnownBankAccountsInfo[];
|
||||||
|
onDelete: (a: KnownBankAccountsInfo) => Promise<void>;
|
||||||
|
}) => VNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formComponentByAccountType: ComponentFormByAccountType = {
|
||||||
|
iban: IbanAddressAccount,
|
||||||
|
bitcoin: BitcoinAddressAccount,
|
||||||
|
"x-taler-bank": TalerBankAddressAccount,
|
||||||
|
};
|
||||||
|
const tableComponentByAccountType: ComponentListByAccountType = {
|
||||||
|
iban: IbanTable,
|
||||||
|
bitcoin: BitcoinTable,
|
||||||
|
"x-taler-bank": TalerBankTable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccountTable = styled.table`
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0px 10px;
|
||||||
|
tbody tr:nth-child(odd) > td:not(.actions, .kyc) {
|
||||||
|
background-color: lightgrey;
|
||||||
|
}
|
||||||
|
.actions,
|
||||||
|
.kyc {
|
||||||
|
width: 10px;
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoadingError
|
||||||
|
title={<i18n.Translate>Could not load</i18n.Translate>}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ReadyView({
|
||||||
|
currency,
|
||||||
|
error,
|
||||||
|
accountType,
|
||||||
|
accountByType,
|
||||||
|
alias,
|
||||||
|
onAccountAdded,
|
||||||
|
deleteAccount,
|
||||||
|
onCancel,
|
||||||
|
uri,
|
||||||
|
}: State.Ready): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<section>
|
||||||
|
<SubTitle>
|
||||||
|
<i18n.Translate>Known accounts for {currency}</i18n.Translate>
|
||||||
|
</SubTitle>
|
||||||
|
<p>
|
||||||
|
<i18n.Translate>
|
||||||
|
To add a new account first select the account type.
|
||||||
|
</i18n.Translate>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<ErrorMessage
|
||||||
|
title={<i18n.Translate>Unable add this account</i18n.Translate>}
|
||||||
|
description={error}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
<Input>
|
||||||
|
<SelectList
|
||||||
|
label={<i18n.Translate>Select account type</i18n.Translate>}
|
||||||
|
list={accountType.list}
|
||||||
|
name="accountType"
|
||||||
|
value={accountType.value}
|
||||||
|
onChange={accountType.onChange}
|
||||||
|
/>
|
||||||
|
</Input>
|
||||||
|
</p>
|
||||||
|
{accountType.value === "" ? undefined : (
|
||||||
|
<Fragment>
|
||||||
|
<p>
|
||||||
|
<CustomFieldByAccountType
|
||||||
|
type={accountType.value as AccountType}
|
||||||
|
field={uri}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<TextField
|
||||||
|
label="Account alias"
|
||||||
|
variant="standard"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
disabled={accountType.value === ""}
|
||||||
|
value={alias.value}
|
||||||
|
onChange={alias.onInput}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
onClick={onCancel.onClick}
|
||||||
|
>
|
||||||
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={onAccountAdded.onClick}
|
||||||
|
disabled={!onAccountAdded.onClick}
|
||||||
|
>
|
||||||
|
<i18n.Translate>Add</i18n.Translate>
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
{Object.entries(accountByType).map(([type, list]) => {
|
||||||
|
const Table = tableComponentByAccountType[type as AccountType];
|
||||||
|
return <Table key={type} list={list} onDelete={deleteAccount} />;
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function IbanTable({
|
||||||
|
list,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
list: KnownBankAccountsInfo[];
|
||||||
|
onDelete: (ac: KnownBankAccountsInfo) => void;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
if (list.length === 0) return <Fragment />;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
<i18n.Translate>IBAN accounts</i18n.Translate>
|
||||||
|
</h1>
|
||||||
|
<AccountTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Alias</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Int. Account Number</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="kyc">
|
||||||
|
<i18n.Translate>KYC</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="actions"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{list.map((account) => {
|
||||||
|
const p = account.uri as PaytoUriIBAN;
|
||||||
|
return (
|
||||||
|
<tr key={account.alias}>
|
||||||
|
<td>{account.alias}</td>
|
||||||
|
<td>{p.targetPath}</td>
|
||||||
|
<td class="kyc">
|
||||||
|
{account.kyc_completed ? (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC done`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: checkIcon }}
|
||||||
|
color="green"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC missing`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: warningIcon }}
|
||||||
|
color="orange"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={deleteIcon}
|
||||||
|
size="small"
|
||||||
|
onClick={async () => onDelete(account)}
|
||||||
|
color="error"
|
||||||
|
>
|
||||||
|
Forget
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</AccountTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TalerBankTable({
|
||||||
|
list,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
list: KnownBankAccountsInfo[];
|
||||||
|
onDelete: (ac: KnownBankAccountsInfo) => void;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
if (list.length === 0) return <Fragment />;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
<i18n.Translate>Taler accounts</i18n.Translate>
|
||||||
|
</h1>
|
||||||
|
<AccountTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Alias</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Host</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Account</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="kyc">
|
||||||
|
<i18n.Translate>KYC</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="actions"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{list.map((account) => {
|
||||||
|
const p = account.uri as PaytoUriTalerBank;
|
||||||
|
return (
|
||||||
|
<tr key={account.alias}>
|
||||||
|
<td>{account.alias}</td>
|
||||||
|
<td>{p.host}</td>
|
||||||
|
<td>{p.account}</td>
|
||||||
|
<td class="kyc">
|
||||||
|
{account.kyc_completed ? (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC done`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: checkIcon }}
|
||||||
|
color="green"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC missing`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: warningIcon }}
|
||||||
|
color="orange"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={deleteIcon}
|
||||||
|
size="small"
|
||||||
|
onClick={async () => onDelete(account)}
|
||||||
|
color="error"
|
||||||
|
>
|
||||||
|
Forget
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</AccountTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BitcoinTable({
|
||||||
|
list,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
list: KnownBankAccountsInfo[];
|
||||||
|
onDelete: (ac: KnownBankAccountsInfo) => void;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
if (list.length === 0) return <Fragment />;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>
|
||||||
|
<i18n.Translate>Bitcoin accounts</i18n.Translate>
|
||||||
|
</h2>
|
||||||
|
<AccountTable>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Alias</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<i18n.Translate>Address</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="kyc">
|
||||||
|
<i18n.Translate>KYC</i18n.Translate>
|
||||||
|
</th>
|
||||||
|
<th class="actions"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{list.map((account) => {
|
||||||
|
const p = account.uri as PaytoUriBitcoin;
|
||||||
|
return (
|
||||||
|
<tr key={account.alias}>
|
||||||
|
<td>{account.alias}</td>
|
||||||
|
<td>{p.targetPath}</td>
|
||||||
|
<td class="kyc">
|
||||||
|
{account.kyc_completed ? (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC done`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: checkIcon }}
|
||||||
|
color="green"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SvgIcon
|
||||||
|
title={i18n.str`KYC missing`}
|
||||||
|
dangerouslySetInnerHTML={{ __html: warningIcon }}
|
||||||
|
color="orange"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={deleteIcon}
|
||||||
|
size="small"
|
||||||
|
onClick={async () => onDelete(account)}
|
||||||
|
color="error"
|
||||||
|
>
|
||||||
|
Forget
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</AccountTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
const [value, setValue] = useState<string | undefined>(undefined);
|
||||||
|
const errors = undefinedIfEmpty({
|
||||||
|
value: !value ? i18n.str`Can't be empty` : undefined,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<TextField
|
||||||
|
label="Bitcoin address"
|
||||||
|
variant="standard"
|
||||||
|
fullWidth
|
||||||
|
value={value}
|
||||||
|
error={value !== undefined && !!errors?.value}
|
||||||
|
onChange={(v) => {
|
||||||
|
setValue(v);
|
||||||
|
if (!errors) {
|
||||||
|
field.onInput(`payto://bitcoin/${v}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{value !== undefined && errors?.value && (
|
||||||
|
<ErrorMessage title={<span>{errors?.value}</span>} />
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
|
||||||
|
return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
|
||||||
|
? obj
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TalerBankAddressAccount({
|
||||||
|
field,
|
||||||
|
}: {
|
||||||
|
field: TextFieldHandler;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
const [host, setHost] = useState<string | undefined>(undefined);
|
||||||
|
const [account, setAccount] = useState<string | undefined>(undefined);
|
||||||
|
const errors = undefinedIfEmpty({
|
||||||
|
host: !host ? i18n.str`Can't be empty` : undefined,
|
||||||
|
account: !account ? i18n.str`Can't be empty` : undefined,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<TextField
|
||||||
|
label="Bank host"
|
||||||
|
variant="standard"
|
||||||
|
fullWidth
|
||||||
|
value={host}
|
||||||
|
error={host !== undefined && !!errors?.host}
|
||||||
|
onChange={(v) => {
|
||||||
|
setHost(v);
|
||||||
|
if (!errors) {
|
||||||
|
field.onInput(`payto://x-taler-bank/${v}/${account}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>{" "}
|
||||||
|
{host !== undefined && errors?.host && (
|
||||||
|
<ErrorMessage title={<span>{errors?.host}</span>} />
|
||||||
|
)}
|
||||||
|
<TextField
|
||||||
|
label="Bank account"
|
||||||
|
variant="standard"
|
||||||
|
fullWidth
|
||||||
|
value={account}
|
||||||
|
error={account !== undefined && !!errors?.account}
|
||||||
|
onChange={(v) => {
|
||||||
|
setAccount(v || "");
|
||||||
|
if (!errors) {
|
||||||
|
field.onInput(`payto://x-taler-bank/${host}/${v}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>{" "}
|
||||||
|
{account !== undefined && errors?.account && (
|
||||||
|
<ErrorMessage title={<span>{errors?.account}</span>} />
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
const [value, setValue] = useState<string | undefined>(undefined);
|
||||||
|
const errors = undefinedIfEmpty({
|
||||||
|
value: !value ? i18n.str`Can't be empty` : undefined,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<TextField
|
||||||
|
label="IBAN number"
|
||||||
|
variant="standard"
|
||||||
|
fullWidth
|
||||||
|
value={value}
|
||||||
|
error={value !== undefined && !!errors?.value}
|
||||||
|
onChange={(v) => {
|
||||||
|
setValue(v);
|
||||||
|
if (!errors) {
|
||||||
|
field.onInput(`payto://iban/${v}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{value !== undefined && errors?.value && (
|
||||||
|
<ErrorMessage title={<span>{errors?.value}</span>} />
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomFieldByAccountType({
|
||||||
|
type,
|
||||||
|
field,
|
||||||
|
}: {
|
||||||
|
type: AccountType;
|
||||||
|
field: TextFieldHandler;
|
||||||
|
}): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
|
const AccountForm = formComponentByAccountType[type];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<WarningText>
|
||||||
|
<i18n.Translate>
|
||||||
|
We can not validate the account so make sure the value is correct.
|
||||||
|
</i18n.Translate>
|
||||||
|
</WarningText>
|
||||||
|
<AccountForm field={field} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -37,6 +37,7 @@ import * as a16 from "./DeveloperPage.stories.js";
|
|||||||
import * as a17 from "./QrReader.stories.js";
|
import * as a17 from "./QrReader.stories.js";
|
||||||
import * as a18 from "./DestinationSelection.stories.js";
|
import * as a18 from "./DestinationSelection.stories.js";
|
||||||
import * as a19 from "./ExchangeSelection/stories.js";
|
import * as a19 from "./ExchangeSelection/stories.js";
|
||||||
|
import * as a20 from "./ManageAccount/stories.js";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
a1,
|
a1,
|
||||||
@ -57,4 +58,5 @@ export default [
|
|||||||
a17,
|
a17,
|
||||||
a18,
|
a18,
|
||||||
a19,
|
a19,
|
||||||
|
a20,
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user