diff options
author | Sebastian <sebasjm@gmail.com> | 2022-12-14 15:35:28 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-12-14 15:35:28 -0300 |
commit | 8d8d71807df6b775e5b0335eb1b2526a56d42ac6 (patch) | |
tree | 6b4a15698e8119d66995ead2e7e69e2fb8938450 /packages/demobank-ui/src/components | |
parent | d0dd7a155fcb63dc4d4859f51a681fc084ec4d7a (diff) | |
download | wallet-core-8d8d71807df6b775e5b0335eb1b2526a56d42ac6.tar.xz |
refactoring transaction component to standard component with test and examples
Diffstat (limited to 'packages/demobank-ui/src/components')
10 files changed, 522 insertions, 17 deletions
diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts index 4b7725264..d9f231019 100644 --- a/packages/demobank-ui/src/components/EmptyComponentExample/index.ts +++ b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts @@ -15,9 +15,9 @@ */ import { Loading } from "../../components/Loading.js"; -import { HookError } from "../../hooks/useAsyncAsHook.js"; -import { compose, StateViewMap } from "../../utils/index.js"; -import { wxApi } from "../../wxApi.js"; +import { HookError, utils } from "@gnu-taler/web-util/lib/index.browser"; +//import { compose, StateViewMap } from "../../utils/index.js"; +//import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; @@ -47,14 +47,13 @@ export namespace State { } } -const viewMapping: StateViewMap<State> = { +const viewMapping: utils.StateViewMap<State> = { loading: Loading, "loading-error": LoadingUriView, ready: ReadyView, }; -export const ComponentName = compose( - "ComponentName", - (p: Props) => useComponentState(p, wxApi), +export const ComponentName = utils.compose( + (p: Props) => useComponentState(p), viewMapping, ); diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/state.ts b/packages/demobank-ui/src/components/EmptyComponentExample/state.ts index d194b3f97..e147a7ccf 100644 --- a/packages/demobank-ui/src/components/EmptyComponentExample/state.ts +++ b/packages/demobank-ui/src/components/EmptyComponentExample/state.ts @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { wxApi } from "../../wxApi.js"; +// import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; -export function useComponentState({ p }: Props, api: typeof wxApi): State { +export function useComponentState({ p }: Props): State { return { status: "ready", error: undefined, diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx b/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx index 696e424c4..e157e6e6f 100644 --- a/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx +++ b/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { createExample } from "../../test-utils.js"; +import { tests } from "@gnu-taler/web-util/lib/index.browser"; import { ReadyView } from "./views.js"; export default { title: "example", }; -export const Ready = createExample(ReadyView, {}); +export const Ready = tests.createExample(ReadyView, {}); diff --git a/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx b/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx index 5784a7db5..e125ff415 100644 --- a/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx +++ b/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx @@ -15,18 +15,16 @@ */ import { h, VNode } from "preact"; -import { LoadingError } from "../../components/LoadingError.js"; -import { useTranslationContext } from "../../context/translation.js"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; 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} - /> + <div> + <i18n.Translate>Could not load</i18n.Translate> + </div> ); } diff --git a/packages/demobank-ui/src/components/Transactions/index.ts b/packages/demobank-ui/src/components/Transactions/index.ts new file mode 100644 index 000000000..618fcfb71 --- /dev/null +++ b/packages/demobank-ui/src/components/Transactions/index.ts @@ -0,0 +1,71 @@ +/* + 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 "../Loading.js"; +import { HookError, utils } from "@gnu-taler/web-util/lib/index.browser"; +// import { compose, StateViewMap } from "../../utils/index.js"; +// import { wxApi } from "../../wxApi.js"; +import { useComponentState } from "./state.js"; +import { LoadingUriView, ReadyView } from "./views.js"; +import { AbsoluteTime, AmountJson } from "@gnu-taler/taler-util"; + +export interface Props { + pageNumber: number; + accountLabel: string; + balanceValue?: 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-error"; + error: HookError; + } + + export interface BaseInfo { + error: undefined; + } + export interface Ready extends BaseInfo { + status: "ready"; + error: undefined; + transactions: Transaction[]; + } +} + +export interface Transaction { + negative: boolean; + counterpart: string; + when: AbsoluteTime; + amount: AmountJson; + subject: string; +} + +const viewMapping: utils.StateViewMap<State> = { + loading: Loading, + "loading-error": LoadingUriView, + ready: ReadyView, +}; + +export const Transactions = utils.compose( + (p: Props) => useComponentState(p), + viewMapping, +); diff --git a/packages/demobank-ui/src/components/Transactions/state.ts b/packages/demobank-ui/src/components/Transactions/state.ts new file mode 100644 index 000000000..ac76e31e2 --- /dev/null +++ b/packages/demobank-ui/src/components/Transactions/state.ts @@ -0,0 +1,133 @@ +/* + 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 { AbsoluteTime, Amounts } from "@gnu-taler/taler-util"; +import { parse } from "date-fns"; +import { useEffect } from "preact/hooks"; +import useSWR from "swr"; +import { Props, State } from "./index.js"; + +export function useComponentState({ accountLabel, pageNumber, balanceValue }: Props): State { + const { data, error, mutate } = useSWR( + `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`, + ); + + useEffect(() => { + if (balanceValue) { + mutate(); + } + }, [balanceValue ?? ""]); + + if (error) { + switch (error.status) { + case 404: + return { + status: "loading-error", + error: { + hasError: true, + operational: false, + message: `Transactions page ${pageNumber} was not found.` + } + } + case 401: + return { + status: "loading-error", + error: { + hasError: true, + operational: false, + message: "Wrong credentials given." + } + } + default: + return { + status: "loading-error", + error: { + hasError: true, + operational: false, + message: `Transaction page ${pageNumber} could not be retrieved.` + } as any + } + } + } + + if (!data) { + return { + status: "loading", + error: undefined + } + } + + + const transactions = data.transactions.map((item: unknown) => { + if (!item || typeof item !== "object" || + !("direction" in item) || + !("creditorIban" in item) || + !("debtorIban" in item) || + !("date" in item) || + !("subject" in item) || + !("currency" in item) || + !("amount" in item) + ) { + //not valid + return; + } + const anyItem = item as any; + if ( + !(typeof anyItem.creditorIban === 'string') || + !(typeof anyItem.debtorIban === 'string') || + !(typeof anyItem.date === 'string') || + !(typeof anyItem.subject === 'string') || + !(typeof anyItem.currency === 'string') || + !(typeof anyItem.amount === 'string') + ) { + return; + } + + const negative = anyItem.direction === "DBIT"; + const counterpart = negative ? anyItem.creditorIban : anyItem.debtorIban; + // Pattern: + // + // DD/MM YYYY subject -5 EUR + // DD/MM YYYY subject 5 EUR + const dateRegex = /^([0-9]{4})-([0-9]{2})-([0-9]{1,2})/; + const dateParse = dateRegex.exec(anyItem.date); + const dateStr = + dateParse !== null + ? `${dateParse[3]}/${dateParse[2]} ${dateParse[1]}` + : undefined; + + const date = parse(dateStr ?? "", "dd/MM yyyy", new Date()) + + const when: AbsoluteTime = { + t_ms: date.getTime() + } + const amount = Amounts.parseOrThrow(`${anyItem.currency}:${anyItem.amount}`); + const subject = anyItem.subject; + return { + negative, + counterpart, + when, + amount, + subject, + } + }); + + return { + status: "ready", + error: undefined, + transactions, + }; +} diff --git a/packages/demobank-ui/src/components/Transactions/stories.tsx b/packages/demobank-ui/src/components/Transactions/stories.tsx new file mode 100644 index 000000000..77fdde092 --- /dev/null +++ b/packages/demobank-ui/src/components/Transactions/stories.tsx @@ -0,0 +1,45 @@ +/* + 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 { tests } from "@gnu-taler/web-util/lib/index.browser"; +import { ReadyView } from "./views.js"; + +export default { + title: "transaction list", +}; + +export const Ready = tests.createExample(ReadyView, { + transactions: [ + { + amount: { + currency: "USD", + fraction: 0, + value: 1, + }, + counterpart: "ASD", + negative: false, + subject: "Some", + when: { + t_ms: new Date().getTime(), + }, + }, + ], +}); diff --git a/packages/demobank-ui/src/components/Transactions/test.ts b/packages/demobank-ui/src/components/Transactions/test.ts new file mode 100644 index 000000000..b746f6bb7 --- /dev/null +++ b/packages/demobank-ui/src/components/Transactions/test.ts @@ -0,0 +1,174 @@ +/* + 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 { tests } from "@gnu-taler/web-util/lib/index.browser"; +import { SwrMockEnvironment } from "@gnu-taler/web-util/lib/tests/swr"; +import { expect } from "chai"; +import { TRANSACTION_API_EXAMPLE } from "../../endpoints.js"; +import { Props } from "./index.js"; +import { useComponentState } from "./state.js"; + + +describe("Transaction states", () => { + + it("should query backend and render transactions", async () => { + + const env = new SwrMockEnvironment(); + + const props: Props = { + accountLabel: "myAccount", + pageNumber: 0 + } + + env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_FIRST_PAGE, { + response: { + "transactions": [ + { + "creditorIban": "DE159593", + "creditorBic": "SANDBOXX", + "creditorName": "exchange company", + "debtorIban": "DE118695", + "debtorBic": "SANDBOXX", + "debtorName": "Name unknown", + "amount": "1", + "currency": "KUDOS", + "subject": "Taler Withdrawal N588V8XE9TR49HKAXFQ20P0EQ0EYW2AC9NNANV8ZP5P59N6N0410", + "date": "2022-12-12Z", + "uid": "8PPFR9EM", + "direction": "DBIT", + "pmtInfId": null, + "msgId": null + }, + { + "creditorIban": "DE159593", + "creditorBic": "SANDBOXX", + "creditorName": "exchange company", + "debtorIban": "DE118695", + "debtorBic": "SANDBOXX", + "debtorName": "Name unknown", + "amount": "5.00", + "currency": "KUDOS", + "subject": "HNEWWT679TQC5P1BVXJS48FX9NW18FWM6PTK2N80Z8GVT0ACGNK0", + "date": "2022-12-07Z", + "uid": "7FZJC3RJ", + "direction": "DBIT", + "pmtInfId": null, + "msgId": null + }, + { + "creditorIban": "DE118695", + "creditorBic": "SANDBOXX", + "creditorName": "Name unknown", + "debtorIban": "DE579516", + "debtorBic": "SANDBOXX", + "debtorName": "The Bank", + "amount": "100", + "currency": "KUDOS", + "subject": "Sign-up bonus", + "date": "2022-12-07Z", + "uid": "I31A06J8", + "direction": "CRDT", + "pmtInfId": null, + "msgId": null + } + ] + } + }); + + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + + expect(status).equals("ready"); + expect(error).undefined; + }, + ], env.buildTestingContext()) + + expect(hookBehavior).deep.eq({ result: "ok" }) + + expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" }) + }); + + it("should show error message on not found", async () => { + + const env = new SwrMockEnvironment(); + + const props: Props = { + accountLabel: "myAccount", + pageNumber: 0 + } + + env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_NOT_FOUND, {}); + + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + expect(status).equals("loading-error"); + expect(error).deep.eq({ + hasError: true, + operational: false, + message: "Transactions page 0 was not found." + }); + }, + ], env.buildTestingContext()) + + expect(hookBehavior).deep.eq({ result: "ok" }) + expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" }) + }); + + it("should show error message on server error", async () => { + + const env = new SwrMockEnvironment(false); + + const props: Props = { + accountLabel: "myAccount", + pageNumber: 0 + } + + env.addRequestExpectation(TRANSACTION_API_EXAMPLE.LIST_ERROR, {}); + + const hookBehavior = await tests.hookBehaveLikeThis(useComponentState, props, [ + ({ status, error }) => { + expect(status).equals("loading"); + expect(error).undefined; + }, + ({ status, error }) => { + expect(status).equals("loading-error"); + expect(error).deep.equal({ + hasError: true, + operational: false, + message: "Transaction page 0 could not be retrieved." + }); + }, + ], env.buildTestingContext()) + + expect(hookBehavior).deep.eq({ result: "ok" }) + expect(env.assertJustExpectedRequestWereMade()).deep.eq({ result: "ok" }) + }); +}); + diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx b/packages/demobank-ui/src/components/Transactions/views.tsx new file mode 100644 index 000000000..b3683b743 --- /dev/null +++ b/packages/demobank-ui/src/components/Transactions/views.tsx @@ -0,0 +1,68 @@ +/* + 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 { h, VNode } from "preact"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { State } from "./index.js"; +import { format } from "date-fns"; +import { Amounts } from "@gnu-taler/taler-util"; + +export function LoadingUriView({ error }: State.LoadingUriError): VNode { + const { i18n } = useTranslationContext(); + + return ( + <div> + <i18n.Translate>Could not load</i18n.Translate> + </div> + ); +} + +export function ReadyView({ transactions }: State.Ready): VNode { + const { i18n } = useTranslationContext(); + return ( + <div class="results"> + <table class="pure-table pure-table-striped"> + <thead> + <tr> + <th>{i18n.str`Date`}</th> + <th>{i18n.str`Amount`}</th> + <th>{i18n.str`Counterpart`}</th> + <th>{i18n.str`Subject`}</th> + </tr> + </thead> + <tbody> + {transactions.map((item, idx) => { + return ( + <tr key={idx}> + <td> + {item.when.t_ms === "never" + ? "never" + : format(item.when.t_ms, "dd/MM/yyyy")} + </td> + <td> + {item.negative ? "-" : ""} + {Amounts.stringifyValue(item.amount)} {item.amount.currency} + </td> + <td>{item.counterpart}</td> + <td>{item.subject}</td> + </tr> + ); + })} + </tbody> + </table> + </div> + ); +} diff --git a/packages/demobank-ui/src/components/index.examples.ts b/packages/demobank-ui/src/components/index.examples.ts new file mode 100644 index 000000000..a741b413d --- /dev/null +++ b/packages/demobank-ui/src/components/index.examples.ts @@ -0,0 +1,17 @@ +/* + 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/> + */ + +export * as tx from "./Transactions/stories.js";
\ No newline at end of file |