gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (5fc8f95a5 -> 8d8d71807)


From: gnunet
Subject: [taler-wallet-core] branch master updated (5fc8f95a5 -> 8d8d71807)
Date: Wed, 14 Dec 2022 19:35:45 +0100

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

sebasjm pushed a change to branch master
in repository wallet-core.

    from 5fc8f95a5 simplify directories
     new 880961034 compose, testing and async into web-util
     new 14363d42e component example
     new ae2df08ab more stories in bank demo
     new e97c808b4 moving testing sdk to web-utils
     new d0dd7a155 same typescript version for every package, added test deps 
to demobank
     new 8d8d71807 refactoring transaction component to standard component with 
test and examples

The 6 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/demobank-ui/build.mjs                     |  25 +-
 packages/demobank-ui/package.json                  |  13 +-
 .../src/components}/EmptyComponentExample/index.ts |  13 +-
 .../src/components}/EmptyComponentExample/state.ts |   4 +-
 .../components}/EmptyComponentExample/stories.tsx  |   4 +-
 .../src/components}/EmptyComponentExample/test.ts  |   0
 .../components}/EmptyComponentExample/views.tsx    |  10 +-
 .../src/components/Loading.tsx}                    |   5 +-
 .../src/components/Transactions}/index.ts          |  29 +-
 .../src/components/Transactions/state.ts           | 133 +++
 .../src/components/Transactions}/stories.tsx       |  28 +-
 .../src/components/Transactions/test.ts            | 174 ++++
 .../src/components/Transactions/views.tsx          |  68 ++
 .../src/components/index.examples.ts}              |   2 +-
 .../stories.tsx => demobank-ui/src/endpoints.ts}   |  32 +-
 packages/demobank-ui/src/pages/AccountPage.tsx     |   2 +-
 .../src/pages/PaymentOptions.stories.tsx}          |  12 +-
 ...ories.tsx => PaytoWireTransferForm.stories.tsx} |  10 +-
 .../demobank-ui/src/pages/PublicHistoriesPage.tsx  |   2 +-
 packages/demobank-ui/src/pages/Transactions.tsx    | 106 ---
 packages/demobank-ui/src/pages/index.stories.tsx   |   2 +
 packages/demobank-ui/src/stories.tsx               |   3 +-
 packages/merchant-backoffice-ui/package.json       |   4 +-
 packages/web-util/build.mjs                        |   4 +-
 packages/web-util/package.json                     |   7 +-
 packages/web-util/src/components/index.ts          |   1 +
 packages/web-util/src/components/utils.ts          |  36 +
 packages/web-util/src/context/index.ts             |   7 +-
 packages/web-util/src/context/translation.ts       |   6 +-
 packages/web-util/src/hooks/index.ts               |  12 +-
 .../src/hooks/useAsyncAsHook.ts                    |  17 +-
 packages/web-util/src/hooks/useLocalStorage.ts     |  36 +-
 packages/web-util/src/index.browser.ts             |   2 +
 packages/web-util/src/live-reload.ts               |  24 +-
 packages/web-util/src/serve.ts                     |  25 +-
 packages/web-util/src/tests/axios.ts               | 136 +++
 packages/web-util/src/tests/hook.ts                | 310 +++++++
 packages/web-util/src/tests/index.ts               |   2 +
 packages/web-util/src/tests/mock.ts                | 458 ++++++++++
 packages/web-util/src/tests/swr.ts                 |  82 ++
 .../src/utils/axios.ts}                            |  55 +-
 packages/web-util/src/utils/index.ts               |   1 +
 pnpm-lock.yaml                                     | 926 +++------------------
 43 files changed, 1769 insertions(+), 1059 deletions(-)
 copy packages/{taler-wallet-webextension/src/wallet => 
demobank-ui/src/components}/EmptyComponentExample/index.ts (81%)
 copy packages/{taler-wallet-webextension/src/wallet => 
demobank-ui/src/components}/EmptyComponentExample/state.ts (87%)
 copy packages/{taler-wallet-webextension/src/wallet => 
demobank-ui/src/components}/EmptyComponentExample/stories.tsx (87%)
 copy packages/{taler-wallet-webextension/src/wallet => 
demobank-ui/src/components}/EmptyComponentExample/test.ts (100%)
 copy packages/{taler-wallet-webextension/src/wallet => 
demobank-ui/src/components}/EmptyComponentExample/views.tsx (80%)
 copy packages/{taler-wallet-webextension/src/mui/input/SelectFilled.tsx => 
demobank-ui/src/components/Loading.tsx} (91%)
 copy packages/{taler-wallet-webextension/src/wallet/EmptyComponentExample => 
demobank-ui/src/components/Transactions}/index.ts (66%)
 create mode 100644 packages/demobank-ui/src/components/Transactions/state.ts
 copy packages/{taler-wallet-webextension/src/cta/Deposit => 
demobank-ui/src/components/Transactions}/stories.tsx (66%)
 create mode 100644 packages/demobank-ui/src/components/Transactions/test.ts
 create mode 100644 packages/demobank-ui/src/components/Transactions/views.tsx
 copy packages/{taler-wallet-webextension/src/i18n/strings-prelude => 
demobank-ui/src/components/index.examples.ts} (93%)
 copy packages/{taler-wallet-webextension/src/cta/TransferPickup/stories.tsx => 
demobank-ui/src/endpoints.ts} (65%)
 copy 
packages/{taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
 => demobank-ui/src/pages/PaymentOptions.stories.tsx} (81%)
 copy packages/demobank-ui/src/pages/{QrCodeSection.stories.tsx => 
PaytoWireTransferForm.stories.tsx} (80%)
 delete mode 100644 packages/demobank-ui/src/pages/Transactions.tsx
 create mode 100644 packages/web-util/src/components/index.ts
 create mode 100644 packages/web-util/src/components/utils.ts
 copy packages/{taler-wallet-webextension => 
web-util}/src/hooks/useAsyncAsHook.ts (88%)
 create mode 100644 packages/web-util/src/tests/axios.ts
 create mode 100644 packages/web-util/src/tests/hook.ts
 create mode 100644 packages/web-util/src/tests/index.ts
 create mode 100644 packages/web-util/src/tests/mock.ts
 create mode 100644 packages/web-util/src/tests/swr.ts
 copy packages/{merchant-backoffice-ui/src/utils/switchableAxios.ts => 
web-util/src/utils/axios.ts} (55%)
 create mode 100644 packages/web-util/src/utils/index.ts

diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs
index c93b4eb67..f30fe5490 100755
--- a/packages/demobank-ui/build.mjs
+++ b/packages/demobank-ui/build.mjs
@@ -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;
 while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
