From dfd23f63ba40a2afb0cb41bf742b0ae647a2b38c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 22 Sep 2023 12:41:43 -0300 Subject: [PATCH] more ui --- .../src/pages/OperationState/index.ts | 117 ++++++++ .../src/pages/OperationState/state.ts | 162 +++++++++++ .../src/pages/OperationState/stories.tsx | 29 ++ .../src/pages/OperationState/test.ts | 32 +++ .../src/pages/OperationState/views.tsx | 65 +++++ .../src/pages/admin/AccountList.tsx | 271 ++++++++---------- packages/demobank-ui/src/pages/admin/Home.tsx | 4 +- 7 files changed, 527 insertions(+), 153 deletions(-) create mode 100644 packages/demobank-ui/src/pages/OperationState/index.ts create mode 100644 packages/demobank-ui/src/pages/OperationState/state.ts create mode 100644 packages/demobank-ui/src/pages/OperationState/stories.tsx create mode 100644 packages/demobank-ui/src/pages/OperationState/test.ts create mode 100644 packages/demobank-ui/src/pages/OperationState/views.tsx diff --git a/packages/demobank-ui/src/pages/OperationState/index.ts b/packages/demobank-ui/src/pages/OperationState/index.ts new file mode 100644 index 000000000..254fcba5f --- /dev/null +++ b/packages/demobank-ui/src/pages/OperationState/index.ts @@ -0,0 +1,117 @@ +/* + 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 + */ + +import { HttpError, HttpResponseOk, HttpResponsePaginated, utils } from "@gnu-taler/web-util/browser"; +import { AbsoluteTime, AmountJson, PaytoUriIBAN, PaytoUriTalerBank, WithdrawUriResult } from "@gnu-taler/taler-util"; +import { Loading } from "../../components/Loading.js"; +import { useComponentState } from "./state.js"; +import { ReadyView, AbortedView, ConfirmedView, InvalidPaytoView, InvalidReserveView, InvalidWithdrawalView, NeedConfirmationView } from "./views.js"; +import { VNode } from "preact"; +import { LoginForm } from "../LoginForm.js"; +import { ErrorLoading } from "../../components/ErrorLoading.js"; + +export interface Props { + currency: string; + onClose: () => void; +} + +export type State = State.Loading | + State.LoadingError | + State.Ready | + State.Aborted | + State.Confirmed | + State.InvalidPayto | + State.InvalidWithdrawal | + State.InvalidReserve | + State.NeedConfirmation; + +export namespace State { + export interface Loading { + status: "loading"; + error: undefined; + } + + export interface LoadingError { + status: "loading-error"; + error: HttpError; + } + + /** + * Need to open the wallet + */ + export interface Ready { + status: "ready"; + error: undefined; + uri: WithdrawUriResult, + onClose: () => void; + } + + export interface InvalidPayto { + status: "invalid-payto", + error: undefined; + payto: string | null; + } + export interface InvalidWithdrawal { + status: "invalid-withdrawal", + error: undefined; + uri: string, + } + export interface InvalidReserve { + status: "invalid-reserve", + error: undefined; + reserve: string | null; + } + export interface NeedConfirmation { + status: "need-confirmation", + error: undefined; + } + export interface Aborted { + status: "aborted", + error: undefined; + onClose: () => void; + } + export interface Confirmed { + status: "confirmed", + error: undefined; + onClose: () => void; + } + +} + +export interface Transaction { + negative: boolean; + counterpart: string; + when: AbsoluteTime; + amount: AmountJson | undefined; + subject: string; +} + +const viewMapping: utils.StateViewMap = { + loading: Loading, + "invalid-payto": InvalidPaytoView, + "invalid-withdrawal": InvalidWithdrawalView, + "invalid-reserve": InvalidReserveView, + "need-confirmation": NeedConfirmationView, + "aborted": AbortedView, + "confirmed": ConfirmedView, + "loading-error": ErrorLoading, + ready: ReadyView, +}; + +export const AccountPage = utils.compose( + (p: Props) => useComponentState(p), + viewMapping, +); diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts new file mode 100644 index 000000000..6fb7bb28f --- /dev/null +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -0,0 +1,162 @@ +/* + 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 + */ + +import { Amounts, HttpStatusCode, TranslatedString, parsePaytoUri, parseWithdrawUri, stringifyWithdrawUri } from "@gnu-taler/taler-util"; +import { ErrorType, RequestError, notify, notifyError, useTranslationContext, utils } from "@gnu-taler/web-util/browser"; +import { useBackendContext } from "../../context/backend.js"; +import { useAccessAPI, useAccountDetails, useWithdrawalDetails } from "../../hooks/access.js"; +import { Props, State } from "./index.js"; +import { useSettings } from "../../hooks/settings.js"; +import { buildRequestErrorMessage } from "../../utils.js"; +import { useEffect } from "preact/hooks"; +import { getInitialBackendBaseURL } from "../../hooks/backend.js"; + +export function useComponentState({ currency, onClose }: Props): utils.RecursiveState { + const { i18n } = useTranslationContext(); + const [settings, updateSettings] = useSettings() + const { createWithdrawal } = useAccessAPI(); + + const amount = settings.maxWithdrawalAmount + async function doSilentStart() { + //FIXME: if amount is not enough use balance + const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`) + + try { + const result = await createWithdrawal({ + amount: Amounts.stringify(parsedAmount), + }); + const uri = parseWithdrawUri(result.data.taler_withdraw_uri); + if (!uri) { + return notifyError( + i18n.str`Server responded with an invalid withdraw URI`, + i18n.str`Withdraw URI: ${result.data.taler_withdraw_uri}`); + } else { + updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId) + } + } catch (error) { + if (error instanceof RequestError) { + notify( + buildRequestErrorMessage(i18n, error.cause, { + onClientError: (status) => + status === HttpStatusCode.Forbidden + ? i18n.str`The operation was rejected due to insufficient funds` + : undefined, + }), + ); + } else { + notifyError( + i18n.str`Operation failed, please report`, + (error instanceof Error + ? error.message + : JSON.stringify(error)) as TranslatedString + ) + } + } + } + + useEffect(() => { + doSilentStart() + }, [settings.fastWithdrawal, amount]) + + const baseUrl = getInitialBackendBaseURL() + const withdrawalOperationId = settings.currentWithdrawalOperationId + + if (!withdrawalOperationId) { + return { + status: "loading", + error: undefined + } + } + + const bankIntegrationApiBaseUrl = `${baseUrl}/integration-api` + const uri = stringifyWithdrawUri({ + bankIntegrationApiBaseUrl, + withdrawalOperationId, + }); + const parsedUri = parseWithdrawUri(uri); + if (!parsedUri) { + return { + status: "invalid-withdrawal", + error: undefined, + uri, + } + } + + return (): utils.RecursiveState => { + const result = useWithdrawalDetails(withdrawalOperationId); + if (!result.ok) { + if (result.loading) { + return { + status: "loading", + error: undefined + } + } + return { + status: "loading-error", + error: result + } + } + const { data } = result; + if (data.aborted) { + return { + status: "aborted", + error: undefined, + onClose, + } + } + + if (data.confirmation_done) { + return { + status: "confirmed", + error: undefined, + onClose, + } + } + + if (!data.selection_done) { + return { + status: "ready", + error: undefined, + uri: parsedUri, + onClose + } + } + + if (!data.selected_reserve_pub) { + return { + status: "invalid-reserve", + error: undefined, + reserve: data.selected_reserve_pub + } + } + + const account = !data.selected_exchange_account ? undefined : parsePaytoUri(data.selected_exchange_account) + + if (!account) { + return { + status: "invalid-payto", + error: undefined, + payto: data.selected_exchange_account + } + } + + return { + status: "need-confirmation", + error: undefined, + } + } + +} diff --git a/packages/demobank-ui/src/pages/OperationState/stories.tsx b/packages/demobank-ui/src/pages/OperationState/stories.tsx new file mode 100644 index 000000000..03917a8fb --- /dev/null +++ b/packages/demobank-ui/src/pages/OperationState/stories.tsx @@ -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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import * as tests from "@gnu-taler/web-util/testing"; +import { ReadyView } from "./views.js"; + +export default { + title: "operation status page", +}; + +export const Ready = tests.createExample(ReadyView, {}); diff --git a/packages/demobank-ui/src/pages/OperationState/test.ts b/packages/demobank-ui/src/pages/OperationState/test.ts new file mode 100644 index 000000000..f4d6cf4b2 --- /dev/null +++ b/packages/demobank-ui/src/pages/OperationState/test.ts @@ -0,0 +1,32 @@ +/* + 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 + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import * as tests from "@gnu-taler/web-util/testing"; +import { SwrMockEnvironment } from "@gnu-taler/web-util/testing"; +import { expect } from "chai"; +import { CASHOUT_API_EXAMPLE } from "../../endpoints.js"; +import { Props } from "./index.js"; +import { useComponentState } from "./state.js"; + +describe("Withdrawal operation states", () => { + it("should do some tests", async () => { + }); +}); diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx new file mode 100644 index 000000000..db25eaf61 --- /dev/null +++ b/packages/demobank-ui/src/pages/OperationState/views.tsx @@ -0,0 +1,65 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, h, VNode } from "preact"; +import { Transactions } from "../../components/Transactions/index.js"; +import { PaymentOptions } from "../PaymentOptions.js"; +import { State } from "./index.js"; +import { CopyButton } from "../../components/CopyButton.js"; +import { bankUiSettings } from "../../settings.js"; +import { useBusinessAccountDetails } from "../../hooks/circuit.js"; +import { useSettings } from "../../hooks/settings.js"; + +export function InvalidPaytoView({ error }: State.InvalidPayto) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} +export function InvalidWithdrawalView({ error }: State.InvalidWithdrawal) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} +export function InvalidReserveView({ error }: State.InvalidReserve) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} + +export function NeedConfirmationView({ error }: State.NeedConfirmation) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} +export function AbortedView({ error }: State.Aborted) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} +export function ConfirmedView({ error }: State.Confirmed) { + return ( +
Payto from server is not valid "{error.data.paytoUri}"
+ ); +} + +export function ReadyView({ account, limit, goToBusinessAccount }: State.Ready): VNode<{}> { + const { i18n } = useTranslationContext(); + + return
+ +} diff --git a/packages/demobank-ui/src/pages/admin/AccountList.tsx b/packages/demobank-ui/src/pages/admin/AccountList.tsx index 56d9c45f9..eb5765533 100644 --- a/packages/demobank-ui/src/pages/admin/AccountList.tsx +++ b/packages/demobank-ui/src/pages/admin/AccountList.tsx @@ -6,163 +6,132 @@ import { Amounts } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; interface Props { - onAction: (type: AccountAction, account: string) => void; - account: string | undefined; - onRegister: () => void; - onCreateAccount: () => void; + onAction: (type: AccountAction, account: string) => void; + account: string | undefined; + onRegister: () => void; + onCreateAccount: () => void; } export function AccountList({ account, onAction, onCreateAccount, onRegister }: Props): VNode { - const result = useBusinessAccounts({ account }); - const { i18n } = useTranslationContext(); + const result = useBusinessAccounts({ account }); + const { i18n } = useTranslationContext(); - if (result.loading) return
; - if (!result.ok) { - return handleNotOkResult(i18n, onRegister)(result); - } + if (result.loading) return
; + if (!result.ok) { + return handleNotOkResult(i18n, onRegister)(result); + } - const { customers } = result.data; - return
-
-
-

- Accounts -

-

- A list of all business account in the bank. -

-
-
- -
-
-
-
-
- {!customers.length ? ( -
- ) : ( - - - - - - - - - - - {customers.map((item, idx) => { - const balance = !item.balance - ? undefined - : Amounts.parse(item.balance.amount); - const balanceIsDebit = - item.balance && - item.balance.credit_debit_indicator == "debit"; - - return - - - - - - })} - - {/* */} - -
{i18n.str`Username`}{i18n.str`Name`}{i18n.str`Balance`} - {i18n.str`Actions`} -
- {item.username} - - {item.name} - - {!balance ? ( - i18n.str`unknown` - ) : ( - - {balanceIsDebit ? - : null} - {`${Amounts.stringifyValue( - balance, - )}`} -   - {`${balance.currency}`} - - )} - - { - e.preventDefault(); - onAction("update-password", item.username) - }} - > - change password - -
- { - e.preventDefault(); - onAction("show-cashout", item.username) - }} - > - cashouts - -
- { - e.preventDefault(); - onAction("remove-account", item.username) - }} - > - remove - -
- )} -
-
-
+ const { customers } = result.data; + return
+
+
+

