gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated (5de299e -> 4d9b35b)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (5de299e -> 4d9b35b)
Date: Mon, 10 Jan 2022 19:29:58 +0100

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository merchant-backoffice.

    from 5de299e  mocking local storage in tests
     new 5ef9192  finish addressing state persistence
     new 4d9b35b  transactions list pagination (WIP)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/bank/src/hooks/index.ts          |   1 +
 packages/bank/src/pages/home/index.tsx    | 166 +++++++++++++++++++++++-------
 packages/bank/tests/__tests__/homepage.js |  55 ++++++++--
 3 files changed, 178 insertions(+), 44 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 50c8c1d..5401987 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -4,7 +4,7 @@ import { useState, useEffect, StateUpdater } from 
"preact/hooks";
 import { Buffer } from "buffer";
 import { useTranslator, Translate } from "../../i18n";
 import { QR } from "../../components/QR";
-import { useNotNullLocalStorage } from "../../hooks";
+import { useNotNullLocalStorage, useLocalStorage } from "../../hooks";
 
 /**********************************************
  * Type definitions for states and API calls. *
@@ -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.
  */
@@ -92,8 +103,14 @@ type BackendStateTypeOpt = BackendStateType | undefined;
 function useBackendState(
   state?: BackendStateType
 ): [BackendStateTypeOpt, StateUpdater<BackendStateTypeOpt>] {
-  if (state) return useState<BackendStateTypeOpt>(state);
-  return useState<BackendStateTypeOpt>();
+
+  const ret = useLocalStorage("backend-state", JSON.stringify(state));
+  const retObj: BackendStateTypeOpt = ret[0] ? JSON.parse(ret[0]) : ret[0];
+  const retSetter: StateUpdater<BackendStateTypeOpt> = function(val) {
+    const newVal = val instanceof Function ? JSON.stringify(val(retObj)) : 
JSON.stringify(val)
+    ret[1](newVal)
+  }
+  return [retObj, retSetter]
 }
 
 /**
@@ -104,10 +121,15 @@ type AccountStateTypeOpt = AccountStateType | undefined;
 function useAccountState(
   state?: AccountStateType
 ): [AccountStateTypeOpt, StateUpdater<AccountStateTypeOpt>] {
-  if (state) return useState<AccountStateTypeOpt>(state);
-  return useState<AccountStateTypeOpt>();
-}
 
+  const ret = useLocalStorage("account-state", JSON.stringify(state));
+  const retObj: AccountStateTypeOpt = ret[0] ? JSON.parse(ret[0]) : ret[0];
+  const retSetter: StateUpdater<AccountStateTypeOpt> = function(val) {
+    const newVal = val instanceof Function ? JSON.stringify(val(retObj)) : 
JSON.stringify(val)
+    ret[1](newVal)
+  }
+  return [retObj, retSetter]
+}
 
 /**
  * Wrapper providing defaults.
@@ -123,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];
@@ -285,7 +308,6 @@ async function confirmWithdrawalCall(
   } else {
     console.log("Withdrawal operation confirmed!");
     pageStateSetter((prevState) => {
-      delete prevState.talerWithdrawUri;
       const { talerWithdrawUri, ...rest } = prevState;
       return {
         ...rest,
@@ -465,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>
@@ -486,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
@@ -509,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>);
 }
 
@@ -585,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 {
@@ -596,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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]