refactoring transaction component to standard component with test and examples
This commit is contained in:
parent
d0dd7a155f
commit
8d8d71807d
packages/demobank-ui
build.mjs
src
@ -44,7 +44,29 @@ const preactCompatPlugin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const entryPoints = ["src/index.tsx", "src/stories.tsx"];
|
function getFilesInDirectory(startPath, regex) {
|
||||||
|
if (!fs.existsSync(startPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const files = fs.readdirSync(startPath);
|
||||||
|
const result = files.flatMap(file => {
|
||||||
|
const filename = path.join(startPath, file);
|
||||||
|
|
||||||
|
const stat = fs.lstatSync(filename);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
return getFilesInDirectory(filename, regex);
|
||||||
|
}
|
||||||
|
else if (regex.test(filename)) {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
}).filter(x => !!x)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTestFiles = getFilesInDirectory(path.join(BASE, 'src'), /.test.ts$/)
|
||||||
|
|
||||||
|
const entryPoints = ["src/index.tsx", "src/stories.tsx", ...allTestFiles];
|
||||||
|
|
||||||
let GIT_ROOT = BASE;
|
let GIT_ROOT = BASE;
|
||||||
while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
|
while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
|
||||||
@ -128,6 +150,7 @@ export const buildConfig = {
|
|||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
jsxFactory: "h",
|
jsxFactory: "h",
|
||||||
jsxFragment: "Fragment",
|
jsxFragment: "Fragment",
|
||||||
|
external: ["async_hooks"],
|
||||||
define: {
|
define: {
|
||||||
__VERSION__: `"${_package.version}"`,
|
__VERSION__: `"${_package.version}"`,
|
||||||
__GIT_HASH__: `"${GIT_HASH}"`,
|
__GIT_HASH__: `"${GIT_HASH}"`,
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Loading } from "../../components/Loading.js";
|
import { Loading } from "../../components/Loading.js";
|
||||||
import { HookError } from "../../hooks/useAsyncAsHook.js";
|
import { HookError, utils } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { compose, StateViewMap } from "../../utils/index.js";
|
//import { compose, StateViewMap } from "../../utils/index.js";
|
||||||
import { wxApi } from "../../wxApi.js";
|
//import { wxApi } from "../../wxApi.js";
|
||||||
import { useComponentState } from "./state.js";
|
import { useComponentState } from "./state.js";
|
||||||
import { LoadingUriView, ReadyView } from "./views.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: Loading,
|
||||||
"loading-error": LoadingUriView,
|
"loading-error": LoadingUriView,
|
||||||
ready: ReadyView,
|
ready: ReadyView,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ComponentName = compose(
|
export const ComponentName = utils.compose(
|
||||||
"ComponentName",
|
(p: Props) => useComponentState(p),
|
||||||
(p: Props) => useComponentState(p, wxApi),
|
|
||||||
viewMapping,
|
viewMapping,
|
||||||
);
|
);
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
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";
|
import { Props, State } from "./index.js";
|
||||||
|
|
||||||
export function useComponentState({ p }: Props, api: typeof wxApi): State {
|
export function useComponentState({ p }: Props): State {
|
||||||
return {
|
return {
|
||||||
status: "ready",
|
status: "ready",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @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";
|
import { ReadyView } from "./views.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "example",
|
title: "example",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Ready = createExample(ReadyView, {});
|
export const Ready = tests.createExample(ReadyView, {});
|
||||||
|
@ -15,18 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { LoadingError } from "../../components/LoadingError.js";
|
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { useTranslationContext } from "../../context/translation.js";
|
|
||||||
import { State } from "./index.js";
|
import { State } from "./index.js";
|
||||||
|
|
||||||
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
export function LoadingUriView({ error }: State.LoadingUriError): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingError
|
<div>
|
||||||
title={<i18n.Translate>Could not load</i18n.Translate>}
|
<i18n.Translate>Could not load</i18n.Translate>
|
||||||
error={error}
|
</div>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
packages/demobank-ui/src/components/Transactions/index.ts
Normal file
71
packages/demobank-ui/src/components/Transactions/index.ts
Normal file
@ -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,
|
||||||
|
);
|
133
packages/demobank-ui/src/components/Transactions/state.ts
Normal file
133
packages/demobank-ui/src/components/Transactions/state.ts
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
45
packages/demobank-ui/src/components/Transactions/stories.tsx
Normal file
45
packages/demobank-ui/src/components/Transactions/stories.tsx
Normal file
@ -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(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
174
packages/demobank-ui/src/components/Transactions/test.ts
Normal file
174
packages/demobank-ui/src/components/Transactions/test.ts
Normal file
@ -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" })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
68
packages/demobank-ui/src/components/Transactions/views.tsx
Normal file
68
packages/demobank-ui/src/components/Transactions/views.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
17
packages/demobank-ui/src/components/index.examples.ts
Normal file
17
packages/demobank-ui/src/components/index.examples.ts
Normal file
@ -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";
|
37
packages/demobank-ui/src/endpoints.ts
Normal file
37
packages/demobank-ui/src/endpoints.ts
Normal file
@ -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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TRANSACTION_API_EXAMPLE = {
|
||||||
|
LIST_FIRST_PAGE: {
|
||||||
|
method: "get" as const,
|
||||||
|
url: "access-api/accounts/myAccount/transactions?page=0",
|
||||||
|
},
|
||||||
|
LIST_ERROR: {
|
||||||
|
method: "get" as const,
|
||||||
|
url: "access-api/accounts/myAccount/transactions?page=0",
|
||||||
|
code: 500
|
||||||
|
},
|
||||||
|
LIST_NOT_FOUND: {
|
||||||
|
method: "get" as const,
|
||||||
|
url: "access-api/accounts/myAccount/transactions?page=0",
|
||||||
|
code: 404
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ import { getIbanFromPayto, prepareHeaders } from "../utils.js";
|
|||||||
import { BankFrame } from "./BankFrame.js";
|
import { BankFrame } from "./BankFrame.js";
|
||||||
import { LoginForm } from "./LoginForm.js";
|
import { LoginForm } from "./LoginForm.js";
|
||||||
import { PaymentOptions } from "./PaymentOptions.js";
|
import { PaymentOptions } from "./PaymentOptions.js";
|
||||||
import { Transactions } from "./Transactions.js";
|
import { Transactions } from "../components/Transactions/index.js";
|
||||||
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
|
import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
|
||||||
|
|
||||||
export function AccountPage(): VNode {
|
export function AccountPage(): VNode {
|
||||||
|
@ -24,7 +24,7 @@ import { PageStateType, usePageContext } from "../context/pageState.js";
|
|||||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
import { getBankBackendBaseUrl } from "../utils.js";
|
import { getBankBackendBaseUrl } from "../utils.js";
|
||||||
import { BankFrame } from "./BankFrame.js";
|
import { BankFrame } from "./BankFrame.js";
|
||||||
import { Transactions } from "./Transactions.js";
|
import { Transactions } from "../components/Transactions/index.js";
|
||||||
|
|
||||||
const logger = new Logger("PublicHistoriesPage");
|
const logger = new Logger("PublicHistoriesPage");
|
||||||
|
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
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 { Logger } from "@gnu-taler/taler-util";
|
|
||||||
import { h, VNode } from "preact";
|
|
||||||
import { useEffect } from "preact/hooks";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
|
||||||
|
|
||||||
const logger = new Logger("Transactions");
|
|
||||||
/**
|
|
||||||
* Show one page of transactions.
|
|
||||||
*/
|
|
||||||
export function Transactions({
|
|
||||||
pageNumber,
|
|
||||||
accountLabel,
|
|
||||||
balanceValue,
|
|
||||||
}: {
|
|
||||||
pageNumber: number;
|
|
||||||
accountLabel: string;
|
|
||||||
balanceValue?: string;
|
|
||||||
}): VNode {
|
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
const { data, error, mutate } = useSWR(
|
|
||||||
`access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
if (balanceValue) {
|
|
||||||
mutate();
|
|
||||||
}
|
|
||||||
}, [balanceValue ?? ""]);
|
|
||||||
if (typeof error !== "undefined") {
|
|
||||||
logger.error("transactions not found error", error);
|
|
||||||
switch (error.status) {
|
|
||||||
case 404: {
|
|
||||||
return <p>Transactions page {pageNumber} was not found.</p>;
|
|
||||||
}
|
|
||||||
case 401: {
|
|
||||||
return <p>Wrong credentials given.</p>;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return <p>Transaction page {pageNumber} could not be retrieved.</p>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!data) {
|
|
||||||
logger.trace(`History data of ${accountLabel} not arrived`);
|
|
||||||
return <p>Transactions page loading...</p>;
|
|
||||||
}
|
|
||||||
logger.trace(`History data of ${accountLabel}`, data);
|
|
||||||
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>
|
|
||||||
{data.transactions.map((item: any, idx: number) => {
|
|
||||||
const sign = item.direction == "DBIT" ? "-" : "";
|
|
||||||
const counterpart =
|
|
||||||
item.direction == "DBIT" ? item.creditorIban : item.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(item.date);
|
|
||||||
const date =
|
|
||||||
dateParse !== null
|
|
||||||
? `${dateParse[3]}/${dateParse[2]} ${dateParse[1]}`
|
|
||||||
: "date not found";
|
|
||||||
return (
|
|
||||||
<tr key={idx}>
|
|
||||||
<td>{date}</td>
|
|
||||||
<td>
|
|
||||||
{sign}
|
|
||||||
{item.amount} {item.currency}
|
|
||||||
</td>
|
|
||||||
<td>{counterpart}</td>
|
|
||||||
<td>{item.subject}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -21,6 +21,7 @@
|
|||||||
import { strings } from "./i18n/strings.js";
|
import { strings } from "./i18n/strings.js";
|
||||||
|
|
||||||
import * as pages from "./pages/index.stories.js";
|
import * as pages from "./pages/index.stories.js";
|
||||||
|
import * as components from "./components/index.examples.js";
|
||||||
|
|
||||||
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
|
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ function SortStories(a: any, b: any): number {
|
|||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
renderStories(
|
renderStories(
|
||||||
{ pages },
|
{ pages, components },
|
||||||
{
|
{
|
||||||
strings,
|
strings,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user