diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
15 files changed, 798 insertions, 50 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts new file mode 100644 index 000000000..3205588af --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/index.ts @@ -0,0 +1,95 @@ +/* + 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 { + AmountJson, + BackupBackupProviderTerms, + TalerErrorDetail, +} from "@gnu-taler/taler-util"; +import { SyncTermsOfServiceResponse } from "@gnu-taler/taler-wallet-core"; +import { Loading } from "../../components/Loading.js"; +import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { + ButtonHandler, + TextFieldHandler, + ToggleHandler, +} from "../../mui/handlers.js"; +import { compose, StateViewMap } from "../../utils/index.js"; +import { wxApi } from "../../wxApi.js"; +import { useComponentState } from "./state.js"; +import { + LoadingUriView, + SelectProviderView, + ConfirmProviderView, +} from "./views.js"; + +export interface Props { + currency: string; + onBack: () => Promise<void>; + onComplete: (pid: string) => Promise<void>; + onPaymentRequired: (uri: string) => Promise<void>; +} + +export type State = + | State.Loading + | State.LoadingUriError + | State.ConfirmProvider + | State.SelectProvider; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface LoadingUriError { + status: "loading-error"; + error: HookError; + } + + export interface ConfirmProvider { + status: "confirm-provider"; + error: undefined | TalerErrorDetail; + url: string; + provider: SyncTermsOfServiceResponse; + tos: ToggleHandler; + onCancel: ButtonHandler; + onAccept: ButtonHandler; + } + + export interface SelectProvider { + status: "select-provider"; + url: TextFieldHandler; + urlOk: boolean; + name: TextFieldHandler; + onConfirm: ButtonHandler; + onCancel: ButtonHandler; + error: undefined | TalerErrorDetail; + } +} + +const viewMapping: StateViewMap<State> = { + loading: Loading, + "loading-error": LoadingUriView, + "select-provider": SelectProviderView, + "confirm-provider": ConfirmProviderView, +}; + +export const AddBackupProviderPage = compose( + "AddBackupProvider", + (p: Props) => useComponentState(p, wxApi), + viewMapping, +); diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts new file mode 100644 index 000000000..0b3c17902 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/state.ts @@ -0,0 +1,260 @@ +/* + 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 { + canonicalizeBaseUrl, + Codec, + TalerErrorDetail, +} from "@gnu-taler/taler-util"; +import { + codecForSyncTermsOfServiceResponse, + WalletApiOperation, +} from "@gnu-taler/taler-wallet-core"; +import { useEffect, useState } from "preact/hooks"; +import { assertUnreachable } from "../../utils/index.js"; +import { wxApi } from "../../wxApi.js"; +import { Props, State } from "./index.js"; + +type UrlState<T> = UrlOk<T> | UrlError; + +interface UrlOk<T> { + status: "ok"; + result: T; +} +type UrlError = + | UrlNetworkError + | UrlClientError + | UrlServerError + | UrlParsingError + | UrlReadError; + +interface UrlNetworkError { + status: "network-error"; + href: string; +} +interface UrlClientError { + status: "client-error"; + code: number; +} +interface UrlServerError { + status: "server-error"; + code: number; +} +interface UrlParsingError { + status: "parsing-error"; + json: any; +} +interface UrlReadError { + status: "url-error"; +} + +function useDebounceEffect( + time: number, + cb: undefined | (() => Promise<void>), + deps: Array<any>, +): void { + const [currentTimer, setCurrentTimer] = useState<any>(); + useEffect(() => { + if (currentTimer !== undefined) clearTimeout(currentTimer); + if (cb !== undefined) { + const tid = setTimeout(cb, time); + setCurrentTimer(tid); + } + }, deps); +} + +function useUrlState<T>( + host: string | undefined, + path: string, + codec: Codec<T>, +): UrlState<T> | undefined { + const [state, setState] = useState<UrlState<T> | undefined>(); + + let href: string | undefined; + try { + if (host) { + const isHttps = + host.startsWith("https://") && host.length > "https://".length; + const isHttp = + host.startsWith("http://") && host.length > "http://".length; + const withProto = isHttp || isHttps ? host : `https://${host}`; + const baseUrl = canonicalizeBaseUrl(withProto); + href = new URL(path, baseUrl).href; + } + } catch (e) { + setState({ + status: "url-error", + }); + } + const constHref = href; + + useDebounceEffect( + 500, + constHref == undefined + ? undefined + : async () => { + const req = await fetch(constHref).catch((e) => { + return setState({ + status: "network-error", + href: constHref, + }); + }); + if (!req) return; + + if (req.status >= 400 && req.status < 500) { + setState({ + status: "client-error", + code: req.status, + }); + return; + } + if (req.status > 500) { + setState({ + status: "server-error", + code: req.status, + }); + return; + } + + const json = await req.json(); + try { + const result = codec.decode(json); + setState({ status: "ok", result }); + } catch (e: any) { + setState({ status: "parsing-error", json }); + } + }, + [host, path], + ); + + return state; +} + +export function useComponentState( + { currency, onBack, onComplete, onPaymentRequired }: Props, + api: typeof wxApi, +): State { + const [url, setHost] = useState<string | undefined>(); + const [name, setName] = useState<string | undefined>(); + const [tos, setTos] = useState(false); + const urlState = useUrlState( + url, + "config", + codecForSyncTermsOfServiceResponse(), + ); + const [operationError, setOperationError] = useState< + TalerErrorDetail | undefined + >(); + const [showConfirm, setShowConfirm] = useState(false); + + async function addBackupProvider() { + if (!url || !name) return; + + const resp = await api.wallet.call(WalletApiOperation.AddBackupProvider, { + backupProviderBaseUrl: url, + name: name, + activate: true, + }); + + switch (resp.status) { + case "payment-required": + return onPaymentRequired(resp.talerUri); + case "error": + return setOperationError(resp.error); + case "ok": + return onComplete(url); + default: + assertUnreachable(resp); + } + } + + if (showConfirm && urlState && urlState.status === "ok") { + return { + status: "confirm-provider", + error: operationError, + onAccept: { + onClick: !tos ? undefined : addBackupProvider, + }, + onCancel: { + onClick: onBack, + }, + provider: urlState.result, + tos: { + value: tos, + button: { + onClick: async () => setTos(!tos), + }, + }, + url: url ?? "", + }; + } + + return { + status: "select-provider", + error: undefined, + name: { + value: name || "", + onInput: async (e) => setName(e), + error: + name === undefined ? undefined : !name ? "Can't be empty" : undefined, + }, + onCancel: { + onClick: onBack, + }, + onConfirm: { + onClick: + !urlState || urlState.status !== "ok" || !name + ? undefined + : async () => { + setShowConfirm(true); + }, + }, + urlOk: urlState?.status === "ok", + url: { + value: url || "", + onInput: async (e) => setHost(e), + error: errorString(urlState), + }, + }; +} + +function errorString(state: undefined | UrlState<any>): string | undefined { + if (!state) return state; + switch (state.status) { + case "ok": + return undefined; + case "client-error": { + switch (state.code) { + case 404: + return "Not found"; + case 401: + return "Unauthorized"; + case 403: + return "Forbidden"; + default: + return `Server says it a client error: ${state.code}.`; + } + } + case "server-error": + return `Server had a problem ${state.code}.`; + case "parsing-error": + return `Server response doesn't have the right format.`; + case "network-error": + return `Unable to connect to ${state.href}.`; + case "url-error": + return "URL is not complete"; + } +} diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx new file mode 100644 index 000000000..ae3e1b091 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/stories.tsx @@ -0,0 +1,109 @@ +/* + 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 { ConfirmProviderView, SelectProviderView } from "./views.js"; + +export default { + title: "wallet/backup/confirm", +}; + +export const DemoService = createExample(ConfirmProviderView, { + url: "https://sync.demo.taler.net/", + provider: { + annual_fee: "KUDOS:0.1", + storage_limit_in_megabytes: 20, + version: "1", + }, + tos: { + button: {}, + }, + onAccept: {}, + onCancel: {}, +}); + +export const FreeService = createExample(ConfirmProviderView, { + url: "https://sync.taler:9667/", + provider: { + annual_fee: "ARS:0", + storage_limit_in_megabytes: 20, + version: "1", + }, + tos: { + button: {}, + }, + onAccept: {}, + onCancel: {}, +}); + +export const Initial = createExample(SelectProviderView, { + url: { value: "" }, + name: { value: "" }, + onCancel: {}, + onConfirm: {}, +}); + +export const WithValue = createExample(SelectProviderView, { + url: { + value: "sync.demo.taler.net", + }, + name: { + value: "Demo backup service", + }, + onCancel: {}, + onConfirm: {}, +}); + +export const WithConnectionError = createExample(SelectProviderView, { + url: { + value: "sync.demo.taler.net", + error: "Network error", + }, + name: { + value: "Demo backup service", + }, + onCancel: {}, + onConfirm: {}, +}); + +export const WithClientError = createExample(SelectProviderView, { + url: { + value: "sync.demo.taler.net", + error: "URL may not be right: (404) Not Found", + }, + name: { + value: "Demo backup service", + }, + onCancel: {}, + onConfirm: {}, +}); + +export const WithServerError = createExample(SelectProviderView, { + url: { + value: "sync.demo.taler.net", + error: "Try another server: (500) Internal Server Error", + }, + name: { + value: "Demo backup service", + }, + onCancel: {}, + onConfirm: {}, +}); diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts new file mode 100644 index 000000000..1143853f8 --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/test.ts @@ -0,0 +1,79 @@ +/* + 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 { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { expect } from "chai"; +import { + createWalletApiMock, + mountHook, + nullFunction, +} from "../../test-utils.js"; +import { Props } from "./index.js"; +import { useComponentState } from "./state.js"; + +const props: Props = { + currency: "KUDOS", + onBack: nullFunction, + onComplete: nullFunction, + onPaymentRequired: nullFunction, +}; +describe("AddBackupProvider states", () => { + it("should start in 'select-provider' state", async () => { + const { handler, mock } = createWalletApiMock(); + + // handler.addWalletCallResponse( + // WalletApiOperation.ListKnownBankAccounts, + // undefined, + // { + // accounts: [], + // }, + // ); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = + mountHook(() => useComponentState(props, mock)); + + { + const state = pullLastResultOrThrow(); + expect(state.status).equal("select-provider"); + if (state.status !== "select-provider") return; + expect(state.name.value).eq(""); + expect(state.url.value).eq(""); + } + + //FIXME: this should not make an extra update + /** + * this may be due to useUrlState because is using an effect over + * a dependency with a timeout + */ + // NOTE: do not remove this comment, keeping as an example + // await waitForStateUpdate() + // { + // const state = pullLastResultOrThrow(); + // expect(state.status).equal("select-provider"); + // if (state.status !== "select-provider") return; + // expect(state.name.value).eq("") + // expect(state.url.value).eq("") + // } + + await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty"); + }); +}); diff --git a/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx new file mode 100644 index 000000000..b633a595f --- /dev/null +++ b/packages/taler-wallet-webextension/src/wallet/AddBackupProvider/views.tsx @@ -0,0 +1,172 @@ +/* + 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 { Fragment, h, VNode } from "preact"; +import { Checkbox } from "../../components/Checkbox.js"; +import { LoadingError } from "../../components/LoadingError.js"; +import { + LightText, + SmallLightText, + SubTitle, + TermsOfService, + Title, +} 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} + /> + ); +} + +export function ConfirmProviderView({ + url, + provider, + tos, + onCancel, + onAccept, +}: State.ConfirmProvider): VNode { + const { i18n } = useTranslationContext(); + const noFee = Amounts.isZero(provider.annual_fee); + return ( + <Fragment> + <section> + <Title> + <i18n.Translate>Review terms of service</i18n.Translate> + </Title> + <div> + <i18n.Translate>Provider URL</i18n.Translate>:{" "} + <a href={url} target="_blank" rel="noreferrer"> + {url} + </a> + </div> + <SmallLightText> + <i18n.Translate> + Please review and accept this provider's terms of service + </i18n.Translate> + </SmallLightText> + <SubTitle> + 1. <i18n.Translate>Pricing</i18n.Translate> + </SubTitle> + <p> + {noFee ? ( + <i18n.Translate>free of charge</i18n.Translate> + ) : ( + <i18n.Translate> + {provider.annual_fee} per year of service + </i18n.Translate> + )} + </p> + <SubTitle> + 2. <i18n.Translate>Storage</i18n.Translate> + </SubTitle> + <p> + <i18n.Translate> + {provider.storage_limit_in_megabytes} megabytes of storage per year + of service + </i18n.Translate> + </p> + {/* replace with <TermsOfService /> */} + <Checkbox + label={<i18n.Translate>Accept terms of service</i18n.Translate>} + name="terms" + onToggle={tos.button.onClick} + enabled={tos.value} + /> + </section> + <footer> + <Button + variant="contained" + color="secondary" + onClick={onCancel.onClick} + > + <i18n.Translate>Cancel</i18n.Translate> + </Button> + <Button variant="contained" color="primary" onClick={onAccept.onClick}> + {noFee ? ( + <i18n.Translate>Add provider</i18n.Translate> + ) : ( + <i18n.Translate>Pay</i18n.Translate> + )} + </Button> + </footer> + </Fragment> + ); +} + +export function SelectProviderView({ + url, + name, + urlOk, + onCancel, + onConfirm, +}: State.SelectProvider): VNode { + const { i18n } = useTranslationContext(); + return ( + <Fragment> + <section> + <Title> + <i18n.Translate>Add backup provider</i18n.Translate> + </Title> + <LightText> + <i18n.Translate> + Backup providers may charge for their service + </i18n.Translate> + </LightText> + <p> + <TextField + label={<i18n.Translate>URL</i18n.Translate>} + placeholder="https://" + color={urlOk ? "success" : undefined} + value={url.value} + error={url.error} + onChange={url.onInput} + /> + </p> + <p> + <TextField + label={<i18n.Translate>Name</i18n.Translate>} + placeholder="provider name" + value={name.value} + error={name.error} + onChange={name.onInput} + /> + </p> + </section> + <footer> + <Button + variant="contained" + color="secondary" + onClick={onCancel.onClick} + > + <i18n.Translate>Cancel</i18n.Translate> + </Button> + <Button variant="contained" color="primary" onClick={onConfirm.onClick}> + <i18n.Translate>Next</i18n.Translate> + </Button> + </footer> + </Fragment> + ); +} diff --git a/packages/taler-wallet-webextension/src/wallet/Application.tsx b/packages/taler-wallet-webextension/src/wallet/Application.tsx index 5934dec00..6b265c1ba 100644 --- a/packages/taler-wallet-webextension/src/wallet/Application.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Application.tsx @@ -65,6 +65,7 @@ import { InvoiceCreatePage } from "../cta/InvoiceCreate/index.js"; import { TransferPickupPage } from "../cta/TransferPickup/index.js"; import { InvoicePayPage } from "../cta/InvoicePay/index.js"; import { RecoveryPage } from "../cta/Recovery/index.js"; +import { AddBackupProviderPage } from "./AddBackupProvider/index.js"; export function Application(): VNode { const [globalNotification, setGlobalNotification] = useState< @@ -221,7 +222,13 @@ export function Application(): VNode { /> <Route path={Pages.backupProviderAdd} - component={ProviderAddPage} + component={AddBackupProviderPage} + onPaymentRequired={(uri: string) => + redirectTo(`${Pages.ctaPay}?talerPayUri=${uri}`) + } + onComplete={(pid: string) => + redirectTo(Pages.backupProviderDetail({ pid })) + } onBack={() => redirectTo(Pages.backup)} /> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts index 85896da26..373045833 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/index.ts @@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, SelectFieldHandler, - TextFieldHandler + TextFieldHandler, } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { wxApi } from "../../wxApi.js"; @@ -31,7 +31,7 @@ import { LoadingErrorView, NoAccountToDepositView, NoEnoughBalanceView, - ReadyView + ReadyView, } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index d8b752d44..91883c823 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -21,7 +21,7 @@ import { KnownBankAccountsInfo, parsePaytoUri, PaytoUri, - stringifyPaytoUri + stringifyPaytoUri, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; @@ -37,10 +37,16 @@ export function useComponentState( const currency = parsed !== undefined ? parsed.currency : currencyStr; const hook = useAsyncAsHook(async () => { - const { balances } = await api.wallet.call(WalletApiOperation.GetBalances, {}); - const { accounts } = await api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { - currency - }); + const { balances } = await api.wallet.call( + WalletApiOperation.GetBalances, + {}, + ); + const { accounts } = await api.wallet.call( + WalletApiOperation.ListKnownBankAccounts, + { + currency, + }, + ); return { accounts, balances }; }); @@ -120,13 +126,13 @@ export function useComponentState( }, }; } - const firstAccount = accounts[0].uri + const firstAccount = accounts[0].uri; const currentAccount = !selectedAccount ? firstAccount : selectedAccount; if (fee === undefined && parsedAmount) { - getFeeForAmount(currentAccount, parsedAmount, api).then(initialFee => { - setFee(initialFee) - }) + getFeeForAmount(currentAccount, parsedAmount, api).then((initialFee) => { + setFee(initialFee); + }); return { status: "loading", error: undefined, @@ -177,10 +183,10 @@ export function useComponentState( const amountError = !isDirty ? undefined : !parsedAmount - ? "Invalid amount" - : Amounts.cmp(balance, parsedAmount) === -1 - ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` - : undefined; + ? "Invalid amount" + : Amounts.cmp(balance, parsedAmount) === -1 + ? `Too much, your current balance is ${Amounts.stringifyValue(balance)}` + : undefined; const unableToDeposit = !parsedAmount || //no amount specified @@ -194,8 +200,9 @@ export function useComponentState( const depositPaytoUri = stringifyPaytoUri(currentAccount); const amount = Amounts.stringify(parsedAmount); await api.wallet.call(WalletApiOperation.CreateDepositGroup, { - amount, depositPaytoUri - }) + amount, + depositPaytoUri, + }); onSuccess(currency); } @@ -242,8 +249,9 @@ async function getFeeForAmount( const depositPaytoUri = `payto://${p.targetType}/${p.targetPath}`; const amount = Amounts.stringify(a); return await api.wallet.call(WalletApiOperation.GetFeeForDeposit, { - amount, depositPaytoUri - }) + amount, + depositPaytoUri, + }); } export function labelForAccountType(id: string) { diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts index ddfaa71f9..a95830f8e 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/index.ts @@ -18,7 +18,7 @@ import { DenomOperationMap, ExchangeFullDetails, ExchangeListItem, - FeeDescriptionPair + FeeDescriptionPair, } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; @@ -33,7 +33,7 @@ import { NoExchangesView, PrivacyContentView, ReadyView, - TosContentView + TosContentView, } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts index ee839cad7..0a66dc381 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/state.ts @@ -15,7 +15,10 @@ */ import { DenomOperationMap, FeeDescription } from "@gnu-taler/taler-util"; -import { createPairTimeline, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + createPairTimeline, + WalletApiOperation, +} from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { wxApi } from "../../wxApi.js"; @@ -41,15 +44,23 @@ export function useComponentState( exchanges.length == 0 ? undefined : exchanges[selectedIdx]; const selected = !selectedExchange ? undefined - : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: selectedExchange.exchangeBaseUrl }); + : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { + exchangeBaseUrl: selectedExchange.exchangeBaseUrl, + }); const initialExchange = selectedIdx === initialValue ? undefined : exchanges[initialValue]; const original = !initialExchange ? undefined - : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { exchangeBaseUrl: initialExchange.exchangeBaseUrl }); + : await api.wallet.call(WalletApiOperation.GetExchangeDetailedInfo, { + exchangeBaseUrl: initialExchange.exchangeBaseUrl, + }); - return { exchanges, selected: selected?.exchange, original: original?.exchange }; + return { + exchanges, + selected: selected?.exchange, + original: original?.exchange, + }; }, [value]); const [showingTos, setShowingTos] = useState<string | undefined>(undefined); diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx index d9a33c5c2..be059630f 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSelection/views.tsx @@ -161,11 +161,14 @@ export function NoExchangesView({ title={<i18n.Translate>Could not find any exchange</i18n.Translate>} /> ); - } return ( <ErrorMessage - title={<i18n.Translate>Could not find any exchange for the currency {currency}</i18n.Translate>} + title={ + <i18n.Translate> + Could not find any exchange for the currency {currency} + </i18n.Translate> + } /> ); } diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts index cd591be74..df4e7586f 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/index.ts @@ -20,7 +20,7 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, SelectFieldHandler, - TextFieldHandler + TextFieldHandler, } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { wxApi } from "../../wxApi.js"; @@ -58,13 +58,13 @@ export namespace State { alias: TextFieldHandler; onAccountAdded: ButtonHandler; onCancel: ButtonHandler; - accountByType: AccountByType, - deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>, + accountByType: AccountByType; + deleteAccount: (a: KnownBankAccountsInfo) => Promise<void>; } } export type AccountByType = { - [key: string]: KnownBankAccountsInfo[] + [key: string]: KnownBankAccountsInfo[]; }; const viewMapping: StateViewMap<State> = { diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts index f180fc1c0..1f920f05f 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/state.ts @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { KnownBankAccountsInfo, 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 { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; @@ -25,7 +29,9 @@ export function useComponentState( { currency, onAccountAdded, onCancel }: Props, api: typeof wxApi, ): State { - const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency })); + const hook = useAsyncAsHook(() => + api.wallet.call(WalletApiOperation.ListKnownBankAccounts, { currency }), + ); const [payto, setPayto] = useState(""); const [alias, setAlias] = useState(""); @@ -61,34 +67,34 @@ export function useComponentState( const normalizedPayto = stringifyPaytoUri(uri); await api.wallet.call(WalletApiOperation.AddKnownBankAccounts, { - alias, currency, payto: normalizedPayto + alias, + currency, + payto: normalizedPayto, }); onAccountAdded(payto); } - const paytoUriError = - found - ? "that account is already present" - : undefined; + const paytoUriError = found ? "that account is already present" : undefined; - const unableToAdd = !type || !alias || paytoUriError !== undefined || uri === undefined; + const unableToAdd = + !type || !alias || paytoUriError !== undefined || uri === undefined; const accountByType: AccountByType = { iban: [], bitcoin: [], "x-taler-bank": [], - } + }; - hook.response.accounts.forEach(acc => { - accountByType[acc.uri.targetType].push(acc) + 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() + payto, + }); + hook?.retry(); } return { diff --git a/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx b/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx index ba5a78570..caf833e79 100644 --- a/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/QrReader.stories.tsx @@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js"; import { QrReaderPage } from "./QrReader.js"; export default { - title: "wallet/qr", + title: "wallet/qr reader", }; export const Reading = createExample(QrReaderPage, {}); diff --git a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx index d63f25ead..42808b573 100644 --- a/packages/taler-wallet-webextension/src/wallet/index.stories.tsx +++ b/packages/taler-wallet-webextension/src/wallet/index.stories.tsx @@ -25,8 +25,7 @@ import * as a4 from "./DepositPage/stories.js"; import * as a5 from "./ExchangeAddConfirm.stories.js"; import * as a6 from "./ExchangeAddSetUrl.stories.js"; import * as a7 from "./History.stories.js"; -import * as a8 from "./ProviderAddConfirmProvider.stories.js"; -import * as a9 from "./ProviderAddSetUrl.stories.js"; +import * as a8 from "./AddBackupProvider/stories.js"; import * as a10 from "./ProviderDetail.stories.js"; import * as a11 from "./ReserveCreated.stories.js"; import * as a12 from "./Settings.stories.js"; @@ -47,7 +46,6 @@ export default [ a6, a7, a8, - a9, a10, a11, a12, |