add senderWire to the withdrawal group again, group payto to avoid duplication
This commit is contained in:
parent
a5f052d69c
commit
6610a0b9d7
@ -573,7 +573,7 @@ export interface ExchangesListRespose {
|
||||
}
|
||||
|
||||
export interface KnownBankAccounts {
|
||||
accounts: PaytoUri[];
|
||||
accounts: { [payto: string]: PaytoUri };
|
||||
}
|
||||
|
||||
export interface ExchangeTos {
|
||||
|
@ -242,7 +242,7 @@ export function selectWithdrawalDenominations(
|
||||
for (const d of denoms) {
|
||||
let count = 0;
|
||||
const cost = Amounts.add(d.value, d.feeWithdraw).amount;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
if (Amounts.cmp(remaining, cost) < 0) {
|
||||
break;
|
||||
}
|
||||
@ -903,8 +903,7 @@ export async function updateWithdrawalDenoms(
|
||||
denom.verificationStatus === DenominationVerificationStatus.Unverified
|
||||
) {
|
||||
logger.trace(
|
||||
`Validating denomination (${current + 1}/${
|
||||
denominations.length
|
||||
`Validating denomination (${current + 1}/${denominations.length
|
||||
}) signature of ${denom.denomPubHash}`,
|
||||
);
|
||||
let valid = false;
|
||||
@ -1031,7 +1030,7 @@ async function queryReserve(
|
||||
if (
|
||||
resp.status === 404 &&
|
||||
result.talerErrorResponse.code ===
|
||||
TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
|
||||
TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
|
||||
) {
|
||||
ws.notify({
|
||||
type: NotificationType.ReserveNotYetFound,
|
||||
@ -1337,7 +1336,7 @@ export async function getExchangeWithdrawalInfo(
|
||||
) {
|
||||
logger.warn(
|
||||
`wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
|
||||
`(exchange has ${exchangeDetails.protocolVersion}), checking for updates`,
|
||||
`(exchange has ${exchangeDetails.protocolVersion}), checking for updates`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1714,6 +1713,7 @@ async function processReserveBankStatus(
|
||||
} else {
|
||||
logger.info("withdrawal: transfer not yet confirmed by bank");
|
||||
r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url;
|
||||
r.senderWire = status.sender_wire;
|
||||
r.retryInfo = RetryInfo.increment(r.retryInfo);
|
||||
}
|
||||
await tx.withdrawalGroups.put(r);
|
||||
|
@ -536,7 +536,7 @@ async function listKnownBankAccounts(
|
||||
ws: InternalWalletState,
|
||||
currency?: string,
|
||||
): Promise<KnownBankAccounts> {
|
||||
const accounts: PaytoUri[] = [];
|
||||
const accounts: { [account: string]: PaytoUri } = {};
|
||||
await ws.db
|
||||
.mktx((x) => ({
|
||||
withdrawalGroups: x.withdrawalGroups,
|
||||
@ -548,9 +548,11 @@ async function listKnownBankAccounts(
|
||||
if (currency && currency !== amount.currency) {
|
||||
continue;
|
||||
}
|
||||
const payto = r.senderWire ? parsePaytoUri(r.senderWire) : undefined;
|
||||
if (payto) {
|
||||
accounts.push(payto);
|
||||
if (r.senderWire) {
|
||||
const payto = parsePaytoUri(r.senderWire);
|
||||
if (payto) {
|
||||
accounts[r.senderWire] = payto;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -85,8 +85,8 @@ export const Pages = {
|
||||
balanceHistory: pageDefinition<{ currency?: string }>(
|
||||
"/balance/history/:currency?",
|
||||
),
|
||||
balanceDeposit: pageDefinition<{ currency: string }>(
|
||||
"/balance/deposit/:currency",
|
||||
balanceDeposit: pageDefinition<{ amount: string }>(
|
||||
"/balance/deposit/:amount",
|
||||
),
|
||||
balanceTransaction: pageDefinition<{ tid: string }>(
|
||||
"/balance/transaction/:tid",
|
||||
@ -108,7 +108,8 @@ export const Pages = {
|
||||
"/settings/exchange/add/:currency?",
|
||||
),
|
||||
|
||||
invoice: pageDefinition<{ amount?: string }>("/receive/invoice/:amount?"),
|
||||
invoice: pageDefinition<{ amount?: string }>("/invoice/:amount?"),
|
||||
send: pageDefinition<{ amount?: string }>("/send/:amount?"),
|
||||
|
||||
cta: pageDefinition<{ action: string }>("/cta/:action"),
|
||||
ctaPay: "/cta/pay",
|
||||
|
@ -78,7 +78,7 @@ export function Application(): VNode {
|
||||
redirectTo(Pages.receiveCash({}))
|
||||
}
|
||||
goToWalletDeposit={(currency: string) =>
|
||||
redirectTo(Pages.balanceDeposit({ currency }))
|
||||
redirectTo(Pages.sendCash({ amount: `${currency}:0` }))
|
||||
}
|
||||
goToWalletHistory={(currency: string) =>
|
||||
redirectTo(Pages.balanceHistory({ currency }))
|
||||
@ -137,6 +137,10 @@ export function Application(): VNode {
|
||||
path={Pages.receiveCash.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.sendCash.pattern}
|
||||
component={RedirectToWalletPage}
|
||||
/>
|
||||
<Route path={Pages.qr} component={RedirectToWalletPage} />
|
||||
<Route path={Pages.settings} component={RedirectToWalletPage} />
|
||||
<Route
|
||||
|
@ -48,7 +48,6 @@ import { BackupPage } from "./BackupPage.js";
|
||||
import { DepositPage } from "./DepositPage.js";
|
||||
import { ExchangeAddPage } from "./ExchangeAddPage.js";
|
||||
import { HistoryPage } from "./History.js";
|
||||
import { ManualWithdrawPage } from "./ManualWithdrawPage.js";
|
||||
import { ProviderAddPage } from "./ProviderAddPage.js";
|
||||
import { ProviderDetailPage } from "./ProviderDetailPage.js";
|
||||
import { SettingsPage } from "./Settings.js";
|
||||
@ -60,8 +59,9 @@ import {
|
||||
DestinationSelectionGetCash,
|
||||
DestinationSelectionSendCash,
|
||||
} from "./DestinationSelection.js";
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { ExchangeSelectionPage } from "./ExchangeSelection/index.js";
|
||||
import { InvoicePage } from "./Invoice/index.js";
|
||||
import { SendPage } from "./Send/index.js";
|
||||
|
||||
export function Application(): VNode {
|
||||
const [globalNotification, setGlobalNotification] = useState<
|
||||
@ -149,6 +149,12 @@ export function Application(): VNode {
|
||||
<Route
|
||||
path={Pages.sendCash.pattern}
|
||||
component={DestinationSelectionSendCash}
|
||||
goToWalletBankDeposit={(amount: string) =>
|
||||
redirectTo(Pages.balanceDeposit({ amount }))
|
||||
}
|
||||
goToWalletWalletSend={(amount: string) =>
|
||||
redirectTo(Pages.send({ amount }))
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={Pages.receiveCash.pattern}
|
||||
@ -157,9 +163,12 @@ export function Application(): VNode {
|
||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||
}
|
||||
goToWalletWalletInvoice={(amount?: string) =>
|
||||
redirectTo(Pages.ctaWithdrawManual({ amount }))
|
||||
redirectTo(Pages.invoice({ amount }))
|
||||
}
|
||||
/>
|
||||
<Route path={Pages.invoice.pattern} component={InvoicePage} />
|
||||
<Route path={Pages.send.pattern} component={SendPage} />
|
||||
|
||||
<Route
|
||||
path={Pages.balanceTransaction.pattern}
|
||||
component={TransactionPage}
|
||||
|
@ -65,7 +65,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:0` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: {} }),
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
|
||||
@ -92,7 +92,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:1` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: {} }),
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
|
||||
@ -111,10 +111,10 @@ describe("DepositPage states", () => {
|
||||
await assertNoPendingUpdate();
|
||||
});
|
||||
|
||||
const ibanPayto = parsePaytoUri("payto://iban/ES8877998399652238")!;
|
||||
const talerBankPayto = parsePaytoUri(
|
||||
"payto://x-taler-bank/ES8877998399652238",
|
||||
)!;
|
||||
const ibanPayto_str = "payto://iban/ES8877998399652238"
|
||||
const ibanPayto = { ibanPayto_str: parsePaytoUri(ibanPayto_str)! };
|
||||
const talerBankPayto_str = "payto://x-taler-bank/ES8877998399652238"
|
||||
const talerBankPayto = { talerBankPayto_str: parsePaytoUri(talerBankPayto_str)! };
|
||||
|
||||
it("should have status 'ready' but unable to deposit ", async () => {
|
||||
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||
@ -124,7 +124,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:1` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
|
||||
@ -156,7 +156,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:1` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||
getFeeForDeposit: withoutFee,
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
@ -205,7 +205,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:1` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||
getFeeForDeposit: withSomeFee,
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
@ -256,7 +256,7 @@ describe("DepositPage states", () => {
|
||||
balances: [{ available: `${currency}:1` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({
|
||||
accounts: [ibanPayto, talerBankPayto],
|
||||
accounts: { ...ibanPayto, ...talerBankPayto },
|
||||
}),
|
||||
getFeeForDeposit: freeJustForIBAN,
|
||||
} as Partial<typeof wxApi> as any),
|
||||
@ -341,7 +341,7 @@ describe("DepositPage states", () => {
|
||||
({
|
||||
balances: [{ available: `${currency}:15` }],
|
||||
} as Partial<BalancesResponse>),
|
||||
listKnownBankAccounts: async () => ({ accounts: [ibanPayto] }),
|
||||
listKnownBankAccounts: async () => ({ accounts: ibanPayto }),
|
||||
getFeeForDeposit: withSomeFee,
|
||||
} as Partial<typeof wxApi> as any),
|
||||
);
|
||||
|
@ -40,12 +40,12 @@ import {
|
||||
import * as wxApi from "../wxApi.js";
|
||||
|
||||
interface Props {
|
||||
currency: string;
|
||||
amount: string;
|
||||
onCancel: (currency: string) => void;
|
||||
onSuccess: (currency: string) => void;
|
||||
}
|
||||
export function DepositPage({ currency, onCancel, onSuccess }: Props): VNode {
|
||||
const state = useComponentState(currency, onCancel, onSuccess, wxApi);
|
||||
export function DepositPage({ amount, onCancel, onSuccess }: Props): VNode {
|
||||
const state = useComponentState(amount, onCancel, onSuccess, wxApi);
|
||||
|
||||
return <View state={state} />;
|
||||
}
|
||||
@ -92,21 +92,27 @@ async function getFeeForAmount(
|
||||
}
|
||||
|
||||
export function useComponentState(
|
||||
currency: string,
|
||||
amountOrCurrency: string,
|
||||
onCancel: (currency: string) => void,
|
||||
onSuccess: (currency: string) => void,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const parsed = Amounts.parse(amountOrCurrency);
|
||||
const currency = parsed !== undefined ? parsed.currency : amountOrCurrency;
|
||||
|
||||
const hook = useAsyncAsHook(async () => {
|
||||
const { balances } = await api.getBalance();
|
||||
const { accounts } = await api.listKnownBankAccounts(currency);
|
||||
const { accounts: accountMap } = await api.listKnownBankAccounts(currency);
|
||||
const accounts = Object.values(accountMap);
|
||||
const defaultSelectedAccount =
|
||||
accounts.length > 0 ? accounts[0] : undefined;
|
||||
return { accounts, balances, defaultSelectedAccount };
|
||||
});
|
||||
|
||||
const initialValue =
|
||||
parsed !== undefined ? Amounts.stringifyValue(parsed) : "0";
|
||||
const [accountIdx, setAccountIdx] = useState(0);
|
||||
const [amount, setAmount] = useState<number>(0);
|
||||
const [amount, setAmount] = useState(initialValue);
|
||||
|
||||
const [selectedAccount, setSelectedAccount] = useState<
|
||||
PaytoUri | undefined
|
||||
@ -167,15 +173,15 @@ export function useComponentState(
|
||||
}
|
||||
|
||||
async function updateAmount(numStr: string): Promise<void> {
|
||||
const num = parseFloat(numStr);
|
||||
const newAmount = Number.isNaN(num) ? 0 : num;
|
||||
if (amount === newAmount || !currentAccount) return;
|
||||
const parsed = Amounts.parse(`${currency}:${newAmount}`);
|
||||
// const num = parseFloat(numStr);
|
||||
// const newAmount = Number.isNaN(num) ? 0 : num;
|
||||
if (amount === numStr || !currentAccount) return;
|
||||
const parsed = Amounts.parse(`${currency}:${numStr}`);
|
||||
if (!parsed) {
|
||||
setAmount(newAmount);
|
||||
setAmount(numStr);
|
||||
} else {
|
||||
const result = await getFeeForAmount(currentAccount, parsed, api);
|
||||
setAmount(newAmount);
|
||||
setAmount(numStr);
|
||||
setFee(result);
|
||||
}
|
||||
}
|
||||
@ -189,7 +195,7 @@ export function useComponentState(
|
||||
? Amounts.sub(parsedAmount, totalFee).amount
|
||||
: Amounts.getZero(currency);
|
||||
|
||||
const isDirty = amount !== 0;
|
||||
const isDirty = amount !== initialValue;
|
||||
const amountError = !isDirty
|
||||
? undefined
|
||||
: !parsedAmount
|
||||
|
@ -46,12 +46,16 @@ const Container = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
action: "send" | "get";
|
||||
interface PropsGet {
|
||||
amount?: string;
|
||||
goToWalletManualWithdraw: (amount: string) => void;
|
||||
goToWalletWalletInvoice: (amount: string) => void;
|
||||
}
|
||||
interface PropsSend {
|
||||
amount?: string;
|
||||
goToWalletBankDeposit: (amount: string) => void;
|
||||
goToWalletWalletSend: (amount: string) => void;
|
||||
}
|
||||
|
||||
type Contact = {
|
||||
icon: string;
|
||||
@ -262,7 +266,7 @@ export function DestinationSelectionGetCash({
|
||||
amount: initialAmount,
|
||||
goToWalletManualWithdraw,
|
||||
goToWalletWalletInvoice,
|
||||
}: Props): VNode {
|
||||
}: PropsGet): VNode {
|
||||
const parsedInitialAmount = !initialAmount
|
||||
? undefined
|
||||
: Amounts.parse(initialAmount);
|
||||
@ -390,7 +394,9 @@ export function DestinationSelectionGetCash({
|
||||
|
||||
export function DestinationSelectionSendCash({
|
||||
amount: initialAmount,
|
||||
}: Props): VNode {
|
||||
goToWalletBankDeposit,
|
||||
goToWalletWalletSend,
|
||||
}: PropsSend): VNode {
|
||||
const parsedInitialAmount = !initialAmount
|
||||
? undefined
|
||||
: Amounts.parse(initialAmount);
|
||||
@ -482,13 +488,23 @@ export function DestinationSelectionSendCash({
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>To my bank account</p>
|
||||
<Button disabled={invalid}>Deposit</Button>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () => goToWalletBankDeposit(currencyAndAmount)}
|
||||
>
|
||||
Deposit
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Paper style={{ padding: 8 }}>
|
||||
<p>To another wallet</p>
|
||||
<Button disabled>Send</Button>
|
||||
<Button
|
||||
disabled={invalid}
|
||||
onClick={async () => goToWalletWalletSend(currencyAndAmount)}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -20,6 +20,8 @@ import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson } from "@gnu-taler/taler-util";
|
||||
import { TextFieldHandler } from "../../mui/handlers.js";
|
||||
|
||||
export interface Props {
|
||||
p: string;
|
||||
@ -47,6 +49,8 @@ export namespace State {
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
amount: AmountJson;
|
||||
subject: TextFieldHandler;
|
||||
error: undefined;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { useState } from "preact/hooks";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
@ -21,8 +23,15 @@ export function useComponentState(
|
||||
{ p }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [subject, setSubject] = useState("");
|
||||
const amount = Amounts.parseOrThrow("ARS:0")
|
||||
return {
|
||||
status: "ready",
|
||||
subject: {
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
amount,
|
||||
error: undefined,
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,13 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { Amounts } from "@gnu-taler/taler-util";
|
||||
import { styled } from "@linaria/react";
|
||||
import { h, VNode } from "preact";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.js";
|
||||
import { TextField } from "../../mui/TextField.js";
|
||||
import { State } from "./index.js";
|
||||
|
||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
@ -30,8 +34,23 @@ export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadyView({ error }: State.Ready): VNode {
|
||||
const Container = styled.div``;
|
||||
|
||||
export function ReadyView({ amount, subject }: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
return <div />;
|
||||
return (
|
||||
<Container>
|
||||
<p>Creating an invoice of {Amounts.stringify(amount)}</p>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
required
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
<p>to:</p>
|
||||
<Button>Scan QR code</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
67
packages/taler-wallet-webextension/src/wallet/Send/index.ts
Normal file
67
packages/taler-wallet-webextension/src/wallet/Send/index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 { Loading } from "../../components/Loading.js";
|
||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
||||
import { compose, StateViewMap } from "../../utils/index.js";
|
||||
import { LoadingUriView, ReadyView } from "./views.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { useComponentState } from "./state.js";
|
||||
import { AmountJson } from "@gnu-taler/taler-util";
|
||||
import { SelectFieldHandler, TextFieldHandler } from "../../mui/handlers.js";
|
||||
|
||||
export interface Props {
|
||||
p: string;
|
||||
}
|
||||
|
||||
export type State =
|
||||
| State.Loading
|
||||
| State.LoadingUriError
|
||||
| State.Ready;
|
||||
|
||||
export namespace State {
|
||||
|
||||
export interface Loading {
|
||||
status: "loading";
|
||||
error: undefined;
|
||||
}
|
||||
|
||||
export interface LoadingUriError {
|
||||
status: "loading-uri";
|
||||
error: HookError;
|
||||
}
|
||||
|
||||
export interface BaseInfo {
|
||||
error: undefined;
|
||||
}
|
||||
export interface Ready extends BaseInfo {
|
||||
status: "ready";
|
||||
amount: AmountJson;
|
||||
exchange: SelectFieldHandler,
|
||||
subject: TextFieldHandler,
|
||||
error: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const viewMapping: StateViewMap<State> = {
|
||||
loading: Loading,
|
||||
"loading-uri": LoadingUriView,
|
||||
"ready": ReadyView,
|
||||
};
|
||||
|
||||
|
||||
export const SendPage = compose("SendPage", (p: Props) => useComponentState(p, wxApi), viewMapping)
|
||||
|
66
packages/taler-wallet-webextension/src/wallet/Send/state.ts
Normal file
66
packages/taler-wallet-webextension/src/wallet/Send/state.ts
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 { Amounts } from "@gnu-taler/taler-util";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
||||
import * as wxApi from "../../wxApi.js";
|
||||
import { Props, State } from "./index.js";
|
||||
|
||||
export function useComponentState(
|
||||
{ p }: Props,
|
||||
api: typeof wxApi,
|
||||
): State {
|
||||
const [subject, setSubject] = useState("");
|
||||
const amount = Amounts.parseOrThrow("ARS:0")
|
||||
|
||||
const hook = useAsyncAsHook(api.listExchanges);
|
||||
const [exchangeIdx, setExchangeIdx] = useState("0")
|
||||
|
||||
if (!hook) {
|
||||
return {
|
||||
status: "loading",
|
||||
error: undefined,
|
||||
}
|
||||
}
|
||||
if (hook.hasError) {
|
||||
return {
|
||||
status: "loading-uri",
|
||||
error: hook,
|
||||
};
|
||||
}
|
||||
|
||||
const exchanges = hook.response.exchanges;
|
||||
const exchangeMap = exchanges.reduce((prev, cur, idx) => ({ ...prev, [cur.exchangeBaseUrl]: String(idx) }), {} as Record<string, string>)
|
||||
const selected = exchanges[Number(exchangeIdx)];
|
||||
|
||||
return {
|
||||
status: "ready",
|
||||
exchange: {
|
||||
list: exchangeMap,
|
||||
value: exchangeIdx,
|
||||
onChange: async (v) => {
|
||||
setExchangeIdx(v)
|
||||
}
|
||||
},
|
||||
subject: {
|
||||
value: subject,
|
||||
onInput: async (e) => setSubject(e)
|
||||
},
|
||||
amount,
|
||||
error: undefined,
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
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/invoice",
|
||||
};
|
||||
|
||||
export const Ready = createExample(ReadyView, {});
|
31
packages/taler-wallet-webextension/src/wallet/Send/test.ts
Normal file
31
packages/taler-wallet-webextension/src/wallet/Send/test.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
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";
|
||||
|
||||
describe("test description", () => {
|
||||
|
||||
it("should assert", () => {
|
||||
|
||||
expect([]).deep.equals([])
|
||||
});
|
||||
})
|
||||
|
58
packages/taler-wallet-webextension/src/wallet/Send/views.tsx
Normal file
58
packages/taler-wallet-webextension/src/wallet/Send/views.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 { Amounts } from "@gnu-taler/taler-util";
|
||||
import { styled } from "@linaria/react";
|
||||
import { h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { LoadingError } from "../../components/LoadingError.js";
|
||||
import { SelectList } from "../../components/SelectList.js";
|
||||
import { Input } from "../../components/styled/index.js";
|
||||
import { useTranslationContext } from "../../context/translation.js";
|
||||
import { Button } from "../../mui/Button.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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div``;
|
||||
|
||||
export function ReadyView({ amount, exchange, subject }: State.Ready): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
return (
|
||||
<Container>
|
||||
<p>Sending {Amounts.stringify(amount)}</p>
|
||||
<TextField
|
||||
label="Subject"
|
||||
variant="filled"
|
||||
required
|
||||
value={subject.value}
|
||||
onChange={subject.onInput}
|
||||
/>
|
||||
<p>to:</p>
|
||||
<Button>Scan QR code</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user