+ Accounts +

+

+ A list of all business account in the bank. +

+
+
+ +
+
+
+
+ {!customers.length ? ( +
+ ) : ( + + + + + + + + + + + {customers.map((item, idx) => { + const balance = !item.balance + ? undefined + : Amounts.parse(item.balance.amount); + const balanceIsDebit = + item.balance && + item.balance.credit_debit_indicator == "debit"; - // return
- //
- //

{i18n.str`Accounts:`}

- //
- //
{i18n.str`Username`}{i18n.str`Name`}{i18n.str`Balance`} + {i18n.str`Actions`} +
- // - // return ( - // - // - // - // - // + - // - // ); - // })} - // - //
- // { - // e.preventDefault(); - // onAction("show-details", item.username) - // }} - // > - // {item.username} - // - // {item.name} - // - // + return
+ { + e.preventDefault(); + onAction("show-details", item.username) + }} + > + {item.username} + - //
- //
- // - // )} - // + + + + {item.name} + + + {!balance ? ( + i18n.str`unknown` + ) : ( + + {balanceIsDebit ? - : null} + {`${Amounts.stringifyValue( + balance, + )}`} +   + {`${balance.currency}`} + + )} + + + { + e.preventDefault(); + onAction("update-password", item.username) + }} + > + change password + +
+ { + e.preventDefault(); + onAction("show-cashout", item.username) + }} + > + cashouts + +
+ { + e.preventDefault(); + onAction("remove-account", item.username) + }} + > + remove + + + + })} + + + + )} +
+
+
+
} \ No newline at end of file diff --git a/packages/demobank-ui/src/pages/admin/Home.tsx b/packages/demobank-ui/src/pages/admin/Home.tsx index 625a49d45..5033b7fdc 100644 --- a/packages/demobank-ui/src/pages/admin/Home.tsx +++ b/packages/demobank-ui/src/pages/admin/Home.tsx @@ -35,7 +35,7 @@ export function AdminHome({ onRegister }: Props): VNode { if (action) { switch (action.type) { - case "show-details": return { @@ -93,7 +93,7 @@ export function AdminHome({ onRegister }: Props): VNode { setAction(undefined); }} /> - case "show-cashouts-details": return {