[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 02/02: transactions list pagination (WIP)
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 02/02: transactions list pagination (WIP) |
Date: |
Mon, 10 Jan 2022 19:30:00 +0100 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository merchant-backoffice.
commit 4d9b35bb4c153b9bf98cd3d8eeccda828d3aea29
Author: ms <ms@taler.net>
AuthorDate: Mon Jan 10 19:29:50 2022 +0100
transactions list pagination (WIP)
---
packages/bank/src/hooks/index.ts | 1 +
packages/bank/src/pages/home/index.tsx | 143 +++++++++++++++++++++++-------
packages/bank/tests/__tests__/homepage.js | 55 ++++++++++--
3 files changed, 161 insertions(+), 38 deletions(-)
diff --git a/packages/bank/src/hooks/index.ts b/packages/bank/src/hooks/index.ts
index 9a1b50a..17a9d1d 100644
--- a/packages/bank/src/hooks/index.ts
+++ b/packages/bank/src/hooks/index.ts
@@ -115,6 +115,7 @@ export function useNotNullLocalStorage(
key: string,
initialValue: string,
): [string, StateUpdater<string>] {
+
const [storedValue, setStoredValue] = useState<string>((): string => {
return typeof window !== "undefined"
? window.localStorage.getItem(key) || initialValue
diff --git a/packages/bank/src/pages/home/index.tsx
b/packages/bank/src/pages/home/index.tsx
index 61cf781..5401987 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -57,6 +57,17 @@ interface AccountStateType {
* Helpers. *
***********/
+function useTransactionPageNumber(): [number, StateUpdater<number>] {
+
+ const ret = useNotNullLocalStorage("transaction-page", "0");
+ const retObj = JSON.parse(ret[0]);
+ const retSetter: StateUpdater<number> = function(val) {
+ const newVal = val instanceof Function ? JSON.stringify(val(retObj)) :
JSON.stringify(val)
+ ret[1](newVal)
+ }
+ return [retObj, retSetter];
+}
+
/**
* Craft headers with Authorization and Content-Type.
*/
@@ -134,6 +145,7 @@ function usePageState(
const retObj: PageStateType = JSON.parse(ret[0]);
const retSetter: StateUpdater<PageStateType> = function(val) {
const newVal = val instanceof Function ? JSON.stringify(val(retObj)) :
JSON.stringify(val)
+ console.log("setting new page state", newVal)
ret[1](newVal)
}
return [retObj, retSetter];
@@ -296,7 +308,6 @@ async function confirmWithdrawalCall(
} else {
console.log("Withdrawal operation confirmed!");
pageStateSetter((prevState) => {
- delete prevState.talerWithdrawUri;
const { talerWithdrawUri, ...rest } = prevState;
return {
...rest,
@@ -476,15 +487,52 @@ async function registrationCall(
* Functional components. *
*************************/
+/**
+ * Show list of transactions.
+ */
+function Transactions(Props: any): VNode {
+
+ const { pageNumber, accountLabel } = Props;
+ const { data, error } = useSWR(
+ `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`
+ );
+ if (typeof error !== "undefined") {
+ console.log("balance 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>
+ }
+ }
+ }
+ var txsPages = <p>"loading..."</p>
+
+ if (data) {
+ txsPages = data.map((item: any) => <div>Result</div>)
+ }
+ return txsPages;
+}
+
/**
* Show only the account's balance.
*/
-function Account(props: any) {
- const { withdrawalOutcome, talerWithdrawUri, accountLabel } = props;
- const { data, error } = useSWR(`access-api/accounts/${props.accountLabel}`);
- console.log("account data", data);
- console.log("account error", error);
+function Account(Props: any): VNode {
+ const {
+ withdrawalOutcome,
+ talerWithdrawUri,
+ accountLabel } = Props;
+ const i18n = useTranslator();
+ /**
+ * Getting the bank account balance.
+ */
+ const { data, error } = useSWR(`access-api/accounts/${accountLabel}`);
if (typeof error !== "undefined") {
+ console.log("account error", error);
switch(error.status) {
case 404: {
return <p>Username was not found</p>
@@ -497,18 +545,25 @@ function Account(props: any) {
}
}
}
-
if (!data) return <p>Retrieving the profile page...</p>;
+
+ /**
+ * Withdrawal reached a final state: show it.
+ */
if (withdrawalOutcome) {
return <Fragment>
<p>{withdrawalOutcome}</p>
- {props.children}
+ {Props.children}
</Fragment>
}
+
/**
- * A Taler withdrawal replaces everything in the page and
- * starts polling the backend until either the wallet selected
- * a exchange and reserve public key, or a error / abort happened.
+ * This block shows the withdrawal QR code.
+ *
+ * A withdrawal operation replaces everything in the page and
+ * (ToDo:) starts polling the backend until either the wallet
+ * selected a exchange and reserve public key, or a error / abort
+ * happened.
*
* After reaching one of the above states, the user should be
* brought to this ("Account") page where they get informed about
@@ -520,12 +575,28 @@ function Account(props: any) {
<div>{QR({text: talerWithdrawUri})}</div>
<a href={talerWithdrawUri}></a>
<p>Withdraw address: <pre>{talerWithdrawUri}</pre></p>
- {props.children}
+ {Props.children}
</Fragment>);
}
+
+ /**
+ * This part shows a list of transactions: with 5 elements by
+ * default and offers a "load more" button.
+ */
+ var [txPageNumber, setTxPageNumber] = useTransactionPageNumber() // Buggy.
+ var txsPages = []
+ for (let i = 0; i <= txPageNumber; i++) {
+ txsPages.push(<Transactions accountLabel={Props.accountLabel}
pageNumber={txPageNumber} />)
+ }
+
return (<Fragment>
<p>Your balance is {data.balance.amount}.</p>
- {props.children}
+ <div>
+ <span>{i18n`Last transactions:`}</span> { txsPages }
+ <button onClick={() => setTxPageNumber(txPageNumber + 1)}>{i18n`Load
more`}</button>
+ <button onClick={() => setTxPageNumber(0)}>{i18n`Reset`}</button>
+ </div>
+ {Props.children}
</Fragment>);
}
@@ -596,7 +667,8 @@ export function BankHome(): VNode {
withdrawalOutcome={pageState.withdrawalOutcome}
talerWithdrawUri={pageState.talerWithdrawUri}
accountLabel={backendState.username}>
-
+
+ { /* The user is logged in: offer to log out. */ }
<button onClick={() => {
pageStateSetter((prevState) => {
const {
@@ -607,30 +679,41 @@ export function BankHome(): VNode {
})
}}>Sign out</button>
- {!pageState.withdrawalInProgress && <button onClick={() => {
- createWithdrawalCall(
- "EUR:5",
- backendState,
- pageStateSetter
- )}}>{i18n`Charge Taler wallet`}</button>
+ { /**
+ * No withdrawal is happening: offer to start one.
+ */
+ !pageState.withdrawalInProgress && <button onClick={() => {
+ createWithdrawalCall(
+ "EUR:5",
+ backendState,
+ pageStateSetter
+ )}}>{i18n`Charge Taler wallet`}</button>
}
- {pageState.withdrawalOutcome && <button onClick={() => {
+ { /**
+ * Withdrawal reached a persisten state: offer to
+ * return back to the pristine profile page.
+ */
+ pageState.withdrawalOutcome && <button onClick={() => {
pageStateSetter((prevState) => {
- const { withdrawalOutcome, ...rest } = prevState;
+ const { withdrawalOutcome, withdrawalId, ...rest } = prevState;
return {...rest, withdrawalInProgress:
false};})}}>{i18n`Close`}</button>
}
- {pageState.talerWithdrawUri && <div><button onClick={() => {
- confirmWithdrawalCall(
- backendState,
- pageState.withdrawalId,
- pageStateSetter);}}>{i18n`Confirm withdrawal`}</button>
- <button onClick={() => {
- abortWithdrawalCall(
+ { /**
+ * The withdrawal QR code is rendered: offer to confirm
+ * or abort the operation.
+ */
+ pageState.talerWithdrawUri && <div><button onClick={() => {
+ confirmWithdrawalCall(
backendState,
pageState.withdrawalId,
- pageStateSetter);}}>{i18n`Abort withdrawal`}</button>
+ pageStateSetter);}}>{i18n`Confirm withdrawal`}</button>
+ <button onClick={() => {
+ abortWithdrawalCall(
+ backendState,
+ pageState.withdrawalId,
+ pageStateSetter);}}>{i18n`Abort withdrawal`}</button>
</div>}
</Account>
</SWRWithCredentials>
diff --git a/packages/bank/tests/__tests__/homepage.js
b/packages/bank/tests/__tests__/homepage.js
index 0145522..f1045f1 100644
--- a/packages/bank/tests/__tests__/homepage.js
+++ b/packages/bank/tests/__tests__/homepage.js
@@ -16,6 +16,34 @@ jest.mock("../../src/i18n")
const i18n = require("../../src/i18n")
i18n.useTranslator.mockImplementation(() => function(arg) {return arg})
+/**
+ * Mocking local storage, see:
+ *
https://stackoverflow.com/questions/32911630/how-do-i-deal-with-localstorage-in-jest-tests
+ */
+class LocalStorageMock {
+ constructor() {
+ this.store = {};
+ }
+
+ clear() {
+ this.store = {};
+ }
+
+ getItem(key) {
+ return this.store[key] || null;
+ }
+
+ setItem(key, value) {
+ this.store[key] = String(value);
+ }
+
+ removeItem(key) {
+ delete this.store[key];
+ }
+}
+
+global.localStotage = new LocalStorageMock();
+
beforeAll(() => {
Object.defineProperty(window, 'location', {
value: {
@@ -23,10 +51,9 @@ beforeAll(() => {
pathname: "/demobanks/default"
}
})
- // Invalidating local storage: makes it more difficult
- // to isolate the individual tests, and it doesn't really
- // participate in the SPA logic.
- global.Storage.prototype.setItem = jest.fn((key, value) => {})
+})
+afterAll(() => {
+ global.localStorage.clear()
})
/**
@@ -135,9 +162,13 @@ describe("withdraw", () => {
)
// assume wallet POSTed the payment details.
const confirmButton = await screen.findByText("confirm withdrawal",
{exact: false})
+ /**
+ * Not expecting a new withdrawal possibility while one is being processed.
+ */
await waitFor(() => expect(
screen.queryByText("charge taler wallet", {exact:
false})).not.toBeInTheDocument());
fetch.once("{}")
+ // Confirm currently processed withdrawal.
fireEvent.click(confirmButton);
/**
* After having confirmed above, wait that the
@@ -161,15 +192,23 @@ describe("withdraw", () => {
await screen.findByText("withdrawal confirmed", {exact: false})
/**
- * Click on a "return to homepage" button, and check that
- * the withdrawal confirmation is gone, and the option to
- * withdraw again reappeared.
+ * Click on a "return to homepage / close" button, and
+ * check that the withdrawal confirmation is gone, and
+ * the option to withdraw again reappeared.
*/
const closeButton = await screen.findByText("close", {exact: false})
fireEvent.click(closeButton);
+
+ /**
+ * After closing the operation, the confirmation message is not expected.
+ */
await waitFor(() => expect(
screen.queryByText("withdrawal confirmed", {exact:
false})).not.toBeInTheDocument()
);
+
+ /**
+ * After closing the operation, the possibility to withdraw again should
be offered.
+ */
await waitFor(() => expect(
screen.queryByText(
"charge taler wallet",
@@ -275,7 +314,7 @@ describe("home page", () => {
"http://localhost/demobanks/default/access-api/testing/register",
expect.anything() // no need to match auth headers.
)
- expect(fetch).toHaveBeenLastCalledWith(
+ expect(fetch).toHaveBeenCalledWith(
`http://localhost/demobanks/default/access-api/accounts/${username}`,
expect.anything() // no need to match auth headers.
)
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.