diff options
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  | 