@@ -128,6 +150,7 @@ export const buildConfig = {
   sourcemap: true,
   jsxFactory: "h",
   jsxFragment: "Fragment",
+  external: ["async_hooks"],
   define: {
     __VERSION__: `"${_package.version}"`,
     __GIT_HASH__: `"${GIT_HASH}"`,
diff --git a/packages/demobank-ui/package.json 
b/packages/demobank-ui/package.json
index 0be83c61c..2c1dced98 100644
--- a/packages/demobank-ui/package.json
+++ b/packages/demobank-ui/package.json
@@ -3,9 +3,12 @@
   "name": "@gnu-taler/demobank-ui",
   "version": "0.1.0",
   "license": "AGPL-3.0-OR-LATER",
+  "type": "module",
   "scripts": {
     "build": "./build.mjs",
     "check": "tsc",
+    "compile": "tsc && ./build.mjs",
+    "test": "pnpm compile && mocha --require source-map-support/register 
'dist/**/test.js'",
     "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
     "i18n:extract": "pogen extract",
     "i18n:merge": "pogen merge",
@@ -41,19 +44,25 @@
   "devDependencies": {
     "@creativebulma/bulma-tooltip": "^1.2.0",
     "@gnu-taler/pogen": "^0.0.5",
+    "@types/chai": "^4.3.0",
     "@types/history": "^4.7.8",
+    "@types/mocha": "^10.0.1",
+    "@types/node": "^18.11.14",
     "@typescript-eslint/eslint-plugin": "^5.41.0",
     "@typescript-eslint/parser": "^5.41.0",
+    "async_hooks": "^1.0.0",
     "bulma": "^0.9.4",
     "bulma-checkbox": "^1.1.1",
     "bulma-radio": "^1.1.1",
+    "chai": "^4.3.6",
     "esbuild": "^0.15.12",
     "eslint-config-preact": "^1.2.0",
+    "mocha": "^9.2.0",
     "po2json": "^0.4.5",
     "sass": "1.56.1",
-    "typescript": "^4.4.4"
+    "typescript": "4.8.4"
   },
   "pogen": {
     "domain": "bank"
   }
-}
\ No newline at end of file
+}
diff --git 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/index.ts
similarity index 81%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts
copy to packages/demobank-ui/src/components/EmptyComponentExample/index.ts
index 4b7725264..d9f231019 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/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/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/state.ts
similarity index 87%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/state.ts
copy to packages/demobank-ui/src/components/EmptyComponentExample/state.ts
index d194b3f97..e147a7ccf 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/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/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
 b/packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx
similarity index 87%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
copy to packages/demobank-ui/src/components/EmptyComponentExample/stories.tsx
index 696e424c4..e157e6e6f 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/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/taler-wallet-webextension/src/wallet/EmptyComponentExample/test.ts 
b/packages/demobank-ui/src/components/EmptyComponentExample/test.ts
similarity index 100%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/test.ts
copy to packages/demobank-ui/src/components/EmptyComponentExample/test.ts
diff --git 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx 
b/packages/demobank-ui/src/components/EmptyComponentExample/views.tsx
similarity index 80%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/views.tsx
copy to packages/demobank-ui/src/components/EmptyComponentExample/views.tsx
index 5784a7db5..e125ff415 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/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/taler-wallet-webextension/src/mui/input/SelectFilled.tsx 
b/packages/demobank-ui/src/components/Loading.tsx
similarity index 91%
copy from packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx
copy to packages/demobank-ui/src/components/Loading.tsx
index 0ae70d06a..8fd01858b 100644
--- a/packages/taler-wallet-webextension/src/mui/input/SelectFilled.tsx
+++ b/packages/demobank-ui/src/components/Loading.tsx
@@ -13,8 +13,9 @@
  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";
 
-export function SelectFilled(): VNode {
-  return <div />;
+export function Loading(): VNode {
+  return <div>loading...</div>;
 }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts 
b/packages/demobank-ui/src/components/Transactions/index.ts
similarity index 66%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts
copy to packages/demobank-ui/src/components/Transactions/index.ts
index 4b7725264..618fcfb71 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/index.ts
+++ b/packages/demobank-ui/src/components/Transactions/index.ts
@@ -14,15 +14,18 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-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 { 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 {
-  p: string;
+  pageNumber: number;
+  accountLabel: string;
+  balanceValue?: string;
 }
 
 export type State = State.Loading | State.LoadingUriError | State.Ready;
@@ -44,17 +47,25 @@ export namespace State {
   export interface Ready extends BaseInfo {
     status: "ready";
     error: undefined;
+    transactions: Transaction[];
   }
 }
 
-const viewMapping: StateViewMap<State> = {
+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 ComponentName = compose(
-  "ComponentName",
-  (p: Props) => useComponentState(p, wxApi),
+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/taler-wallet-webextension/src/cta/Deposit/stories.tsx 
b/packages/demobank-ui/src/components/Transactions/stories.tsx
similarity index 66%
copy from packages/taler-wallet-webextension/src/cta/Deposit/stories.tsx
copy to packages/demobank-ui/src/components/Transactions/stories.tsx
index 6d1535953..77fdde092 100644
--- a/packages/taler-wallet-webextension/src/cta/Deposit/stories.tsx
+++ b/packages/demobank-ui/src/components/Transactions/stories.tsx
@@ -19,19 +19,27 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { Amounts } from "@gnu-taler/taler-util";
-import { createExample } from "../../test-utils.js";
+import { tests } from "@gnu-taler/web-util/lib/index.browser";
 import { ReadyView } from "./views.js";
 
 export default {
-  title: "deposit",
+  title: "transaction list",
 };
 
-export const Ready = createExample(ReadyView, {
-  status: "ready",
-  confirm: {},
-  cost: Amounts.parseOrThrow("EUR:1.2"),
-  effective: Amounts.parseOrThrow("EUR:1"),
-  fee: Amounts.parseOrThrow("EUR:0.2"),
-  error: undefined,
+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/taler-wallet-webextension/src/i18n/strings-prelude 
b/packages/demobank-ui/src/components/index.examples.ts
similarity index 93%
copy from packages/taler-wallet-webextension/src/i18n/strings-prelude
copy to packages/demobank-ui/src/components/index.examples.ts
index 7d9d13136..a741b413d 100644
--- a/packages/taler-wallet-webextension/src/i18n/strings-prelude
+++ b/packages/demobank-ui/src/components/index.examples.ts
@@ -14,4 +14,4 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-export const strings: {[s: string]: any} = {};
+export * as tx from "./Transactions/stories.js";
\ No newline at end of file
diff --git 
a/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx 
b/packages/demobank-ui/src/endpoints.ts
similarity index 65%
copy from packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx
copy to packages/demobank-ui/src/endpoints.ts
index 250e99ae1..0130e94f8 100644
--- a/packages/taler-wallet-webextension/src/cta/TransferPickup/stories.tsx
+++ b/packages/demobank-ui/src/endpoints.ts
@@ -19,23 +19,19 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample } from "../../test-utils.js";
-import { ReadyView } from "./views.js";
-
-export default {
-  title: "transfer pickup",
-};
-
-export const Ready = createExample(ReadyView, {
-  amount: {
-    currency: "ARS",
-    value: 1,
-    fraction: 0,
+export const TRANSACTION_API_EXAMPLE = {
+  LIST_FIRST_PAGE: {
+    method: "get" as const,
+    url: "access-api/accounts/myAccount/transactions?page=0",
   },
-  summary: "some subject",
-  expiration: {
-    t_ms: new Date().getTime() + 1000 * 60 * 60,
+  LIST_ERROR: {
+    method: "get" as const,
+    url: "access-api/accounts/myAccount/transactions?page=0",
+    code: 500
   },
-  accept: {},
-  cancel: {},
-});
+  LIST_NOT_FOUND: {
+    method: "get" as const,
+    url: "access-api/accounts/myAccount/transactions?page=0",
+    code: 404
+  }
+}
\ No newline at end of file
diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx 
b/packages/demobank-ui/src/pages/AccountPage.tsx
index 7ec4d36fb..5dd820b53 100644
--- a/packages/demobank-ui/src/pages/AccountPage.tsx
+++ b/packages/demobank-ui/src/pages/AccountPage.tsx
@@ -27,7 +27,7 @@ import { getIbanFromPayto, prepareHeaders } from 
"../utils.js";
 import { BankFrame } from "./BankFrame.js";
 import { LoginForm } from "./LoginForm.js";
 import { PaymentOptions } from "./PaymentOptions.js";
-import { Transactions } from "./Transactions.js";
+import { Transactions } from "../components/Transactions/index.js";
 import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
 
 export function AccountPage(): VNode {
diff --git 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
 b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
similarity index 81%
copy from 
packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
copy to packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
index 696e424c4..aced5eea9 100644
--- 
a/packages/taler-wallet-webextension/src/wallet/EmptyComponentExample/stories.tsx
+++ b/packages/demobank-ui/src/pages/PaymentOptions.stories.tsx
@@ -19,11 +19,15 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { createExample } from "../../test-utils.js";
-import { ReadyView } from "./views.js";
+import { PaymentOptions } from "./PaymentOptions.js";
 
 export default {
-  title: "example",
+  title: "PaymentOptions",
 };
 
-export const Ready = createExample(ReadyView, {});
+export const USD = {
+  component: PaymentOptions,
+  props: {
+    currency: "USD",
+  },
+};
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx 
b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
similarity index 80%
copy from packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
copy to packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
index 521d4255e..141b9addf 100644
--- a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
+++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.stories.tsx
@@ -19,15 +19,15 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { QrCodeSection } from "./QrCodeSection.js";
+import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 
 export default {
-  title: "Qr Code Selection",
+  title: "PaytoWireTransferForm",
 };
 
-export const SimpleExample = {
-  component: QrCodeSection,
+export const USD = {
+  component: PaytoWireTransferForm,
   props: {
-    talerWithdrawUri: "taler://withdraw/asdasdasd",
+    currency: "USD",
   },
 };
diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx 
b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
index be9f4aee1..7bf5c41c7 100644
--- a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -24,7 +24,7 @@ import { PageStateType, usePageContext } from 
"../context/pageState.js";
 import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
 import { getBankBackendBaseUrl } from "../utils.js";
 import { BankFrame } from "./BankFrame.js";
-import { Transactions } from "./Transactions.js";
+import { Transactions } from "../components/Transactions/index.js";
 
 const logger = new Logger("PublicHistoriesPage");
 
diff --git a/packages/demobank-ui/src/pages/Transactions.tsx 
b/packages/demobank-ui/src/pages/Transactions.tsx
deleted file mode 100644
index ca88abd4d..000000000
--- a/packages/demobank-ui/src/pages/Transactions.tsx
+++ /dev/null
@@ -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>
-  );
-}
diff --git a/packages/demobank-ui/src/pages/index.stories.tsx 
b/packages/demobank-ui/src/pages/index.stories.tsx
index 1c4c64cdb..10ce479a1 100644
--- a/packages/demobank-ui/src/pages/index.stories.tsx
+++ b/packages/demobank-ui/src/pages/index.stories.tsx
@@ -15,3 +15,5 @@
  */
 
 export * as qr from "./QrCodeSection.stories.js";
+export * as po from "./PaymentOptions.stories.js";
+export * as ptf from "./PaytoWireTransferForm.stories.js";
diff --git a/packages/demobank-ui/src/stories.tsx 
b/packages/demobank-ui/src/stories.tsx
index 611d63879..a8ebcd867 100644
--- a/packages/demobank-ui/src/stories.tsx
+++ b/packages/demobank-ui/src/stories.tsx
@@ -21,6 +21,7 @@
 import { strings } from "./i18n/strings.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";
 
@@ -32,7 +33,7 @@ function SortStories(a: any, b: any): number {
 
 function main(): void {
   renderStories(
-    { pages },
+    { pages, components },
     {
       strings,
     },
diff --git a/packages/merchant-backoffice-ui/package.json 
b/packages/merchant-backoffice-ui/package.json
index 91c4c1857..a772f6296 100644
--- a/packages/merchant-backoffice-ui/package.json
+++ b/packages/merchant-backoffice-ui/package.json
@@ -104,7 +104,7 @@
     "script-ext-html-webpack-plugin": "^2.1.5",
     "sirv-cli": "^1.0.11",
     "typedoc": "^0.20.36",
-    "typescript": "4.4.4"
+    "typescript": "4.8.4"
   },
   "jest": {
     "preset": "jest-preset-preact",
@@ -123,4 +123,4 @@
       
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$":
 "<rootDir>/tests/__mocks__/fileTransformer.js"
     }
   }
-}
\ No newline at end of file
+}
diff --git a/packages/web-util/build.mjs b/packages/web-util/build.mjs
index ba277b666..e7aede81c 100755
--- a/packages/web-util/build.mjs
+++ b/packages/web-util/build.mjs
@@ -78,13 +78,13 @@ const buildConfigNode = {
 
 const buildConfigBrowser = {
   ...buildConfigBase,
-  entryPoints: ["src/index.browser.ts", "src/live-reload.ts", 
'src/stories.tsx'],
+  entryPoints: ["src/tests/axios.ts", "src/tests/swr.ts", 
"src/index.browser.ts", "src/live-reload.ts", 'src/stories.tsx'],
   outExtension: {
     '.js': '.mjs'
   },
   format: 'esm',
   platform: 'browser',
-  external: ["preact", "@gnu-taler/taler-util", "jed"],
+  external: ["preact", "@gnu-taler/taler-util", "jed","swr","axios"],
   jsxFactory: 'h',
   jsxFragment: 'Fragment',
 };
diff --git a/packages/web-util/package.json b/packages/web-util/package.json
index a4d1c116b..ad87304fe 100644
--- a/packages/web-util/package.json
+++ b/packages/web-util/package.json
@@ -12,6 +12,8 @@
   "license": "AGPL-3.0-or-later",
   "private": false,
   "exports": {
+    "./lib/tests/swr": "./lib/tests/swr.mjs",
+    "./lib/tests/axios": "./lib/tests/axios.mjs",
     "./lib/index.browser": "./lib/index.browser.mjs",
     "./lib/index.node": "./lib/index.node.cjs"
   },
@@ -27,14 +29,17 @@
     "@types/node": "^18.11.9",
     "@types/web": "^0.0.82",
     "@types/ws": "^8.5.3",
+    "axios": "^1.2.1",
     "chokidar": "^3.5.3",
     "esbuild": "^0.14.21",
     "express": "^4.18.2",
     "preact": "10.11.3",
+    "preact-render-to-string": "^5.2.6",
     "prettier": "^2.5.1",
     "rimraf": "^3.0.2",
+    "swr": "1.3.0",
     "tslib": "^2.4.0",
     "typescript": "^4.8.4",
     "ws": "7.4.5"
   }
-}
+}
\ No newline at end of file
diff --git a/packages/web-util/src/components/index.ts 
b/packages/web-util/src/components/index.ts
new file mode 100644
index 000000000..9441e971d
--- /dev/null
+++ b/packages/web-util/src/components/index.ts
@@ -0,0 +1 @@
+export * as utils from "./utils.js";
diff --git a/packages/web-util/src/components/utils.ts 
b/packages/web-util/src/components/utils.ts
new file mode 100644
index 000000000..71824e14f
--- /dev/null
+++ b/packages/web-util/src/components/utils.ts
@@ -0,0 +1,36 @@
+import { createElement, VNode } from "preact";
+
+export type StateFunc<S> = (p: S) => VNode;
+
+export type StateViewMap<StateType extends { status: string }> = {
+  [S in StateType as S["status"]]: StateFunc<S>;
+};
+
+export type RecursiveState<S extends object> = S | (() => RecursiveState<S>);
+
+export function compose<SType extends { status: string }, PType>(
+  hook: (p: PType) => RecursiveState<SType>,
+  viewMap: StateViewMap<SType>,
+): (p: PType) => VNode {
+  function withHook(stateHook: () => RecursiveState<SType>): () => VNode {
+    function ComposedComponent(): VNode {
+      const state = stateHook();
+
+      if (typeof state === "function") {
+        const subComponent = withHook(state);
+        return createElement(subComponent, {});
+      }
+
+      const statusName = state.status as unknown as SType["status"];
+      const viewComponent = viewMap[statusName] as unknown as StateFunc<SType>;
+      return createElement(viewComponent, state);
+    }
+
+    return ComposedComponent;
+  }
+
+  return (p: PType) => {
+    const h = withHook(() => hook(p));
+    return h();
+  };
+}
diff --git a/packages/web-util/src/context/index.ts 
b/packages/web-util/src/context/index.ts
index 0ac2c752a..4bc1b22f2 100644
--- a/packages/web-util/src/context/index.ts
+++ b/packages/web-util/src/context/index.ts
@@ -1,2 +1,5 @@
-
-export { InternationalizationAPI, TranslationProvider, useTranslationContext } 
from "./translation.js";
+export {
+  InternationalizationAPI,
+  TranslationProvider,
+  useTranslationContext,
+} from "./translation.js";
diff --git a/packages/web-util/src/context/translation.ts 
b/packages/web-util/src/context/translation.ts
index ce140ec42..3b79e31d3 100644
--- a/packages/web-util/src/context/translation.ts
+++ b/packages/web-util/src/context/translation.ts
@@ -19,7 +19,7 @@ import { ComponentChildren, createContext, h, VNode } from 
"preact";
 import { useContext, useEffect } from "preact/hooks";
 import { useLang } from "../hooks/index.js";
 
-export type InternationalizationAPI = typeof i18n
+export type InternationalizationAPI = typeof i18n;
 
 interface Type {
   lang: string;
@@ -54,7 +54,7 @@ interface Props {
   initial?: string;
   children: ComponentChildren;
   forceLang?: string;
-  source: Record<string, any>
+  source: Record<string, any>;
 }
 
 // Outmost UI wrapper.
@@ -62,7 +62,7 @@ export const TranslationProvider = ({
   initial,
   children,
   forceLang,
-  source
+  source,
 }: Props): VNode => {
   const [lang, changeLanguage, isSaved] = useLang(initial);
   useEffect(() => {
diff --git a/packages/web-util/src/hooks/index.ts 
b/packages/web-util/src/hooks/index.ts
index f18d61b9c..393a6fcbb 100644
--- a/packages/web-util/src/hooks/index.ts
+++ b/packages/web-util/src/hooks/index.ts
@@ -1,3 +1,11 @@
-
 export { useLang } from "./useLang.js";
-export { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js"
\ No newline at end of file
+export { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js";
+export {
+  useAsyncAsHook,
+  HookError,
+  HookOk,
+  HookResponse,
+  HookResponseWithRetry,
+  HookGenericError,
+  HookOperationalError,
+} from "./useAsyncAsHook.js";
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts 
b/packages/web-util/src/hooks/useAsyncAsHook.ts
similarity index 88%
copy from packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
copy to packages/web-util/src/hooks/useAsyncAsHook.ts
index 1b2929317..48d29aa45 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/web-util/src/hooks/useAsyncAsHook.ts
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import { TalerErrorDetail } from "@gnu-taler/taler-util";
-import { TalerError } from "@gnu-taler/taler-wallet-core";
+// import { TalerError } from "@gnu-taler/taler-wallet-core";
 import { useEffect, useMemo, useState } from "preact/hooks";
 
 export interface HookOk<T> {
@@ -65,13 +65,14 @@ export function useAsyncAsHook<T>(
       if (response === false) return;
       setHookResponse({ hasError: false, response });
     } catch (e) {
-      if (e instanceof TalerError) {
-        setHookResponse({
-          hasError: true,
-          operational: true,
-          details: e.errorDetail,
-        });
-      } else if (e instanceof Error) {
+      // if (e instanceof TalerError) {
+      //   setHookResponse({
+      //     hasError: true,
+      //     operational: true,
+      //     details: e.errorDetail,
+      //   });
+      // } else
+      if (e instanceof Error) {
         setHookResponse({
           hasError: true,
           operational: false,
diff --git a/packages/web-util/src/hooks/useLocalStorage.ts 
b/packages/web-util/src/hooks/useLocalStorage.ts
index f518405b6..ab786db13 100644
--- a/packages/web-util/src/hooks/useLocalStorage.ts
+++ b/packages/web-util/src/hooks/useLocalStorage.ts
@@ -35,13 +35,13 @@ export function useLocalStorage(
 
   useEffect(() => {
     const listener = buildListenerForKey(key, (newValue) => {
-      setStoredValue(newValue ?? initialValue)
-    })
-    window.addEventListener('storage', listener)
+      setStoredValue(newValue ?? initialValue);
+    });
+    window.addEventListener("storage", listener);
     return () => {
-      window.removeEventListener('storage', listener)
-    }
-  }, [])
+      window.removeEventListener("storage", listener);
+    };
+  }, []);
 
   const setValue = (
     value?: string | ((val?: string) => string | undefined),
@@ -62,11 +62,14 @@ export function useLocalStorage(
   return [storedValue, setValue];
 }
 
-function buildListenerForKey(key: string, onUpdate: (newValue: string | 
undefined) => void): () => void {
+function buildListenerForKey(
+  key: string,
+  onUpdate: (newValue: string | undefined) => void,
+): () => void {
   return function listenKeyChange() {
-    const value = window.localStorage.getItem(key)
-    onUpdate(value ?? undefined)
-  }
+    const value = window.localStorage.getItem(key);
+    onUpdate(value ?? undefined);
+  };
 }
 
 //TODO: merge with the above function
@@ -80,16 +83,15 @@ export function useNotNullLocalStorage(
       : initialValue;
   });
 
-
   useEffect(() => {
     const listener = buildListenerForKey(key, (newValue) => {
-      setStoredValue(newValue ?? initialValue)
-    })
-    window.addEventListener('storage', listener)
+      setStoredValue(newValue ?? initialValue);
+    });
+    window.addEventListener("storage", listener);
     return () => {
-      window.removeEventListener('storage', listener)
-    }
-  })
+      window.removeEventListener("storage", listener);
+    };
+  });
 
   const setValue = (value: string | ((val: string) => string)): void => {
     const valueToStore = value instanceof Function ? value(storedValue) : 
value;
diff --git a/packages/web-util/src/index.browser.ts 
b/packages/web-util/src/index.browser.ts
index 2197d1b24..d3aeae168 100644
--- a/packages/web-util/src/index.browser.ts
+++ b/packages/web-util/src/index.browser.ts
@@ -1,3 +1,5 @@
 export * from "./hooks/index.js";
 export * from "./context/index.js";
+export * from "./components/index.js";
+export * as tests from "./tests/index.js";
 export { renderStories, parseGroupImport } from "./stories.js";
diff --git a/packages/web-util/src/live-reload.ts 
b/packages/web-util/src/live-reload.ts
index 901127f83..74d542956 100644
--- a/packages/web-util/src/live-reload.ts
+++ b/packages/web-util/src/live-reload.ts
@@ -15,24 +15,24 @@ function setupLiveReload(): void {
         return;
       }
       if (event.type === "file-updated-failed") {
-        const h1 = document.getElementById("overlay-text")
+        const h1 = document.getElementById("overlay-text");
         if (h1) {
-          h1.innerHTML = "compilation failed"
-          h1.style.color = 'red'
-          h1.style.margin = ''
+          h1.innerHTML = "compilation failed";
+          h1.style.color = "red";
+          h1.style.margin = "";
         }
-        const div = document.getElementById("overlay")
+        const div = document.getElementById("overlay");
         if (div) {
-          const content = JSON.stringify(event.data, undefined, 2)
+          const content = JSON.stringify(event.data, undefined, 2);
           const pre = document.createElement("pre");
-          pre.id = "error-text"
+          pre.id = "error-text";
           pre.style.margin = "";
           pre.textContent = content;
           div.style.backgroundColor = "rgba(0,0,0,0.8)";
-          div.style.flexDirection = 'column'
+          div.style.flexDirection = "column";
           div.appendChild(pre);
         }
-        console.error(event.data.error)
+        console.error(event.data.error);
         return;
       }
       if (event.type === "file-updated") {
@@ -56,17 +56,17 @@ setupLiveReload();
 
 function showReloadOverlay(): void {
   const d = document.createElement("div");
-  d.id = "overlay"
+  d.id = "overlay";
   d.style.position = "absolute";
   d.style.width = "100%";
   d.style.height = "100%";
   d.style.color = "white";
   d.style.backgroundColor = "rgba(0,0,0,0.5)";
   d.style.display = "flex";
-  d.style.zIndex = String(Number.MAX_SAFE_INTEGER)
+  d.style.zIndex = String(Number.MAX_SAFE_INTEGER);
   d.style.justifyContent = "center";
   const h = document.createElement("h1");
-  h.id = "overlay-text"
+  h.id = "overlay-text";
   h.style.margin = "auto";
   h.innerHTML = "reloading...";
   d.appendChild(h);
diff --git a/packages/web-util/src/serve.ts b/packages/web-util/src/serve.ts
index 3248bbeb8..f3a97e2e2 100644
--- a/packages/web-util/src/serve.ts
+++ b/packages/web-util/src/serve.ts
@@ -77,23 +77,26 @@ export async function serve(opts: {
 
       if (opts.onUpdate) {
         sendToAllClients({ type: "file-updated-start", data: { path } });
-        opts.onUpdate().then((result) => {
-          sendToAllClients({
-            type: "file-updated-done",
-            data: { path, result },
+        opts
+          .onUpdate()
+          .then((result) => {
+            sendToAllClients({
+              type: "file-updated-done",
+              data: { path, result },
+            });
+          })
+          .catch((error) => {
+            sendToAllClients({
+              type: "file-updated-failed",
+              data: { path, error },
+            });
           });
-        }).catch((error) => {
-          sendToAllClients({
-            type: "file-updated-failed",
-            data: { path, error },
-          });
-        });
       } else {
         sendToAllClients({ type: "file-change", data: { path } });
       }
     });
 
-    if (opts.onUpdate) opts.onUpdate()
+    if (opts.onUpdate) opts.onUpdate();
 
     app.get(PATHS.EXAMPLE, function (req: any, res: any) {
       res.set("Content-Type", "text/html");
diff --git a/packages/web-util/src/tests/axios.ts 
b/packages/web-util/src/tests/axios.ts
new file mode 100644
index 000000000..38f8a9899
--- /dev/null
+++ b/packages/web-util/src/tests/axios.ts
@@ -0,0 +1,136 @@
+/*
+ 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 axios, { AxiosPromise, AxiosRequestConfig } from "axios";
+import * as axios from "axios";
+import {
+  setAxiosRequestAsTestingEnvironment,
+  mockAxiosOnce,
+} from "../utils/axios.js";
+
+const TESTING_DEBUG_LOG = process.env["TESTING_DEBUG_LOG"] !== undefined;
+
+const defaultCallback = (
+  actualQuery?: axios.AxiosRequestConfig,
+): axios.AxiosPromise<any> => {
+  if (TESTING_DEBUG_LOG) {
+    console.log("UNEXPECTED QUERY", actualQuery);
+  }
+  throw Error(
+    "Default Axios mock callback is called, this mean that the test did a 
tried to use axios but there was no expectation in place, try using 
JEST_DEBUG_LOG env",
+  );
+};
+
+setAxiosRequestAsTestingEnvironment(defaultCallback);
+
+export type Query<Req, Res> = {
+  method: axios.Method;
+  url: string;
+  code?: number;
+};
+
+type ExpectationValues = {
+  query: Query<any, any>;
+  params?: {
+    auth?: string;
+    request?: object;
+    qparam?: Record<string, string>;
+    response?: object;
+  };
+};
+
+type TestValues = [
+  axios.AxiosRequestConfig | undefined,
+  ExpectationValues | undefined,
+];
+
+export class AxiosMockEnvironment {
+  expectations: Array<
+    | {
+        query: Query<any, any>;
+        auth?: string;
+        params?: {
+          request?: object;
+          qparam?: Record<string, string>;
+          response?: object;
+        };
+        result: { args: axios.AxiosRequestConfig | undefined };
+      }
+    | undefined
+  > = [];
+  // axiosMock: jest.MockedFunction<axios.AxiosStatic>
+
+  addRequestExpectation<
+    RequestType extends object,
+    ResponseType extends object,
+  >(
+    expectedQuery: Query<RequestType, ResponseType>,
+    params: {
+      auth?: string;
+      request?: RequestType;
+      qparam?: any;
+      response?: ResponseType;
+    },
+  ): void {
+    const result = mockAxiosOnce(function (
+      actualQuery?: axios.AxiosRequestConfig,
+    ): axios.AxiosPromise {
+      if (TESTING_DEBUG_LOG) {
+        console.log("query to the backend is made", actualQuery);
+      }
+      if (!expectedQuery) {
+        return Promise.reject("a query was made but it was not expected");
+      }
+      if (TESTING_DEBUG_LOG) {
+        console.log("expected query:", params?.request);
+        console.log("expected qparams:", params?.qparam);
+        console.log("sending response:", params?.response);
+      }
+
+      const responseCode = expectedQuery.code || 200;
+
+      //This response is what buildRequestOk is expecting in file 
hook/backend.ts
+      if (responseCode >= 200 && responseCode < 300) {
+        return Promise.resolve({
+          data: params?.response,
+          config: {
+            data: params?.response,
+            params: actualQuery?.params || {},
+          },
+          request: { params: actualQuery?.params || {} },
+        } as any);
+      }
+      //This response is what buildRequestFailed is expecting in file 
hook/backend.ts
+      return Promise.reject({
+        response: {
+          status: responseCode,
+        },
+        request: {
+          data: params?.response,
+          params: actualQuery?.params || {},
+        },
+      });
+    } as any);
+
+    this.expectations.push({ query: expectedQuery, params, result });
+  }
+
+  getLastTestValues(): TestValues {
+    const expectedQuery = this.expectations.shift();
+
+    return [expectedQuery?.result.args, expectedQuery];
+  }
+}
diff --git a/packages/web-util/src/tests/hook.ts 
b/packages/web-util/src/tests/hook.ts
new file mode 100644
index 000000000..f5bebbd6d
--- /dev/null
+++ b/packages/web-util/src/tests/hook.ts
@@ -0,0 +1,310 @@
+/*
+ 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 {
+  ComponentChildren,
+  Fragment,
+  FunctionalComponent,
+  h as create,
+  options,
+  render as renderIntoDom,
+  VNode
+} from "preact";
+
+// This library is expected to be included in testing environment only
+// When doing tests we want the requestAnimationFrame to be as fast as 
possible.
+// without this option the RAF will timeout after 100ms making the tests slower
+options.requestAnimationFrame = (fn: () => void) => {
+  return fn();
+};
+
+export function createExample<Props>(
+  Component: FunctionalComponent<Props>,
+  props: Partial<Props> | (() => Partial<Props>),
+): ComponentChildren {
+  const evaluatedProps = typeof props === "function" ? props() : props;
+  const Render = (args: any): VNode => create(Component, args);
+
+  return {
+    component: Render,
+    props: evaluatedProps,
+  };
+}
+
+// export function createExampleWithCustomContext<Props, ContextProps>(
+//   Component: FunctionalComponent<Props>,
+//   props: Partial<Props> | (() => Partial<Props>),
+//   ContextProvider: FunctionalComponent<ContextProps>,
+//   contextProps: Partial<ContextProps>,
+// ): ComponentChildren {
+//   /**
+//    * FIXME:
+//    * This may not be useful since the example can be created with context
+//    * already
+//    */
+//   const evaluatedProps = typeof props === "function" ? props() : props;
+//   const Render = (args: any): VNode => create(Component, args);
+//   const WithContext = (args: any): VNode =>
+//     create(ContextProvider, {
+//       ...contextProps,
+//       children: [Render(args)],
+//     } as any);
+
+//   return {
+//     component: WithContext,
+//     props: evaluatedProps,
+//   };
+// }
+
+const isNode = typeof window === "undefined";
+
+/**
+ * To be used on automated unit test.
+ * So test will run under node or browser
+ * @param Component
+ * @param args
+ */
+export function renderNodeOrBrowser(
+  Component: any,
+  args: any,
+  Context: any,
+): void {
+  const vdom = !Context
+    ? create(Component, args)
+    : create(Context, { children: [create(Component, args)] });
+
+  const customElement = {} as Element;
+  const parentElement = isNode ? customElement : document.createElement("div");
+  if (!isNode) {
+    document.body.appendChild(parentElement);
+  }
+
+  // renderIntoDom works also in nodejs
+  // if the VirtualDOM is composed only by functional components
+  // then no called is going to be made to the DOM api.
+  // vdom should not have any 'div' or other html component
+  renderIntoDom(vdom, parentElement);
+
+  if (!isNode) {
+    document.body.removeChild(parentElement);
+  }
+}
+type RecursiveState<S> = S | (() => RecursiveState<S>);
+
+interface Mounted<T> {
+  // unmount: () => void;
+  pullLastResultOrThrow: () => Exclude<T, VoidFunction>;
+  assertNoPendingUpdate: () => Promise<boolean>;
+  // waitNextUpdate: (s?: string) => Promise<void>;
+  waitForStateUpdate: () => Promise<boolean>;
+}
+
+/**
+ * Manual API mount the hook and return testing API
+ * Consider using hookBehaveLikeThis() function
+ * 
+ * @param hookToBeTested
+ * @param Context
+ * 
+ * @returns testing API
+ */
+export function mountHook<T extends object>(
+  hookToBeTested: () => RecursiveState<T>,
+  Context?: ({ children }: { children: any }) => VNode | null,
+): Mounted<T> {
+  let lastResult: Exclude<T, VoidFunction> | Error | null = null;
+
+  const listener: Array<() => void> = [];
+
+  // component that's going to hold the hook
+  function Component(): VNode {
+    try {
+      let componentOrResult = hookToBeTested();
+      while (typeof componentOrResult === "function") {
+        componentOrResult = componentOrResult();
+      }
+      //typecheck fails here
+      const l: Exclude<T, () => void> = componentOrResult as any;
+      lastResult = l;
+    } catch (e) {
+      if (e instanceof Error) {
+        lastResult = e;
+      } else {
+        lastResult = new Error(`mounting the hook throw an exception: ${e}`);
+      }
+    }
+
+    // notify to everyone waiting for an update and clean the queue
+    listener.splice(0, listener.length).forEach((cb) => cb());
+    return create(Fragment, {});
+  }
+
+  renderNodeOrBrowser(Component, {}, Context);
+
+  function pullLastResult(): Exclude<T | Error | null, VoidFunction> {
+    const copy: Exclude<T | Error | null, VoidFunction> = lastResult;
+    lastResult = null;
+    return copy;
+  }
+
+  function pullLastResultOrThrow(): Exclude<T, VoidFunction> {
+    const r = pullLastResult();
+    if (r instanceof Error) throw r;
+    //sanity check
+    if (!r) throw Error("there was no last result");
+    return r;
+  }
+
+  async function assertNoPendingUpdate(): Promise<boolean> {
+    await new Promise((res, rej) => {
+      const tid = setTimeout(() => {
+        res(true);
+      }, 10);
+
+      listener.push(() => {
+        clearTimeout(tid);
+        res(false);
+        //   Error(`Expecting no pending result but the hook got updated.
+        //  If the update was not intended you need to check the hook 
dependencies
+        //  (or dependencies of the internal state) but otherwise make
+        //  sure to consume the result before ending the test.`),
+        // );
+      });
+    });
+
+    const r = pullLastResult();
+    if (r) {
+      return Promise.resolve(false);
+    }
+    return Promise.resolve(true);
+    // throw Error(`There are still pending results.
+    //  This may happen because the hook did a new update but the test didn't 
consume the result using pullLastResult`);
+  }
+  async function waitForStateUpdate(): Promise<boolean> {
+    return await new Promise((res, rej) => {
+      const tid = setTimeout(() => {
+        res(false);
+      }, 10);
+
+      listener.push(() => {
+        clearTimeout(tid);
+        res(true);
+      });
+    });
+  }
+
+  return {
+    // unmount,
+    pullLastResultOrThrow,
+    waitForStateUpdate,
+    assertNoPendingUpdate,
+  };
+}
+
+export const nullFunction = (): void => {
+  null;
+};
+export const nullAsyncFunction = (): Promise<void> => {
+  return Promise.resolve();
+};
+
+type HookTestResult = HookTestResultOk | HookTestResultError;
+
+interface HookTestResultOk {
+  result: "ok";
+}
+interface HookTestResultError {
+  result: "fail";
+  error: string;
+  index: number;
+}
+
+/**
+ * Main testing driver.
+ * It will assert that there are no more and no less hook updates than 
expected. 
+ * 
+ * @param hookFunction hook function to be tested
+ * @param props initial props for the hook
+ * @param checks step by step state validation
+ * @param Context additional testing context for overrides
+ * 
+ * @returns testing result, should also be checked to be "ok"
+ */
+export async function hookBehaveLikeThis<T extends object, PropsType>(
+  hookFunction: (p: PropsType) => RecursiveState<T>,
+  props: PropsType,
+  checks: Array<(state: T) => void>,
+  Context?: ({ children }: { children: any }) => VNode | null,
+): Promise<HookTestResult> {
+  const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } =
+    mountHook<T>(() => hookFunction(props), Context);
+
+  const [firstCheck, ...resultOfTheChecks] = checks;
+  {
+    const state = pullLastResultOrThrow();
+    const checkError = firstCheck(state);
+    if (checkError !== undefined) {
+      return {
+        result: "fail",
+        index: 0,
+        error: `Check return not undefined error: ${checkError}`,
+      };
+    }
+  }
+
+  let index = 1;
+  for (const check of resultOfTheChecks) {
+    const hasNext = await waitForStateUpdate();
+    if (!hasNext) {
+      return {
+        result: "fail",
+        error: "Component didn't update and the test expected one more state",
+        index,
+      };
+    }
+    const state = pullLastResultOrThrow();
+    const checkError = check(state);
+    if (checkError !== undefined) {
+      return {
+        result: "fail",
+        index,
+        error: `Check return not undefined error: ${checkError}`,
+      };
+    }
+    index++;
+  }
+
+  const hasNext = await waitForStateUpdate();
+  if (hasNext) {
+    return {
+      result: "fail",
+      index,
+      error: "Component updated and test didn't expect more states",
+    };
+  }
+  const noMoreUpdates = await assertNoPendingUpdate();
+  if (noMoreUpdates === false) {
+    return {
+      result: "fail",
+      index,
+      error: "Component was updated but the test does not cover the update",
+    };
+  }
+
+  return {
+    result: "ok",
+  };
+}
diff --git a/packages/web-util/src/tests/index.ts 
b/packages/web-util/src/tests/index.ts
new file mode 100644
index 000000000..2c0d929f8
--- /dev/null
+++ b/packages/web-util/src/tests/index.ts
@@ -0,0 +1,2 @@
+export * from "./hook.js";
+// export * from "./axios.js"
diff --git a/packages/web-util/src/tests/mock.ts 
b/packages/web-util/src/tests/mock.ts
new file mode 100644
index 000000000..563e437e5
--- /dev/null
+++ b/packages/web-util/src/tests/mock.ts
@@ -0,0 +1,458 @@
+/*
+ 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";
+
+type HttpMethod =
+  | "get"
+  | "GET"
+  | "delete"
+  | "DELETE"
+  | "head"
+  | "HEAD"
+  | "options"
+  | "OPTIONS"
+  | "post"
+  | "POST"
+  | "put"
+  | "PUT"
+  | "patch"
+  | "PATCH"
+  | "purge"
+  | "PURGE"
+  | "link"
+  | "LINK"
+  | "unlink"
+  | "UNLINK";
+
+export type Query<Req, Res> = {
+  method: HttpMethod;
+  url: string;
+  code?: number;
+};
+
+type ExpectationValues = {
+  query: Query<any, any>;
+  auth?: string;
+  params?: {
+    request?: object;
+    qparam?: Record<string, string>;
+    response?: object;
+  };
+};
+
+type TestValues = {
+  currentExpectedQuery: ExpectationValues | undefined;
+  lastQuery: ExpectationValues | undefined;
+};
+
+const logger = new Logger("testing/swr.ts");
+
+export abstract class MockEnvironment {
+  expectations: Array<ExpectationValues> = [];
+  queriesMade: Array<ExpectationValues> = [];
+  index = 0;
+
+  debug: boolean;
+  constructor(debug: boolean) {
+    this.debug = debug;
+    this.registerRequest.bind(this);
+  }
+
+  public addRequestExpectation<
+    RequestType extends object,
+    ResponseType extends object,
+  >(
+    query: Query<RequestType, ResponseType>,
+    params: {
+      auth?: string;
+      request?: RequestType;
+      qparam?: any;
+      response?: ResponseType;
+    },
+  ): void {
+    const expected = { query, params, auth: params.auth };
+    this.expectations.push(expected);
+    if (this.debug) {
+      logger.info("saving query as expected", expected);
+    }
+    this.mockApiIfNeeded();
+  }
+
+  abstract mockApiIfNeeded(): void;
+
+  public registerRequest<
+    RequestType extends object,
+    ResponseType extends object,
+  >(
+    query: Query<RequestType, ResponseType>,
+    params: {
+      auth?: string;
+      request?: RequestType;
+      qparam?: any;
+      response?: ResponseType;
+    },
+  ): { status: number; payload: ResponseType } | undefined {
+    const queryMade = { query, params, auth: params.auth };
+    this.queriesMade.push(queryMade);
+    const expectedQuery = this.expectations[this.index];
+    if (!expectedQuery) {
+      if (this.debug) {
+        logger.info("unexpected query made", queryMade);
+      }
+      return undefined;
+    }
+    const responseCode = this.expectations[this.index].query.code ?? 200;
+    const mockedResponse = this.expectations[this.index].params
+      ?.response as ResponseType;
+    if (this.debug) {
+      logger.info("tracking query made", {
+        queryMade,
+        expectedQuery,
+      });
+    }
+    this.index++;
+    return { status: responseCode, payload: mockedResponse };
+  }
+
+  public assertJustExpectedRequestWereMade(): AssertStatus {
+    let queryNumber = 0;
+
+    while (queryNumber < this.expectations.length) {
+      const r = this.assertNextRequest(queryNumber);
+      if (r.result !== "ok") return r;
+      queryNumber++;
+    }
+    return this.assertNoMoreRequestWereMade(queryNumber);
+  }
+
+  private getLastTestValues(idx: number): TestValues {
+    const currentExpectedQuery = this.expectations[idx];
+    const lastQuery = this.queriesMade[idx];
+
+    return { currentExpectedQuery, lastQuery };
+  }
+
+  private assertNoMoreRequestWereMade(idx: number): AssertStatus {
+    const { currentExpectedQuery, lastQuery } = this.getLastTestValues(idx);
+
+    if (lastQuery !== undefined) {
+      return {
+        result: "error-did-one-more",
+        made: lastQuery,
+      };
+    }
+    if (currentExpectedQuery !== undefined) {
+      return {
+        result: "error-did-one-less",
+        expected: currentExpectedQuery,
+      };
+    }
+
+    return {
+      result: "ok",
+    };
+  }
+
+  private assertNextRequest(idx: number): AssertStatus {
+    const { currentExpectedQuery, lastQuery } = this.getLastTestValues(idx);
+
+    if (!currentExpectedQuery) {
+      return {
+        result: "error-query-missing",
+      };
+    }
+
+    if (!lastQuery) {
+      return {
+        result: "error-did-one-less",
+        expected: currentExpectedQuery,
+      };
+    }
+
+    if (lastQuery.query.method) {
+      if (currentExpectedQuery.query.method !== lastQuery.query.method) {
+        return {
+          result: "error-difference",
+          diff: "method",
+        };
+      }
+      if (currentExpectedQuery.query.url !== lastQuery.query.url) {
+        return {
+          result: "error-difference",
+          diff: "url",
+        };
+      }
+    }
+    if (
+      !deepEquals(
+        currentExpectedQuery.params?.request,
+        lastQuery.params?.request,
+      )
+    ) {
+      return {
+        result: "error-difference",
+        diff: "query-body",
+      };
+    }
+    if (
+      !deepEquals(currentExpectedQuery.params?.qparam, 
lastQuery.params?.qparam)
+    ) {
+      return {
+        result: "error-difference",
+        diff: "query-params",
+      };
+    }
+    if (!deepEquals(currentExpectedQuery.auth, lastQuery.auth)) {
+      return {
+        result: "error-difference",
+        diff: "query-auth",
+      };
+    }
+
+    return {
+      result: "ok",
+    };
+  }
+}
+
+type AssertStatus =
+  | AssertOk
+  | AssertQueryNotMadeButExpected
+  | AssertQueryMadeButNotExpected
+  | AssertQueryMissing
+  | AssertExpectedQueryMethodMismatch
+  | AssertExpectedQueryUrlMismatch
+  | AssertExpectedQueryAuthMismatch
+  | AssertExpectedQueryBodyMismatch
+  | AssertExpectedQueryParamsMismatch;
+
+interface AssertOk {
+  result: "ok";
+}
+
+//trying to assert for a expected query but there is
+//no expected query in the queue
+interface AssertQueryMissing {
+  result: "error-query-missing";
+}
+
+//tested component did one more query that expected
+interface AssertQueryNotMadeButExpected {
+  result: "error-did-one-more";
+  made: ExpectationValues;
+}
+
+//tested component didn't make an expected query
+interface AssertQueryMadeButNotExpected {
+  result: "error-did-one-less";
+  expected: ExpectationValues;
+}
+
+interface AssertExpectedQueryMethodMismatch {
+  result: "error-difference";
+  diff: "method";
+}
+interface AssertExpectedQueryUrlMismatch {
+  result: "error-difference";
+  diff: "url";
+}
+interface AssertExpectedQueryAuthMismatch {
+  result: "error-difference";
+  diff: "query-auth";
+}
+interface AssertExpectedQueryBodyMismatch {
+  result: "error-difference";
+  diff: "query-body";
+}
+interface AssertExpectedQueryParamsMismatch {
+  result: "error-difference";
+  diff: "query-params";
+}
+
+/**
+ * helpers
+ *
+ */
+export type Tester = (a: any, b: any) => boolean | undefined;
+
+function deepEquals(
+  a: unknown,
+  b: unknown,
+  aStack: Array<unknown> = [],
+  bStack: Array<unknown> = [],
+): boolean {
+  //one if the element is null or undefined
+  if (a === null || b === null || b === undefined || a === undefined) {
+    return a === b;
+  }
+  //both are errors
+  if (a instanceof Error && b instanceof Error) {
+    return a.message == b.message;
+  }
+  //is the same object
+  if (Object.is(a, b)) {
+    return true;
+  }
+  //both the same class
+  const name = Object.prototype.toString.call(a);
+  if (name != Object.prototype.toString.call(b)) {
+    return false;
+  }
+  //
+  switch (name) {
+    case "[object Boolean]":
+    case "[object String]":
+    case "[object Number]":
+      if (typeof a !== typeof b) {
+        // One is a primitive, one a `new Primitive()`
+        return false;
+      } else if (typeof a !== "object" && typeof b !== "object") {
+        // both are proper primitives
+        return Object.is(a, b);
+      } else {
+        // both are `new Primitive()`s
+        return Object.is(a.valueOf(), b.valueOf());
+      }
+    case "[object Date]": {
+      const _a = a as Date;
+      const _b = b as Date;
+      return _a == _b;
+    }
+    case "[object RegExp]": {
+      const _a = a as RegExp;
+      const _b = b as RegExp;
+      return _a.source === _b.source && _a.flags === _b.flags;
+    }
+    case "[object Array]": {
+      const _a = a as Array<any>;
+      const _b = b as Array<any>;
+      if (_a.length !== _b.length) {
+        return false;
+      }
+    }
+  }
+  if (typeof a !== "object" || typeof b !== "object") {
+    return false;
+  }
+
+  if (
+    typeof a === "object" &&
+    typeof b === "object" &&
+    !Array.isArray(a) &&
+    !Array.isArray(b) &&
+    hasIterator(a) &&
+    hasIterator(b)
+  ) {
+    return iterable(a, b);
+  }
+
+  // Used to detect circular references.
+  let length = aStack.length;
+  while (length--) {
+    if (aStack[length] === a) {
+      return bStack[length] === b;
+    } else if (bStack[length] === b) {
+      return false;
+    }
+  }
+  aStack.push(a);
+  bStack.push(b);
+
+  const aKeys = allKeysFromObject(a);
+  const bKeys = allKeysFromObject(b);
+  let keySize = aKeys.length;
+
+  //same number of keys
+  if (bKeys.length !== keySize) {
+    return false;
+  }
+
+  let keyIterator: string;
+  while (keySize--) {
+    const _a = a as Record<string, object>;
+    const _b = b as Record<string, object>;
+
+    keyIterator = aKeys[keySize];
+
+    const de = deepEquals(_a[keyIterator], _b[keyIterator], aStack, bStack);
+    if (!de) {
+      return false;
+    }
+  }
+
+  aStack.pop();
+  bStack.pop();
+
+  return true;
+}
+
+function allKeysFromObject(obj: object): Array<string> {
+  const keys = [];
+  for (const key in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, key)) {
+      keys.push(key);
+    }
+  }
+  return keys;
+}
+
+const IteratorSymbol = Symbol.iterator;
+
+function hasIterator(object: any): boolean {
+  return !!(object != null && object[IteratorSymbol]);
+}
+
+function iterable(
+  a: unknown,
+  b: unknown,
+  aStack: Array<unknown> = [],
+  bStack: Array<unknown> = [],
+): boolean {
+  if (a === null || b === null || b === undefined || a === undefined) {
+    return a === b;
+  }
+  if (a.constructor !== b.constructor) {
+    return false;
+  }
+  let length = aStack.length;
+  while (length--) {
+    if (aStack[length] === a) {
+      return bStack[length] === b;
+    }
+  }
+  aStack.push(a);
+  bStack.push(b);
+
+  const aIterator = (a as any)[IteratorSymbol]();
+  const bIterator = (b as any)[IteratorSymbol]();
+
+  const nextA = aIterator.next();
+  while (nextA.done) {
+    const nextB = bIterator.next();
+    if (nextB.done || !deepEquals(nextA.value, nextB.value)) {
+      return false;
+    }
+  }
+  if (!bIterator.next().done) {
+    return false;
+  }
+
+  // Remove the first value from the stack of traversed values.
+  aStack.pop();
+  bStack.pop();
+  return true;
+}
diff --git a/packages/web-util/src/tests/swr.ts 
b/packages/web-util/src/tests/swr.ts
new file mode 100644
index 000000000..95c62ebea
--- /dev/null
+++ b/packages/web-util/src/tests/swr.ts
@@ -0,0 +1,82 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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 { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
+import { MockEnvironment, Query } from "./mock.js";
+import { SWRConfig } from "swr";
+
+export { Query };
+/**
+ * Helper for hook that use SWR inside.
+ * 
+ * buildTestingContext() will return a testing context
+ * 
+ */
+export class SwrMockEnvironment extends MockEnvironment {
+  constructor(debug = false) {
+    super(debug);
+  }
+
+  mockApiIfNeeded(): void {
+    null; // do nothing
+  }
+
+  public buildTestingContext(): FunctionalComponent<{
+    children: ComponentChildren;
+  }> {
+    const __REGISTER_REQUEST = this.registerRequest.bind(this);
+    return function TestingContext({
+      children,
+    }: {
+      children: ComponentChildren;
+    }): VNode {
+      return h(
+        SWRConfig,
+        {
+          value: {
+            fetcher: (url: string, options: object) => {
+              const mocked = __REGISTER_REQUEST(
+                {
+                  method: "get",
+                  url,
+                },
+                {},
+              );
+              if (!mocked) return undefined;
+              if (mocked.status > 400) {
+                const e: any = Error("simulated error for testing");
+                //example error handling from 
https://swr.vercel.app/docs/error-handling
+                e.status = mocked.status;
+                throw e;
+              }
+              return mocked.payload;
+            },
+            //These options are set for ending the test faster
+            //otherwise SWR will create timeouts that will live after the test 
finished
+            loadingTimeout: 0,
+            dedupingInterval: 0,
+            shouldRetryOnError: false,
+            errorRetryInterval: 0,
+            errorRetryCount: 0,
+            //clean cache for every test
+            provider: () => new Map(),
+          },
+        },
+        children,
+      );
+    };
+  }
+}
diff --git a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts 
b/packages/web-util/src/utils/axios.ts
similarity index 55%
copy from packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
copy to packages/web-util/src/utils/axios.ts
index be7eedd48..c38314009 100644
--- a/packages/merchant-backoffice-ui/src/utils/switchableAxios.ts
+++ b/packages/web-util/src/utils/axios.ts
@@ -21,46 +21,59 @@ import axios, { AxiosPromise, AxiosRequestConfig } from 
"axios";
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+//FIXME: remove this, since it is not used anymore
+/**
+ * @deprecated
+ */
 export let removeAxiosCancelToken = false;
 
-export let axiosHandler = function doAxiosRequest(config: AxiosRequestConfig): 
AxiosPromise<any> {
-  return axios(config)
-}
+export let axiosHandler = function doAxiosRequest(
+  config: AxiosRequestConfig,
+): AxiosPromise<any> {
+  return axios(config);
+};
+
+const listOfHandlersToUseOnce = new Array<AxiosHandler>();
 
 /**
  * Set this backend library to testing mode.
  * Instead of calling the axios library the @handler will be called
- * 
- * @param handler callback that will mock axios 
+ *
+ * @param handler callback that will mock axios
  */
-export function setAxiosRequestAsTestingEnvironment(handler: AxiosHandler): 
void {
+export function setAxiosRequestAsTestingEnvironment(
+  handler: AxiosHandler,
+): void {
   removeAxiosCancelToken = true;
   axiosHandler = function defaultTestingHandler(config) {
-    const currentHanlder = listOfHandlersToUseOnce.shift()
+    const currentHanlder = listOfHandlersToUseOnce.shift();
     if (!currentHanlder) {
-      return handler(config)
+      return handler(config);
     }
 
-    return currentHanlder(config)
-  }
+    return currentHanlder(config);
+  };
 }
 
 type AxiosHandler = (config: AxiosRequestConfig) => AxiosPromise<any>;
-type AxiosArguments = { args: AxiosRequestConfig | undefined }
-
-
-const listOfHandlersToUseOnce = new Array<AxiosHandler>()
+type AxiosArguments = { args: AxiosRequestConfig | undefined };
 
 /**
- * 
+ * Replace Axios handler with a mock.
+ * Throw if is called more than once
+ *
  * @param handler mock function
  * @returns savedArgs
  */
-export function mockAxiosOnce(handler: AxiosHandler): { args: 
AxiosRequestConfig | undefined } {
-  const savedArgs: AxiosArguments = { args: undefined }
-  listOfHandlersToUseOnce.push((config: AxiosRequestConfig): AxiosPromise<any> 
=> {
-    savedArgs.args = config;
-    return handler(config)
-  })
+export function mockAxiosOnce(handler: AxiosHandler): {
+  args: AxiosRequestConfig | undefined;
+} {
+  const savedArgs: AxiosArguments = { args: undefined };
+  listOfHandlersToUseOnce.push(
+    (config: AxiosRequestConfig): AxiosPromise<any> => {
+      savedArgs.args = config;
+      return handler(config);
+    },
+  );
   return savedArgs;
 }
diff --git a/packages/web-util/src/utils/index.ts 
b/packages/web-util/src/utils/index.ts
new file mode 100644
index 000000000..6dfbd5f8d
--- /dev/null
+++ b/packages/web-util/src/utils/index.ts
@@ -0,0 +1 @@
+export * from "./axios.js";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index daf339e8d..c5255b4f1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -96,24 +96,30 @@ importers:
       '@gnu-taler/pogen': ^0.0.5
       '@gnu-taler/taler-util': workspace:*
       '@gnu-taler/web-util': workspace:*
+      '@types/chai': ^4.3.0
       '@types/history': ^4.7.8
+      '@types/mocha': ^10.0.1
+      '@types/node': ^18.11.14
       '@typescript-eslint/eslint-plugin': ^5.41.0
       '@typescript-eslint/parser': ^5.41.0
+      async_hooks: ^1.0.0
       bulma: ^0.9.4
       bulma-checkbox: ^1.1.1
       bulma-radio: ^1.1.1
+      chai: ^4.3.6
       date-fns: 2.29.3
       esbuild: ^0.15.12
       eslint-config-preact: ^1.2.0
       history: 4.10.1
       jed: 1.1.1
+      mocha: ^9.2.0
       po2json: ^0.4.5
       preact: 10.11.3
       preact-router: 3.2.1
       qrcode-generator: ^1.4.4
       sass: 1.56.1
       swr: 1.3.0
-      typescript: ^4.4.4
+      typescript: 4.8.4
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
       '@gnu-taler/web-util': link:../web-util
@@ -127,14 +133,20 @@ importers:
     devDependencies:
       '@creativebulma/bulma-tooltip': 1.2.0
       '@gnu-taler/pogen': link:../pogen
+      '@types/chai': 4.3.3
       '@types/history': 4.7.11
+      '@types/mocha': 10.0.1
+      '@types/node': 18.11.14
       '@typescript-eslint/eslint-plugin': 5.41.0_2dmizcyfdgb4npqs6z4fehioli
       '@typescript-eslint/parser': 5.41.0_typescript@4.8.4
+      async_hooks: 1.0.0
       bulma: 0.9.4
       bulma-checkbox: 1.2.1
       bulma-radio: 1.2.0
+      chai: 4.3.6
       esbuild: 0.15.12
       eslint-config-preact: 1.3.0_qqbgcrpnpybc6dh47gt272vyy4
+      mocha: 9.2.2
       po2json: 0.4.5
       sass: 1.56.1
       typescript: 4.8.4
@@ -383,7 +395,7 @@ importers:
       sirv-cli: ^1.0.11
       swr: 1.3.0
       typedoc: ^0.20.36
-      typescript: 4.4.4
+      typescript: 4.8.4
       yup: ^0.32.9
     dependencies:
       '@gnu-taler/taler-util': link:../taler-util
@@ -405,9 +417,9 @@ importers:
       '@gnu-taler/pogen': link:../pogen
       '@storybook/addon-a11y': 6.5.13_@preact+compat@17.1.2
       '@storybook/addon-actions': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-essentials': 6.5.13_e4oandlgwkzlaxn6zc3btvol24
+      '@storybook/addon-essentials': 6.5.13_2nt5iz47gcdduxcnvfnexph4hq
       '@storybook/addon-links': 6.5.13_@preact+compat@17.1.2
-      '@storybook/preact': 6.5.13_q57tjbu375p52k2lkxkownwouu
+      '@storybook/preact': 6.5.13_xa5zyoe4qu34opzfrw5imxak3u
       '@storybook/preset-scss': 1.0.3_sass-loader@10.1.1
       '@testing-library/preact': 2.0.1_preact@10.6.5
       '@testing-library/preact-hooks': 1.1.0_vfcmu6iy7nffpurikpgxo6gwxi
@@ -415,8 +427,8 @@ importers:
       '@types/jest': 26.0.24
       '@types/mocha': 8.2.3
       '@types/node': 18.11.5
-      '@typescript-eslint/eslint-plugin': 4.33.0_zrqxgwgitu7trrjeml3nqco3jq
-      '@typescript-eslint/parser': 4.33.0_wnilx7boviscikmvsfkd6ljepe
+      '@typescript-eslint/eslint-plugin': 4.33.0_k4l66av2tbo6kxzw52jzgbfzii
+      '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim
       babel-loader: 8.2.5_@babel+core@7.18.9
       base64-inline-loader: 1.1.1
       bulma: 0.9.4
@@ -428,7 +440,7 @@ importers:
       bulma-upload-control: 1.2.0
       dotenv: 8.6.0
       eslint: 7.32.0
-      eslint-config-preact: 1.3.0_55vw575o5aj4h37h7cossdtfje
+      eslint-config-preact: 1.3.0_nxlzr75jbqkso2fds5zjovs2ii
       eslint-plugin-header: 3.1.1_eslint@7.32.0
       html-webpack-inline-chunk-plugin: 1.1.1
       html-webpack-inline-source-plugin: 0.0.10
@@ -445,8 +457,8 @@ importers:
       sass-loader: 10.1.1_sass@1.55.0
       script-ext-html-webpack-plugin: 2.1.5
       sirv-cli: 1.0.14
-      typedoc: 0.20.37_typescript@4.4.4
-      typescript: 4.4.4
+      typedoc: 0.20.37_typescript@4.8.4
+      typescript: 4.8.4
 
   packages/pogen:
     specifiers:
@@ -697,12 +709,15 @@ importers:
       '@types/node': ^18.11.9
       '@types/web': ^0.0.82
       '@types/ws': ^8.5.3
+      axios: ^1.2.1
       chokidar: ^3.5.3
       esbuild: ^0.14.21
       express: ^4.18.2
       preact: 10.11.3
+      preact-render-to-string: ^5.2.6
       prettier: ^2.5.1
       rimraf: ^3.0.2
+      swr: 1.3.0
       tslib: ^2.4.0
       typescript: ^4.8.4
       ws: 7.4.5
@@ -712,12 +727,15 @@ importers:
       '@types/node': 18.11.9
       '@types/web': 0.0.82
       '@types/ws': 8.5.3
+      axios: 1.2.1
       chokidar: 3.5.3
       esbuild: 0.14.54
       express: 4.18.2
       preact: 10.11.3
+      preact-render-to-string: 5.2.6_preact@10.11.3
       prettier: 2.7.1
       rimraf: 3.0.2
+      swr: 1.3.0
       tslib: 2.4.1
       typescript: 4.8.4
       ws: 7.4.5
@@ -4633,7 +4651,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/addon-controls/6.5.13_ipjevasairp6knvwkrpikcsgoq:
+  /@storybook/addon-controls/6.5.13_j3fy4dsuv6tweqy6dixwnav3v4:
     resolution: {integrity: 
sha512-lYq3uf2mlVevm0bi6ueL3H6TpUMRYW9s/pTNTVJT225l27kLdFR9wEKxAkCBrlKaTgDLJmzzDRsJE3NLZlR/5Q==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -4648,7 +4666,7 @@ packages:
       '@storybook/api': 6.5.13_@preact+compat@17.1.2
       '@storybook/client-logger': 6.5.13
       '@storybook/components': 6.5.13_@preact+compat@17.1.2
-      '@storybook/core-common': 6.5.13_ipjevasairp6knvwkrpikcsgoq
+      '@storybook/core-common': 6.5.13_j3fy4dsuv6tweqy6dixwnav3v4
       '@storybook/csf': 0.0.2--canary.4566f4d.1
       '@storybook/node-logger': 6.5.13
       '@storybook/store': 6.5.13_@preact+compat@17.1.2
@@ -4666,7 +4684,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/addon-docs/6.5.13_dazlt7ye7nu7xsezygxn7bviwy:
+  /@storybook/addon-docs/6.5.13_2nt5iz47gcdduxcnvfnexph4hq:
     resolution: {integrity: 
sha512-RG/NjsheD9FixZ789RJlNyNccaR2Cuy7CtAwph4oUNi3aDFjtOI8Oe9L+FOT7qtVnZLw/YMjF+pZxoDqJNKLPw==}
     peerDependencies:
       '@storybook/mdx2-csf': ^0.0.3
@@ -4683,26 +4701,27 @@ packages:
       '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.18.9
       '@babel/preset-env': 7.19.4_@babel+core@7.18.9
       '@jest/transform': 26.6.2
-      '@mdx-js/react': 1.6.22
-      '@storybook/addons': 6.5.13
-      '@storybook/api': 6.5.13
-      '@storybook/components': 6.5.13
-      '@storybook/core-common': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
+      '@mdx-js/react': 1.6.22_@preact+compat@17.1.2
+      '@storybook/addons': 6.5.13_@preact+compat@17.1.2
+      '@storybook/api': 6.5.13_@preact+compat@17.1.2
+      '@storybook/components': 6.5.13_@preact+compat@17.1.2
+      '@storybook/core-common': 6.5.13_j3fy4dsuv6tweqy6dixwnav3v4
       '@storybook/core-events': 6.5.13
       '@storybook/csf': 0.0.2--canary.4566f4d.1
-      '@storybook/docs-tools': 6.5.13
+      '@storybook/docs-tools': 6.5.13_@preact+compat@17.1.2
       '@storybook/mdx1-csf': 0.0.1_@babel+core@7.18.9
       '@storybook/node-logger': 6.5.13
       '@storybook/postinstall': 6.5.13
-      '@storybook/preview-web': 6.5.13
-      '@storybook/source-loader': 6.5.13
-      '@storybook/store': 6.5.13
-      '@storybook/theming': 6.5.13
+      '@storybook/preview-web': 6.5.13_@preact+compat@17.1.2
+      '@storybook/source-loader': 6.5.13_@preact+compat@17.1.2
+      '@storybook/store': 6.5.13_@preact+compat@17.1.2
+      '@storybook/theming': 6.5.13_@preact+compat@17.1.2
       babel-loader: 8.2.5_@babel+core@7.18.9
       core-js: 3.26.0
       fast-deep-equal: 3.1.3
       global: 4.4.0
       lodash: 4.17.21
+      react: /@preact/compat/17.1.2_preact@10.6.5
       regenerator-runtime: 0.13.10
       remark-external-links: 8.0.0
       remark-slug: 6.1.0
@@ -4719,7 +4738,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/addon-docs/6.5.13_e4oandlgwkzlaxn6zc3btvol24:
+  /@storybook/addon-docs/6.5.13_dazlt7ye7nu7xsezygxn7bviwy:
     resolution: {integrity: 
sha512-RG/NjsheD9FixZ789RJlNyNccaR2Cuy7CtAwph4oUNi3aDFjtOI8Oe9L+FOT7qtVnZLw/YMjF+pZxoDqJNKLPw==}
     peerDependencies:
       '@storybook/mdx2-csf': ^0.0.3
@@ -4736,27 +4755,26 @@ packages:
       '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.18.9
       '@babel/preset-env': 7.19.4_@babel+core@7.18.9
       '@jest/transform': 26.6.2
-      '@mdx-js/react': 1.6.22_@preact+compat@17.1.2
-      '@storybook/addons': 6.5.13_@preact+compat@17.1.2
-      '@storybook/api': 6.5.13_@preact+compat@17.1.2
-      '@storybook/components': 6.5.13_@preact+compat@17.1.2
-      '@storybook/core-common': 6.5.13_ipjevasairp6knvwkrpikcsgoq
+      '@mdx-js/react': 1.6.22
+      '@storybook/addons': 6.5.13
+      '@storybook/api': 6.5.13
+      '@storybook/components': 6.5.13
+      '@storybook/core-common': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
       '@storybook/core-events': 6.5.13
       '@storybook/csf': 0.0.2--canary.4566f4d.1
-      '@storybook/docs-tools': 6.5.13_@preact+compat@17.1.2
+      '@storybook/docs-tools': 6.5.13
       '@storybook/mdx1-csf': 0.0.1_@babel+core@7.18.9
       '@storybook/node-logger': 6.5.13
       '@storybook/postinstall': 6.5.13
-      '@storybook/preview-web': 6.5.13_@preact+compat@17.1.2
-      '@storybook/source-loader': 6.5.13_@preact+compat@17.1.2
-      '@storybook/store': 6.5.13_@preact+compat@17.1.2
-      '@storybook/theming': 6.5.13_@preact+compat@17.1.2
+      '@storybook/preview-web': 6.5.13
+      '@storybook/source-loader': 6.5.13
+      '@storybook/store': 6.5.13
+      '@storybook/theming': 6.5.13
       babel-loader: 8.2.5_@babel+core@7.18.9
       core-js: 3.26.0
       fast-deep-equal: 3.1.3
       global: 4.4.0
       lodash: 4.17.21
-      react: /@preact/compat/17.1.2_preact@10.6.5
       regenerator-runtime: 0.13.10
       remark-external-links: 8.0.0
       remark-slug: 6.1.0
@@ -4773,7 +4791,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/addon-essentials/6.5.13_dazlt7ye7nu7xsezygxn7bviwy:
+  /@storybook/addon-essentials/6.5.13_2nt5iz47gcdduxcnvfnexph4hq:
     resolution: {integrity: 
sha512-G9FVAWV7ixjVLWeLgIX+VT90tcAk6yQxfZQegfg5ucRilGysJCDaNnoab4xuuvm1R40TfFhba3iAGZtQYsddmw==}
     peerDependencies:
       '@babel/core': ^7.9.6
@@ -4831,19 +4849,20 @@ packages:
         optional: true
     dependencies:
       '@babel/core': 7.18.9
-      '@storybook/addon-actions': 6.5.13
-      '@storybook/addon-backgrounds': 6.5.13
-      '@storybook/addon-controls': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
-      '@storybook/addon-docs': 6.5.13_dazlt7ye7nu7xsezygxn7bviwy
-      '@storybook/addon-measure': 6.5.13
-      '@storybook/addon-outline': 6.5.13
-      '@storybook/addon-toolbars': 6.5.13
-      '@storybook/addon-viewport': 6.5.13
-      '@storybook/addons': 6.5.13
-      '@storybook/api': 6.5.13
-      '@storybook/core-common': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
+      '@storybook/addon-actions': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addon-backgrounds': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addon-controls': 6.5.13_j3fy4dsuv6tweqy6dixwnav3v4
+      '@storybook/addon-docs': 6.5.13_2nt5iz47gcdduxcnvfnexph4hq
+      '@storybook/addon-measure': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addon-outline': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addon-toolbars': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addon-viewport': 6.5.13_@preact+compat@17.1.2
+      '@storybook/addons': 6.5.13_@preact+compat@17.1.2
+      '@storybook/api': 6.5.13_@preact+compat@17.1.2
+      '@storybook/core-common': 6.5.13_j3fy4dsuv6tweqy6dixwnav3v4
       '@storybook/node-logger': 6.5.13
       core-js: 3.26.0
+      react: /@preact/compat/17.1.2_preact@10.6.5
       regenerator-runtime: 0.13.10
       ts-dedent: 2.2.0
     transitivePeerDependencies:
@@ -4856,7 +4875,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/addon-essentials/6.5.13_e4oandlgwkzlaxn6zc3btvol24:
+  /@storybook/addon-essentials/6.5.13_dazlt7ye7nu7xsezygxn7bviwy:
     resolution: {integrity: 
sha512-G9FVAWV7ixjVLWeLgIX+VT90tcAk6yQxfZQegfg5ucRilGysJCDaNnoab4xuuvm1R40TfFhba3iAGZtQYsddmw==}
     peerDependencies:
       '@babel/core': ^7.9.6
@@ -4914,20 +4933,19 @@ packages:
         optional: true
     dependencies:
       '@babel/core': 7.18.9
-      '@storybook/addon-actions': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-backgrounds': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-controls': 6.5.13_ipjevasairp6knvwkrpikcsgoq
-      '@storybook/addon-docs': 6.5.13_e4oandlgwkzlaxn6zc3btvol24
-      '@storybook/addon-measure': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-outline': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-toolbars': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addon-viewport': 6.5.13_@preact+compat@17.1.2
-      '@storybook/addons': 6.5.13_@preact+compat@17.1.2
-      '@storybook/api': 6.5.13_@preact+compat@17.1.2
-      '@storybook/core-common': 6.5.13_ipjevasairp6knvwkrpikcsgoq
+      '@storybook/addon-actions': 6.5.13
+      '@storybook/addon-backgrounds': 6.5.13
+      '@storybook/addon-controls': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
+      '@storybook/addon-docs': 6.5.13_dazlt7ye7nu7xsezygxn7bviwy
+      '@storybook/addon-measure': 6.5.13
+      '@storybook/addon-outline': 6.5.13
+      '@storybook/addon-toolbars': 6.5.13
+      '@storybook/addon-viewport': 6.5.13
+      '@storybook/addons': 6.5.13
+      '@storybook/api': 6.5.13
+      '@storybook/core-common': 6.5.13_3rubbgt5ekhqrcgx4uwls3neim
       '@storybook/node-logger': 6.5.13
       core-js: 3.26.0
-      react: /@preact/compat/17.1.2_preact@10.6.5
       regenerator-runtime: 0.13.10
       ts-dedent: 2.2.0
     transitivePeerDependencies:
@@ -5309,75 +5327,6 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/builder-webpack4/6.5.13_oj3zznhuofsh7mvlmigrvm3x7y:
-    resolution: {integrity: 
sha512-Agqy3IKPv3Nl8QqdS7PjtqLp+c0BD8+/3A2ki/YfKqVz+F+J34EpbZlh3uU053avm1EoNQHSmhZok3ZlWH6O7A==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@babel/core': 7.18.9
-      '@storybook/addons': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/api': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/channel-postmessage': 6.5.13
-      '@storybook/channels': 6.5.13
-      '@storybook/client-api': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/client-logger': 6.5.13
-      '@storybook/components': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/core-common': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@storybook/core-events': 6.5.13
-      '@storybook/node-logger': 6.5.13
-      '@storybook/preview-web': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/router': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/semver': 7.3.2
-      '@storybook/store': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/theming': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/ui': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@types/node': 16.18.0
-      '@types/webpack': 4.41.33
-      autoprefixer: 9.8.8
-      babel-loader: 8.2.5_7uc2ny5pnz7ums2wq2q562bf6y
-      case-sensitive-paths-webpack-plugin: 2.4.0
-      core-js: 3.26.0
-      css-loader: 3.6.0_webpack@4.46.0
-      file-loader: 6.2.0_webpack@4.46.0
-      find-up: 5.0.0
-      fork-ts-checker-webpack-plugin: 4.1.6_bbqhrndznz6a4k7d23h2kkrexi
-      glob: 7.2.3
-      glob-promise: 3.4.0_glob@7.2.3
-      global: 4.4.0
-      html-webpack-plugin: 4.5.2_webpack@4.46.0
-      pnp-webpack-plugin: 1.6.4_typescript@4.4.4
-      postcss: 7.0.39
-      postcss-flexbugs-fixes: 4.2.1
-      postcss-loader: 4.3.0_gzaxsinx64nntyd3vmdqwl7coe
-      raw-loader: 4.0.2_webpack@4.46.0
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      stable: 0.1.8
-      style-loader: 1.3.0_webpack@4.46.0
-      terser-webpack-plugin: 4.2.3_webpack@4.46.0
-      ts-dedent: 2.2.0
-      typescript: 4.4.4
-      url-loader: 4.1.1_lit45vopotvaqup7lrvlnvtxwy
-      util-deprecate: 1.0.2
-      webpack: 4.46.0
-      webpack-dev-middleware: 3.7.3_webpack@4.46.0
-      webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
-      webpack-hot-middleware: 2.25.2
-      webpack-virtual-modules: 0.2.2
-    transitivePeerDependencies:
-      - bluebird
-      - eslint
-      - supports-color
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
   /@storybook/builder-webpack4/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
     resolution: {integrity: 
sha512-Agqy3IKPv3Nl8QqdS7PjtqLp+c0BD8+/3A2ki/YfKqVz+F+J34EpbZlh3uU053avm1EoNQHSmhZok3ZlWH6O7A==}
     peerDependencies:
@@ -5565,80 +5514,6 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/core-client/6.5.13_4z4xstjutcmkuoh5zkinl7lmym:
-    resolution: {integrity: 
sha512-YuELbRokTBdqjbx/R4/7O4rou9kvbBIOJjlUkor9hdLLuJ3P0yGianERGNkZFfvcfMBAxU0p52o7QvDldSR3kA==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-      webpack: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@storybook/addons': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/channel-postmessage': 6.5.13
-      '@storybook/channel-websocket': 6.5.13
-      '@storybook/client-api': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/client-logger': 6.5.13
-      '@storybook/core-events': 6.5.13
-      '@storybook/csf': 0.0.2--canary.4566f4d.1
-      '@storybook/preview-web': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/store': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/ui': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      airbnb-js-shims: 2.2.1
-      ansi-to-html: 0.6.15
-      core-js: 3.26.0
-      global: 4.4.0
-      lodash: 4.17.21
-      qs: 6.11.0
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      regenerator-runtime: 0.13.10
-      ts-dedent: 2.2.0
-      typescript: 4.4.4
-      unfetch: 4.2.0
-      util-deprecate: 1.0.2
-      webpack: 5.74.0
-    dev: true
-
-  /@storybook/core-client/6.5.13_6dhvdvok4rm6xhvn3ntojenmae:
-    resolution: {integrity: 
sha512-YuELbRokTBdqjbx/R4/7O4rou9kvbBIOJjlUkor9hdLLuJ3P0yGianERGNkZFfvcfMBAxU0p52o7QvDldSR3kA==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-      webpack: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@storybook/addons': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/channel-postmessage': 6.5.13
-      '@storybook/channel-websocket': 6.5.13
-      '@storybook/client-api': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/client-logger': 6.5.13
-      '@storybook/core-events': 6.5.13
-      '@storybook/csf': 0.0.2--canary.4566f4d.1
-      '@storybook/preview-web': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/store': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/ui': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      airbnb-js-shims: 2.2.1
-      ansi-to-html: 0.6.15
-      core-js: 3.26.0
-      global: 4.4.0
-      lodash: 4.17.21
-      qs: 6.11.0
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      regenerator-runtime: 0.13.10
-      ts-dedent: 2.2.0
-      typescript: 4.4.4
-      unfetch: 4.2.0
-      util-deprecate: 1.0.2
-      webpack: 4.46.0
-    dev: true
-
   /@storybook/core-client/6.5.13_plmdyubmb7xm6euvqu3qohl7ea:
     resolution: {integrity: 
sha512-YuELbRokTBdqjbx/R4/7O4rou9kvbBIOJjlUkor9hdLLuJ3P0yGianERGNkZFfvcfMBAxU0p52o7QvDldSR3kA==}
     peerDependencies:
@@ -5782,7 +5657,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/core-common/6.5.13_ipjevasairp6knvwkrpikcsgoq:
+  /@storybook/core-common/6.5.13_j3fy4dsuv6tweqy6dixwnav3v4:
     resolution: {integrity: 
sha512-+DVZrRsteE9pw0X5MNffkdBgejQnbnL+UOG3qXkE9xxUamQALnuqS/w1BzpHE9WmOHuf7RWMKflyQEW3OLKAJg==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5826,7 +5701,7 @@ packages:
       express: 4.18.2
       file-system-cache: 1.1.0
       find-up: 5.0.0
-      fork-ts-checker-webpack-plugin: 6.5.2_bbqhrndznz6a4k7d23h2kkrexi
+      fork-ts-checker-webpack-plugin: 6.5.2_3n2x3j6farblcaf52bherr6og4
       fs-extra: 9.1.0
       glob: 7.2.3
       handlebars: 4.7.7
@@ -5841,7 +5716,7 @@ packages:
       slash: 3.0.0
       telejson: 6.0.8
       ts-dedent: 2.2.0
-      typescript: 4.4.4
+      typescript: 4.8.4
       util-deprecate: 1.0.2
       webpack: 4.46.0
     transitivePeerDependencies:
@@ -5852,7 +5727,7 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/core-common/6.5.13_oj3zznhuofsh7mvlmigrvm3x7y:
+  /@storybook/core-common/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
     resolution: {integrity: 
sha512-+DVZrRsteE9pw0X5MNffkdBgejQnbnL+UOG3qXkE9xxUamQALnuqS/w1BzpHE9WmOHuf7RWMKflyQEW3OLKAJg==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5896,7 +5771,7 @@ packages:
       express: 4.18.2
       file-system-cache: 1.1.0
       find-up: 5.0.0
-      fork-ts-checker-webpack-plugin: 6.5.2_bbqhrndznz6a4k7d23h2kkrexi
+      fork-ts-checker-webpack-plugin: 6.5.2_3n2x3j6farblcaf52bherr6og4
       fs-extra: 9.1.0
       glob: 7.2.3
       handlebars: 4.7.7
@@ -5912,7 +5787,7 @@ packages:
       slash: 3.0.0
       telejson: 6.0.8
       ts-dedent: 2.2.0
-      typescript: 4.4.4
+      typescript: 4.8.4
       util-deprecate: 1.0.2
       webpack: 4.46.0
     transitivePeerDependencies:
@@ -5923,173 +5798,25 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/core-common/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
-    resolution: {integrity: 
sha512-+DVZrRsteE9pw0X5MNffkdBgejQnbnL+UOG3qXkE9xxUamQALnuqS/w1BzpHE9WmOHuf7RWMKflyQEW3OLKAJg==}
+  /@storybook/core-events/6.5.13:
+    resolution: {integrity: 
sha512-kL745tPpRKejzHToA3/CoBNbI+NPRVk186vGxXBmk95OEg0TlwgQExP8BnqEtLlRZMbW08e4+6kilc1M1M4N5w==}
+    dependencies:
+      core-js: 3.26.0
+    dev: true
+
+  /@storybook/core-server/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
+    resolution: {integrity: 
sha512-vs7tu3kAnFwuINio1p87WyqDNlFyZESmeh9s7vvrZVbe/xS/ElqDscr9DT5seW+jbtxufAaHsx+JUTver1dheQ==}
     peerDependencies:
+      '@storybook/builder-webpack5': '*'
+      '@storybook/manager-webpack5': '*'
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       typescript: '*'
     peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@babel/core': 7.18.9
-      '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.18.9
-      '@babel/plugin-proposal-decorators': 7.19.6_@babel+core@7.18.9
-      '@babel/plugin-proposal-export-default-from': 7.18.10_@babel+core@7.18.9
-      '@babel/plugin-proposal-nullish-coalescing-operator': 
7.18.6_@babel+core@7.18.9
-      '@babel/plugin-proposal-object-rest-spread': 7.19.4_@babel+core@7.18.9
-      '@babel/plugin-proposal-optional-chaining': 7.18.9_@babel+core@7.18.9
-      '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.18.9
-      '@babel/plugin-proposal-private-property-in-object': 
7.18.6_@babel+core@7.18.9
-      '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.18.9
-      '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.18.9
-      '@babel/plugin-transform-block-scoping': 7.19.4_@babel+core@7.18.9
-      '@babel/plugin-transform-classes': 7.19.0_@babel+core@7.18.9
-      '@babel/plugin-transform-destructuring': 7.19.4_@babel+core@7.18.9
-      '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.18.9
-      '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.18.9
-      '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.18.9
-      '@babel/plugin-transform-spread': 7.19.0_@babel+core@7.18.9
-      '@babel/preset-env': 7.19.4_@babel+core@7.18.9
-      '@babel/preset-react': 7.18.6_@babel+core@7.18.9
-      '@babel/preset-typescript': 7.18.6_@babel+core@7.18.9
-      '@babel/register': 7.18.9_@babel+core@7.18.9
-      '@storybook/node-logger': 6.5.13
-      '@storybook/semver': 7.3.2
-      '@types/node': 16.18.0
-      '@types/pretty-hrtime': 1.0.1
-      babel-loader: 8.2.5_7uc2ny5pnz7ums2wq2q562bf6y
-      babel-plugin-macros: 3.1.0
-      babel-plugin-polyfill-corejs3: 0.1.7_@babel+core@7.18.9
-      chalk: 4.1.2
-      core-js: 3.26.0
-      express: 4.18.2
-      file-system-cache: 1.1.0
-      find-up: 5.0.0
-      fork-ts-checker-webpack-plugin: 6.5.2_3n2x3j6farblcaf52bherr6og4
-      fs-extra: 9.1.0
-      glob: 7.2.3
-      handlebars: 4.7.7
-      interpret: 2.2.0
-      json5: 2.2.1
-      lazy-universal-dotenv: 3.0.1
-      picomatch: 2.3.1
-      pkg-dir: 5.0.0
-      pretty-hrtime: 1.0.3
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      resolve-from: 5.0.0
-      slash: 3.0.0
-      telejson: 6.0.8
-      ts-dedent: 2.2.0
-      typescript: 4.8.4
-      util-deprecate: 1.0.2
-      webpack: 4.46.0
-    transitivePeerDependencies:
-      - eslint
-      - supports-color
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
-  /@storybook/core-events/6.5.13:
-    resolution: {integrity: 
sha512-kL745tPpRKejzHToA3/CoBNbI+NPRVk186vGxXBmk95OEg0TlwgQExP8BnqEtLlRZMbW08e4+6kilc1M1M4N5w==}
-    dependencies:
-      core-js: 3.26.0
-    dev: true
-
-  /@storybook/core-server/6.5.13_oj3zznhuofsh7mvlmigrvm3x7y:
-    resolution: {integrity: 
sha512-vs7tu3kAnFwuINio1p87WyqDNlFyZESmeh9s7vvrZVbe/xS/ElqDscr9DT5seW+jbtxufAaHsx+JUTver1dheQ==}
-    peerDependencies:
-      '@storybook/builder-webpack5': '*'
-      '@storybook/manager-webpack5': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      '@storybook/builder-webpack5':
-        optional: true
-      '@storybook/manager-webpack5':
-        optional: true
-      typescript:
-        optional: true
-    dependencies:
-      '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-webpack4': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@storybook/core-client': 6.5.13_6dhvdvok4rm6xhvn3ntojenmae
-      '@storybook/core-common': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@storybook/core-events': 6.5.13
-      '@storybook/csf': 0.0.2--canary.4566f4d.1
-      '@storybook/csf-tools': 6.5.13
-      '@storybook/manager-webpack4': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@storybook/node-logger': 6.5.13
-      '@storybook/semver': 7.3.2
-      '@storybook/store': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/telemetry': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@types/node': 16.18.0
-      '@types/node-fetch': 2.6.2
-      '@types/pretty-hrtime': 1.0.1
-      '@types/webpack': 4.41.33
-      better-opn: 2.1.1
-      boxen: 5.1.2
-      chalk: 4.1.2
-      cli-table3: 0.6.3
-      commander: 6.2.1
-      compression: 1.7.4
-      core-js: 3.26.0
-      cpy: 8.1.2
-      detect-port: 1.5.1
-      express: 4.18.2
-      fs-extra: 9.1.0
-      global: 4.4.0
-      globby: 11.1.0
-      ip: 2.0.0
-      lodash: 4.17.21
-      node-fetch: 2.6.7
-      open: 8.4.0
-      pretty-hrtime: 1.0.3
-      prompts: 2.4.2
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      regenerator-runtime: 0.13.10
-      serve-favicon: 2.5.0
-      slash: 3.0.0
-      telejson: 6.0.8
-      ts-dedent: 2.2.0
-      typescript: 4.4.4
-      util-deprecate: 1.0.2
-      watchpack: 2.4.0
-      webpack: 4.46.0
-      ws: 8.10.0
-      x-default-browser: 0.4.0
-    transitivePeerDependencies:
-      - '@storybook/mdx2-csf'
-      - bluebird
-      - bufferutil
-      - encoding
-      - eslint
-      - supports-color
-      - utf-8-validate
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
-  /@storybook/core-server/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
-    resolution: {integrity: 
sha512-vs7tu3kAnFwuINio1p87WyqDNlFyZESmeh9s7vvrZVbe/xS/ElqDscr9DT5seW+jbtxufAaHsx+JUTver1dheQ==}
-    peerDependencies:
-      '@storybook/builder-webpack5': '*'
-      '@storybook/manager-webpack5': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      '@storybook/builder-webpack5':
-        optional: true
-      '@storybook/manager-webpack5':
-        optional: true
+      '@storybook/builder-webpack5':
+        optional: true
+      '@storybook/manager-webpack5':
+        optional: true
       typescript:
         optional: true
     dependencies:
@@ -6190,42 +5917,6 @@ packages:
       - webpack-command
     dev: true
 
-  /@storybook/core/6.5.13_ujfskrypehrywuobbsgnsb3724:
-    resolution: {integrity: 
sha512-kw1lCgbsxzUimGww6t5rmuWJmFPe9kGGyzIqvj4RC4BBcEsP40LEu9XhSfvnb8vTOLIULFZeZpdRFfJs4TYbUw==}
-    peerDependencies:
-      '@storybook/builder-webpack5': '*'
-      '@storybook/manager-webpack5': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-      webpack: '*'
-    peerDependenciesMeta:
-      '@storybook/builder-webpack5':
-        optional: true
-      '@storybook/manager-webpack5':
-        optional: true
-      typescript:
-        optional: true
-    dependencies:
-      '@storybook/core-client': 6.5.13_4z4xstjutcmkuoh5zkinl7lmym
-      '@storybook/core-server': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      typescript: 4.4.4
-      webpack: 5.74.0
-    transitivePeerDependencies:
-      - '@storybook/mdx2-csf'
-      - bluebird
-      - bufferutil
-      - encoding
-      - eslint
-      - supports-color
-      - utf-8-validate
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
   /@storybook/csf-tools/6.5.13:
     resolution: {integrity: 
sha512-63Ev+VmBqzwSwfUzbuXOLKBD5dMTK2zBYLQ9anTVw70FuTikwTsGIbPgb098K0vsxRCgxl7KM7NpivHqtZtdjw==}
     peerDependencies:
@@ -6290,64 +5981,6 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/manager-webpack4/6.5.13_oj3zznhuofsh7mvlmigrvm3x7y:
-    resolution: {integrity: 
sha512-pURzS5W3XM0F7bCBWzpl7TRsuy+OXFwLXiWLaexuvo0POZe31Ueo2A1R4rx3MT5Iee8O9mYvG2XTmvK9MlLefQ==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@babel/core': 7.18.9
-      '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.18.9
-      '@babel/preset-react': 7.18.6_@babel+core@7.18.9
-      '@storybook/addons': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/core-client': 6.5.13_6dhvdvok4rm6xhvn3ntojenmae
-      '@storybook/core-common': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      '@storybook/node-logger': 6.5.13
-      '@storybook/theming': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/ui': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@types/node': 16.18.0
-      '@types/webpack': 4.41.33
-      babel-loader: 8.2.5_7uc2ny5pnz7ums2wq2q562bf6y
-      case-sensitive-paths-webpack-plugin: 2.4.0
-      chalk: 4.1.2
-      core-js: 3.26.0
-      css-loader: 3.6.0_webpack@4.46.0
-      express: 4.18.2
-      file-loader: 6.2.0_webpack@4.46.0
-      find-up: 5.0.0
-      fs-extra: 9.1.0
-      html-webpack-plugin: 4.5.2_webpack@4.46.0
-      node-fetch: 2.6.7
-      pnp-webpack-plugin: 1.6.4_typescript@4.4.4
-      react: 16.14.0
-      react-dom: 16.14.0_react@16.14.0
-      read-pkg-up: 7.0.1
-      regenerator-runtime: 0.13.10
-      resolve-from: 5.0.0
-      style-loader: 1.3.0_webpack@4.46.0
-      telejson: 6.0.8
-      terser-webpack-plugin: 4.2.3_webpack@4.46.0
-      ts-dedent: 2.2.0
-      typescript: 4.4.4
-      url-loader: 4.1.1_lit45vopotvaqup7lrvlnvtxwy
-      util-deprecate: 1.0.2
-      webpack: 4.46.0
-      webpack-dev-middleware: 3.7.3_webpack@4.46.0
-      webpack-virtual-modules: 0.2.2
-    transitivePeerDependencies:
-      - bluebird
-      - encoding
-      - eslint
-      - supports-color
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
   /@storybook/manager-webpack4/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
     resolution: {integrity: 
sha512-pURzS5W3XM0F7bCBWzpl7TRsuy+OXFwLXiWLaexuvo0POZe31Ueo2A1R4rx3MT5Iee8O9mYvG2XTmvK9MlLefQ==}
     peerDependencies:
@@ -6441,7 +6074,7 @@ packages:
       core-js: 3.26.0
     dev: true
 
-  /@storybook/preact/6.5.13_q57tjbu375p52k2lkxkownwouu:
+  /@storybook/preact/6.5.13_xa5zyoe4qu34opzfrw5imxak3u:
     resolution: {integrity: 
sha512-5/ufRgxh5VypFcOeIBQMg/AqZQ2+KfUw4Glo9HU75dbIe/kYqSnN3+5SEv8J6ykxHHtUWcmnILS1r7/I5R7j/w==}
     engines: {node: '>=10.13.0'}
     hasBin: true
@@ -6452,8 +6085,8 @@ packages:
       '@babel/core': 7.18.9
       '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.18.9
       '@storybook/addons': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
-      '@storybook/core': 6.5.13_ujfskrypehrywuobbsgnsb3724
-      '@storybook/core-common': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
+      '@storybook/core': 6.5.13_cadditq4xyv3neitvabz3hzhjy
+      '@storybook/core-common': 6.5.13_u5cwnb36e3nipolzgtjnnpepdu
       '@storybook/csf': 0.0.2--canary.4566f4d.1
       '@storybook/store': 6.5.13_wcqkhtmu7mswc6yz4uyexck3ty
       '@types/node': 16.18.0
@@ -6776,33 +6409,6 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/telemetry/6.5.13_oj3zznhuofsh7mvlmigrvm3x7y:
-    resolution: {integrity: 
sha512-PFJEfGbunmfFWabD3rdCF8EHH+45578OHOkMPpXJjqXl94vPQxUH2XTVKQgEQJbYrgX0Vx9Z4tSkdMHuzYDbWQ==}
-    dependencies:
-      '@storybook/client-logger': 6.5.13
-      '@storybook/core-common': 6.5.13_oj3zznhuofsh7mvlmigrvm3x7y
-      chalk: 4.1.2
-      core-js: 3.26.0
-      detect-package-manager: 2.0.1
-      fetch-retry: 5.0.3
-      fs-extra: 9.1.0
-      global: 4.4.0
-      isomorphic-unfetch: 3.1.0
-      nanoid: 3.3.4
-      read-pkg-up: 7.0.1
-      regenerator-runtime: 0.13.10
-    transitivePeerDependencies:
-      - encoding
-      - eslint
-      - react
-      - react-dom
-      - supports-color
-      - typescript
-      - vue-template-compiler
-      - webpack-cli
-      - webpack-command
-    dev: true
-
   /@storybook/telemetry/6.5.13_u5cwnb36e3nipolzgtjnnpepdu:
     resolution: {integrity: 
sha512-PFJEfGbunmfFWabD3rdCF8EHH+45578OHOkMPpXJjqXl94vPQxUH2XTVKQgEQJbYrgX0Vx9Z4tSkdMHuzYDbWQ==}
     dependencies:
@@ -7210,6 +6816,10 @@ packages:
     resolution: {integrity: 
sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
     dev: true
 
+  /@types/mocha/10.0.1:
+    resolution: {integrity: 
sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==}
+    dev: true
+
   /@types/mocha/8.2.3:
     resolution: {integrity: 
sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==}
     dev: true
@@ -7233,6 +6843,10 @@ packages:
     resolution: {integrity: 
sha512-LqYqYzYvnbCaQfLAwRt0zboqnsViwhZm+vjaMSqcfN36vulAg7Pt0T83q4WZO2YOBw3XdyHi8cQ88H22zmULOA==}
     dev: true
 
+  /@types/node/18.11.14:
+    resolution: {integrity: 
sha512-0KXV57tENYmmJMl+FekeW9V3O/rlcqGQQJ/hNh9r8pKIj304pskWuEd8fCyNT86g/TpO0gcOTiLzsHLEURFMIQ==}
+    dev: true
+
   /@types/node/18.11.5:
     resolution: {integrity: 
sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==}
 
@@ -7421,34 +7035,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/experimental-utils': 
4.33.0_3rubbgt5ekhqrcgx4uwls3neim
-      '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim
-      '@typescript-eslint/scope-manager': 4.33.0
-      debug: 4.3.4
-      eslint: 7.32.0
-      functional-red-black-tree: 1.0.1
-      ignore: 5.2.0
-      regexpp: 3.2.0
-      semver: 7.3.8
-      tsutils: 3.21.0_typescript@4.8.4
-      typescript: 4.8.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@typescript-eslint/eslint-plugin/4.33.0_zrqxgwgitu7trrjeml3nqco3jq:
-    resolution: {integrity: 
sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==}
-    engines: {node: ^10.12.0 || >=12.0.0}
-    peerDependencies:
-      '@typescript-eslint/parser': ^4.0.0
-      eslint: ^5.0.0 || ^6.0.0 || ^7.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@typescript-eslint/experimental-utils': 
4.33.0_wnilx7boviscikmvsfkd6ljepe
-      '@typescript-eslint/parser': 4.33.0_wnilx7boviscikmvsfkd6ljepe
+      '@typescript-eslint/experimental-utils': 
4.33.0_3rubbgt5ekhqrcgx4uwls3neim
+      '@typescript-eslint/parser': 4.33.0_3rubbgt5ekhqrcgx4uwls3neim
       '@typescript-eslint/scope-manager': 4.33.0
       debug: 4.3.4
       eslint: 7.32.0
@@ -7456,8 +7044,8 @@ packages:
       ignore: 5.2.0
       regexpp: 3.2.0
       semver: 7.3.8
-      tsutils: 3.21.0_typescript@4.4.4
-      typescript: 4.4.4
+      tsutils: 3.21.0_typescript@4.8.4
+      typescript: 4.8.4
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -7531,24 +7119,6 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/experimental-utils/4.33.0_wnilx7boviscikmvsfkd6ljepe:
-    resolution: {integrity: 
sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==}
-    engines: {node: ^10.12.0 || >=12.0.0}
-    peerDependencies:
-      eslint: '*'
-    dependencies:
-      '@types/json-schema': 7.0.11
-      '@typescript-eslint/scope-manager': 4.33.0
-      '@typescript-eslint/types': 4.33.0
-      '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.4.4
-      eslint: 7.32.0
-      eslint-scope: 5.1.1
-      eslint-utils: 3.0.0_eslint@7.32.0
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
-    dev: true
-
   /@typescript-eslint/experimental-utils/5.41.0_3rubbgt5ekhqrcgx4uwls3neim:
     resolution: {integrity: 
sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7574,19 +7144,6 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/experimental-utils/5.41.0_wnilx7boviscikmvsfkd6ljepe:
-    resolution: {integrity: 
sha512-/qxT2Kd2q/A22JVIllvws4rvc00/3AT4rAo/0YgEN28y+HPhbJbk6X4+MAHEoZzpNyAOugIT7D/OLnKBW8FfhA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
-    dependencies:
-      '@typescript-eslint/utils': 5.41.0_wnilx7boviscikmvsfkd6ljepe
-      eslint: 7.32.0
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
-    dev: true
-
   /@typescript-eslint/parser/4.33.0_3rubbgt5ekhqrcgx4uwls3neim:
     resolution: {integrity: 
sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -7607,26 +7164,6 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser/4.33.0_wnilx7boviscikmvsfkd6ljepe:
-    resolution: {integrity: 
sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==}
-    engines: {node: ^10.12.0 || >=12.0.0}
-    peerDependencies:
-      eslint: ^5.0.0 || ^6.0.0 || ^7.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@typescript-eslint/scope-manager': 4.33.0
-      '@typescript-eslint/types': 4.33.0
-      '@typescript-eslint/typescript-estree': 4.33.0_typescript@4.4.4
-      debug: 4.3.4
-      eslint: 7.32.0
-      typescript: 4.4.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@typescript-eslint/parser/5.41.0_typescript@4.8.4:
     resolution: {integrity: 
sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7731,27 +7268,6 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /@typescript-eslint/typescript-estree/4.33.0_typescript@4.4.4:
-    resolution: {integrity: 
sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==}
-    engines: {node: ^10.12.0 || >=12.0.0}
-    peerDependencies:
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@typescript-eslint/types': 4.33.0
-      '@typescript-eslint/visitor-keys': 4.33.0
-      debug: 4.3.4
-      globby: 11.1.0
-      is-glob: 4.0.3
-      semver: 7.3.8
-      tsutils: 3.21.0_typescript@4.4.4
-      typescript: 4.4.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@typescript-eslint/typescript-estree/4.33.0_typescript@4.8.4:
     resolution: {integrity: 
sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -7773,27 +7289,6 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/typescript-estree/5.41.0_typescript@4.4.4:
-    resolution: {integrity: 
sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      '@typescript-eslint/types': 5.41.0
-      '@typescript-eslint/visitor-keys': 5.41.0
-      debug: 4.3.4
-      globby: 11.1.0
-      is-glob: 4.0.3
-      semver: 7.3.8
-      tsutils: 3.21.0_typescript@4.4.4
-      typescript: 4.4.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@typescript-eslint/typescript-estree/5.41.0_typescript@4.8.4:
     resolution: {integrity: 
sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7854,26 +7349,6 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/utils/5.41.0_wnilx7boviscikmvsfkd6ljepe:
-    resolution: {integrity: 
sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
-    dependencies:
-      '@types/json-schema': 7.0.11
-      '@types/semver': 7.3.12
-      '@typescript-eslint/scope-manager': 5.41.0
-      '@typescript-eslint/types': 5.41.0
-      '@typescript-eslint/typescript-estree': 5.41.0_typescript@4.4.4
-      eslint: 7.32.0
-      eslint-scope: 5.1.1
-      eslint-utils: 3.0.0_eslint@7.32.0
-      semver: 7.3.8
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
-    dev: true
-
   /@typescript-eslint/utils/5.41.0_wyqvi574yv7oiwfeinomdzmc3m:
     resolution: {integrity: 
sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -8713,6 +8188,10 @@ packages:
     resolution: {integrity: 
sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
     dev: true
 
+  /async_hooks/1.0.0:
+    resolution: {integrity: 
sha512-t4BSJgx48V3e7U6Ll3/WOUNmxIRPzmPdxVfgbyzcnRItEnn4iKp4F//b0sV3L9hzbdr5qxWdNWzOF7t+rjYSfA==}
+    dev: true
+
   /asynckit/0.4.0:
     resolution: {integrity: 
sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
 
@@ -8914,6 +8393,16 @@ packages:
       - debug
     dev: true
 
+  /axios/1.2.1:
+    resolution: {integrity: 
sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==}
+    dependencies:
+      follow-redirects: 1.15.2
+      form-data: 4.0.0
+      proxy-from-env: 1.1.0
+    transitivePeerDependencies:
+      - debug
+    dev: true
+
   /axobject-query/2.2.0:
     resolution: {integrity: 
sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==}
     dev: true
@@ -12486,28 +11975,6 @@ packages:
       eslint-plugin-import: 2.26.0_c2flhriocdzler6lrwbyxxyoca
     dev: true
 
-  /eslint-config-preact/1.3.0_55vw575o5aj4h37h7cossdtfje:
-    resolution: {integrity: 
sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw==}
-    peerDependencies:
-      eslint: 6.x || 7.x || 8.x
-    dependencies:
-      '@babel/core': 7.18.9
-      '@babel/eslint-parser': 7.19.1_o5peei4wpze5egwf42u76kwdva
-      '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.18.9
-      '@babel/plugin-syntax-decorators': 7.19.0_@babel+core@7.18.9
-      '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.18.9
-      eslint: 7.32.0
-      eslint-plugin-compat: 4.0.2_eslint@7.32.0
-      eslint-plugin-jest: 25.7.0_55vw575o5aj4h37h7cossdtfje
-      eslint-plugin-react: 7.31.10_eslint@7.32.0
-      eslint-plugin-react-hooks: 4.6.0_eslint@7.32.0
-    transitivePeerDependencies:
-      - '@typescript-eslint/eslint-plugin'
-      - jest
-      - supports-color
-      - typescript
-    dev: true
-
   /eslint-config-preact/1.3.0_nxlzr75jbqkso2fds5zjovs2ii:
     resolution: {integrity: 
sha512-yHYXg5qNzEJd3D/30AmsIW0W8MuY858KpApXp7xxBF08IYUljSKCOqMx+dVucXHQnAm7+11wOnMkgVHIBAechw==}
     peerDependencies:
@@ -12670,28 +12137,6 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-jest/25.7.0_55vw575o5aj4h37h7cossdtfje:
-    resolution: {integrity: 
sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
-    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
-    peerDependencies:
-      '@typescript-eslint/eslint-plugin': ^4.0.0 || ^5.0.0
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
-      jest: '*'
-    peerDependenciesMeta:
-      '@typescript-eslint/eslint-plugin':
-        optional: true
-      jest:
-        optional: true
-    dependencies:
-      '@typescript-eslint/eslint-plugin': 4.33.0_zrqxgwgitu7trrjeml3nqco3jq
-      '@typescript-eslint/experimental-utils': 
5.41.0_wnilx7boviscikmvsfkd6ljepe
-      eslint: 7.32.0
-      jest: 26.6.3
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
-    dev: true
-
   /eslint-plugin-jest/25.7.0_nxlzr75jbqkso2fds5zjovs2ii:
     resolution: {integrity: 
sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -13659,34 +13104,6 @@ packages:
       - supports-color
     dev: true
 
-  /fork-ts-checker-webpack-plugin/4.1.6_bbqhrndznz6a4k7d23h2kkrexi:
-    resolution: {integrity: 
sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==}
-    engines: {node: '>=6.11.5', yarn: '>=1.0.0'}
-    peerDependencies:
-      eslint: '>= 6'
-      typescript: '>= 2.7'
-      vue-template-compiler: '*'
-      webpack: '>= 4'
-    peerDependenciesMeta:
-      eslint:
-        optional: true
-      vue-template-compiler:
-        optional: true
-    dependencies:
-      '@babel/code-frame': 7.18.6
-      chalk: 2.4.2
-      eslint: 7.32.0
-      micromatch: 3.1.10
-      minimatch: 3.1.2
-      semver: 5.7.1
-      tapable: 1.1.3
-      typescript: 4.4.4
-      webpack: 4.46.0
-      worker-rpc: 0.1.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /fork-ts-checker-webpack-plugin/4.1.6_gplzhsecki363wzvnzp4wfrwvi:
     resolution: {integrity: 
sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==}
     engines: {node: '>=6.11.5', yarn: '>=1.0.0'}
@@ -13774,38 +13191,6 @@ packages:
       webpack: 4.46.0
     dev: true
 
-  /fork-ts-checker-webpack-plugin/6.5.2_bbqhrndznz6a4k7d23h2kkrexi:
-    resolution: {integrity: 
sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==}
-    engines: {node: '>=10', yarn: '>=1.0.0'}
-    peerDependencies:
-      eslint: '>= 6'
-      typescript: '>= 2.7'
-      vue-template-compiler: '*'
-      webpack: '>= 4'
-    peerDependenciesMeta:
-      eslint:
-        optional: true
-      vue-template-compiler:
-        optional: true
-    dependencies:
-      '@babel/code-frame': 7.18.6
-      '@types/json-schema': 7.0.11
-      chalk: 4.1.2
-      chokidar: 3.5.3
-      cosmiconfig: 6.0.0
-      deepmerge: 4.2.2
-      eslint: 7.32.0
-      fs-extra: 9.1.0
-      glob: 7.2.3
-      memfs: 3.4.7
-      minimatch: 3.1.2
-      schema-utils: 2.7.0
-      semver: 7.3.8
-      tapable: 1.1.3
-      typescript: 4.4.4
-      webpack: 4.46.0
-    dev: true
-
   /form-data/2.3.3:
     resolution: {integrity: 
sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
     engines: {node: '>= 0.12'}
@@ -18324,15 +17709,6 @@ packages:
     resolution: {integrity: 
sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==}
     dev: true
 
-  /pnp-webpack-plugin/1.6.4_typescript@4.4.4:
-    resolution: {integrity: 
sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==}
-    engines: {node: '>=6'}
-    dependencies:
-      ts-pnp: 1.2.0_typescript@4.4.4
-    transitivePeerDependencies:
-      - typescript
-    dev: true
-
   /pnp-webpack-plugin/1.6.4_typescript@4.8.4:
     resolution: {integrity: 
sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==}
     engines: {node: '>=6'}
@@ -21624,7 +21000,6 @@ packages:
     resolution: {integrity: 
sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
     peerDependencies:
       react: ^16.11.0 || ^17.0.0 || ^18.0.0
-    dev: false
 
   /swr/1.3.0_@preact+compat@17.1.2:
     resolution: {integrity: 
sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
@@ -22039,18 +21414,6 @@ packages:
       tslib: 2.4.1
     dev: true
 
-  /ts-pnp/1.2.0_typescript@4.4.4:
-    resolution: {integrity: 
sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
-    engines: {node: '>=6'}
-    peerDependencies:
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-    dependencies:
-      typescript: 4.4.4
-    dev: true
-
   /ts-pnp/1.2.0_typescript@4.6.4:
     resolution: {integrity: 
sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
     engines: {node: '>=6'}
@@ -22083,16 +21446,6 @@ packages:
     resolution: {integrity: 
sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
     dev: true
 
-  /tsutils/3.21.0_typescript@4.4.4:
-    resolution: {integrity: 
sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
-    engines: {node: '>= 6'}
-    peerDependencies:
-      typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || 
>= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
-    dependencies:
-      tslib: 1.14.1
-      typescript: 4.4.4
-    dev: true
-
   /tsutils/3.21.0_typescript@4.8.4:
     resolution: {integrity: 
sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
     engines: {node: '>= 6'}
@@ -22188,27 +21541,6 @@ packages:
     engines: {node: '>= 8'}
     dev: true
 
-  /typedoc/0.20.37_typescript@4.4.4:
-    resolution: {integrity: 
sha512-9+qDhdc4X00qTNOtii6QX2z7ndAeWVOso7w3MPSoSJdXlVhpwPfm1yEp4ooKuWA9fiQILR8FKkyjmeqa13hBbw==}
-    engines: {node: '>= 10.8.0'}
-    hasBin: true
-    peerDependencies:
-      typescript: 3.9.x || 4.0.x || 4.1.x || 4.2.x
-    dependencies:
-      colors: 1.4.0
-      fs-extra: 9.1.0
-      handlebars: 4.7.7
-      lodash: 4.17.21
-      lunr: 2.3.9
-      marked: 2.0.7
-      minimatch: 3.1.2
-      progress: 2.0.3
-      shelljs: 0.8.5
-      shiki: 0.9.15
-      typedoc-default-themes: 0.12.10
-      typescript: 4.4.4
-    dev: true
-
   /typedoc/0.20.37_typescript@4.8.4:
     resolution: {integrity: 
sha512-9+qDhdc4X00qTNOtii6QX2z7ndAeWVOso7w3MPSoSJdXlVhpwPfm1yEp4ooKuWA9fiQILR8FKkyjmeqa13hBbw==}
     engines: {node: '>= 10.8.0'}
@@ -22244,12 +21576,6 @@ packages:
       typescript: 4.8.4
     dev: true
 
-  /typescript/4.4.4:
-    resolution: {integrity: 
sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==}
-    engines: {node: '>=4.2.0'}
-    hasBin: true
-    dev: true
-
   /typescript/4.6.4:
     resolution: {integrity: 
sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==}
     engines: {node: '>=4.2.0'}

-- 
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]