diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta/Deposit')
5 files changed, 386 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts new file mode 100644 index 000000000..c2d700617 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -0,0 +1,70 @@ +/* + 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, AmountString } from "@gnu-taler/taler-util"; +import { Loading } from "../../components/Loading.js"; +import { HookError } from "../../hooks/useAsyncAsHook.js"; +import { ButtonHandler } from "../../mui/handlers.js"; +import { compose, StateViewMap } from "../../utils/index.js"; +import * as wxApi from "../../wxApi.js"; +import { useComponentState } from "./state.js"; +import { CompletedView, LoadingUriView, ReadyView } from "./views.js"; + + + +export interface Props { + talerDepositUri: string | undefined, + amountStr: AmountString | undefined, +} + +export type State = + | State.Loading + | State.LoadingUriError + | State.Ready + | State.Completed; + +export namespace State { + + export interface Loading { + status: "loading"; + error: undefined; + } + export interface LoadingUriError { + status: "loading-uri"; + error: HookError; + } + export interface Ready { + status: "ready"; + error: undefined; + fee: AmountJson; + cost: AmountJson; + effective: AmountJson; + confirm: ButtonHandler; + } + export interface Completed { + status: "completed"; + error: undefined; + } +} + +const viewMapping: StateViewMap<State> = { + "loading": Loading, + "loading-uri": LoadingUriView, + completed: CompletedView, + ready: ReadyView, +}; + +export const DepositPage = compose("Deposit", (p: Props) => useComponentState(p, wxApi), viewMapping) diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts new file mode 100644 index 000000000..8876a2971 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -0,0 +1,76 @@ +/* + 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, CreateDepositGroupResponse } 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( + { talerDepositUri, amountStr }: Props, + api: typeof wxApi, +): State { + const [result, setResult] = useState<CreateDepositGroupResponse | undefined>( + undefined, + ); + + const info = useAsyncAsHook(async () => { + if (!talerDepositUri) throw Error("ERROR_NO-URI-FOR-DEPOSIT"); + if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); + const amount = Amounts.parse(amountStr); + if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT"); + const deposit = await api.prepareDeposit( + talerDepositUri, + Amounts.stringify(amount), + ); + return { deposit, uri: talerDepositUri, amount }; + }); + + if (!info) return { status: "loading", error: undefined } + if (info.hasError) { + return { + status: "loading-uri", + error: info, + }; + } + + const { deposit, uri, amount } = info.response; + async function doDeposit(): Promise<void> { + const resp = await api.createDepositGroup(uri, Amounts.stringify(amount)); + setResult(resp); + } + + if (result !== undefined) { + return { + status: "completed", + error: undefined, + }; + } + + return { + status: "ready", + error: undefined, + confirm: { + onClick: doDeposit, + }, + fee: Amounts.sub(deposit.totalDepositCost, deposit.effectiveDepositAmount) + .amount, + cost: deposit.totalDepositCost, + effective: deposit.effectiveDepositAmount, + }; +}
\ No newline at end of file diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/stories.tsx b/packages/taler-wallet-webextension/src/cta/Deposit/stories.tsx new file mode 100644 index 000000000..a4168bcc2 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/Deposit/stories.tsx @@ -0,0 +1,37 @@ +/* + 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 { Amounts } from "@gnu-taler/taler-util"; +import { createExample } from "../../test-utils.js"; +import { ReadyView } from "./views.js"; + +export default { + title: "cta/deposit", +}; + +export const Ready = createExample(ReadyView, { + status: "ready", + confirm: {}, + cost: Amounts.parseOrThrow("EUR:1.2"), + effective: Amounts.parseOrThrow("EUR:1"), + fee: Amounts.parseOrThrow("EUR:0.2"), + error: undefined, +}); diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts new file mode 100644 index 000000000..6e7aaf237 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -0,0 +1,94 @@ +/* + 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 { + Amounts, PrepareDepositResponse +} from "@gnu-taler/taler-util"; +import { expect } from "chai"; +import { mountHook } from "../../test-utils.js"; +import { useComponentState } from "./state.js"; + +describe("Deposit CTA states", () => { + it("should tell the user that the URI is missing", async () => { + const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + mountHook(() => + useComponentState({ talerDepositUri: undefined, amountStr: undefined }, { + prepareRefund: async () => ({}), + applyRefund: async () => ({}), + onUpdateNotification: async () => ({}), + } as any), + ); + + { + const { status } = getLastResultOrThrow(); + expect(status).equals("loading"); + } + + await waitNextUpdate(); + + { + const { status, error } = getLastResultOrThrow(); + + expect(status).equals("loading-uri"); + + if (!error) expect.fail(); + if (!error.hasError) expect.fail(); + if (error.operational) expect.fail(); + expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); + } + + await assertNoPendingUpdate(); + }); + + it("should be ready after loading", async () => { + const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + mountHook(() => + useComponentState({ talerDepositUri: "payto://refund/asdasdas", amountStr: "EUR:1" }, { + prepareDeposit: async () => + ({ + effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), + totalDepositCost: Amounts.parseOrThrow("EUR:1.2"), + } as PrepareDepositResponse as any), + createDepositGroup: async () => ({}), + } as any), + ); + + { + const { status } = getLastResultOrThrow(); + expect(status).equals("loading"); + } + + await waitNextUpdate(); + + { + const state = getLastResultOrThrow(); + + if (state.status !== "ready") expect.fail(); + if (state.error) expect.fail(); + expect(state.confirm.onClick).not.undefined; + expect(state.cost).deep.eq(Amounts.parseOrThrow("EUR:1.2")); + expect(state.fee).deep.eq(Amounts.parseOrThrow("EUR:0.2")); + expect(state.effective).deep.eq(Amounts.parseOrThrow("EUR:1")); + } + + await assertNoPendingUpdate(); + }); +}); diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx b/packages/taler-wallet-webextension/src/cta/Deposit/views.tsx new file mode 100644 index 000000000..ba1ca58d6 --- /dev/null +++ b/packages/taler-wallet-webextension/src/cta/Deposit/views.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/> + */ + +import { Amounts } from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { Amount } from "../../components/Amount.js"; +import { LoadingError } from "../../components/LoadingError.js"; +import { LogoHeader } from "../../components/LogoHeader.js"; +import { Part } from "../../components/Part.js"; +import { SubTitle, WalletAction } from "../../components/styled/index.js"; +import { useTranslationContext } from "../../context/translation.js"; +import { Button } from "../../mui/Button.js"; +import { State } from "./index.js"; + +/** + * + * @author sebasjm + */ + +export function LoadingUriView({ error }: State.LoadingUriError): VNode { + const { i18n } = useTranslationContext(); + + return ( + <LoadingError + title={<i18n.Translate>Could not load deposit status</i18n.Translate>} + error={error} + /> + ); +} +export function CompletedView(state: State.Completed): VNode { + const { i18n } = useTranslationContext(); + + return ( + <WalletAction> + <LogoHeader /> + + <SubTitle> + <i18n.Translate>Digital cash deposit</i18n.Translate> + </SubTitle> + <section> + <p> + <i18n.Translate>deposit completed</i18n.Translate> + </p> + </section> + </WalletAction> + ); +} + +export function ReadyView(state: State.Ready): VNode { + const { i18n } = useTranslationContext(); + + return ( + <WalletAction> + <LogoHeader /> + + <SubTitle> + <i18n.Translate>Digital cash deposit</i18n.Translate> + </SubTitle> + <section> + {Amounts.isNonZero(state.cost) && ( + <Part + big + title={<i18n.Translate>Cost</i18n.Translate>} + text={<Amount value={state.cost} />} + kind="negative" + /> + )} + {Amounts.isNonZero(state.fee) && ( + <Part + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={<Amount value={state.fee} />} + kind="negative" + /> + )} + <Part + big + title={<i18n.Translate>To be received</i18n.Translate>} + text={<Amount value={state.effective} />} + kind="positive" + /> + </section> + <section> + <Button + variant="contained" + color="success" + onClick={state.confirm.onClick} + > + <i18n.Translate> + Deposit {<Amount value={state.effective} />} + </i18n.Translate> + </Button> + </section> + </WalletAction> + ); +} |